From 1d85e2307d2692454772e52a2731b50c403c4b08 Mon Sep 17 00:00:00 2001 From: Peter McGoron Date: Sun, 3 Mar 2024 22:35:19 +0000 Subject: [PATCH] waveform: write and start simulation --- build/Makefile | 2 + gateware/Makefile | 4 + gateware/extio.py | 126 ++++++- gateware/rtl/Makefile | 7 + gateware/rtl/spi/spi_master_ss_wb.v | 6 +- gateware/rtl/testbench.hpp | 9 + gateware/rtl/waveform/Makefile | 45 +-- gateware/rtl/waveform/bram_dma.cpp | 64 ---- gateware/rtl/waveform/bram_dma.hpp | 30 -- gateware/rtl/waveform/bram_interface.v | 126 ------- gateware/rtl/waveform/bram_interface_sim.cpp | 112 ------ gateware/rtl/waveform/bram_interface_sim.v | 88 ----- gateware/rtl/waveform/dma_sim.v | 50 --- gateware/rtl/waveform/waveform.cpp | 108 ++++++ gateware/rtl/waveform/waveform.v | 344 +++++++++---------- gateware/rtl/waveform/waveform_sim.cpp | 118 ------- gateware/rtl/waveform/waveform_sim.v | 248 +++++++------ gateware/soc.py | 1 - 18 files changed, 556 insertions(+), 932 deletions(-) delete mode 100644 gateware/rtl/waveform/bram_dma.cpp delete mode 100644 gateware/rtl/waveform/bram_dma.hpp delete mode 100644 gateware/rtl/waveform/bram_interface.v delete mode 100644 gateware/rtl/waveform/bram_interface_sim.cpp delete mode 100644 gateware/rtl/waveform/bram_interface_sim.v delete mode 100644 gateware/rtl/waveform/dma_sim.v create mode 100644 gateware/rtl/waveform/waveform.cpp delete mode 100644 gateware/rtl/waveform/waveform_sim.cpp diff --git a/build/Makefile b/build/Makefile index 801b2f4..831a038 100644 --- a/build/Makefile +++ b/build/Makefile @@ -97,6 +97,8 @@ verilator-execute: make clean && \ make test \ ' +verilator-copy-waveform: + docker cp upsilon-verilator:/home/user/upsilon/gateware/rtl/waveform/waveform.fst ./ verilator-clean: -docker container stop upsilon-verilator -docker container rm upsilon-verilator diff --git a/gateware/Makefile b/gateware/Makefile index c382271..4dbc94f 100644 --- a/gateware/Makefile +++ b/gateware/Makefile @@ -29,9 +29,13 @@ mmio.py csr.json build/digilent_arty/digilent_arty.bit: soc.py clean: rm -rf build csr.json arty.dts arty.dtb mmio.py + cd rtl && make clean arty.dts: csr.json litex_json2dts_linux csr.json > arty.dts arty.dtb: arty.dts dtc -O dtb -o arty.dtb arty.dts + +test: + cd rtl && make test diff --git a/gateware/extio.py b/gateware/extio.py index cdceb8a..b959f9b 100644 --- a/gateware/extio.py +++ b/gateware/extio.py @@ -8,6 +8,45 @@ from migen import * from litex.soc.interconnect.wishbone import Interface from util import * +from region import * + +''' +class Waveform(LiteXModule): + """ Read from a memory location and output the values in that location + to a DAC. """ + + def __init__(self, max_size=16): + # Interface used by Waveform to read and write data + self.bus = Interface(data_width = 32, address_width = 32, addressing="byte") + + # Interface used by Main CPU to control Waveform + self.mainbus = Interface(data_width = 32, address_width = 32, addressing="byte") + self.mmap = MemoryMap() + + self.start = Signal() + self.cntr = Signal(max_size) + self.loop = Signal() + self.finished = Signal() + self.ready = Signal() + + self.comb += [ + If(self.mainbus.cyc & self.mainbus.stb & ~self.mainbus.ack, + Cases(self.mainbus.adr[0:4], { + 0x0: self.mainbus.dat_r.eq(self.finished << 1 | self.ready), + 0x4: If(self.mainbus.we, + self.start.eq(self.mainbus.dat_w[0]), + self.loop.eq(self.mainbus.dat_w[1]), + ).Else( + self.mainbus.dat_r.eq(self.start << 1 | self.loop), + ), + 0x8: self.mainbus.dat_r.eq(self.cntr) + }), + self.mainbus.ack.eq(1), + ).Elif(~self.mainbus.cyc, + self.mainbus.ack.eq(0), + ), + ] +''' class SPIMaster(Module): AD5791_PARAMS = { @@ -28,29 +67,48 @@ class SPIMaster(Module): "enable_mosi" : 0, } - width = 0x10 + width = 0x20 public_registers = { - "finished_or_ready": { + # The first bit is the "finished" bit, when the master is + # armed and finished with a transmission. + # The second bit is the "ready" bit, when the master is + # not armed and ready to be armed. + "ready_or_finished": { "origin" : 0, "width" : 4, "rw": False, }, + + # One bit to initiate a transmission cycle. Transmission + # cycles CANNOT be interrupted. "arm" : { "origin": 4, "width": 4, "rw": True, }, + + # Data sent from the SPI slave. "from_slave": { "origin": 8, "width": 4, "rw": False, }, + + # Data sent to the SPI slave. "to_slave": { "origin": 0xC, "width": 4, "rw": True }, + + # Same as ready_or_finished, but halts until ready or finished + # goes high. Dangerous, might cause cores to hang! + "wait_ready_or_finished": { + "origin": 0x10, + "width": 4, + "rw" : False, + }, } """ Wrapper for the SPI master verilog code. """ @@ -83,11 +141,60 @@ class SPIMaster(Module): self.bus = Interface(data_width = 32, address_width=32, addressing="byte") + from_slave = Signal(spi_wid) + to_slave = Signal(spi_wid) + finished_or_ready = Signal(2) + arm = Signal() + self.comb += [ self.bus.err.eq(0), ] - self.specials += Instance("spi_master_ss_wb", + self.sync += [ + If(self.bus.cyc & self.bus.stb & ~self.bus.ack, + Cases(self.bus.adr[0:5], { + 0x0: [ + self.bus.dat_r[2:].eq(0), + self.bus.dat_r[0:2].eq(finished_or_ready), + self.bus.ack.eq(1), + ], + 0x4: [ + If(self.bus.we, + arm.eq(self.bus.dat_w[0]), + ).Else( + self.bus.dat_r[1:].eq(0), + self.bus.dat_r[0].eq(arm), + ), + self.bus.ack.eq(1), + ] + 0x8: [ + self.bus.dat_r[wid:].eq(0), + self.bus.dat_r[0:wid].eq(from_slave), + self.bus.ack.eq(1), + ], + 0xC: [ + If(self.bus.we, + to_slave.eq(self.bus.dat_r[0:wid]), + ).Else( + self.bus.dat_r[wid:].eq(0), + self.bus.dat_r[0:wid].eq(to_slave), + ), + self.bus.ack.eq(1), + ] + 0x10: If(finished | ready_to_arm, + self.bus.dat_r[1:].eq(0), + self.bus.dat_r.eq(finished_or_ready), + ), + "default": + # 0xSPI00SPI + self.bus.dat_r.eq(0x57100571), + }), + ).Elif(~self.bus.clk, + self.bus.ack.eq(0) + ) + ] + + self.specials += Instance("spi_master_ss", p_SS_WAIT = ss_wait, p_SS_WAIT_TIMER_LEN = minbits(ss_wait), p_CYCLE_HALF_WAIT = spi_cycle_half_wait, @@ -106,12 +213,9 @@ class SPIMaster(Module): o_sck_wire = sck, o_ss_L = ss_L, - i_wb_cyc = self.bus.cyc, - i_wb_stb = self.bus.stb, - i_wb_we = self.bus.we, - i_wb_sel = self.bus.sel, - i_wb_addr = self.bus.adr, - i_wb_dat_w = self.bus.dat_w, - o_wb_ack = self.bus.ack, - o_wb_dat_r = self.bus.dat_r, + o_from_slave = from_slave, + i_to_slave = to_slave, + o_finished = finished_or_ready[1], + o_ready_to_arm = finished_or_ready[0], + i_arm = arm, ) diff --git a/gateware/rtl/Makefile b/gateware/rtl/Makefile index 702d664..dda32c1 100644 --- a/gateware/rtl/Makefile +++ b/gateware/rtl/Makefile @@ -8,6 +8,13 @@ all: make_spi make_spi: cd spi && make codegen +test_waveform: + cd waveform && make test + +test: test_waveform + +clean: + cd waveform && make clean #make_bram: # cd bram && make codegen diff --git a/gateware/rtl/spi/spi_master_ss_wb.v b/gateware/rtl/spi/spi_master_ss_wb.v index 973749c..6c20f43 100644 --- a/gateware/rtl/spi/spi_master_ss_wb.v +++ b/gateware/rtl/spi/spi_master_ss_wb.v @@ -37,6 +37,10 @@ module spi_master_ss_wb input [BUS_WID-1:0] wb_dat_w, output reg wb_ack, output reg [BUS_WID-1:0] wb_dat_r, + + /* Used in Migen code */ + output finished, + output ready_to_arm, ); /* Address map: @@ -52,8 +56,6 @@ module spi_master_ss_wb wire [WID-1:0] from_slave; reg [WID-1:0] to_slave; -wire finished; -wire ready_to_arm; reg arm; spi_master_ss #( diff --git a/gateware/rtl/testbench.hpp b/gateware/rtl/testbench.hpp index 766ae52..f4c59a3 100644 --- a/gateware/rtl/testbench.hpp +++ b/gateware/rtl/testbench.hpp @@ -24,6 +24,15 @@ template class TB { mod.final(); } + /* This function is called at the positive edge of ever clock + * cycle. + * + * It's intended use is for glue code, like a bus handler. + * + * The bulk of the simulation code (driving external inputs into the + * simulated module and observing results) should be done outside of + * this function. + */ virtual void posedge() {} void run_clock() { diff --git a/gateware/rtl/waveform/Makefile b/gateware/rtl/waveform/Makefile index e61c444..5959c5f 100644 --- a/gateware/rtl/waveform/Makefile +++ b/gateware/rtl/waveform/Makefile @@ -1,41 +1,22 @@ -# Copyright 2023 (C) Peter McGoron +# Copyright 2024 (C) Peter McGoron # This file is a part of Upsilon, a free and open source software project. # For license terms, refer to the files in `doc/copying` in the Upsilon # source distribution. # Makefile for tests and hardware verification. -include ../common.makefile +.PHONY: test clean codegen all -.PHONY: test clean codegen - -all: test codegen -test: obj_dir/Vbram_interface_sim obj_dir/Vwaveform_sim -CODEGEN_FILES=bram_interface_preprocessed.v waveform_preprocessed.v - -codegen: ${CODEGEN_FILES} - -bram_SRC= bram_interface_sim.v dma_sim.v bram_interface.v bram_interface_sim.cpp - -obj_dir/Vbram_interface_sim.mk: $(bram_SRC) - verilator --cc --exe -Wall --trace --trace-fst \ - -CFLAGS -DWORD_AMNT=2048 \ - -CFLAGS -DRAM_WID=32 \ - $(bram_SRC) -obj_dir/Vbram_interface_sim: obj_dir/Vbram_interface_sim.mk - cd obj_dir && make -f Vbram_interface_sim.mk - ./obj_dir/Vbram_interface_sim - -waveform_src = waveform_sim.v waveform.v bram_interface.v dma_sim.v waveform_sim.cpp ../spi/spi_slave_no_write.v -obj_dir/Vwaveform_sim.mk: $(waveform_src) - verilator --cc --exe -Wall --trace --trace-fst -I../spi \ - -CFLAGS -DWORD_AMNT=2048 \ - -CFLAGS -DRAM_WID=32 \ - -DVERILATOR_SIMULATION \ - $(waveform_src) -obj_dir/Vwaveform_sim: obj_dir/Vwaveform_sim.mk $(waveform_src) - cd obj_dir && make -f Vwaveform_sim.mk - ./obj_dir/Vwaveform_sim +CPP_FILE=waveform.cpp +VERILOG=waveform_sim.v waveform.v +test: obj_dir/Vwaveform_sim + obj_dir/Vwaveform_sim clean: - rm -rf obj_dir/ ${CODEGEN_FILES} + rm -rf obj_dir +obj_dir/Vwaveform_sim.mk: ${VERILOG} ${CPP_FILE} + verilator --cc --exe -Wall --trace --trace-fst \ + ${VERILOG} ${CPP_FILE} + +obj_dir/Vwaveform_sim: obj_dir/Vwaveform_sim.mk + cd obj_dir && make -f Vwaveform_sim.mk diff --git a/gateware/rtl/waveform/bram_dma.cpp b/gateware/rtl/waveform/bram_dma.cpp deleted file mode 100644 index 76c17c1..0000000 --- a/gateware/rtl/waveform/bram_dma.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* Copyright 2023 (C) Peter McGoron - * This file is a part of Upsilon, a free and open source software project. - * For license terms, refer to the files in `doc/copying` in the Upsilon - * source distribution. - */ -#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/gateware/rtl/waveform/bram_dma.hpp b/gateware/rtl/waveform/bram_dma.hpp deleted file mode 100644 index f3381a2..0000000 --- a/gateware/rtl/waveform/bram_dma.hpp +++ /dev/null @@ -1,30 +0,0 @@ -/* Copyright 2023 (C) Peter McGoron - * This file is a part of Upsilon, a free and open source software project. - * For license terms, refer to the files in `doc/copying` in the Upsilon - * source distribution. - */ -#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/gateware/rtl/waveform/bram_interface.v b/gateware/rtl/waveform/bram_interface.v deleted file mode 100644 index 0f8f045..0000000 --- a/gateware/rtl/waveform/bram_interface.v +++ /dev/null @@ -1,126 +0,0 @@ -/* Copyright 2023 (C) Peter McGoron - * This file is a part of Upsilon, a free and open source software project. - * For license terms, refer to the files in `doc/copying` in the Upsilon - * source distribution. - */ -module bram_interface #( - parameter WORD_WID = 24, - parameter WORD_AMNT_WID = 11, - /* This is the last INDEX, not the LENGTH of the word array. */ - 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 rst_L, - - /* autoapproach interface */ - output reg [WORD_WID-1:0] word, - input word_next, - output reg word_last, - output reg word_ok, - input 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 -); - -initial word = 0; -initial word_last = 0; -initial word_ok = 0; -initial refresh_finished = 0; -initial ram_dma_addr = 0; -initial ram_read = 0; - -/* TODO: how to initialize? */ -reg [WORD_WID-1:0] backing_buffer [WORD_AMNT:0]; - -localparam WAIT_ON_REFRESH = 0; -localparam READ_LOW_WORD = 1; -localparam READ_HIGH_WORD = 2; -localparam WAIT_ON_REFRESH_DEASSERT = 3; - -reg [1:0] refresh_state = WAIT_ON_REFRESH; -reg [WORD_AMNT_WID-1:0] word_cntr_refresh = 0; - -always @ (posedge clk) if (!rst_L) begin - word <= 0; - word_last <= 0; - word_ok <= 0; - refresh_finished <= 0; - ram_dma_addr <= 0; - ram_read <= 0; - /* Do not reset backing buffer because that would take too long */ - refresh_state <= WAIT_ON_REFRESH; - word_cntr_refresh <= 0; -end else case (refresh_state) -WAIT_ON_REFRESH: if (refresh_start) begin - ram_dma_addr <= start_addr; - refresh_state <= READ_LOW_WORD; - word_cntr_refresh <= 0; -end -READ_LOW_WORD: if (!ram_read) begin - ram_read <= 1; -end else if (ram_valid) begin - refresh_state <= READ_HIGH_WORD; - ram_dma_addr <= ram_dma_addr + RAM_WORD_INCR; - ram_read <= 0; - backing_buffer[word_cntr_refresh][RAM_WORD_WID-1:0] <= ram_word; -end -READ_HIGH_WORD: if (!ram_read) begin - ram_read <= 1; -end else if (ram_valid) begin - ram_dma_addr <= ram_dma_addr + RAM_WORD_INCR; - ram_read <= 0; - word_cntr_refresh <= word_cntr_refresh + 1; - backing_buffer[word_cntr_refresh][WORD_WID-1:RAM_WORD_WID] <= ram_word[WORD_WID-RAM_WORD_WID-1:0]; - - if (word_cntr_refresh == WORD_AMNT) - refresh_state <= WAIT_ON_REFRESH_DEASSERT; - else - refresh_state <= READ_LOW_WORD; -end -WAIT_ON_REFRESH_DEASSERT: begin - if (!refresh_start) begin - refresh_finished <= 0; - refresh_state <= WAIT_ON_REFRESH; - end else begin - refresh_finished <= 1; - end -end -endcase - -reg [WORD_AMNT_WID-1:0] auto_cntr = 0; - -always @ (posedge clk) if (word_rst || !rst_L) begin - auto_cntr <= 0; - word_ok <= 0; - word_last <= 0; - word <= 0; -end else if (word_next && !word_ok) begin - if (refresh_state == WAIT_ON_REFRESH) begin - word <= backing_buffer[auto_cntr]; - word_ok <= 1; - if (auto_cntr == WORD_AMNT) begin - auto_cntr <= 0; - word_last <= 1; - end else begin - auto_cntr <= auto_cntr + 1; - word_last <= 0; - end - end -end else if (!word_next && word_ok) begin - word_ok <= 0; -end - -endmodule -`undefineall diff --git a/gateware/rtl/waveform/bram_interface_sim.cpp b/gateware/rtl/waveform/bram_interface_sim.cpp deleted file mode 100644 index 58abec9..0000000 --- a/gateware/rtl/waveform/bram_interface_sim.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* Copyright 2023 (C) Peter McGoron - * This file is a part of Upsilon, a free and open source software project. - * For license terms, refer to the files in `doc/copying` in the Upsilon - * source distribution. - */ -#include -#include -#include -#include -#include - -#include "Vbram_interface_sim.h" -#include "../testbench.hpp" - -std::array ram_refresh_data; -TB *tb; - -static void handle_read_aa(size_t &i) { - if (tb->mod.word_ok) { - uint32_t val = sign_extend(tb->mod.word, 20); - tb->mod.word_next = 0; - - my_assert(val == ram_refresh_data[i], "received value %x (%zu) != %x", i, val, ram_refresh_data[i]); - i++; - } else if (!tb->mod.word_next) { - tb->mod.word_next = 1; - } -} - -/* Test reading the entire array twice. */ -static void test_aa_read_1() { - size_t ind = 0; - - tb->mod.word_next = 1; - tb->run_clock(); - while (!tb->mod.word_last || (tb->mod.word_last && tb->mod.word_next)) { - handle_read_aa(ind); - tb->run_clock(); - } - my_assert(ind == WORD_AMNT, "read value %zu != %d\n", ind, WORD_AMNT); - - tb->mod.word_next = 1; - tb->run_clock(); - ind = 0; - while (!tb->mod.word_last || (tb->mod.word_last && tb->mod.word_next)) { - handle_read_aa(ind); - tb->run_clock(); - } - my_assert(ind == WORD_AMNT, "second read value %zu != %d\n", ind, WORD_AMNT); -} - -static void test_aa_read_interrupted() { - size_t ind = 0; - - tb->mod.word_next = 1; - tb->run_clock(); - for (int i = 0; i < 100; i++) { - handle_read_aa(ind); - tb->run_clock(); - my_assert(!tb->mod.word_last, "too many reads"); - } - tb->mod.word_rst = 1; - tb->run_clock(); - tb->mod.word_rst = 0; - tb->run_clock(); - - test_aa_read_1(); -} - -static void refresh_data() { - 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; - tb->mod.backing_store[i*2+1] = val >> 16; - } - - tb->mod.refresh_start = 1; - tb->mod.start_addr = 0x12340; - tb->run_clock(); - - while (!tb->mod.refresh_finished) - tb->run_clock(); - - tb->mod.refresh_start = 0; - tb->run_clock(); - -} - -int main(int argc, char *argv[]) { - Verilated::commandArgs(argc, argv); - Verilated::traceEverOn(true); - Verilated::fatalOnError(false); - - tb = new TB(); - tb->mod.rst_L = 1; - - printf("test basic read/write\n"); - refresh_data(); - printf("\ttest 1\n"); - test_aa_read_1(); - refresh_data(); - printf("\ttest 2\n"); - test_aa_read_1(); - - printf("test resetting\n"); - test_aa_read_interrupted(); - - printf("ok\n"); - - return 0; -} diff --git a/gateware/rtl/waveform/bram_interface_sim.v b/gateware/rtl/waveform/bram_interface_sim.v deleted file mode 100644 index ad84b89..0000000 --- a/gateware/rtl/waveform/bram_interface_sim.v +++ /dev/null @@ -1,88 +0,0 @@ -/* Copyright 2023 (C) Peter McGoron - * This file is a part of Upsilon, a free and open source software project. - * For license terms, refer to the files in `doc/copying` in the Upsilon - * source distribution. - */ -module bram_interface_sim #( - parameter WORD_WID = 20, - 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_REAL_START = 32'h12340, - parameter RAM_CNTR_LEN = 12, - parameter TOTAL_RAM_WORD_MINUS_ONE = 4095, - parameter DELAY_CNTR_LEN = 8, - parameter DELAY_TOTAL = 12, - parameter RAM_WORD_INCR = 2 -) ( - input clk, - input rst_L, - - /* autoapproach interface */ - output [WORD_WID-1:0] word, - input word_next, - output word_last, - output word_ok, - input word_rst, - - /* User interface */ - input refresh_start, - input [RAM_WID-1:0] start_addr, - output refresh_finished, - - 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) -); - -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_interface ( - .clk(clk), - .rst_L(rst_L), - .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) -); - -initial begin - $dumpfile("bram.fst"); - $dumpvars(); -end - -endmodule diff --git a/gateware/rtl/waveform/dma_sim.v b/gateware/rtl/waveform/dma_sim.v deleted file mode 100644 index 5abb44f..0000000 --- a/gateware/rtl/waveform/dma_sim.v +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright 2023 (C) Peter McGoron - * This file is a part of Upsilon, a free and open source software project. - * For license terms, refer to the files in `doc/copying` in the Upsilon - * source distribution. - */ -/* This module is used to simulate direct memory access, where only - * a small amount of memory is valid to read. - */ -module dma_sim #( - parameter RAM_WID = 32, - parameter RAM_WORD_WID = 16, - parameter RAM_REAL_START = 32'h12340, - parameter RAM_CNTR_LEN = 12, - parameter TOTAL_RAM_WORD_MINUS_ONE = 4095, - parameter DELAY_CNTR_LEN = 8, - parameter DELAY_TOTAL = 12 -) ( - input clk, - - /* DMA interface */ - input [RAM_WID-1:0] ram_dma_addr, - output reg [RAM_WORD_WID-1:0] ram_word, - input ram_read, - output reg ram_valid, - - /*- Verilator interface */ - input [RAM_WORD_WID-1:0] backing_store[TOTAL_RAM_WORD_MINUS_ONE:0] -); - -reg [DELAY_CNTR_LEN-1:0] delay_cntr = 0; - -always @ (posedge clk) begin - if (!ram_read) begin - delay_cntr <= 0; - ram_valid <= 0; - end else if (delay_cntr < DELAY_TOTAL) begin - delay_cntr <= delay_cntr + 1; - end else if (!ram_valid) begin - if (ram_dma_addr < RAM_REAL_START || ram_dma_addr > RAM_REAL_START + 2*TOTAL_RAM_WORD_MINUS_ONE) begin - $display("ram_dma_addr %x out of bounds", ram_dma_addr); - $stop(); - end else begin - ram_word <= backing_store[(RAM_CNTR_LEN)'((ram_dma_addr - RAM_REAL_START)/2)]; - ram_valid <= 1; - end - end -end - -endmodule -`undefineall diff --git a/gateware/rtl/waveform/waveform.cpp b/gateware/rtl/waveform/waveform.cpp new file mode 100644 index 0000000..1bda865 --- /dev/null +++ b/gateware/rtl/waveform/waveform.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include +#include "Vwaveform_sim.h" +#include "../testbench.hpp" + +class WaveformTestbench : public TB { + public: + void start_test(int, int, int, bool); + void halt_check(bool); + WaveformTestbench(int _bailout) : TB(_bailout) {} + private: + std::vector send_words; + std::vector recv_words; + void fill_data(int num); + void check_data(); + void posedge() override; +}; + +void WaveformTestbench::posedge() { + if (!mod.ram_finished) { + if ((mod.enable & 0b10) != 0) { + recv_words.push_back(mod.spi_data); + mod.ram_finished = 1; + } else if ((mod.enable & 0b01) != 0) { + mod.ram_data = send_words.at(mod.offset); + mod.ram_finished = 1; + } + } else if ((mod.enable & 0b11) == 0) { + mod.ram_finished = 0; + } +} + +void WaveformTestbench::fill_data(int num) { + auto engine = std::default_random_engine{}; + auto distrib = std::uniform_int_distribution(0,(1 << 20) - 1); + + send_words.clear(); + for (int i = 0; i < num; i++) { + send_words.push_back(distrib(engine)); + } +} + +void WaveformTestbench::check_data() { + auto len = send_words.size(); + auto recv_size = recv_words.size(); + + for (decltype(len) i = 0; i < recv_size; i++) { + /* SPI message has an extra bit to access DAC register */ + auto was_sent = 1 << 20 | send_words.at(i % len); + if (was_sent != recv_words.at(i % len)) { + std::cout << i << ":" << was_sent << "!=" << recv_words[i % len] << std::endl; + std::exit(1); + } + } +} + +void WaveformTestbench::start_test(int wform_size, int spi_max_wait, int timer_spacing, bool do_loop) { + fill_data(wform_size); + + mod.run = 1; + mod.wform_size = wform_size; + mod.spi_max_wait = spi_max_wait; + mod.timer_spacing = timer_spacing; + mod.do_loop = do_loop; + run_clock(); +} + +void WaveformTestbench::halt_check(bool check_for_finish) { + if (check_for_finish) { + while (!mod.finished) { + run_clock(); + } + mod.run = 0; + run_clock(); + } else { + mod.run = 0; + run_clock(); + while (!mod.ready) { + run_clock(); + } + } + + check_data(); +} + +WaveformTestbench *tb; + +void cleanup() { + delete tb; +} + +int main(int argc, char *argv[]) { + Verilated::commandArgs(argc, argv); + Verilated::traceEverOn(true); + tb = new WaveformTestbench(100000); + atexit(cleanup); + + tb->start_test(64, 14, 20, false); + tb->halt_check(true); + + tb->start_test(64, 14, 20, true); + tb->halt_check(false); + + return 0; +} diff --git a/gateware/rtl/waveform/waveform.v b/gateware/rtl/waveform/waveform.v index ce18ff5..f26524d 100644 --- a/gateware/rtl/waveform/waveform.v +++ b/gateware/rtl/waveform/waveform.v @@ -1,208 +1,184 @@ -/* Copyright 2023 (C) Peter McGoron +/* Copyright 2024 (C) Peter McGoron + * * This file is a part of Upsilon, a free and open source software project. * For license terms, refer to the files in `doc/copying` in the Upsilon * source distribution. */ -/* Write a waveform to a DAC. */ -/* TODO: Add "how many values to go" counter. */ + module waveform #( - parameter DAC_WID = 24, - parameter DAC_WID_SIZ = 5, - parameter DAC_POLARITY = 0, - parameter DAC_PHASE = 1, - parameter DAC_CYCLE_HALF_WAIT = 10, - parameter DAC_CYCLE_HALF_WAIT_SIZ = 4, - parameter DAC_SS_WAIT = 5, - parameter DAC_SS_WAIT_SIZ = 3, - parameter TIMER_WID = 32, - parameter WORD_WID = 20, - 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 RAM_START_ADDR = 32'h0, + parameter SPI_START_ADDR = 32'h10000000, + parameter COUNTER_MAX_WID = 16, + parameter TIMER_WID = 16 ) ( input clk, - input rst_L, - input arm, - input halt_on_finish, - /* NOTE: - * finished is used when a module wants to wait for a - * waveform with the halt_on_finish flag finishes - * one waveform. - * - * running is used when a module wants to know when - * the waveform module has finished running after - * deasserting arm. - * - * When in doubt, deassert arm and wait for running - * to be deasserted. - */ + + /* Waveform output control */ + input run, + output reg[COUNTER_MAX_WID-1:0] cntr, + input do_loop, output reg finished, - output running, - input [TIMER_WID-1:0] time_to_wait, + output reg ready, + input [COUNTER_MAX_WID-1:0] wform_size, + output reg [TIMER_WID-1:0] timer, + input [TIMER_WID-1:0] timer_spacing, - /* 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. */ - output mosi, - output sck, - output ss_L + /* Bus master */ + output reg [32-1:0] wb_adr, + output reg wb_cyc, + output reg wb_we, + output wb_stb, + output [4-1:0] wb_sel, + output reg [32-1:0] wb_dat_w, + input [32-1:0] wb_dat_r, + input wb_ack ); -wire [WORD_WID-1:0] word; -reg word_next = 0; -wire word_ok; -wire word_last; -reg word_rst = 1; +/* When a Wishbone cycle starts, the output is stable. */ +assign wb_stb = wb_cyc; -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), - .rst_L(rst_L), - .word(word), - .word_next(word_next), - .word_last(word_last), - .word_ok(word_ok), - .word_rst(word_rst), +/* Always write 32 bits */ +assign wb_sel = 4'b1111; - .refresh_start(refresh_start), - .start_addr(start_addr), - .refresh_finished(refresh_finished), +localparam CHECK_START = 0; +localparam CHECK_LEN = 1; +localparam WAIT_FINISHED = 2; +localparam READ_RAM = 3; +localparam WAIT_RAM = 4; +localparam WRITE_DAC_DATA_ADR = 5; +localparam WAIT_DAC_DATA_ADR = 6; +localparam WRITE_DAC_ARM_ADR = 7; +localparam WAIT_DAC_ARM_ADR = 8; +localparam WRITE_DAC_DISARM_ADR = 9; +localparam WAIT_DAC_DISARM_ADR = 10; +localparam READ_DAC_FIN_ADR = 11; +localparam WAIT_DAC_FIN_ADR = 12; +localparam WAIT_PERIOD = 13; - .ram_dma_addr(ram_dma_addr), - .ram_word(ram_word), - .ram_read(ram_read), - .ram_valid(ram_valid) -); +reg [4-1:0] state = CHECK_START; -wire dac_finished; -reg dac_arm = 0; -reg [DAC_WID-1:0] dac_out = 0; -wire dac_ready_to_arm_unused; - -spi_master_ss_no_read #( - .WID(DAC_WID), - .WID_LEN(DAC_WID_SIZ), - .CYCLE_HALF_WAIT(DAC_CYCLE_HALF_WAIT), - .TIMER_LEN(DAC_CYCLE_HALF_WAIT_SIZ), - .POLARITY(DAC_POLARITY), - .PHASE(DAC_PHASE), - .SS_WAIT(DAC_SS_WAIT), - .SS_WAIT_TIMER_LEN(DAC_SS_WAIT_SIZ) -) dac_master ( - .clk(clk), - .rst_L(rst_L), - .ready_to_arm(dac_ready_to_arm_unused), - .mosi(mosi), - .sck_wire(sck), - .ss_L(ss_L), - .finished(dac_finished), - .arm(dac_arm), - .to_slave(dac_out) -); - -localparam WAIT_ON_ARM = 0; -localparam DO_WAIT = 1; -localparam RECV_WORD = 2; -localparam WAIT_ON_DAC = 3; -localparam WAIT_ON_DISARM = 4; -reg [2:0] state = WAIT_ON_ARM; - -reg [TIMER_WID-1:0] wait_timer = 0; - -assign running = state != WAIT_ON_ARM; - -always @ (posedge clk) if (!rst_L) begin - state <= WAIT_ON_ARM; - wait_timer <= 0; - finished <= 0; - word_rst <= 1; - word_next <= 0; - dac_out <= 0; - dac_arm <= 0; -end else case (state) -WAIT_ON_ARM: begin - finished <= 0; - if (arm) begin - state <= DO_WAIT; - word_rst <= 0; - wait_timer <= time_to_wait; +always @ (posedge clk) case (state) +CHECK_START: if (run) begin + cntr <= 0; + ready <= 0; + state <= CHECK_LEN; end else begin - word_rst <= 1; + ready <= 1; end -end -DO_WAIT: if (!arm) begin - state <= WAIT_ON_ARM; -end else if (wait_timer == 0) begin - word_next <= 1; - state <= RECV_WORD; - wait_timer <= time_to_wait; -end else begin - wait_timer <= wait_timer - 1; -end -RECV_WORD: begin -`ifdef VERILATOR_SIMULATION - if (!word_next) begin - $error("RECV_WORD: word_next not asserted means hang"); - end -`endif - - if (word_ok) begin - dac_out <= {4'b0001, word}; - dac_arm <= 1; - - word_next <= 0; - state <= WAIT_ON_DAC; - end -end -WAIT_ON_DAC: begin -`ifdef VERILATOR_SIMULATION - if (!dac_arm) begin - $error("WAIT_ON_DAC: dac_arm not asserted means hang"); - end -`endif - - if (dac_finished) begin - dac_arm <= 0; - /* Was the last word read *the* last word? */ - if (word_last && halt_on_finish) begin - state <= WAIT_ON_DISARM; - finished <= 1; +CHECK_LEN: if (cntr >= wform_size) begin + if (do_loop) begin + cntr <= 0; + state <= READ_RAM; end else begin - state <= DO_WAIT; - wait_timer <= time_to_wait; + state <= WAIT_FINISHED; end + end else begin + state <= READ_RAM; end +WAIT_FINISHED: if (!run) begin + finished <= 0; + state <= CHECK_START; + end else if (do_loop) begin + state <= READ_RAM; + finished <= 0; + cntr <= 0; + end else begin + finished <= 1; + end +READ_RAM: begin + wb_adr <= RAM_START_ADDR + {16'b0, cntr}; + wb_cyc <= 1; /* Always assigned STB when CYC is */ + wb_we <= 0; + state <= WAIT_RAM; end -WAIT_ON_DISARM: if (!arm) begin - state <= WAIT_ON_ARM; +WAIT_RAM: if (wb_ack) begin + wb_cyc <= 0; + wb_dat_w <= 1 << 20 | wb_dat_r; + state <= WRITE_DAC_DATA_ADR; end +WRITE_DAC_DATA_ADR: begin + wb_adr <= SPI_START_ADDR + 32'hC; + wb_cyc <= 1; + wb_we <= 1; + state <= WAIT_DAC_DATA_ADR; +end +WAIT_DAC_DATA_ADR: if (wb_ack) begin + wb_cyc <= 0; + /* This is not needed, since the next bus cycle is also a write. */ + /* wb_we <= 0; */ + state <= WRITE_DAC_ARM_ADR; +end +WRITE_DAC_ARM_ADR: begin + wb_adr <= SPI_START_ADDR + 32'h4; + wb_dat_w[0] <= 1; + wb_cyc <= 1; + /* wb_we <= 1; */ + state <= WAIT_DAC_ARM_ADR; +end +WAIT_DAC_ARM_ADR: if (wb_ack) begin + wb_cyc <= 0; + /* This is not needed, since the next bus cycle is also a write. */ + /* wb_we <= 0; */ + state <= WRITE_DAC_DISARM_ADR; +end +/* + * After arming the SPI core, immediately disarm it. + * The SPI core will continue to execute after being disarmed until + * it completes a transmission cycle. Otherwise the core would spend + * two extra clock cycles if it disarmed after checking that the SPI + * master was done. + */ +WRITE_DAC_DISARM_ADR: begin + wb_adr <= SPI_START_ADDR + 32'h4; + wb_dat_w[0] <= 0; + wb_cyc <= 1; + /* This is not needed, since the previous bus cycle is also a write. */ + /* wb_we <= 1; */ + state <= WAIT_DAC_DISARM_ADR; +end +WAIT_DAC_DISARM_ADR: if (wb_ack) begin + wb_cyc <= 0; + /* Disable writes because next bus cycle is a write */ + wb_we <= 0; + state <= READ_DAC_FIN_ADR; +end +/* + * This core reads from "wait_ready_or_finished", which will + * stall until the SPI core is ready to arm or finished with + * it's transmission. + * If the SPI device is disconnected it will just return 0 at all + * times (see PreemptiveInterface). To avoid getting the core stuck + * the FSM will continue without checking that the DAC is actually + * ready or finished. + */ +READ_DAC_FIN_ADR: begin + wb_adr <= SPI_START_ADDR + 32'h10; + wb_cyc <= 1; + state <= WAIT_DAC_FIN_ADR; +end +WAIT_DAC_FIN_ADR: if (wb_cyc) begin + wb_cyc <= 0; + state <= WAIT_PERIOD; + timer <= 0; +end +/* If the core tells the block to stop running, stop when SPI is not + * transmitting to the DAC. + * + * If you want the module to run until the end of the waveform, turn + * off looping and wait until the waveform ends. If you want the module + * to stop the waveform and keep the DAC in a known state, just turn + * the running flag off. If you need to stop it immediately, flip the + * master switch for the DAC to the main CPU. + */ +WAIT_PERIOD: if (!run) begin + finished <= 0; + state <= CHECK_START; +end else if (timer < timer_spacing) begin + timer <= timer + 1; +end else begin + cntr <= cntr + 1; + state <= CHECK_LEN; +end +default: state <= CHECK_START; endcase - -/* Warning! This will crash verilator with a segmentation fault! -`ifdef VERILATOR -initial begin - $dumpfile("waveform.fst"); - $dumpvars(); -end -`endif -*/ - endmodule -`undefineall diff --git a/gateware/rtl/waveform/waveform_sim.cpp b/gateware/rtl/waveform/waveform_sim.cpp deleted file mode 100644 index 9663163..0000000 --- a/gateware/rtl/waveform/waveform_sim.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* Copyright 2023 (C) Peter McGoron - * This file is a part of Upsilon, a free and open source software project. - * For license terms, refer to the files in `doc/copying` in the Upsilon - * source distribution. - */ -/* TODO: impleement reset for dma and test both separetely */ -#include -#include "Vwaveform_sim.h" -#include "../testbench.hpp" - -class WaveformTestbench : public TB { -private: - void refresh_posedge(); - void spi_posedge(); -public: - std::array ram_refresh_data; - int cur_ind; - void posedge() override; - void refresh_data(); - WaveformTestbench(int _bailout = 0) : TB(_bailout) - , ram_refresh_data{} - , cur_ind{0} {} -}; - -void WaveformTestbench::refresh_data() { - for (size_t i = 0; i < WORD_AMNT; i++) { - uint32_t val = mask_extend(rand(), 20); - ram_refresh_data[i] = val; - mod.backing_store[i*2] = val & 0xFFFF; - mod.backing_store[i*2+1] = val >> 16; - } - - mod.refresh_start = 1; - mod.start_addr = 0x12340; -} - -void WaveformTestbench::refresh_posedge() { - if (mod.refresh_finished) { - mod.refresh_start = 0; - } -} - -void WaveformTestbench::spi_posedge() { - if (mod.finished) { - mod.rdy = 0; - // Check for proper DAC register. - my_assert(mod.from_master >> 20 == 0x1, "%d", mod.from_master >> 20); - uint32_t val = mask_extend(mod.from_master & 0xFFFFF, 20); - my_assert(val == ram_refresh_data[cur_ind], "(%d) %X != %X", - cur_ind, val, ram_refresh_data[cur_ind]); - cur_ind++; - if (cur_ind == WORD_AMNT) - cur_ind = 0; - } else if (!mod.finished && !mod.rdy) { - mod.rdy = 1; - } -} - -void WaveformTestbench::posedge() { - refresh_posedge(); - spi_posedge(); -} - -WaveformTestbench *tb; - -int main(int argc, char *argv[]) { - int j = 0; - Verilated::commandArgs(argc, argv); - // Verilated::traceEverOn(true); - Verilated::fatalOnError(false); - - tb = new WaveformTestbench(); - tb->mod.rdy = 1; - tb->mod.rst_L = 1; - tb->refresh_data(); - tb->mod.time_to_wait = 10; - tb->mod.halt_on_finish = 1; - tb->mod.arm = 1; - - do { - tb->run_clock(); - } while (!tb->mod.refresh_finished); - - printf("first run\n"); - do { - tb->run_clock(); - } while (!tb->mod.waveform_finished); - printf("waveform finished\n"); - - tb->mod.halt_on_finish = 0; - tb->mod.arm = 0; - tb->run_clock(); - tb->mod.arm = 1; - tb->mod.halt_on_finish = 1; - - printf("second run\n"); - do { - tb->run_clock(); - } while (!tb->mod.waveform_finished); - - tb->mod.rdy = 0; - tb->mod.halt_on_finish = 0; - tb->mod.refresh_start = 1; - do { - tb->run_clock(); - } while (!tb->mod.refresh_finished); - - tb->mod.rdy = 1; - tb->mod.halt_on_finish = 1; - - printf("third run\n"); - do { - tb->run_clock(); - } while (!tb->mod.waveform_finished); - - delete tb; - return 0; -} diff --git a/gateware/rtl/waveform/waveform_sim.v b/gateware/rtl/waveform/waveform_sim.v index 6ed09eb..e8480b0 100644 --- a/gateware/rtl/waveform/waveform_sim.v +++ b/gateware/rtl/waveform/waveform_sim.v @@ -1,132 +1,152 @@ -/* Copyright 2023 (C) Peter McGoron +/* Copyright 2024 (C) Peter McGoron + * * This file is a part of Upsilon, a free and open source software project. * For license terms, refer to the files in `doc/copying` in the Upsilon * source distribution. */ + module waveform_sim #( - parameter DAC_WID = 24, - parameter DAC_WID_SIZ = 5, - parameter DAC_POLARITY = 0, - parameter DAC_PHASE = 1, - parameter DAC_CYCLE_HALF_WAIT = 10, - parameter DAC_CYCLE_HALF_WAIT_SIZ = 4, - parameter DAC_SS_WAIT = 5, - parameter DAC_SS_WAIT_SIZ = 3, - parameter TIMER_WID = 32, - parameter WORD_WID = 20, - parameter WORD_AMNT_WID = 11, - parameter [WORD_AMNT_WID-1:0] WORD_AMNT = 2047, - parameter RAM_REAL_START = 32'h12340, - parameter RAM_CNTR_LEN = 12, - parameter TOTAL_RAM_WORD_MINUS_ONE = 4095, - parameter DELAY_CNTR_LEN = 8, - parameter DELAY_TOTAL = 12, - parameter RAM_WID = 32, - parameter RAM_WORD_WID = 16, - parameter RAM_WORD_INCR = 2 + parameter RAM_START_ADDR = 32'h0, + parameter SPI_START_ADDR = 32'h10000000, + parameter COUNTER_MAX_WID = 16, + parameter TIMER_WID = 16 ) ( input clk, - input rst_L, - input arm, - input halt_on_finish, - output waveform_finished, - output running, - input [TIMER_WID-1:0] time_to_wait, - /* User interface */ - input refresh_start, - input [RAM_WID-1:0] start_addr, - output reg refresh_finished, - - output [DAC_WID-1:0] from_master, + /* Waveform output control */ + input run, + output [COUNTER_MAX_WID-1:0] cntr, + input do_loop, output finished, - input rdy, - output spi_err, + output ready, + input [COUNTER_MAX_WID-1:0] wform_size, + output [TIMER_WID-1:0] timer, + input [TIMER_WID-1:0] timer_spacing, - input[RAM_WORD_WID-1:0] backing_store [TOTAL_RAM_WORD_MINUS_ONE:0] + /* data requests to Verilator */ + + output reg [32-1:0] offset, + output reg [32-1:0] spi_data, + input [32-1:0] ram_data, + output reg [1:0] enable, + input ram_finished, + + /* Misc */ + input [32-1:0] spi_max_wait ); -wire sck; -wire ss_L; -wire mosi; - -spi_slave_no_write #( - .WID(DAC_WID), - .WID_LEN(DAC_WID_SIZ), - .POLARITY(DAC_POLARITY), - .PHASE(DAC_PHASE) -) slave ( - .clk(clk), - .sck(sck), - .rst_L(rst_L), - .ss_L(ss_L), - .mosi(mosi), - .from_master(from_master), - .finished(finished), - .rdy(rdy), - .err(spi_err) -); - -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) -); +wire [32-1:0] wb_adr; +wire wb_cyc; +wire wb_we; +wire wb_stb; +wire [32-1:0] wb_dat_w; +reg [32-1:0] wb_dat_r; +wire [4-1:0] wb_sel; +reg wb_ack = 0; waveform #( - .DAC_WID(DAC_WID), - .DAC_WID_SIZ(DAC_WID_SIZ), - .DAC_POLARITY(DAC_POLARITY), - .DAC_PHASE(DAC_PHASE), - .DAC_CYCLE_HALF_WAIT(DAC_CYCLE_HALF_WAIT), - .DAC_CYCLE_HALF_WAIT_SIZ(DAC_CYCLE_HALF_WAIT_SIZ), - .DAC_SS_WAIT(DAC_SS_WAIT), - .DAC_SS_WAIT_SIZ(DAC_SS_WAIT_SIZ), - .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) -) waveform ( + .RAM_START_ADDR(RAM_START_ADDR), + .SPI_START_ADDR(SPI_START_ADDR), + .COUNTER_MAX_WID(COUNTER_MAX_WID), + .TIMER_WID(TIMER_WID) +) wf ( .clk(clk), - .arm(arm), - .rst_L(rst_L), - .halt_on_finish(halt_on_finish), - .running(running), - .finished(waveform_finished), - .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), - - .mosi(mosi), - .sck(sck), - .ss_L(ss_L) + .run(run), + .cntr(cntr), + .do_loop(do_loop), + .finished(finished), + .ready(ready), + .wform_size(wform_size), + .timer(timer), + .timer_spacing(timer_spacing), + .wb_adr(wb_adr), + .wb_cyc(wb_cyc), + .wb_we(wb_we), + .wb_stb(wb_stb), + .wb_dat_w(wb_dat_w), + .wb_dat_r(wb_dat_r), + .wb_ack(wb_ack), + .wb_sel(wb_sel) ); +reg [32-1:0] spi_cntr = 0; +reg spi_armed = 0; +reg spi_running = 0; +reg spi_ready_to_arm = 1; +reg spi_finished = 0; + +/* SPI Delay simulation */ + +always @ (posedge clk) if ((spi_armed || spi_running) && !spi_finished) begin + spi_running <= 1; + spi_ready_to_arm <= 0; + if (spi_cntr == spi_max_wait) begin + spi_cntr <= 0; + spi_finished <= 1; + end else begin + spi_cntr <= spi_cntr + 1; + end +end else if (spi_finished) begin + spi_running <= 0; + if (!spi_armed) begin + spi_finished <= 0; + spi_ready_to_arm <= 1; + end +end + +/* Bus handlers */ + +always @* if (wb_we && wb_sel != 4'b1111) + $error("Write request without writing entire word"); + +always @ (posedge clk) if (wb_cyc & wb_stb & ~wb_ack) begin + if (wb_adr >= SPI_START_ADDR) case (wb_adr) + SPI_START_ADDR | 32'h4: if (wb_we) begin + spi_armed <= wb_dat_w[0]; + wb_ack <= 1; + end else begin + wb_dat_r[0] <= spi_armed; + wb_ack <= 1; + end + SPI_START_ADDR | 32'hC: begin + if (!wb_we) $error("Waveform should never read the to_slave register"); + + /* Write SPI Data to verilator immediately, but have a counter + * running in the background to simulate SPI transfer */ + spi_data <= wb_dat_w; + enable <= 2'b10; + if (ram_finished) begin + enable <= 0; + wb_ack <= 1; + end + end + SPI_START_ADDR | 32'h10: begin + if (wb_we) $error("Blocking SPI status check is read only register"); + if (spi_ready_to_arm || spi_finished) begin + wb_dat_r[0] <= spi_ready_to_arm; + wb_dat_r[1] <= spi_finished; + wb_ack <= 1; + end + end + default: begin + $error("Invalid SPI address"); + end + endcase else begin + offset <= wb_adr; + enable <= 2'b01; + if (ram_finished) begin + wb_dat_r <= ram_data; + enable <= 0; + wb_ack <= 1; + end + end +end else if (~wb_cyc) begin + wb_ack <= 0; +end + +initial begin + $dumpfile("waveform.fst"); + $dumpvars; +end + endmodule -`undefineall diff --git a/gateware/soc.py b/gateware/soc.py index 5ad34df..a382b5a 100644 --- a/gateware/soc.py +++ b/gateware/soc.py @@ -285,7 +285,6 @@ class UpsilonSoC(SoCCore): platform.add_source("rtl/picorv32/picorv32.v") platform.add_source("rtl/spi/spi_master_preprocessed.v") platform.add_source("rtl/spi/spi_master_ss.v") - platform.add_source("rtl/spi/spi_master_ss_wb.v") # SoCCore does not have sane defaults (no integrated rom) SoCCore.__init__(self,