From 45ab4846417862c2435cf413a92c6a277d310971 Mon Sep 17 00:00:00 2001 From: stefan Date: Fri, 15 May 2026 23:23:32 +0900 Subject: [PATCH] some useful options for vcd converter, some docs --- Makefile | 8 +++++-- README.md | 21 +++++++++++++++--- picorv32_vcd_import.py | 50 ++++++++++++++++++++++++++++-------------- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index bd21f6e..38bd9de 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,11 @@ jpeg_rtl_sim: $(JPEG_CORE_VVP) # picorv32 targets # -picorv32_rtl_sim: +test_vcd: make -C picorv32 test_vcd - cp picorv32/testbench.vcd picorv32.vcd +test_ez_vcd: + make -C picorv32 test_ez_vcd + +testbench.npy: + ./picorv32_vcd_import.py picorv32/testbench.vcd testbench.npy \ No newline at end of file diff --git a/README.md b/README.md index 38b6617..4bb9688 100644 --- a/README.md +++ b/README.md @@ -18,18 +18,33 @@ If `nix` is not installed, follow this [guide](https://librelane.readthedocs.io/ ## Usage Compile jpeg decoder core using `iverilog` and run RTL simulation of the jpeg decoder core using `vvp`: - ``` nix develop make uv run jpeg_core_tb_run_plasma.py ``` +Call `uv run jpeg_core_tb_run_plasma.py --help` for more options. Load synthesized circuits and display statistics (example code): - ``` uv run load_sky130_circuits.py ``` -Works also outside a `nix develop` shell if [uv](https://docs.astral.sh/uv/) is installed on the base system. The script demonstrates how to obtain synthesized netlists via nix derivations [published in this github repository](https://github.com/s-holst/benchmark-circuits). These circuits along with layout and timings are built on-demand (using LibreLane) if not yet available in local nix store. +Run picorv32's built-in testbench (generate `picorv32/testbench.vcd`): +``` +make test_vcd +``` +or +``` +make test_ez_vcd +``` + +Import generated VCD with kyupy and convert it to a pattern file for later fault simulation: +``` +uv run picorv32_vcd_import.py picorv32/testbench.vcd patterns.npy +``` +Call `uv run picorv32_vcd_import.py --help` for more options. + + +Some `uv` commands work also outside a `nix develop` shell if [uv](https://docs.astral.sh/uv/) is installed on the base system. The script demonstrates how to obtain synthesized netlists via nix derivations [published in this github repository](https://github.com/s-holst/benchmark-circuits). These circuits along with layout and timings are built on-demand (using LibreLane) if not yet available in local nix store. diff --git a/picorv32_vcd_import.py b/picorv32_vcd_import.py index 93f0af9..bcb4c9c 100755 --- a/picorv32_vcd_import.py +++ b/picorv32_vcd_import.py @@ -2,6 +2,8 @@ import re import argparse +from pathlib import Path + import numpy as np from PIL import Image @@ -10,6 +12,19 @@ from kyupy.techlib import SKY130 from kyupy.logic_sim import LogicSim4V +def path_for(circuit: str): + import subprocess + return Path(subprocess.check_output( + ["nix", "build", "--print-out-paths", "--no-link", + f"github:s-holst/benchmark-circuits#{circuit}" ], + text=True, + ).strip()) + + +def verilog_nl_path_for(circuit: str): + return next(path_for(circuit).glob("*/nl/*.nl.v")) + + def check_conflicts(patterns1, patterns2, names): conflicts = (patterns1 != patterns2) & (patterns1 != logic.UNKNOWN) & (patterns1 != logic.UNASSIGNED) & (patterns2 != logic.UNKNOWN) & (patterns2 != logic.UNASSIGNED) if conflicts.sum() > 0: @@ -20,7 +35,7 @@ def check_conflicts(patterns1, patterns2, names): assert conflicts.sum() == 0, "incompatible patterns" -def propagate_and_check(sim: LogicSim4V, patterns: np.ndarray, node2name: dict): +def sim_and_check(sim: LogicSim4V, patterns: np.ndarray, node2name: dict): responses = np.full_like(patterns, logic.UNASSIGNED) sim.simulate(patterns, responses) #to_image(np.concatenate([patterns, responses]), 'pat_res.png') @@ -61,15 +76,17 @@ def find_name(c, n): def main(): - parser = argparse.ArgumentParser() - parser.add_argument('dut', type=str, help='verilog file with unit under test (uut)') - parser.add_argument('vcd', type=str, help='vcd file to process') + parser = argparse.ArgumentParser(description='Imports a VCD file from PicoRV32 simulation into kyupy, simulates a few cycles and save the patterns as npy file for later fault simulation.') + parser.add_argument('vcd', type=str, help='input vcd file') + parser.add_argument('npy', type=str, help='output pattern file in npy format') + parser.add_argument('-c', type=int, default=16, help='number of clock cycles to re-simulate for filling unknowns') + parser.add_argument('-n', type=int, default=0, help='number of patterns to convert (default 0=all)') args = parser.parse_args() - c = verilog.load(args.dut, tlib=SKY130) + c = verilog.load(verilog_nl_path_for("picorv32-sky130"), tlib=SKY130) log.info(f'circuit: {c}') c.resolve_tlib_cells(tlib=SKY130) - s_locs = {} #{n.name: l for l, n in enumerate(c.s_nodes)} + s_locs = {} name2node = {} node2name = {} @@ -97,7 +114,7 @@ def main(): locs.append(-1) return locs - def var_locs_picorv(var: vcd.Var): + def var_locs(var: vcd.Var): if var.scope.name in ['uut', 'picorv32_core', 'pcpi_mul', 'pcpi_div']: if var.reference.startswith('cpu_state'): # FSM got re-coded during synthesis return [] @@ -122,13 +139,14 @@ def main(): return [] def step_filter(time, values, var_map): - clk_val = values[clk_loc] - return clk_val == 0 and time < 100e6 + if args.n > 0 and time is not None and time >= (args.n*10000): + return False + return values[clk_loc] == 0 - sim = LogicSim4V(c, sims=1024) + sim = LogicSim4V(c, sims=10240, c_reuse=True, strip_forks=True) log.info(f'sim: {sim}') - v = vcd.load(args.vcd, var_locs=var_locs_picorv, step_filter=step_filter, s_len=sim.s_len) + v = vcd.load(args.vcd, var_locs=var_locs, step_filter=step_filter, s_len=sim.s_len) log.info(f'vcd: {v}') unknown_pi = 0 @@ -146,12 +164,12 @@ def main(): log.info(f'Total bits unassigned: {(v.data == logic.UNASSIGNED).sum()} unknown: {(v.data == logic.UNKNOWN).sum()}') merged = v.data - np.save('testbench_orig.npy', merged) - for i in range(30): - merged = propagate_and_check(sim, merged, node2name) - log.info(f'PropCycle[{i}] total bits unassigned: {(merged == logic.UNASSIGNED).sum()} unknown: {(merged == logic.UNKNOWN).sum()}') + for i in range(args.c): + merged = sim_and_check(sim, merged, node2name) + log.info(f'Cycle[{i}] total bits unassigned: {(merged == logic.UNASSIGNED).sum()} unknown: {(merged == logic.UNKNOWN).sum()}') - np.save('testbench_prop.npy', merged) + log.info(f'Writing {args.npy}') + np.save(args.npy, merged) if __name__ == "__main__":