generated from saji/ecp5-template
wip: text fixture refactor
This commit is contained in:
parent
3a76a55e56
commit
da9c0c05a7
|
@ -20,7 +20,17 @@ FetchContent_Declare(
|
|||
GIT_TAG 55bc9cf3fa4051d485d10412c75c893c3135e885
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(sokol dear_imgui)
|
||||
|
||||
FetchContent_Declare(
|
||||
Catch2
|
||||
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||
GIT_TAG v3.4.0 # or a later release
|
||||
)
|
||||
|
||||
|
||||
FetchContent_MakeAvailable(sokol dear_imgui Catch2)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) # needed for the catch_discover_tests function
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
||||
|
||||
|
@ -31,6 +41,14 @@ target_sources(sim PRIVATE
|
|||
src/main.cpp
|
||||
)
|
||||
|
||||
|
||||
|
||||
list(APPEND VSOURCES ../verilog/hub75e.sv ../verilog/lineram.v)
|
||||
|
||||
verilate(sim SOURCES ${VSOURCES} TRACE VERILATOR_ARGS -Wno-MULTITOP)
|
||||
|
||||
target_link_libraries(sim PRIVATE Catch2::Catch2WithMain)
|
||||
|
||||
include(CTest)
|
||||
include(Catch)
|
||||
catch_discover_tests(sim)
|
||||
|
|
232
sim/src/main.cpp
232
sim/src/main.cpp
|
@ -2,12 +2,22 @@
|
|||
#include "verilated.h"
|
||||
#include "verilated_vcd_c.h"
|
||||
#include <array>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers.hpp>
|
||||
#include <catch2/matchers/catch_matchers_all.hpp>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
class CosimulatedDevice {
|
||||
public:
|
||||
virtual ~CosimulatedDevice(){};
|
||||
|
||||
virtual void tick() = 0;
|
||||
};
|
||||
|
||||
// slices the RGB values for us.
|
||||
uint8_t rgb_slice(uint32_t rgb, uint8_t bit) {
|
||||
if (bit > 8) {
|
||||
|
@ -19,19 +29,20 @@ uint8_t rgb_slice(uint32_t rgb, uint8_t bit) {
|
|||
uint8_t b = (rgb >> bit) & 1;
|
||||
return (r << 2) & (g << 1) & (b << 1);
|
||||
}
|
||||
class HUB75Reciever {
|
||||
|
||||
class HUB75Reciever : public CosimulatedDevice {
|
||||
|
||||
typedef std::vector<unsigned char> row_array;
|
||||
int xsize;
|
||||
int ysize;
|
||||
|
||||
row_array row_upper;
|
||||
row_array row_lower;
|
||||
row_array row_upper{};
|
||||
row_array row_lower{};
|
||||
|
||||
// the previous row values that were latched in.
|
||||
std::vector<row_array> past_rows;
|
||||
std::vector<row_array> past_rows{};
|
||||
// the pulse width for each output, in clock cycles.
|
||||
std::vector<int> pulse_widths;
|
||||
std::vector<int> pulse_widths{};
|
||||
|
||||
int bit_position = 7; // the bit that is currently being shifted in
|
||||
|
||||
|
@ -46,19 +57,29 @@ class HUB75Reciever {
|
|||
|
||||
unsigned char prev_oe = 0;
|
||||
|
||||
// references to the panel driver signals.
|
||||
VL_IN8(&display_clk, 0, 0);
|
||||
VL_IN8(&out_enable, 0, 0);
|
||||
VL_IN8(&latch, 0, 0);
|
||||
VL_IN8(&rgb0, 2, 0);
|
||||
VL_IN8(&rgb1, 2, 0);
|
||||
|
||||
public:
|
||||
HUB75Reciever(int xsize, int ysize) : row_upper(xsize, 0) {
|
||||
HUB75Reciever(int xsize, int ysize, Vhub75e &dut)
|
||||
: display_clk(dut.display_clk), out_enable(dut.out_enable),
|
||||
latch(dut.latch), rgb0(dut.panel_rgb0), rgb1(dut.panel_rgb1) {
|
||||
this->xsize = xsize;
|
||||
this->ysize = ysize;
|
||||
row_upper.clear();
|
||||
};
|
||||
|
||||
// evaluates the reciever.
|
||||
void eval(const int oe, const int latch, const int display_clk,
|
||||
const int rgb0) {
|
||||
virtual void tick() override {
|
||||
|
||||
if (prev_display_clk == 0 && display_clk == 1) {
|
||||
// display clock rising edge.
|
||||
row_upper.push_back(rgb0);
|
||||
row_lower.push_back(rgb1);
|
||||
}
|
||||
|
||||
if (prev_latch == 0 && latch == 1) {
|
||||
|
@ -67,7 +88,7 @@ public:
|
|||
past_rows.push_back(row_upper);
|
||||
row_upper.clear();
|
||||
}
|
||||
if (oe == 0) {
|
||||
if (out_enable == 0) {
|
||||
if (prev_oe == 1) {
|
||||
// falling edge.
|
||||
output_period_cnt = 0;
|
||||
|
@ -81,17 +102,37 @@ public:
|
|||
// update previous values
|
||||
prev_display_clk = display_clk;
|
||||
prev_latch = latch;
|
||||
prev_oe = oe;
|
||||
prev_oe = out_enable;
|
||||
}
|
||||
|
||||
const std::vector<row_array> &get_past_rows() { return this->past_rows; }
|
||||
const std::vector<int> &get_pulse_widths() { return this->pulse_widths; }
|
||||
|
||||
// return the RGB version.
|
||||
std::vector<unsigned int> transpose() {
|
||||
auto vec = std::vector<unsigned int>();
|
||||
|
||||
for (auto bits : this->row_upper) {
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
};
|
||||
|
||||
class FakeBRAM {
|
||||
std::array<uint64_t, 512> ram{};
|
||||
class FakeBRAM : public CosimulatedDevice {
|
||||
std::array<unsigned long, 512> ram{};
|
||||
|
||||
std::queue<uint16_t> addr_lookup_q;
|
||||
std::queue<unsigned long> addr_lookup_q;
|
||||
|
||||
unsigned short &addr_in;
|
||||
unsigned long &data_out;
|
||||
|
||||
unsigned char &clk;
|
||||
|
||||
public:
|
||||
FakeBRAM(int latency) : addr_lookup_q() {
|
||||
FakeBRAM(int latency, unsigned char &clk, unsigned short &addr_in,
|
||||
unsigned long &data_out)
|
||||
: addr_lookup_q(), addr_in(addr_in), data_out(data_out), clk(clk) {
|
||||
for (int i = 0; i < latency; i++) {
|
||||
addr_lookup_q.push(0);
|
||||
}
|
||||
|
@ -101,17 +142,20 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
uint64_t tick(int addr) {
|
||||
void tick() {
|
||||
// we push and pop in the same tick: this way we keep the queue the same
|
||||
// size, acting as a pipeline delay.
|
||||
addr_lookup_q.push(addr);
|
||||
addr_lookup_q.push(addr_in);
|
||||
|
||||
auto addr_to_load = addr_lookup_q.front();
|
||||
addr_lookup_q.pop();
|
||||
return ram.at(addr_to_load);
|
||||
data_out = ram.at(addr_to_load);
|
||||
}
|
||||
|
||||
// TODO: allow accessing/setting the data
|
||||
std::array<uint64_t, 512> &dump() { return ram; }
|
||||
|
||||
void write(unsigned short addr, unsigned long data) { ram[addr] = data; }
|
||||
};
|
||||
|
||||
void LineDriverTest(VerilatedContext &ctx) {
|
||||
|
@ -119,14 +163,16 @@ void LineDriverTest(VerilatedContext &ctx) {
|
|||
|
||||
// we generate 512 random color values (24 bits)
|
||||
// we load them in.
|
||||
unsigned long counter = 0; // counts positive edges.
|
||||
unsigned long posedge = 0; // counts positive edges.
|
||||
unsigned long simtime = 0;
|
||||
auto dut = std::make_unique<Vhub75e>(&ctx, "dut");
|
||||
|
||||
VerilatedVcdC *m_trace = new VerilatedVcdC;
|
||||
dut->trace(m_trace, 5);
|
||||
|
||||
auto bram = FakeBRAM(1);
|
||||
auto bram = FakeBRAM(1, dut->clk, dut->pixbuf_addr, dut->pixbuf_data);
|
||||
|
||||
auto hub75 = HUB75Reciever(128, 64, *dut);
|
||||
printf("Performing basic test\n");
|
||||
bool done = false;
|
||||
m_trace->open("waveform.vcd");
|
||||
|
@ -137,24 +183,154 @@ void LineDriverTest(VerilatedContext &ctx) {
|
|||
|
||||
if (dut->clk == 1) {
|
||||
// rising edge.
|
||||
counter++;
|
||||
dut->pixbuf_data = bram.tick(dut->pixbuf_addr);
|
||||
posedge++;
|
||||
bram.tick();
|
||||
}
|
||||
|
||||
dut->write_trig = counter < 20 ? 1 : 0;
|
||||
dut->write_trig = posedge < 2 ? 1 : 0;
|
||||
|
||||
if (counter >= 250000) {
|
||||
if (posedge >= 250000) {
|
||||
done = true;
|
||||
}
|
||||
hub75.tick();
|
||||
m_trace->dump(simtime);
|
||||
simtime++;
|
||||
}
|
||||
m_trace->close();
|
||||
};
|
||||
|
||||
int main() {
|
||||
auto ctx = std::make_unique<VerilatedContext>();
|
||||
ctx->traceEverOn(true);
|
||||
printf("hello world!\n");
|
||||
LineDriverTest(*ctx);
|
||||
// int main() {
|
||||
// auto ctx = std::make_unique<VerilatedContext>();
|
||||
// ctx->traceEverOn(true);
|
||||
// printf("hello world!\n");
|
||||
// LineDriverTest(*ctx);
|
||||
// };
|
||||
|
||||
// test fixture to reduce amount of runtime code.
|
||||
// Supports:
|
||||
// adding external modules
|
||||
// running the test
|
||||
// storing if the done flag was raised (or not)
|
||||
//
|
||||
// TODO: tracing.
|
||||
template <typename DUT> class VerilatorTestFixture {
|
||||
// we call .tick() on all of these. They are bound externally to the DUT.
|
||||
std::vector<std::shared_ptr<CosimulatedDevice>> external_devices;
|
||||
std::unique_ptr<VerilatedContext> ctx;
|
||||
std::unique_ptr<DUT> dut;
|
||||
|
||||
unsigned long timeout =
|
||||
1000000; // clock cycles to execute before ending the simulation.
|
||||
unsigned long simtime = 0;
|
||||
unsigned long posedge = 0;
|
||||
|
||||
// stores the termination-condition for the simulation.
|
||||
// if false, it means we timed out. If true, our done_condition returned true.
|
||||
bool done_condition = false;
|
||||
|
||||
typedef std::function<bool(DUT &, unsigned long)> done_callback;
|
||||
// callback function to determine execution completion.
|
||||
// This can be used to add arbitrary finish-conditions to the execution.
|
||||
// If the function returns true, we set the done_set boolean to true;
|
||||
done_callback done_check;
|
||||
|
||||
public:
|
||||
// Create a new test fixture with a given timeout. Everything else (including
|
||||
// done-condition) can be set later.
|
||||
VerilatorTestFixture() {
|
||||
ctx = std::make_unique<VerilatedContext>();
|
||||
dut = std::make_unique<DUT>(ctx.get(), "dut");
|
||||
}
|
||||
|
||||
void set_timeout(unsigned long new_timeout) { this->timeout = new_timeout; }
|
||||
|
||||
void set_done_callback(done_callback d) { this->done_check = d; }
|
||||
|
||||
void add_module(std::shared_ptr<CosimulatedDevice> device) {
|
||||
external_devices.push_back(device);
|
||||
}
|
||||
|
||||
void exec() {
|
||||
bool done = false;
|
||||
while (!done) {
|
||||
dut->clk ^= 1;
|
||||
dut->eval();
|
||||
|
||||
if (dut->clk == 1) {
|
||||
posedge++;
|
||||
}
|
||||
if (done_check) {
|
||||
if (done_check(dut, posedge)) {
|
||||
done_condition = true;
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (posedge >= timeout) {
|
||||
done = true;
|
||||
}
|
||||
// run our external devices
|
||||
for (auto dev : external_devices) {
|
||||
dev->tick();
|
||||
}
|
||||
dut->eval(); // allow combinational logic to settle if it's being set on the negative clock.
|
||||
simtime++;
|
||||
}
|
||||
}
|
||||
|
||||
// return a reference to the DUT itself. Useful for bespoke tests.
|
||||
DUT &get() { return dut; }
|
||||
};
|
||||
|
||||
TEST_CASE("Hub75 Test") {
|
||||
auto ctx = std::make_unique<VerilatedContext>();
|
||||
// setup DUT
|
||||
|
||||
unsigned long posedge = 0; // counts positive edges.
|
||||
unsigned long simtime = 0;
|
||||
auto dut = std::make_unique<Vhub75e>(ctx.get(), "dut");
|
||||
|
||||
auto bram = FakeBRAM(1, dut->clk, dut->pixbuf_addr, dut->pixbuf_data);
|
||||
|
||||
auto hub75 = HUB75Reciever(128, 64, *dut);
|
||||
bool done = false;
|
||||
|
||||
bool driver_done = false;
|
||||
while (!done) {
|
||||
dut->clk ^= 1; // toggle clock
|
||||
dut->eval();
|
||||
|
||||
if (dut->clk == 1) {
|
||||
// rising edge.
|
||||
posedge++;
|
||||
}
|
||||
|
||||
dut->write_trig = posedge < 2 ? 1 : 0;
|
||||
if (dut->done) {
|
||||
done = true;
|
||||
driver_done = true;
|
||||
}
|
||||
|
||||
if (posedge >= 250000) {
|
||||
done = true;
|
||||
driver_done = false;
|
||||
}
|
||||
hub75.tick();
|
||||
bram.tick();
|
||||
simtime++;
|
||||
}
|
||||
REQUIRE(driver_done);
|
||||
SECTION("Bit Sizing/Count") {
|
||||
|
||||
CHECK(hub75.get_past_rows().size() == 8);
|
||||
|
||||
auto rows = hub75.get_past_rows();
|
||||
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
auto r = rows[i];
|
||||
using Catch::Matchers::SizeIs;
|
||||
CHECK_THAT(r, SizeIs(128));
|
||||
}
|
||||
}
|
||||
SECTION("Pulse width") {}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue