waveform: write and start simulation

This commit is contained in:
Peter McGoron 2024-03-03 22:35:19 +00:00
parent 35f55c8e1d
commit 1d85e2307d
18 changed files with 556 additions and 932 deletions

View File

@ -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

View File

@ -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

View File

@ -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,
)

View File

@ -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

View File

@ -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 #(

View File

@ -24,6 +24,15 @@ template <class TOP> 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() {

View File

@ -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

View File

@ -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 <cstdlib>
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;
}
}

View File

@ -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 <cstddef>
template<size_t WORD_AMNT, size_t TIMER_MAX>
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);
};

View File

@ -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

View File

@ -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 <limits>
#include <cstdlib>
#include <random>
#include <unistd.h>
#include <verilated.h>
#include "Vbram_interface_sim.h"
#include "../testbench.hpp"
std::array<uint32_t, WORD_AMNT> ram_refresh_data;
TB<Vbram_interface_sim> *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<Vbram_interface_sim>();
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;
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,108 @@
#include <vector>
#include <random>
#include <stdexcept>
#include <iostream>
#include <cstdlib>
#include "Vwaveform_sim.h"
#include "../testbench.hpp"
class WaveformTestbench : public TB<Vwaveform_sim> {
public:
void start_test(int, int, int, bool);
void halt_check(bool);
WaveformTestbench(int _bailout) : TB<Vwaveform_sim>(_bailout) {}
private:
std::vector<uint32_t> send_words;
std::vector<uint32_t> 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<uint32_t>(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;
}

View File

@ -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

View File

@ -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 <vector>
#include "Vwaveform_sim.h"
#include "../testbench.hpp"
class WaveformTestbench : public TB<Vwaveform_sim> {
private:
void refresh_posedge();
void spi_posedge();
public:
std::array<uint32_t, WORD_AMNT> ram_refresh_data;
int cur_ind;
void posedge() override;
void refresh_data();
WaveformTestbench(int _bailout = 0) : TB<Vwaveform_sim>(_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;
}

View File

@ -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

View File

@ -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,