Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions hdl/rtl/riscv/riscv_isa_i_pkg.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions hdl/rtl/riscv/riscv_isa_pkg.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions syn/uhdm2rtlil/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# build / run artifacts
work/
mem_if.mem
*.log
slpp_all/
104 changes: 104 additions & 0 deletions syn/uhdm2rtlil/README.md
Original file line number Diff line number Diff line change
@@ -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/<top>_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 <name>` 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.
6 changes: 6 additions & 0 deletions syn/uhdm2rtlil/boot.hex
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
800200b7
05a00113
0020a023
0ff00113
0020a023
0000006f
79 changes: 79 additions & 0 deletions syn/uhdm2rtlil/build.sh
Original file line number Diff line number Diff line change
@@ -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/ (<top>_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"
62 changes: 62 additions & 0 deletions syn/uhdm2rtlil/cosim.sh
Original file line number Diff line number Diff line change
@@ -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"
34 changes: 34 additions & 0 deletions syn/uhdm2rtlil/cosim_main.cpp
Original file line number Diff line number Diff line change
@@ -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 <verilated.h>
#include "Vtb.h"
#include <cstdio>
#include <cstdlib>

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;
}
52 changes: 52 additions & 0 deletions syn/uhdm2rtlil/cosim_tb.sv
Original file line number Diff line number Diff line change
@@ -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
Loading