generated from saji/ecp5-template
Compare commits
2 commits
3a76a55e56
...
2a7908eae9
Author | SHA1 | Date | |
---|---|---|---|
saji | 2a7908eae9 | ||
saji | da9c0c05a7 |
|
@ -20,7 +20,17 @@ FetchContent_Declare(
|
||||||
GIT_TAG 55bc9cf3fa4051d485d10412c75c893c3135e885
|
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)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
|
||||||
|
|
||||||
|
@ -31,6 +41,16 @@ target_sources(sim PRIVATE
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_include_directories(sim PRIVATE inc/)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
list(APPEND VSOURCES ../verilog/hub75e.sv ../verilog/lineram.v)
|
list(APPEND VSOURCES ../verilog/hub75e.sv ../verilog/lineram.v)
|
||||||
|
|
||||||
verilate(sim SOURCES ${VSOURCES} TRACE VERILATOR_ARGS -Wno-MULTITOP)
|
verilate(sim SOURCES ${VSOURCES} TRACE VERILATOR_ARGS -Wno-MULTITOP)
|
||||||
|
|
||||||
|
target_link_libraries(sim PRIVATE Catch2::Catch2WithMain)
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
include(Catch)
|
||||||
|
catch_discover_tests(sim)
|
||||||
|
|
150
sim/inc/devices.hpp
Normal file
150
sim/inc/devices.hpp
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
// Project-specific cosimuluated devices.
|
||||||
|
#pragma once
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "Vhub75e.h"
|
||||||
|
|
||||||
|
// slices the RGB values for us.
|
||||||
|
uint8_t rgb_slice(uint32_t rgb, uint8_t bit) {
|
||||||
|
if (bit > 8) {
|
||||||
|
// todo: panic
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint8_t r = (rgb >> (16 + bit)) & 1;
|
||||||
|
uint8_t g = (rgb >> (8 + bit)) & 1;
|
||||||
|
uint8_t b = (rgb >> bit) & 1;
|
||||||
|
return (r << 2) & (g << 1) & (b << 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rgb_unslice(unsigned int &rgb, uint8_t bits, uint8_t bitpos) {
|
||||||
|
if (bitpos > 7 || bits > 0b111) {
|
||||||
|
// TODO: panic.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto r = (bits >> 2) & 1;
|
||||||
|
auto g = (bits >> 1) & 1;
|
||||||
|
auto b = (bits >> 0) & 1;
|
||||||
|
|
||||||
|
rgb |= r << bitpos << 16;
|
||||||
|
rgb |= g << bitpos << 8;
|
||||||
|
rgb |= b << bitpos << 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class HUB75Reciever : public CosimulatedDevice {
|
||||||
|
|
||||||
|
typedef std::vector<unsigned char> row_array;
|
||||||
|
int xsize;
|
||||||
|
int ysize;
|
||||||
|
|
||||||
|
row_array row0{};
|
||||||
|
row_array row1{};
|
||||||
|
|
||||||
|
// the previous row values that were latched in.
|
||||||
|
std::vector<std::pair<row_array, row_array>> past_rows{};
|
||||||
|
// the pulse width for each output, in clock cycles.
|
||||||
|
std::vector<int> pulse_widths{};
|
||||||
|
|
||||||
|
int bit_position = 7; // the bit that is currently being shifted in
|
||||||
|
|
||||||
|
int output_period_cnt;
|
||||||
|
// if oe = 0, count clocks. when oe = 1, store value into
|
||||||
|
// pulse_widths[display_bit];
|
||||||
|
|
||||||
|
// previous latch value, used to identify when to latch.
|
||||||
|
unsigned char prev_latch = 0;
|
||||||
|
// previous display clock value, used to detect rising edge.
|
||||||
|
unsigned char prev_display_clk = 0;
|
||||||
|
|
||||||
|
unsigned char prev_clk = 0;
|
||||||
|
|
||||||
|
unsigned char prev_oe = 1; // assuming starting high.
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
VL_IN8(&clk, 0, 0);
|
||||||
|
|
||||||
|
public:
|
||||||
|
HUB75Reciever(int xsize, int ysize, const Vhub75e &dut)
|
||||||
|
: clk(dut.clk), 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;
|
||||||
|
row0.clear();
|
||||||
|
prev_oe = out_enable;
|
||||||
|
prev_display_clk = display_clk;
|
||||||
|
prev_latch = latch;
|
||||||
|
prev_clk = clk;
|
||||||
|
};
|
||||||
|
|
||||||
|
// evaluates the reciever.
|
||||||
|
virtual void tick() override {
|
||||||
|
|
||||||
|
if (prev_display_clk == 0 && display_clk == 1) {
|
||||||
|
// display clock rising edge.
|
||||||
|
row0.push_back(rgb0);
|
||||||
|
row1.push_back(rgb1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev_latch == 0 && latch == 1) {
|
||||||
|
// latch in the data: reverse the rows, and pu
|
||||||
|
std::reverse(row0.begin(), row0.end());
|
||||||
|
std::reverse(row1.begin(), row1.end());
|
||||||
|
past_rows.push_back(std::pair(row0, row1));
|
||||||
|
row0.clear();
|
||||||
|
row1.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev_clk == 0 && clk == 1) {
|
||||||
|
if (out_enable == 0) {
|
||||||
|
if (prev_oe == 1) {
|
||||||
|
// falling edge.
|
||||||
|
output_period_cnt = 1;
|
||||||
|
} else {
|
||||||
|
output_period_cnt++;
|
||||||
|
}
|
||||||
|
} else { // out_enable == 1
|
||||||
|
if (prev_oe == 1) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
if (prev_oe == 0) {
|
||||||
|
// rising edge
|
||||||
|
pulse_widths.push_back(output_period_cnt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update previous values
|
||||||
|
prev_display_clk = display_clk;
|
||||||
|
prev_latch = latch;
|
||||||
|
prev_oe = out_enable;
|
||||||
|
prev_clk = clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &get_past_rows() { return this->past_rows; }
|
||||||
|
const std::vector<int> &get_pulse_widths() { return this->pulse_widths; }
|
||||||
|
|
||||||
|
// return the RGB version.
|
||||||
|
std::pair<std::vector<unsigned int>, std::vector<unsigned int>> transpose() {
|
||||||
|
auto r0rgb = std::vector<unsigned int>(xsize, 0);
|
||||||
|
auto r1rgb = std::vector<unsigned int>(xsize, 0);
|
||||||
|
|
||||||
|
auto bitdepth = pulse_widths.size();
|
||||||
|
|
||||||
|
// TODO: use more sophisticated slicing.
|
||||||
|
auto slice = bitdepth - 1;
|
||||||
|
for (const auto &[row0slice, row1slice] : this->past_rows) {
|
||||||
|
|
||||||
|
for (int i = 0; i < row0slice.size(); i++) {
|
||||||
|
rgb_unslice(r0rgb[i], row0slice[i], slice);
|
||||||
|
rgb_unslice(r1rgb[i], row1slice[i], slice);
|
||||||
|
}
|
||||||
|
|
||||||
|
slice--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::pair(r0rgb, r1rgb);
|
||||||
|
}
|
||||||
|
};
|
204
sim/inc/tests.hpp
Normal file
204
sim/inc/tests.hpp
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
// Common Verilator Simulation/test constructs.
|
||||||
|
// Includes a simulation-fixture useful for writing tests.
|
||||||
|
#pragma once
|
||||||
|
#include "verilated.h"
|
||||||
|
#include "verilated_vcd_c.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
#include <memory>
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
// represents a generic co-simulated device.
|
||||||
|
// While this abstract class is very simple, it enables dynamic behavior to be added to the
|
||||||
|
// simulation fixture.
|
||||||
|
class CosimulatedDevice {
|
||||||
|
public:
|
||||||
|
virtual ~CosimulatedDevice(){};
|
||||||
|
|
||||||
|
virtual void tick() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Simple stimulus class used to trigger basic operations
|
||||||
|
class PulseStimulus : public CosimulatedDevice {
|
||||||
|
unsigned long width;
|
||||||
|
unsigned char &signal;
|
||||||
|
|
||||||
|
unsigned long counter = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PulseStimulus(unsigned char &signal, unsigned long width) : signal(signal) {
|
||||||
|
this->width = width;
|
||||||
|
}
|
||||||
|
virtual void tick() override {
|
||||||
|
if (counter < width) {
|
||||||
|
signal = 1;
|
||||||
|
} else {
|
||||||
|
signal = 0;
|
||||||
|
}
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FakeBRAM : public CosimulatedDevice {
|
||||||
|
std::array<unsigned long, 512> ram{};
|
||||||
|
|
||||||
|
std::queue<unsigned long> addr_lookup_q;
|
||||||
|
|
||||||
|
unsigned short &addr_in;
|
||||||
|
unsigned long &data_out;
|
||||||
|
|
||||||
|
unsigned char &clk;
|
||||||
|
|
||||||
|
public:
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < ram.size(); i++) {
|
||||||
|
ram[i] = i + 3;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FakeBRAM(int latency, unsigned char &clk, unsigned short &addr_in,
|
||||||
|
unsigned long &data_out, std::array<unsigned long, 512> ram)
|
||||||
|
: addr_lookup_q(), addr_in(addr_in), data_out(data_out), clk(clk),
|
||||||
|
ram(ram) {
|
||||||
|
for (int i = 0; i < latency; i++) {
|
||||||
|
addr_lookup_q.push(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_in);
|
||||||
|
|
||||||
|
auto addr_to_load = addr_lookup_q.front();
|
||||||
|
addr_lookup_q.pop();
|
||||||
|
data_out = ram.at(addr_to_load);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: allow accessing/setting the data
|
||||||
|
const std::span<uint64_t, 512> get() { return this->ram; }
|
||||||
|
|
||||||
|
void write(unsigned short addr, unsigned long data) { ram[addr] = data; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
public:
|
||||||
|
enum class FinishReason { Ok, Timeout };
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 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.
|
||||||
|
enum FinishReason reason;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
std::unique_ptr<VerilatedVcdC> trace;
|
||||||
|
|
||||||
|
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");
|
||||||
|
dut->eval(); // let values settle before adding modules.
|
||||||
|
}
|
||||||
|
|
||||||
|
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 enable_trace(std::string name) {
|
||||||
|
if (!trace) {
|
||||||
|
ctx->traceEverOn(true);
|
||||||
|
trace = std::make_unique<VerilatedVcdC>();
|
||||||
|
dut->trace(trace.get(), 99);
|
||||||
|
trace->open(name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void exec() {
|
||||||
|
bool done = false;
|
||||||
|
dut->eval(); // pre-eval.
|
||||||
|
while (!done) {
|
||||||
|
dut->clk ^= 1;
|
||||||
|
dut->eval();
|
||||||
|
|
||||||
|
if (trace) {
|
||||||
|
trace->dump(10 * simtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dut->clk == 1) {
|
||||||
|
posedge++;
|
||||||
|
}
|
||||||
|
if (done_check) {
|
||||||
|
if (done_check(*dut, posedge)) {
|
||||||
|
reason = FinishReason::Ok;
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (posedge >= timeout) {
|
||||||
|
reason = FinishReason::Timeout;
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
// run our external devices
|
||||||
|
for (auto dev : external_devices) {
|
||||||
|
dev->tick();
|
||||||
|
}
|
||||||
|
if (trace) {
|
||||||
|
trace->dump(10 * simtime + 5);
|
||||||
|
}
|
||||||
|
dut->eval(); // allow combinational logic to settle if it's being set on
|
||||||
|
// the negative clock.
|
||||||
|
if (trace) {
|
||||||
|
trace->dump(10 * simtime + 6);
|
||||||
|
}
|
||||||
|
simtime++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trace) {
|
||||||
|
// close the trace.
|
||||||
|
trace->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a reference to the DUT itself. Useful for bespoke tests.
|
||||||
|
const DUT &get() { return *dut; }
|
||||||
|
|
||||||
|
const FinishReason get_reason() { return reason; }
|
||||||
|
};
|
241
sim/src/main.cpp
241
sim/src/main.cpp
|
@ -1,132 +1,29 @@
|
||||||
#include "Vhub75e.h"
|
#include "Vhub75e.h"
|
||||||
|
#include "tests.hpp"
|
||||||
|
#include "devices.hpp"
|
||||||
#include "verilated.h"
|
#include "verilated.h"
|
||||||
#include "verilated_vcd_c.h"
|
#include "verilated_vcd_c.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// slices the RGB values for us.
|
|
||||||
uint8_t rgb_slice(uint32_t rgb, uint8_t bit) {
|
|
||||||
if (bit > 8) {
|
|
||||||
// todo: panic
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
uint8_t r = (rgb >> (16 + bit)) & 1;
|
|
||||||
uint8_t g = (rgb >> (8 + bit)) & 1;
|
|
||||||
uint8_t b = (rgb >> bit) & 1;
|
|
||||||
return (r << 2) & (g << 1) & (b << 1);
|
|
||||||
}
|
|
||||||
class HUB75Reciever {
|
|
||||||
|
|
||||||
typedef std::vector<unsigned char> row_array;
|
|
||||||
int xsize;
|
|
||||||
int ysize;
|
|
||||||
|
|
||||||
row_array row_upper;
|
|
||||||
row_array row_lower;
|
|
||||||
|
|
||||||
// the previous row values that were latched in.
|
|
||||||
std::vector<row_array> past_rows;
|
|
||||||
// the pulse width for each output, in clock cycles.
|
|
||||||
std::vector<int> pulse_widths;
|
|
||||||
|
|
||||||
int bit_position = 7; // the bit that is currently being shifted in
|
|
||||||
|
|
||||||
int output_period_cnt;
|
|
||||||
// if oe = 0, count clocks. when oe = 1, store value into
|
|
||||||
// pulse_widths[display_bit];
|
|
||||||
|
|
||||||
// previous latch value, used to identify when to latch.
|
|
||||||
unsigned char prev_latch = 0;
|
|
||||||
// previous display clock value, used to detect rising edge.
|
|
||||||
unsigned char prev_display_clk = 0;
|
|
||||||
|
|
||||||
unsigned char prev_oe = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
HUB75Reciever(int xsize, int ysize) : row_upper(xsize, 0) {
|
|
||||||
this->xsize = xsize;
|
|
||||||
this->ysize = ysize;
|
|
||||||
};
|
|
||||||
|
|
||||||
// evaluates the reciever.
|
|
||||||
void eval(const int oe, const int latch, const int display_clk,
|
|
||||||
const int rgb0) {
|
|
||||||
|
|
||||||
if (prev_display_clk == 0 && display_clk == 1) {
|
|
||||||
// display clock rising edge.
|
|
||||||
row_upper.push_back(rgb0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev_latch == 0 && latch == 1) {
|
|
||||||
// latch in the data: reverse the rows, and pu
|
|
||||||
std::reverse(row_upper.begin(), row_upper.end());
|
|
||||||
past_rows.push_back(row_upper);
|
|
||||||
row_upper.clear();
|
|
||||||
}
|
|
||||||
if (oe == 0) {
|
|
||||||
if (prev_oe == 1) {
|
|
||||||
// falling edge.
|
|
||||||
output_period_cnt = 0;
|
|
||||||
} else {
|
|
||||||
output_period_cnt++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// rising edge: store the output
|
|
||||||
}
|
|
||||||
|
|
||||||
// update previous values
|
|
||||||
prev_display_clk = display_clk;
|
|
||||||
prev_latch = latch;
|
|
||||||
prev_oe = oe;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class FakeBRAM {
|
|
||||||
std::array<uint64_t, 512> ram{};
|
|
||||||
|
|
||||||
std::queue<uint16_t> addr_lookup_q;
|
|
||||||
|
|
||||||
public:
|
|
||||||
FakeBRAM(int latency) : addr_lookup_q() {
|
|
||||||
for (int i = 0; i < latency; i++) {
|
|
||||||
addr_lookup_q.push(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < ram.size(); i++) {
|
|
||||||
ram[i] = i;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
uint64_t tick(int addr) {
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
auto addr_to_load = addr_lookup_q.front();
|
|
||||||
addr_lookup_q.pop();
|
|
||||||
return ram.at(addr_to_load);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: allow accessing/setting the data
|
|
||||||
};
|
|
||||||
|
|
||||||
void LineDriverTest(VerilatedContext &ctx) {
|
void LineDriverTest(VerilatedContext &ctx) {
|
||||||
// create the hub75e driver and run some basic tests
|
// create the hub75e driver and run some basic tests
|
||||||
|
|
||||||
// we generate 512 random color values (24 bits)
|
// we generate 512 random color values (24 bits)
|
||||||
// we load them in.
|
// we load them in.
|
||||||
unsigned long counter = 0; // counts positive edges.
|
unsigned long posedge = 0; // counts positive edges.
|
||||||
unsigned long simtime = 0;
|
unsigned long simtime = 0;
|
||||||
auto dut = std::make_unique<Vhub75e>(&ctx, "dut");
|
auto dut = std::make_unique<Vhub75e>(&ctx, "dut");
|
||||||
|
|
||||||
VerilatedVcdC *m_trace = new VerilatedVcdC;
|
VerilatedVcdC *m_trace = new VerilatedVcdC;
|
||||||
dut->trace(m_trace, 5);
|
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");
|
printf("Performing basic test\n");
|
||||||
bool done = false;
|
bool done = false;
|
||||||
m_trace->open("waveform.vcd");
|
m_trace->open("waveform.vcd");
|
||||||
|
@ -137,24 +34,130 @@ void LineDriverTest(VerilatedContext &ctx) {
|
||||||
|
|
||||||
if (dut->clk == 1) {
|
if (dut->clk == 1) {
|
||||||
// rising edge.
|
// rising edge.
|
||||||
counter++;
|
posedge++;
|
||||||
dut->pixbuf_data = bram.tick(dut->pixbuf_addr);
|
bram.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
dut->write_trig = counter < 20 ? 1 : 0;
|
dut->write_trig = posedge < 2 ? 1 : 0;
|
||||||
|
|
||||||
if (counter >= 250000) {
|
if (posedge >= 250000) {
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
|
hub75.tick();
|
||||||
m_trace->dump(simtime);
|
m_trace->dump(simtime);
|
||||||
simtime++;
|
simtime++;
|
||||||
}
|
}
|
||||||
m_trace->close();
|
m_trace->close();
|
||||||
};
|
};
|
||||||
|
|
||||||
int main() {
|
// int main() {
|
||||||
|
// auto ctx = std::make_unique<VerilatedContext>();
|
||||||
|
// ctx->traceEverOn(true);
|
||||||
|
// printf("hello world!\n");
|
||||||
|
// LineDriverTest(*ctx);
|
||||||
|
// };
|
||||||
|
|
||||||
|
TEST_CASE("Hub75 Test") {
|
||||||
auto ctx = std::make_unique<VerilatedContext>();
|
auto ctx = std::make_unique<VerilatedContext>();
|
||||||
ctx->traceEverOn(true);
|
// setup DUT
|
||||||
printf("hello world!\n");
|
|
||||||
LineDriverTest(*ctx);
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SECTION("Pulse width") {}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("HUB75E Driver Test") {
|
||||||
|
auto fixture = VerilatorTestFixture<Vhub75e>();
|
||||||
|
// very simple done checker.
|
||||||
|
auto done_check = [](Vhub75e &dut, unsigned long time) {
|
||||||
|
return dut.done == 1;
|
||||||
|
};
|
||||||
|
fixture.set_done_callback(done_check);
|
||||||
|
const Vhub75e &dut = fixture.get();
|
||||||
|
auto stim = std::make_shared<PulseStimulus>(dut.write_trig, 4);
|
||||||
|
fixture.set_timeout(250000);
|
||||||
|
|
||||||
|
fixture.add_module(stim);
|
||||||
|
|
||||||
|
SECTION("Smoke Tests") {
|
||||||
|
fixture.enable_trace("testing.vcd");
|
||||||
|
auto bram = std::make_shared<FakeBRAM>(1, dut.clk, dut.pixbuf_addr,
|
||||||
|
dut.pixbuf_data);
|
||||||
|
fixture.add_module(bram);
|
||||||
|
auto display = std::make_shared<HUB75Reciever>(128, 64, dut);
|
||||||
|
fixture.add_module(display);
|
||||||
|
|
||||||
|
fixture.exec();
|
||||||
|
|
||||||
|
CHECK(fixture.get_reason() ==
|
||||||
|
VerilatorTestFixture<Vhub75e>::FinishReason::Ok);
|
||||||
|
|
||||||
|
auto rows = display->get_past_rows();
|
||||||
|
CHECK(rows.size() == 8);
|
||||||
|
for (int i = 0; i < rows.size(); i++) {
|
||||||
|
auto &[r0, r1] = rows[i];
|
||||||
|
CHECK(r0.size() == 128);
|
||||||
|
CHECK(r1.size() == 128);
|
||||||
|
}
|
||||||
|
// pulse width smoke tests.
|
||||||
|
auto pulses = display->get_pulse_widths();
|
||||||
|
REQUIRE(pulses.size() == rows.size());
|
||||||
|
for (int i = 1; i < pulses.size(); i++) {
|
||||||
|
REQUIRE(pulses[i] == pulses[i - 1] / 2);
|
||||||
|
}
|
||||||
|
auto [row0, row1] = display->transpose();
|
||||||
|
REQUIRE(row0.size() == 128);
|
||||||
|
REQUIRE(row1.size() == 128);
|
||||||
|
auto ram_ref = bram->get();
|
||||||
|
|
||||||
|
CAPTURE(row0);
|
||||||
|
CHECK(std::equal(ram_ref.begin(), ram_ref.begin() + 128, row0.begin(),
|
||||||
|
row0.end()));
|
||||||
|
}
|
||||||
|
SECTION("Line Correctness") {
|
||||||
|
// this is the part where we validate that the line in = line out.
|
||||||
|
// we have to generate different values since the
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue