diff --git a/hdl/rtl/hamster/r5p_hamster.sv b/hdl/rtl/hamster/r5p_hamster.sv index 5949900..521d4e9 100644 --- a/hdl/rtl/hamster/r5p_hamster.sv +++ b/hdl/rtl/hamster/r5p_hamster.sv @@ -264,7 +264,7 @@ assign add_zro = add_sum[32-1:0] == 32'd0; /////////////////////////////////////////////////////////////////////////////// always_comb -unique case (idu_buf.alu.fn3) +unique case (idu_buf.fn3) // bitwise logical operations AND : log_val = log_op1 & log_op2; OR : log_val = log_op1 | log_op2; @@ -283,7 +283,7 @@ endfunction // bit inversion always_comb -unique case (idu_buf.alu.fn3) +unique case (idu_buf.fn3) // barrel shifter SR : shf_tmp = shf_op1 ; SL : shf_tmp = bitrev(shf_op1); @@ -292,7 +292,7 @@ endcase // sign extension to (32+1) always_comb -unique case (idu_buf.alu.fn7[5]) +unique case (idu_buf.fn7[5]) 1'b1 : shf_ext = {shf_tmp[32-1], shf_tmp}; 1'b0 : shf_ext = {1'b0 , shf_tmp}; endcase @@ -341,7 +341,7 @@ end else begin // progress into the next state if (ctl_run & (bus_trn | ~bus_vld)) begin // GPR (write rd) - gpr_wad <= idu_buf.gpr.adr.rd; + gpr_wad <= idu_buf.rd; // system bus unique case (ctl_fsm) // instruction fetch state @@ -350,7 +350,7 @@ end else begin ctl_fsm <= SLS; // PC: program counter // TODO: PC is only reloaded if the prediction is correct - if ((idu_buf.opc == BRANCH) && (idu_buf.bru.imm[12] != bru_tkn)) begin + if ((idu_buf.opc == BRANCH) && (idu_buf.i_b[12] != bru_tkn)) begin // on mispredicted branch ignore the fetched instruction end else begin ifu_pcr <= bus_adr; @@ -370,7 +370,7 @@ end else begin bus_vld <= 1'b0; // GPR gpr_wen <= 1'b1; - bus_wdt <= idu_buf.uiu; + bus_wdt <= idu_buf.i_u; end AUIPC : begin // TCB @@ -384,7 +384,7 @@ end else begin bus_vld <= 1'b1; bus_wen <= 1'b0; bus_adr <= {add_sum[32-1:2], 2'b00}; - case (idu_buf.ldu.fn3) + case (idu_buf.fn3) LB, LBU: case (add_sum[1:0]) 2'b00: bus_ben <= 4'b0001; 2'b01: bus_ben <= 4'b0010; @@ -403,14 +403,14 @@ end else begin gpr_wen <= 1'b0; // read data multiplexer rdm_adr <= add_sum[ 2-1:0]; - rdm_fn3 <= idu_buf.ldu.fn3; + rdm_fn3 <= fn3_ldu_et'(idu_buf.fn3); end STORE : begin // TCB bus_vld <= 1'b1; bus_wen <= 1'b1; bus_adr <= {add_sum[32-1:2], 2'b00}; - case (idu_buf.stu.fn3) + case (idu_buf.fn3) SB : case (add_sum[1:0]) 2'b00: begin bus_wdt[ 7: 0] <= gpr_rdt[ 7: 0]; bus_ben <= 4'b0001; end 2'b01: begin bus_wdt[15: 8] <= gpr_rdt[ 7: 0]; bus_ben <= 4'b0010; end @@ -454,7 +454,7 @@ end else begin // FSM control (go to the next state) ctl_fsm <= SIF; // instruction buffer - if ((idu_buf.opc == BRANCH) && (idu_buf.bru.imm[12] != buf_tkn)) begin + if ((idu_buf.opc == BRANCH) && (idu_buf.i_b[12] != buf_tkn)) begin // on mispredicted branch ignore the fetched instruction ifu_buf <= NOP; end else begin @@ -522,7 +522,7 @@ begin SIF: begin // GPR (read rs1) gpr_ren = 1'b1; - gpr_rad = idu_buf.gpr.adr.rs2; + gpr_rad = idu_buf.rs2; // LOAD: read data multiplexer rdm_dtw = bus_rdt[31: 0]; rdm_dth = rdm_adr[1] ? rdm_dtw[31:16] : rdm_dtw[15: 0]; @@ -552,27 +552,27 @@ begin // adder add_inc = 1'b0; add_op1 = ext_sgn(bus_wdt); - add_op2 = ext_sgn(idu_buf.uiu.imm); + add_op2 = ext_sgn(idu_buf.i_u); end LOAD : begin // adder for load address add_inc = 1'b0; add_op1 = ext_sgn(bus_wdt); - add_op2 = ext_sgn(32'(idu_buf.ldu.imm)); + add_op2 = ext_sgn(32'(idu_buf.i_l)); end STORE : begin // adder for store address add_inc = 1'b0; add_op1 = ext_sgn(bus_wdt); - add_op2 = ext_sgn(32'(idu_buf.stu.imm)); + add_op2 = ext_sgn(32'(idu_buf.i_s)); end OP : begin // arithmetic operations - case (idu_buf.alu.fn3) + case (idu_buf.fn3) ADD : begin - add_inc = idu_buf.alu.fn7[5]; + add_inc = idu_buf.fn7[5]; add_op1 = ext_sgn(bus_wdt); - add_op2 = ext_sgn(gpr_rdt ^ {32{idu_buf.alu.fn7[5]}}); + add_op2 = ext_sgn(gpr_rdt ^ {32{idu_buf.fn7[5]}}); end SLT : begin add_inc = 1'b1; @@ -596,36 +596,36 @@ begin end OP_IMM : begin // arithmetic operations - case (idu_buf.alu.fn3) + case (idu_buf.fn3) ADD : begin add_inc = 1'b0; add_op1 = ext_sgn(bus_wdt); - add_op2 = ext_sgn(32'(idu_buf.alu.imm)); + add_op2 = ext_sgn(32'(idu_buf.i_i)); end SLT : begin add_inc = 1'b1; add_op1 = ext_sgn( bus_wdt); - add_op2 = ext_sgn(~32'(idu_buf.alu.imm)); + add_op2 = ext_sgn(~32'(idu_buf.i_i)); end SLTU : begin add_inc = 1'b1; add_op1 = {1'b0, bus_wdt}; - add_op2 = {1'b1, ~32'(idu_buf.alu.imm)}; + add_op2 = {1'b1, ~32'(idu_buf.i_i)}; end default: begin end endcase // logic operations log_op1 = bus_wdt; - log_op2 = 32'(idu_buf.alu.imm); + log_op2 = 32'(idu_buf.i_i); // shift operations shf_op1 = bus_wdt; - shf_op2 = idu_buf.alu.imm[5-1:0]; + shf_op2 = idu_buf.i_i[5-1:0]; end BRANCH : begin // subtraction add_inc = 1'b1; - unique case (idu_buf.bru.fn3) + unique case (idu_buf.fn3) BEQ , BNE , BLT , @@ -643,7 +643,7 @@ begin add_op2 = 33'dx; end endcase - unique case (idu_buf.bru.fn3) + unique case (idu_buf.fn3) BEQ : bru_tkn = add_zro; BNE : bru_tkn = ~add_zro; BLT : bru_tkn = add_sgn; @@ -657,7 +657,7 @@ begin end endcase // ALU output - case (idu_buf.alu.fn3) + case (idu_buf.fn3) // adder based inw_bufuctions ADD : alu_out = add_sum[32-1:0]; SLT , @@ -677,16 +677,16 @@ begin SLS: begin // GPR (read rs1) gpr_ren = 1'b1; - gpr_rad = idu_rdt.gpr.adr.rs1; + gpr_rad = idu_rdt.rs1; gpr_wdt = bus_wdt; // decode operation code - if ((idu_buf.opc == BRANCH) && (idu_buf.bru.imm[12] != buf_tkn)) begin + if ((idu_buf.opc == BRANCH) && (idu_buf.i_b[12] != buf_tkn)) begin // on mispredicted branch reverse static branch prediction decisions - if (~idu_buf.bru.imm[12]) begin + if (~idu_buf.i_b[12]) begin // backward branches are predicted taken add_inc = 1'b0; add_op1 = 33'(ifu_pcr); - add_op2 = 33'(idu_buf.bru.imm); + add_op2 = 33'(idu_buf.i_b); end else begin // forward branches are predicted not taken add_inc = 1'b0; @@ -702,20 +702,20 @@ begin JAL : begin add_inc = 1'b0; add_op1 = 33'(ifu_pcr); - add_op2 = 33'(idu_rdt.jmp.jmp); + add_op2 = 33'(idu_rdt.i_j); end JALR : begin add_inc = 1'b0; add_op1 = 33'(gpr_rdt); - add_op2 = 33'(idu_rdt.jmp.imm); + add_op2 = 33'(idu_rdt.i_i); end BRANCH : begin // static branch prediction - if (idu_rdt.bru.imm[12]) begin + if (idu_rdt.i_b[12]) begin // backward branches are predicted taken add_inc = 1'b0; add_op1 = 33'(ifu_pcr); - add_op2 = 33'(idu_rdt.bru.imm); + add_op2 = 33'(idu_rdt.i_b); end else begin // forward branches are predicted not taken add_inc = 1'b0; diff --git a/hdl/rtl/riscv/riscv_isa_i_pkg.sv b/hdl/rtl/riscv/riscv_isa_i_pkg.sv index 4008d5f..414030a 100644 --- a/hdl/rtl/riscv/riscv_isa_i_pkg.sv +++ b/hdl/rtl/riscv/riscv_isa_i_pkg.sv @@ -259,10 +259,14 @@ typedef enum { // decoder structure /////////////////////////////////////////////////////////////////////////////// - typedef struct { + // `packed` (and `siz` a fixed-width vector rather than `integer`) so the + // decoder struct is a bit-addressable type: r5p_* modules take `dec_t` as a + // PORT, and synthesis / Yosys can only map a flat netlist port onto a struct + // port when the struct is packed. + typedef struct packed { // instruction encodings ill_t ill; // illegal - integer siz; // instruction size + logic signed [31:0] siz; // instruction size opc_t opc; // operation code fn3_t fn3; // func3 fn7_t fn7; // func7 diff --git a/hdl/rtl/riscv/riscv_isa_pkg.sv b/hdl/rtl/riscv/riscv_isa_pkg.sv index 0c733df..ddb9ff0 100644 --- a/hdl/rtl/riscv/riscv_isa_pkg.sv +++ b/hdl/rtl/riscv/riscv_isa_pkg.sv @@ -111,8 +111,10 @@ typedef enum logic [$bits(isa_ext_t)-1:0] { } isa_ext_et; // ISA specification configuration -// TODO: change when Verilator supports unpacked structures -typedef struct { +// made `packed` so the enum below can use `logic [$bits(isa_spec_t)-1:0]` as +// its base and drive it through synthesis / Yosys as a flat vector (the +// upstream "change when Verilator supports unpacked structures" TODO). +typedef struct packed { isa_base_t base; isa_ext_t ext; } isa_spec_t; diff --git a/syn/uhdm2rtlil/.gitignore b/syn/uhdm2rtlil/.gitignore new file mode 100644 index 0000000..2cecc57 --- /dev/null +++ b/syn/uhdm2rtlil/.gitignore @@ -0,0 +1,5 @@ +# build / run artifacts +work/ +mem_if.mem +*.log +slpp_all/ diff --git a/syn/uhdm2rtlil/README.md b/syn/uhdm2rtlil/README.md new file mode 100644 index 0000000..f768271 --- /dev/null +++ b/syn/uhdm2rtlil/README.md @@ -0,0 +1,104 @@ +# rp32 — Yosys synthesis via UHDM/Surelog (uhdm2rtlil) + +This directory adds a **Yosys synthesis flow driven by the Surelog SystemVerilog +front end** ([uhdm2rtlil](https://github.com/alainmarcel/uhdm2rtlil)), alongside +the existing `syn/` (Yosys‑slang) and `fpga/*/yosys` flows. + +Surelog parses the design to UHDM and the uhdm2rtlil plugin imports it to Yosys +RTLIL (`read_sv`), so the full IEEE‑1800 SystemVerilog the rp32 cores use +(packages, structs, interfaces) is synthesisable through Yosys. + +The gate‑level result is **verified by co‑simulation**: the synthesized netlist +is run against the original RTL under Verilator and the outputs are compared +every cycle — the same methodology uhdm2rtlil uses for designs the native Yosys +Verilog front end cannot parse. + +## Prerequisites + +* A **built** [uhdm2rtlil](https://github.com/alainmarcel/uhdm2rtlil) checkout + (provides `out/current/bin/yosys`, `build/uhdm2rtlil.so`, and the Yosys + `simcells.v`). Point `UHDM2RTLIL_ROOT` at it (defaults to `~/uhdm2rtlil`): + + ```bash + export UHDM2RTLIL_ROOT=/path/to/uhdm2rtlil + ``` + +* **Verilator 5.x** in `PATH` (for the co‑simulation) — the repo's + `submodules/verilator` build works; see `settings-verilator.sh`. + +## Usage + +```bash +cd syn/uhdm2rtlil + +./build.sh --list # list the catalogued designs +./build.sh mouse_soc_simple # synthesise one design (work/_uhdm.v/.json) +./cosim.sh # gate-level co-sim of the Mouse simple SoC +./run.sh # synthesise ALL designs + co-sim, print a table +``` + +The design catalog (cores + SoCs) lives in `designs.sh`; `build.sh ` picks +one. The full SoCs need the TCB submodule: + +```bash +git submodule update --init submodules/tcb +``` + +## Design status + +`./run.sh` synthesises every design and co-simulates the ones with a +deterministic self-contained testbench: + +| design | synth | co-sim | notes | +|---------------------|:-----:|:------:|-------| +| `mouse` | ✅ | (det.) | standalone core; only deterministic streams are equiv-able | +| `mouse_soc_simple` | ✅ | ✅ **PASS** | complete Mouse SoC, inline RAM — 0 mismatches / 6000 cyc | +| `mouse_soc` | ⚠️ | n/a | full Mouse SoC synthesises but is functionally stuck — the TCB **interface-config** struct refs (`sub.CFG.HSK.DLY`, `sub.MOD`) are unresolved (front-end limitation); Verilator can't build the interface RTL either, so no co-sim | +| `degu` | ⚠️ | n/a | core synthesises with unresolved TCB interface-config reads | +| `degu_soc` | ⚠️ | n/a | as above | +| `hamster` | ⚠️ | n/a | core synthesises with unresolved decoder-struct reads (`idu_rdt.jmp.jmp`) | + +✅ = clean; ⚠️ = produces a netlist but with unresolved struct/interface reads +(undriven wires ⇒ not functionally correct yet). The remaining gaps are all +SystemVerilog-interface-config resolution in the UHDM front end. + +## The memory‑preserving synthesis flow + +A design with an initialised ROM/RAM (`$readmemh`) **must not** be run through +the plain `synth` / `synth_*` shortcut: those run `opt_mem`, which trims an +initialised memory to its "used" width and mangles the `$meminit` constant +(e.g. `0x800200B7` → garbage), so the fetched program reads back wrong and the +CPU never boots. `build.sh` therefore drives the passes explicitly: + +``` +read_sv → proc → flatten → memory_collect (no opt_mem / memory -nomap) + → opt -full → techmap → dfflegalize → abc → opt_clean +``` + +`flatten` **before** `memory_collect` is what lets the `$readmemh` init reach +the combinational read port. + +## Files + +| file | purpose | +|-----------------|------------------------------------------------------------------| +| `designs.sh` | design catalog: name → sources + top (cores + SoCs) | +| `build.sh` | synthesise one design through uhdm2rtlil (memory‑preserving flow)| +| `run.sh` | synthesise all designs + co‑sim, print a status table | +| `cosim.sh` | synth + Verilator co‑simulation (RTL vs gate netlist) | +| `cosim_tb.sv` | testbench: RTL and gate netlist side by side, per‑cycle compare | +| `cosim_main.cpp`| Verilator driver (reset, free‑run, exit non‑zero on mismatch) | +| `boot.hex` | tiny deterministic boot program (GPIO writes) for the SoC RAM | + +`work/` (netlists, logs, Verilator objects) is generated and git‑ignored. + +## Notes + +* The **complete Mouse SoC** co‑simulates cleanly because its boot program is + deterministic. The **standalone `r5p_mouse` core** synthesises correctly too, + but under *random* instruction stimulus it diverges on the design's own `'x` + don't‑cares (illegal opcodes), so only a deterministic instruction stream is a + meaningful equivalence check for it — use the SoC for the end‑to‑end gate check. +* This flow depends on two uhdm2rtlil front‑end fixes for the rp32 cores: comb + `case` blocking‑read value threading, and byte‑enable (`mem[a][hi:lo] <= …`) + memory‑write emission. Use a uhdm2rtlil build that includes them. diff --git a/syn/uhdm2rtlil/boot.hex b/syn/uhdm2rtlil/boot.hex new file mode 100644 index 0000000..c1416a1 --- /dev/null +++ b/syn/uhdm2rtlil/boot.hex @@ -0,0 +1,6 @@ +800200b7 +05a00113 +0020a023 +0ff00113 +0020a023 +0000006f diff --git a/syn/uhdm2rtlil/build.sh b/syn/uhdm2rtlil/build.sh new file mode 100755 index 0000000..7768ff7 --- /dev/null +++ b/syn/uhdm2rtlil/build.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# ============================================================================ +# rp32 — synthesise a design through the UHDM/Surelog front end (uhdm2rtlil) +# ============================================================================ +# +# Runs the Surelog SystemVerilog front end in-process (`read_sv`, from the +# uhdm2rtlil plugin) and synthesises an rp32 design to a gate-level netlist +# under ./work/ (_uhdm.v / .json). +# +# export UHDM2RTLIL_ROOT=/path/to/uhdm2rtlil # built checkout (default ~/uhdm2rtlil) +# ./build.sh [design] # design from designs.sh (default mouse_soc_simple) +# ./build.sh --list # list known designs +# +# See designs.sh for the design catalog (cores + SoCs). R5P_RENAME renames the +# netlist top (used by cosim.sh so RTL and gate can share a Verilator build). +# +# --- memory-preserving flow ------------------------------------------------- +# A design with an initialised ROM/RAM (`$readmemh`) must NOT be run through the +# plain `synth`/`synth_*` shortcut: those run `opt_mem`, which trims an +# initialised memory to its "used" width and MANGLES the `$meminit` constant +# (0x800200B7 -> garbage) and maps the RAM to hundreds of thousands of FFs. We +# drive the passes explicitly and keep the init: +# read_sv -> proc -> flatten -> memory_collect (NO opt_mem / memory -nomap) +# `flatten` before `memory_collect` lets the $readmemh init reach the read port. +set -euo pipefail +cd "$(dirname "$0")" + +export R5P_RTL="$(cd ../../hdl/rtl && pwd)" +export R5P_TCB="$(cd ../../submodules/tcb/hdl/rtl && pwd 2>/dev/null || echo /nonexistent)" +source ./designs.sh + +if [ "${1:-}" = "--list" ]; then design_list; exit 0; fi + +DESIGN="${1:-mouse_soc_simple}" +design_select "$DESIGN" +OUT="${R5P_OUT:-${TOP}_uhdm}" + +ROOT="${UHDM2RTLIL_ROOT:-$HOME/uhdm2rtlil}" +YOSYS="$ROOT/out/current/bin/yosys" +PLUGIN="$ROOT/build/uhdm2rtlil.so" +for f in "$YOSYS" "$PLUGIN"; do + if [ ! -e "$f" ]; then + echo "ERROR: $f not found." >&2 + echo "Set UHDM2RTLIL_ROOT to a built uhdm2rtlil checkout (currently '$ROOT')." >&2 + exit 1 + fi +done +if [ ! -d "$R5P_TCB" ]; then + echo "NOTE: TCB submodule not initialised (needed for degu/full SoCs):" >&2 + echo " git submodule update --init submodules/tcb" >&2 +fi + +mkdir -p work +# SoCs read their boot image via $readmemh("mem_if.mem", ...); provide it. +cp -f boot.hex work/mem_if.mem + +echo "== uhdm2rtlil: $ROOT" +echo "== design: $DESIGN (top $TOP)" + +( cd work && "$YOSYS" -m "$PLUGIN" -p " + read_sv -parse -nobuiltin -top $TOP $SRCS + hierarchy -check -top $TOP + proc + flatten + memory_collect + opt -full + techmap + opt + dfflegalize -cell \$_DFF_P_ 0 -cell \$_DFF_PP0_ 0 -cell \$_DFF_PP1_ 0 \ + -cell \$_DFFE_PP0P_ 0 -cell \$_DFFE_PP1P_ 0 + abc -g AND,NAND,OR,NOR,XOR,XNOR,ANDNOT,ORNOT,MUX + opt_clean + ${R5P_RENAME:+rename $TOP $R5P_RENAME} + stat + write_verilog -noattr ${OUT}.v + write_json ${OUT}.json +" ) + +echo "== netlist written: $(pwd)/work/${OUT}.v" diff --git a/syn/uhdm2rtlil/cosim.sh b/syn/uhdm2rtlil/cosim.sh new file mode 100755 index 0000000..cbf8218 --- /dev/null +++ b/syn/uhdm2rtlil/cosim.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# ============================================================================ +# rp32 — gate-level co-simulation: RTL vs uhdm2rtlil-synthesized netlist +# ============================================================================ +# +# Synthesises an r5p_mouse SoC through uhdm2rtlil (top renamed to *_gate), then +# uses Verilator to run the original RTL and the gate netlist side by side under +# the same boot program, comparing outputs every cycle. This is the gate-level +# equivalence check for the Yosys/UHDM flow — the same methodology uhdm2rtlil +# uses for designs the native Verilog front end can't parse (co-simulate the +# synthesized netlist against the RTL). +# +# Works for any COSIM="yes" SoC in designs.sh whose top ports match cosim_tb.sv +# (clk/rst/gpio_o/gpio_e/gpio_i/uart_txd/uart_rxd) — currently the discrete +# `mouse_soc_simple` and the TCB-interface `mouse_soc`. +# +# export UHDM2RTLIL_ROOT=/path/to/uhdm2rtlil # built checkout (default ~/uhdm2rtlil) +# ./cosim.sh [design] [cycles] # design default: mouse_soc_simple +# +set -euo pipefail +cd "$(dirname "$0")" + +ROOT="${UHDM2RTLIL_ROOT:-$HOME/uhdm2rtlil}" +SIMCELLS="$ROOT/out/current/share/yosys/simcells.v" +export R5P_RTL="$(cd ../../hdl/rtl && pwd)" +export R5P_TCB="$(cd ../../submodules/tcb/hdl/rtl && pwd 2>/dev/null || echo /nonexistent)" +source ./designs.sh + +DESIGN="${1:-mouse_soc_simple}" +CYCLES="${2:-6000}" +design_select "$DESIGN" # sets TOP and SRCS (the RTL source list) +GATE="${TOP}_gate" + +command -v verilator >/dev/null || { echo "ERROR: verilator not found in PATH" >&2; exit 1; } +[ -e "$SIMCELLS" ] || { echo "ERROR: $SIMCELLS not found (build uhdm2rtlil / set UHDM2RTLIL_ROOT)" >&2; exit 1; } + +# 1. synthesise the SoC to a FLAT gate netlist with the top renamed to *_gate +# (flat, so its submodules don't collide with the RTL sources in Verilator). +echo "== [1/2] synthesising $DESIGN ($TOP) via uhdm2rtlil (-> $GATE)" +R5P_OUT="cosim_gate" R5P_RENAME="$GATE" ./build.sh "$DESIGN" >work/cosim_synth.log 2>&1 \ + || { echo "ERROR: synthesis failed; see work/cosim_synth.log" >&2; tail -20 work/cosim_synth.log; exit 1; } + +# 2. build + run the Verilator co-sim (RTL + gate netlist + Yosys gate cells). +# The RTL side runs `$readmemh("mem_if.mem", ...)` relative to Vtb's cwd (here); +# the gate netlist already has the boot image baked in as memory init. The same +# cosim_tb.sv serves every SoC — cosim.sh passes the RTL/gate module names via +# +define+ (defaults in the .sv keep it usable standalone). +echo "== [2/2] Verilator co-sim ($CYCLES cycles)" +cp -f boot.hex mem_if.mem +rm -rf work/obj_dir +verilator --cc --exe --build -j 4 \ + -Wno-fatal -Wno-WIDTH -Wno-UNUSED -Wno-UNOPTFLAT -Wno-CASEINCOMPLETE \ + -Wno-MULTIDRIVEN -Wno-BLKANDNBLK -Wno-DECLFILENAME -Wno-PINMISSING \ + "+define+TOP_RTL=$TOP" "+define+TOP_GATE=$GATE" \ + --top-module tb --Mdir work/obj_dir \ + cosim_tb.sv \ + $SRCS \ + work/cosim_gate.v \ + "$SIMCELLS" \ + cosim_main.cpp + +./work/obj_dir/Vtb "$CYCLES" diff --git a/syn/uhdm2rtlil/cosim_main.cpp b/syn/uhdm2rtlil/cosim_main.cpp new file mode 100644 index 0000000..ee55715 --- /dev/null +++ b/syn/uhdm2rtlil/cosim_main.cpp @@ -0,0 +1,34 @@ +// Verilator driver for the r5p_mouse SoC gate-level co-simulation. +// Holds reset, then free-runs the boot program, checking RTL == netlist every +// cycle. Exit code 0 on match, 1 on any mismatch. +#include +#include "Vtb.h" +#include +#include + +int main(int argc, char** argv) { + Verilated::commandArgs(argc, argv); + unsigned ncyc = (argc > 1) ? std::strtoul(argv[1], nullptr, 0) : 6000; + + Vtb* d = new Vtb; + auto tick = [&]() { d->clk = 0; d->eval(); d->clk = 1; d->eval(); }; + + // hold reset + d->rst = 1; d->gpio_i = 0x12345678; d->uart_rxd = 1; + for (int i = 0; i < 8; i++) tick(); + d->rst = 0; + + long mismatches = 0; + int first = -1; + for (unsigned i = 0; i < ncyc; i++) { + tick(); + if (d->mismatch) { if (first < 0) first = (int)i; mismatches++; } + } + d->final(); + + std::printf("cycles=%u mismatches=%ld first_mismatch=%d\n", + ncyc, mismatches, first); + if (mismatches) { std::printf("COSIM FAIL\n"); return 1; } + std::printf("COSIM PASS\n"); + return 0; +} diff --git a/syn/uhdm2rtlil/cosim_tb.sv b/syn/uhdm2rtlil/cosim_tb.sv new file mode 100644 index 0000000..fe8ead0 --- /dev/null +++ b/syn/uhdm2rtlil/cosim_tb.sv @@ -0,0 +1,52 @@ +// ============================================================================ +// Gate-level co-simulation testbench for the r5p_mouse SoCs. +// +// Instantiates the original RTL (`TOP_RTL`) and the uhdm2rtlil-synthesized gate +// netlist (`TOP_GATE`) side by side, drives them with the same +// clock/reset/inputs, and raises `mismatch` whenever any observable output +// differs. Both run the same boot program from memory, so the comparison is +// fully deterministic. +// +// The two module names are supplied by cosim.sh via +define+ so the SAME +// testbench serves both the discrete `mouse_soc_simple` and the TCB-interface +// `mouse_soc` (their top-level ports are identical). Defaults keep the file +// usable standalone for the simple SoC. +// ============================================================================ +`ifndef TOP_RTL + `define TOP_RTL r5p_mouse_soc_simple_top +`endif +`ifndef TOP_GATE + `define TOP_GATE r5p_mouse_soc_simple_top_gate +`endif + +module tb ( + input logic clk, + input logic rst, + input logic [31:0] gpio_i, + input logic uart_rxd, + output logic mismatch +); + // RTL reference outputs + logic [31:0] r_gpio_o, r_gpio_e; + logic r_uart_txd; + // gate-level netlist outputs + logic [31:0] g_gpio_o, g_gpio_e; + logic g_uart_txd; + + `TOP_RTL dut_rtl ( + .clk (clk), .rst (rst), + .gpio_o (r_gpio_o), .gpio_e (r_gpio_e), .gpio_i (gpio_i), + .uart_txd (r_uart_txd), .uart_rxd (uart_rxd) + ); + + `TOP_GATE dut_gate ( + .clk (clk), .rst (rst), + .gpio_o (g_gpio_o), .gpio_e (g_gpio_e), .gpio_i (gpio_i), + .uart_txd (g_uart_txd), .uart_rxd (uart_rxd) + ); + + always_comb + mismatch = (r_gpio_o !== g_gpio_o) + || (r_gpio_e !== g_gpio_e) + || (r_uart_txd !== g_uart_txd); +endmodule diff --git a/syn/uhdm2rtlil/designs.sh b/syn/uhdm2rtlil/designs.sh new file mode 100644 index 0000000..805b766 --- /dev/null +++ b/syn/uhdm2rtlil/designs.sh @@ -0,0 +1,111 @@ +# ============================================================================ +# rp32 design catalog for the uhdm2rtlil Yosys flow. +# +# Sourced by build.sh / run.sh. `design_select ` sets: +# TOP top module name +# SRCS space-separated, dependency-ordered source list (absolute paths) +# COSIM "yes" if a deterministic gate-level co-simulation is wired up +# +# Requires R5P_RTL (abs path to hdl/rtl) and R5P_TCB (abs path to the TCB RTL, +# submodules/tcb/hdl/rtl) to be exported by the caller. +# ============================================================================ + +# RISC-V ISA packages shared by the hierarchical cores. +_riscv_pkgs() { + echo "$R5P_RTL/riscv/riscv_isa_pkg.sv \ + $R5P_RTL/riscv/riscv_priv_pkg.sv \ + $R5P_RTL/riscv/riscv_isa_i_pkg.sv \ + $R5P_RTL/riscv/riscv_isa_c_pkg.sv \ + $R5P_RTL/riscv/rv32_csr_pkg.sv \ + $R5P_RTL/riscv/rv64_csr_pkg.sv" +} + +# degu core sources (packages + submodules + core), dependency-ordered. +_degu_core() { + echo "$R5P_TCB/tcb_lite_pkg.sv $R5P_TCB/tcb_lite_if.sv \ + $(_riscv_pkgs) \ + $R5P_RTL/degu/r5p_pkg.sv $R5P_RTL/degu/r5p_degu_pkg.sv \ + $R5P_RTL/core/r5p_gpr_2r1w.sv \ + $R5P_RTL/degu/r5p_bru.sv $R5P_RTL/degu/r5p_alu.sv \ + $R5P_RTL/degu/r5p_mdu.sv $R5P_RTL/degu/r5p_lsu.sv \ + $R5P_RTL/degu/r5p_wbu.sv $R5P_RTL/degu/r5p_degu.sv" +} + +# TCB "lite" peripheral library + devices used by the full SoCs. +_tcb_soc_lib() { + echo "$R5P_TCB/lite_lib/tcb_lite_lib_error.sv \ + $R5P_TCB/lite_lib/tcb_lite_lib_passthrough.sv \ + $R5P_TCB/lite_lib/tcb_lite_lib_register_request.sv \ + $R5P_TCB/lite_lib/tcb_lite_lib_register_response.sv \ + $R5P_TCB/lite_lib/tcb_lite_lib_register_backpressure.sv \ + $R5P_TCB/lite_lib/tcb_lite_lib_arbiter.sv \ + $R5P_TCB/lite_lib/tcb_lite_lib_multiplexer.sv \ + $R5P_TCB/lite_lib/tcb_lite_lib_decoder.sv \ + $R5P_TCB/lite_lib/tcb_lite_lib_demultiplexer.sv \ + $R5P_TCB/lite_lib/tcb_lite_lib_logsize2byteena.sv \ + $R5P_TCB/dev/gpio/tcb_dev_gpio_cdc__generic.sv \ + $R5P_TCB/dev/gpio/tcb_dev_gpio.sv \ + $R5P_TCB/lite_dev/gpio/tcb_lite_dev_gpio.sv \ + $R5P_TCB/dev/uart/tcb_dev_uart_ser.sv \ + $R5P_TCB/dev/uart/tcb_dev_uart_des.sv \ + $R5P_TCB/dev/uart/tcb_dev_uart_fifo.sv \ + $R5P_TCB/dev/uart/tcb_dev_uart.sv \ + $R5P_TCB/lite_dev/uart/tcb_lite_dev_uart.sv" +} + +# List all known design names. +design_list() { + echo "mouse hamster degu mouse_soc_simple mouse_soc degu_soc" +} + +design_select() { + COSIM="no" + case "$1" in + # --- cores --------------------------------------------------------- + mouse) + TOP=r5p_mouse + SRCS="$R5P_RTL/mouse/r5p_mouse.sv" ;; + hamster) + TOP=r5p_hamster + SRCS="$(_riscv_pkgs) $R5P_RTL/core/r5p_gpr_1r1w.sv $R5P_RTL/hamster/r5p_hamster.sv" ;; + degu) + TOP=r5p_degu + SRCS="$(_degu_core)" ;; + # --- SoCs ---------------------------------------------------------- + mouse_soc_simple) + TOP=r5p_mouse_soc_simple_top + SRCS="$R5P_RTL/mouse/r5p_mouse.sv $R5P_RTL/soc/r5p_mouse_soc_simple_top.sv" + COSIM="yes" ;; + mouse_soc) + TOP=r5p_mouse_soc_top + SRCS="$R5P_TCB/tcb_lite_pkg.sv $R5P_TCB/tcb_lite_if.sv $(_tcb_soc_lib) \ + $R5P_RTL/mouse/r5p_mouse.sv $R5P_RTL/soc/r5p_soc_memory__gowin_inference.sv \ + $R5P_RTL/soc/r5p_mouse_soc_top.sv" + # COSIM stays "no": the gate-level co-sim needs Verilator to elaborate the + # ORIGINAL RTL as an independent reference, but Verilator (5.048) cannot + # unroll the tcb_lite_if delay-line loop + # `for (genvar i=1; i<=CFG.HSK.DLY; i++)` + # (%Error-UNSUPPORTED: Can't unroll generate for) — CFG.HSK.DLY is an + # interface-parameter struct field it won't fold at elaboration. That is + # exactly the SystemVerilog uhdm2rtlil handles and the native Verilog/ + # Verilator flow can't, so there is no independent RTL reference to + # co-simulate the full TCB-interface SoC against. The discrete + # `mouse_soc_simple` (no interfaces) IS Verilatable and stays cosim'd; + # the interface SoC's frontend correctness is covered by the per-module + # tests + its structural `check` in the uhdm2rtlil suite. + ;; + degu_soc) + TOP=r5p_degu_soc_top + SRCS="$R5P_TCB/tcb_lite_pkg.sv $R5P_TCB/tcb_lite_if.sv $(_tcb_soc_lib) \ + $(_riscv_pkgs) \ + $R5P_RTL/core/r5p_gpr_2r1w.sv \ + $R5P_RTL/degu/r5p_pkg.sv $R5P_RTL/degu/r5p_bru.sv \ + $R5P_RTL/degu/r5p_alu.sv $R5P_RTL/degu/r5p_mdu.sv \ + $R5P_RTL/degu/r5p_lsu.sv $R5P_RTL/degu/r5p_wbu.sv \ + $R5P_RTL/degu/r5p_degu_pkg.sv $R5P_RTL/degu/r5p_degu.sv \ + $R5P_RTL/soc/r5p_soc_memory__gowin_inference.sv $R5P_RTL/soc/r5p_degu_soc_top.sv" ;; + *) + echo "unknown design '$1'; known: $(design_list)" >&2 + return 1 ;; + esac +} diff --git a/syn/uhdm2rtlil/run.sh b/syn/uhdm2rtlil/run.sh new file mode 100755 index 0000000..1d4c707 --- /dev/null +++ b/syn/uhdm2rtlil/run.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# ============================================================================ +# rp32 — synthesise every catalogued design through uhdm2rtlil and report a +# pass/fail table; co-simulate the designs that have a deterministic testbench. +# +# export UHDM2RTLIL_ROOT=/path/to/uhdm2rtlil +# ./run.sh +# ============================================================================ +cd "$(dirname "$0")" +export R5P_RTL="$(cd ../../hdl/rtl && pwd)" +export R5P_TCB="$(cd ../../submodules/tcb/hdl/rtl && pwd 2>/dev/null || echo /nonexistent)" +source ./designs.sh + +mkdir -p work +printf '%-20s %-10s %s\n' DESIGN SYNTH CO-SIM +printf '%-20s %-10s %s\n' "------" "-----" "------" + +rc_all=0 +for d in $(design_list); do + design_select "$d" + log="work/${d}.synth.log" + out="work/${TOP}_uhdm.v" + rm -f "$out" + ./build.sh "$d" >"$log" 2>&1 || true + # A netlist is only produced when synthesis actually completed. + if [ -s "$out" ]; then + warn=$(grep -ic 'could not resolve\|unknown signal\|used but has no driver' "$log" || true) + if [ "$warn" -gt 0 ]; then synth="ok(${warn}w)"; else synth="ok"; fi + else + synth="FAIL"; rc_all=1 + fi + + cosim="-" + if [ "$COSIM" = "yes" ] && [ "$synth" = "ok" ]; then + if ./cosim.sh >"work/${d}.cosim.log" 2>&1; then cosim="PASS" + else cosim="FAIL"; rc_all=1; fi + fi + printf '%-20s %-10s %s\n' "$d" "$synth" "$cosim" +done +exit $rc_all