diff --git a/doc/programmers_manual.md b/doc/programmers_manual.md index 8cdb831..067205f 100644 --- a/doc/programmers_manual.md +++ b/doc/programmers_manual.md @@ -183,3 +183,21 @@ In Verilog, in order to replace a macro identifier with the value of the macro, you must put a backtick before the name: i.e. `VALUE + +## Forth scripting + +The user controls the kernel through Forth scripts. The particular +implementation used is zForth. + +Forth has the following memory access primitives: + +* `addr` `@`: get value at `addr` +* `val` `addr` `!`: write `val` to `addr` +* `val` `,`: allocate a cell in "data space" (think the heap) and store + the data there. +* `addr` `#`: return the size of the value at addr (not standard Forth) + +Each of these are not primitives in zForth. zForth allows for peeks and +pokes to get values of different lengths, and the standard operators are +for addressing a variable length value. + diff --git a/firmware/rtl/autoapproach/autoapproach.v b/firmware/rtl/autoapproach/autoapproach.v index e4ab970..1d9f244 100644 --- a/firmware/rtl/autoapproach/autoapproach.v +++ b/firmware/rtl/autoapproach/autoapproach.v @@ -7,7 +7,13 @@ module autoapproach #( parameter DAC_WID = 24, parameter DAC_DATA_WID = 20, parameter ADC_WID = 24, - parameter TIMER_WID = 32 + parameter TIMER_WID = 32, + parameter WORD_WID = 24, + parameter WORD_AMNT_WID = 11, + parameter [WORD_AMNT_WID-1:0] WORD_AMNT = 2047, + parameter RAM_WID = 32, + parameter RAM_WORD_WID = 16, + parameter RAM_WORD_INCR = 2 ) ( input clk, input arm, @@ -18,20 +24,20 @@ module autoapproach #( input [ADC_WID-1:0] setpoint, input [TIMER_WID-1:0] time_to_wait, - /* BRAM memory interface. Each pulse returns the next value in - * the sequence, and also informs the module if the sequence - * is completed. The kernel interacts primarily with this interface. - */ - input [DAC_DATA_WID-1:0] word, - output word_next, - input word_last, - input word_ok, - output word_rst, + /* User interface */ + input refresh_start, + input [RAM_WID-1:0] start_addr, + output reg refresh_finished, + + /* RAM interface */ + output reg [RAM_WID-1:0] ram_dma_addr, + input [RAM_WORD_WID-1:0] ram_word, + output reg ram_read, + input ram_valid, /* DAC wires. */ input dac_finished, output dac_arm, - input [DAC_WID-1:0] dac_in, output [DAC_WID-1:0] dac_out, input adc_finished, @@ -39,6 +45,28 @@ module autoapproach #( input [ADC_WID-1:0] measurement ); +bram_interface #( + .WORD_WID(WORD_WID), + .WORD_AMNT_WID(WORD_AMNT_WID), + .WORD_AMNT(WORD_AMNT), + .RAM_WID(RAM_WID), + .RAM_WORD_WID(RAM_WORD_WID), + .RAM_WORD_INCR(RAM_WORD_INCR) +) bram ( + .clk(clk), + .word(word), + .word_next(word_next), + .word_last(word_last), + .word_ok(word_ok), + .word_rst(word_rst), + .refresh_start(refresh_start), + .start_addr(start_addr), + .refresh_finished(refresh_finished), + .ram_dma_addr(ram_dma_addr), + .ram_word(ram_word), + .ram_read(ram_read), + .ram_valid(ram_valid) +); localparam WAIT_ON_ARM = 0; localparam DO_WAIT = 1; diff --git a/firmware/rtl/autoapproach/autoapproach_sim.cpp b/firmware/rtl/autoapproach/autoapproach_sim.cpp new file mode 100644 index 0000000..9412b30 --- /dev/null +++ b/firmware/rtl/autoapproach/autoapproach_sim.cpp @@ -0,0 +1,125 @@ +#include +#include + +#include "Vautoapproach_sim.h" +#include "../testbench.hpp" + +/* TODO: generalize so bram_interface_sim can use it. + * This should make a triangle wave. + */ +class RefreshModule { + uint32_t *store_32; + size_t word_amnt; + bool pending_refresh_start; + + public: + void posedge(uint8_t &refresh_start, uint32_t &start_addr, + uint8_t refresh_finished) { + if (refresh_start && refresh_finished) { + refresh_start = 0; + } else if (pending_refresh_start && !refresh_start) { + pending_refresh_start = false; + refresh_start = 1; + } + } + + RefreshModule(size_t _word_amnt, size_t _start_addr) + : word_amnt{_word_amnt} + , start_addr{_start_addr} { + store_32 = new uint32_t[_start_addr]; + for (size_t i = 0; i < start_addr; i++) { + /* 0xFFFFF is the maximum DAC value */ + store_32[i] = 0xFFFFF*max(double)i/start_addr; + } + pending_refresh_start = true; + } + + ~RefreshModule() { + delete[] store_32; + } +}; + +/* TODO: make generic SPI delay class because this code has been duplicated + * many times over now. This function is also similar to the control loop + * ADC simulation. */ +class GaussianZPiezo { + std::default_random_engine generator; + std::normal_distribution<> dist; + double scale; + double setpt; + double midpt; + double stretch; + + double sample() {return scale*dist(generator);} + + GaussianZPiezo(double scale, double mean, double dev, double setpt, + double midpt, double stretch, int seed, + uint16_t dac_wait_count, + uint16_t adc_wait_count) + : scale{scale}, dist{mean,dev}, generator{}, + , setpt{setpt}, midpt{midpt}, stretch{stretch}, + , dac_wait_count_max{dac_wait_count} + , adc_wait_count_max{adc_wait_count} { + if (seed < 0) { + std::random_device rd; + generator.seed(rd()); + } else { + generator.seed(seed); + } + } + /* Sigmoid function. This function is + + c(x-d) + f(x) = A*------------------- + sqrt(1+(c(x-d))^2) + + where A is the setpoint and c is how compressed the sigmoid is. + */ + + double f(uint32_t x) { + double x_shift = x - midpt + sample(); + return setpt*stretch*x_shift/sqrt(fma(x_shift,x_shift,1)); + } + + public: + + void posedge(uint8_t &dac_finished, uint8_t dac_arm, + uint32_t dac_out, + uint8_t &adc_finished, uint8_t adc_arm, + uint32_t &adc_out) { + if (adc_arm && adc_wait_counter == adc_wait_counter_max && + !adc_finished) { + adc_finished = 1; + adc_out = sample(); + } else if (!adc_arm) { + adc_finished = 0; + adc_wait_counter = 0; + } else { + adc_wait_counter++; + } + + if (dac_arm && dac_wait_counter == dac_wait_counter_max && + !dac_finished) { + dac_finished = 1; + } +}; + +class AA_TB : TB { + RefreshModule refresh; + GaussianZPiezo piezo; + + void posedge() override; + AA_TB(size_t word_amnt, uint32_t start_addr) + : refresh{word_amnt, start_addr} {} + + ~AA_TB() {} +}; + +AA_TB::posedge() { + refresh.posedge(mod.refresh_start, mod.start_addr, + mod.refresh_finished); +} + +int main(int argc, char **argv) { + return 0; +} diff --git a/firmware/rtl/autoapproach/autoapproach_sim.v b/firmware/rtl/autoapproach/autoapproach_sim.v new file mode 100644 index 0000000..592f616 --- /dev/null +++ b/firmware/rtl/autoapproach/autoapproach_sim.v @@ -0,0 +1,96 @@ +module autoapproach_sim #( + parameter DAC_WID = 24, + parameter DAC_DATA_WID = 20, + parameter ADC_WID = 24, + parameter TIMER_WID = 32, + parameter WORD_WID = 24, + parameter WORD_AMNT_WID = 11, + parameter [WORD_AMNT_WID-1:0] WORD_AMNT = 2047, + parameter RAM_WID = 32, + parameter RAM_WORD_WID = 16, + parameter RAM_WORD_INCR = 2, + parameter TOTAL_RAM_WORD_MINUS_ONE = 4095 +) ( + input clk, + input arm, + output stopped, + output detected, + + input polarity, + input [ADC_WID-1:0] setpoint, + input [TIMER_WID-1:0] time_to_wait, + + /* User interface */ + input refresh_start, + input [RAM_WID-1:0] start_addr, + output refresh_finished, + + /* DAC wires. */ + input dac_finished, + output dac_arm, + output [DAC_WID-1:0] dac_out, + + input adc_finished, + output adc_arm, + input [ADC_WID-1:0] measurement + + input[RAM_WORD_WID-1:0] backing_store [TOTAL_RAM_WORD_MINUS_ONE:0] +); + +wire [RAM_WID-1:0] ram_dma_addr; +wire [RAM_WORD_WID-1:0] ram_word; +wire ram_read; +wire ram_valid; + +dma_sim #( + .RAM_WID(RAM_WID), + .RAM_WORD_WID(RAM_WORD_WID), + .RAM_REAL_START(RAM_REAL_START), + .RAM_CNTR_LEN(RAM_CNTR_LEN), + .TOTAL_RAM_WORD_MINUS_ONE(TOTAL_RAM_WORD_MINUS_ONE), + .DELAY_CNTR_LEN(DELAY_CNTR_LEN), + .DELAY_TOTAL(DELAY_TOTAL) +) dma_sim ( + .clk(clk), + .ram_dma_addr(ram_dma_addr), + .ram_word(ram_word), + .ram_read(ram_read), + .ram_valid(ram_valid), + .backing_store(backing_store) +); + +autoapproach #( + .DAC_WID(DAC_WID), + .DAC_DATA_WID(DAC_DATA_WID), + .ADC_WID(ADC_WID), + .TIMER_WID(TIMER_WID), + .WORD_WID(WORD_WID), + .WORD_AMNT_WID(WORD_AMNT_WID), + .WORD_AMNT(WORD_AMNT), + .RAM_WID(RAM_WID), + .RAM_WORD_WID(RAM_WORD_WID), + .RAM_WORD_INCR(RAM_WORD_INCR) +) aa ( + .clk(clk), + .arm(arm), + .stopped(stopped), + .detected(detected), + .polarity(polarity), + .setpoint(setpoint), + .time_to_wait(time_to_wait), + .refresh_start(refresh_start), + .start_addr(start_addr), + .refresh_finished(refresh_finished), + .ram_dma_addr(ram_dma_addr), + .ram_word(ram_word), + .ram_read(ram_read), + .ram_valid(ram_valid), + .dac_finished(dac_finished), + .dac_arm(dac_arm), + .dac_out(dac_out), + .adc_finished(adc_finished), + .adc_arm(adc_arm), + .measurement(measurement) +); + +endmodule diff --git a/firmware/rtl/autoapproach/bram_dma.cpp b/firmware/rtl/autoapproach/bram_dma.cpp new file mode 100644 index 0000000..3bd2238 --- /dev/null +++ b/firmware/rtl/autoapproach/bram_dma.cpp @@ -0,0 +1,59 @@ +#include "bram_dma.hpp" +#include "../util.hpp" +#include + +BRAM_DMA_Sim::BRAM_DMA_Sim(uint32_t _start_addr, + size_t _word_amnt, + size_t _timer_max) { + my_assert(_start_addr / 4 == 0, "start addr %d not 16 bit aligned", + _start_addr); + start_addr = _start_addr; + word_amnt = _word_amnt; + timer_max = _timer_max; + + ram = new uint32_t[word_amnt]; +} + +BRAM_DMA_SIM::~BRAM_DMA_Sim() { + delete[] ram; +} + +void BRAM_DMA_Sim::generate_random_data() { + for (size_t i = 0; i < word_amnt; i++) { + ram[i] = mask_extend(rand(), 20); + } +} + +void BRAM_DMA_Sim::execute_ram_access(uint32_t ram_dma_addr, + uint32_t &ram_word, + uint32_t &ram_valid) { + ram_valid = 1; + my_assert(ram_dma_addr < start_addr + || ram_dma_addr >= start_addr + word_amnt*4, + "bad address %x\n", ram_dma_addr); + my_assert(ram_dma_addr >= start_addr, "left oob access %x", + tb->mod.ram_dma_addr); + my_assert(ram_dma_addr < start_addr + WORD_AMNT*4, + "right oob access %x", ram_dma_addr); + my_assert(ram_dma_addr % 2 == 0, "unaligned access %x", + ram_dma_addr); + + const auto addr = (ram_dma_addr - start_addr) / 4; + if (tb->mod.ram_dma_addr % 4 == 0) { + ram_word = ram_refresh_data[addr] & 0xFFFF; + } else { + ram_word = ram_refresh_data[addr] >> 16; + } +} + +void BRAM_DMA_Sim::posedge(uint32_t ram_dma_addr, uint32_t &ram_word, + uint32_t ram_read, uint32_t &ram_valid) { + if (ram_read && timer < timer_max) { + timer++; + if (timer == timer_max) + execute_ram_access(ram_dma_addr, ram_word, ram_valid); + } else { + ram_valid = 0; + timer = 0; + } +} diff --git a/firmware/rtl/autoapproach/bram_dma.hpp b/firmware/rtl/autoapproach/bram_dma.hpp new file mode 100644 index 0000000..b3f982b --- /dev/null +++ b/firmware/rtl/autoapproach/bram_dma.hpp @@ -0,0 +1,25 @@ +#pragma once +#include + +template +class BRAM_DMA_Sim { + uint32_t *ram; + + uint32_t start_addr; + size_t word_amnt; + size_t timer_max; + + int sim_timer; + + void execute_ram_access(uint32_t ram_dma_addr, uint32_t &ram_word, + uint32_t &ram_valid); + public: + void generate_random_data(); + BRAM_DMA(uint32_t _start_addr = 0x12340, + size_t _word_amnt = 2048, + size_t _timer_max = 10); + ~BRAM_DMA(); + void posedge(uint32_t ram_dma_addr, uint32_t &ram_word, + uint32_t ram_read, uint32_t &ram_valid); + +}; diff --git a/firmware/rtl/autoapproach/bram_interface_sim.cpp b/firmware/rtl/autoapproach/bram_interface_sim.cpp index 617bc44..2cabf96 100644 --- a/firmware/rtl/autoapproach/bram_interface_sim.cpp +++ b/firmware/rtl/autoapproach/bram_interface_sim.cpp @@ -63,7 +63,7 @@ static void test_aa_read_interrupted() { } static void refresh_data() { - for (size_t i = 0; i < RAM_WID; i++) { + for (size_t i = 0; i < WORD_AMNT; i++) { uint32_t val = mask_extend(rand(), 20); ram_refresh_data[i] = val; tb->mod.backing_store[i*2] = val & 0xFFFF; diff --git a/firmware/rtl/testbench.hpp b/firmware/rtl/testbench.hpp index bc4aa34..60f44ae 100644 --- a/firmware/rtl/testbench.hpp +++ b/firmware/rtl/testbench.hpp @@ -19,10 +19,13 @@ template class TB { mod.final(); } + virtual void posedge() {} + void run_clock() { mod.clk = !mod.clk; mod.eval(); Verilated::timeInc(1); + posedge(); mod.clk = !mod.clk; mod.eval();