From da9c0c05a713699a9c033c2ad09a5a27b0d89af7 Mon Sep 17 00:00:00 2001 From: saji Date: Tue, 21 May 2024 18:08:29 -0500 Subject: [PATCH] wip: text fixture refactor --- sim/CMakeLists.txt | 20 +++- sim/src/main.cpp | 232 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 223 insertions(+), 29 deletions(-) diff --git a/sim/CMakeLists.txt b/sim/CMakeLists.txt index 8e1d91d..850bdf2 100644 --- a/sim/CMakeLists.txt +++ b/sim/CMakeLists.txt @@ -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) diff --git a/sim/src/main.cpp b/sim/src/main.cpp index 0d720ab..a55eb4a 100644 --- a/sim/src/main.cpp +++ b/sim/src/main.cpp @@ -2,12 +2,22 @@ #include "verilated.h" #include "verilated_vcd_c.h" #include +#include +#include +#include #include #include #include #include #include +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 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 past_rows; + std::vector past_rows{}; // the pulse width for each output, in clock cycles. - std::vector pulse_widths; + std::vector 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 &get_past_rows() { return this->past_rows; } + const std::vector &get_pulse_widths() { return this->pulse_widths; } + + // return the RGB version. + std::vector transpose() { + auto vec = std::vector(); + + for (auto bits : this->row_upper) { + } + + return vec; } }; -class FakeBRAM { - std::array ram{}; +class FakeBRAM : public CosimulatedDevice { + std::array ram{}; - std::queue addr_lookup_q; + std::queue 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 &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(&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(); - ctx->traceEverOn(true); - printf("hello world!\n"); - LineDriverTest(*ctx); +// int main() { +// auto ctx = std::make_unique(); +// 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 class VerilatorTestFixture { + // we call .tick() on all of these. They are bound externally to the DUT. + std::vector> external_devices; + std::unique_ptr ctx; + std::unique_ptr 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 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(); + dut = std::make_unique(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 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(); + // setup DUT + + unsigned long posedge = 0; // counts positive edges. + unsigned long simtime = 0; + auto dut = std::make_unique(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") {} +}