diff --git a/flake.nix b/flake.nix index 6f7a6f4..75c0890 100644 --- a/flake.nix +++ b/flake.nix @@ -58,6 +58,9 @@ # loader openfpgaloader ecpdap # easier to poke probes. + + # for building the simulator + cmake ]; }; }); diff --git a/sim/.gitignore b/sim/.gitignore new file mode 100644 index 0000000..a4fb4fb --- /dev/null +++ b/sim/.gitignore @@ -0,0 +1,2 @@ +build/ +.cache/ diff --git a/sim/CMakeLists.txt b/sim/CMakeLists.txt new file mode 100644 index 0000000..8e1d91d --- /dev/null +++ b/sim/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.20) + +include(FetchContent) + +project(groovylight-sim) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_C_STANDARD 11) + + +FetchContent_Declare( + dear_imgui + GIT_REPOSITORY https://github.com/ocornut/imgui.git + GIT_TAG 231cbee0fc4f59dbe5b8b853a11b08dc84e57c65 # version 1.90.5 +) + +FetchContent_Declare( + sokol + GIT_REPOSITORY https://github.com/floooh/sokol.git + GIT_TAG 55bc9cf3fa4051d485d10412c75c893c3135e885 +) + +FetchContent_MakeAvailable(sokol dear_imgui) + +set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) + +find_package(verilator HINTS $ENV{VERILATOR_ROOT}) +add_executable(sim) + +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) diff --git a/sim/src/main.cpp b/sim/src/main.cpp new file mode 100644 index 0000000..0d720ab --- /dev/null +++ b/sim/src/main.cpp @@ -0,0 +1,160 @@ +#include "Vhub75e.h" +#include "verilated.h" +#include "verilated_vcd_c.h" +#include +#include +#include +#include +#include +#include + +// 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 row_array; + int xsize; + int ysize; + + row_array row_upper; + row_array row_lower; + + // the previous row values that were latched in. + std::vector past_rows; + // the pulse width for each output, in clock cycles. + std::vector 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 ram{}; + + std::queue 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) { + // create the hub75e driver and run some basic tests + + // we generate 512 random color values (24 bits) + // we load them in. + unsigned long counter = 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); + printf("Performing basic test\n"); + bool done = false; + m_trace->open("waveform.vcd"); + + while (!done) { + dut->clk ^= 1; // toggle clock + dut->eval(); + + if (dut->clk == 1) { + // rising edge. + counter++; + dut->pixbuf_data = bram.tick(dut->pixbuf_addr); + } + + dut->write_trig = counter < 20 ? 1 : 0; + + if (counter >= 250000) { + done = true; + } + m_trace->dump(simtime); + simtime++; + } + m_trace->close(); +}; + +int main() { + auto ctx = std::make_unique(); + ctx->traceEverOn(true); + printf("hello world!\n"); + LineDriverTest(*ctx); +}; diff --git a/verilog/lineram.v b/verilog/lineram.v index d3ca3e0..5f97d91 100644 --- a/verilog/lineram.v +++ b/verilog/lineram.v @@ -1,115 +1,119 @@ module lineram ( input [35:0] din, input [8:0] addr_w, - output wire [35:0] dout, + output reg [35:0] dout, input [8:0] addr_r, input write_en, input read_clk, input write_clk ); -// `ifdef YOSYS -// // use the ECP5 primitive. -// PDPW16KD ram ( -// .DI0 (din[0]), -// .DI1 (din[1]), -// .DI2 (din[2]), -// .DI3 (din[3]), -// .DI4 (din[4]), -// .DI5 (din[5]), -// .DI6 (din[6]), -// .DI7 (din[7]), -// .DI8 (din[8]), -// .DI9 (din[9]), -// .DI10(din[10]), -// .DI11(din[11]), -// .DI12(din[12]), -// .DI13(din[13]), -// .DI14(din[14]), -// .DI15(din[15]), -// .DI16(din[16]), -// .DI17(din[17]), -// .DI18(din[18]), -// .DI19(din[19]), -// .DI20(din[20]), -// .DI21(din[21]), -// .DI22(din[22]), -// .DI23(din[23]), -// .DI24(din[24]), -// .DI25(din[25]), -// .DI26(din[26]), -// .DI27(din[27]), -// .DI28(din[28]), -// .DI29(din[29]), -// .DI30(din[30]), -// .DI31(din[31]), -// .DI32(din[32]), -// .DI33(din[33]), -// .DI34(din[34]), -// .DI35(din[35]), -// .ADW0(addr_w[0]), -// .ADW1(addr_w[1]), -// .ADW2(addr_w[2]), -// .ADW3(addr_w[3]), -// .ADW4(addr_w[4]), -// .ADW5(addr_w[5]), -// .ADW6(addr_w[6]), -// .ADW7(addr_w[7]), -// .ADW8(addr_w[8]), -// .DO0 (dout[0]), -// .DO1 (dout[1]), -// .DO2 (dout[2]), -// .DO3 (dout[3]), -// .DO4 (dout[4]), -// .DO5 (dout[5]), -// .DO6 (dout[6]), -// .DO7 (dout[7]), -// .DO8 (dout[8]), -// .DO9 (dout[9]), -// .DO10(dout[10]), -// .DO11(dout[11]), -// .DO12(dout[12]), -// .DO13(dout[13]), -// .DO14(dout[14]), -// .DO15(dout[15]), -// .DO16(dout[16]), -// .DO17(dout[17]), -// .DO18(dout[18]), -// .DO19(dout[19]), -// .DO20(dout[20]), -// .DO21(dout[21]), -// .DO22(dout[22]), -// .DO23(dout[23]), -// .DO24(dout[24]), -// .DO25(dout[25]), -// .DO26(dout[26]), -// .DO27(dout[27]), -// .DO28(dout[28]), -// .DO29(dout[29]), -// .DO30(dout[30]), -// .DO31(dout[31]), -// .DO32(dout[32]), -// .DO33(dout[33]), -// .DO34(dout[34]), -// .DO35(dout[35]), -// .ADR0(addr_r[0]), -// .ADR1(addr_r[1]), -// .ADR2(addr_r[2]), -// .ADR3(addr_r[3]), -// .ADR4(addr_r[4]), -// .ADR5(addr_r[5]), -// .ADR6(addr_r[6]), -// .ADR7(addr_r[7]), -// .ADR8(addr_r[8]), -// .CER(1), -// .CEW(1), -// .CLKR(read_clk), -// .CLKW(write_clk), -// -// -// ); -// `else - reg [35:0] ram [2**9]; + `ifdef YOSYS + // use the ECP5 primitive. + PDPW16KD #(.REGMODE("OUTREG")) ram ( + .DI0 (din[0]), + .DI1 (din[1]), + .DI2 (din[2]), + .DI3 (din[3]), + .DI4 (din[4]), + .DI5 (din[5]), + .DI6 (din[6]), + .DI7 (din[7]), + .DI8 (din[8]), + .DI9 (din[9]), + .DI10(din[10]), + .DI11(din[11]), + .DI12(din[12]), + .DI13(din[13]), + .DI14(din[14]), + .DI15(din[15]), + .DI16(din[16]), + .DI17(din[17]), + .DI18(din[18]), + .DI19(din[19]), + .DI20(din[20]), + .DI21(din[21]), + .DI22(din[22]), + .DI23(din[23]), + .DI24(din[24]), + .DI25(din[25]), + .DI26(din[26]), + .DI27(din[27]), + .DI28(din[28]), + .DI29(din[29]), + .DI30(din[30]), + .DI31(din[31]), + .DI32(din[32]), + .DI33(din[33]), + .DI34(din[34]), + .DI35(din[35]), + .ADW0(addr_w[0]), + .ADW1(addr_w[1]), + .ADW2(addr_w[2]), + .ADW3(addr_w[3]), + .ADW4(addr_w[4]), + .ADW5(addr_w[5]), + .ADW6(addr_w[6]), + .ADW7(addr_w[7]), + .ADW8(addr_w[8]), + .DO0 (dout[0]), + .DO1 (dout[1]), + .DO2 (dout[2]), + .DO3 (dout[3]), + .DO4 (dout[4]), + .DO5 (dout[5]), + .DO6 (dout[6]), + .DO7 (dout[7]), + .DO8 (dout[8]), + .DO9 (dout[9]), + .DO10(dout[10]), + .DO11(dout[11]), + .DO12(dout[12]), + .DO13(dout[13]), + .DO14(dout[14]), + .DO15(dout[15]), + .DO16(dout[16]), + .DO17(dout[17]), + .DO18(dout[18]), + .DO19(dout[19]), + .DO20(dout[20]), + .DO21(dout[21]), + .DO22(dout[22]), + .DO23(dout[23]), + .DO24(dout[24]), + .DO25(dout[25]), + .DO26(dout[26]), + .DO27(dout[27]), + .DO28(dout[28]), + .DO29(dout[29]), + .DO30(dout[30]), + .DO31(dout[31]), + .DO32(dout[32]), + .DO33(dout[33]), + .DO34(dout[34]), + .DO35(dout[35]), + .ADR0(addr_r[0]), + .ADR1(addr_r[1]), + .ADR2(addr_r[2]), + .ADR3(addr_r[3]), + .ADR4(addr_r[4]), + .ADR5(addr_r[5]), + .ADR6(addr_r[6]), + .ADR7(addr_r[7]), + .ADR8(addr_r[8]), + .CER(1), + .CEW(1), + .CLKR(read_clk), + .CLKW(write_clk), + .CSW1(0), + .CSW2(0), + .CSW3(0), + .CSR1(0), + .CSR2(0), + .CSR3(0), + ); +`else + reg [35:0] ram [2**9]/*verilator public*/; `ifndef YOSYS initial begin @@ -125,5 +129,5 @@ module lineram ( always @(posedge read_clk) begin dout <= ram[addr_r]; end -// `endif +`endif endmodule