waveform: write and start simulation
This commit is contained in:
parent
35f55c8e1d
commit
1d85e2307d
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 #(
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
};
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
CHECK_LEN: if (cntr >= wform_size) begin
|
||||
if (do_loop) begin
|
||||
cntr <= 0;
|
||||
state <= READ_RAM;
|
||||
end else begin
|
||||
wait_timer <= wait_timer - 1;
|
||||
state <= WAIT_FINISHED;
|
||||
end
|
||||
RECV_WORD: begin
|
||||
`ifdef VERILATOR_SIMULATION
|
||||
if (!word_next) begin
|
||||
$error("RECV_WORD: word_next not asserted means hang");
|
||||
end else begin
|
||||
state <= READ_RAM;
|
||||
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;
|
||||
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 else begin
|
||||
state <= DO_WAIT;
|
||||
wait_timer <= time_to_wait;
|
||||
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_RAM: if (wb_ack) begin
|
||||
wb_cyc <= 0;
|
||||
wb_dat_w <= 1 << 20 | wb_dat_r;
|
||||
state <= WRITE_DAC_DATA_ADR;
|
||||
end
|
||||
WAIT_ON_DISARM: if (!arm) begin
|
||||
state <= WAIT_ON_ARM;
|
||||
WRITE_DAC_DATA_ADR: begin
|
||||
wb_adr <= SPI_START_ADDR + 32'hC;
|
||||
wb_cyc <= 1;
|
||||
wb_we <= 1;
|
||||
state <= WAIT_DAC_DATA_ADR;
|
||||
end
|
||||
endcase
|
||||
|
||||
/* Warning! This will crash verilator with a segmentation fault!
|
||||
`ifdef VERILATOR
|
||||
initial begin
|
||||
$dumpfile("waveform.fst");
|
||||
$dumpvars();
|
||||
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
|
||||
`endif
|
||||
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
|
||||
endmodule
|
||||
`undefineall
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue