FSIMX STUDIO SERIES · PART 02 · FSIMX STUDIO

FSiMX: RISC-V Hardware-Software Co-Simulation Debug

That is what co-simulation debug in FSiMX Studio is for.
WIOWIZ Technologies • August 2026 • 8 min read

Run the RTL core.
Run a golden instruction-set reference beside it.
Compare every retirement, and let the disagreement point at the gate.

A passing RISC-V testbench tells you the firmware finished. It does not tell you the pipeline computed every value correctly along the way. The difference shows up on the day a branch is taken on stale operands and the program still ends in a state the self-check accepts. Co-simulation debug closes that gap: the RTL core runs against a golden instruction-set simulator, retirement for retirement, and the first place the two disagree is the first place to look.

The design under debug is AH1Q, a WIOWIZ-original RV64 core used here as a stand-in for any in-order pipeline. The golden reference is a standard ISA simulator run with commit logging. FSiMX Studio drives the RTL run, records each retired instruction, and hands both streams to the waveform tool for a retirement-level compare.

Retirement lockstep against the golden reference. The compare stops at the first divergence and names the retirement index.

The retirement stream, side by side

Each side emits one record per retired instruction: order, program counter, the encoding, and the register writeback. The RTL side comes from the core's retirement monitor; the golden side comes from the reference's commit log. FSiMX aligns them and compares field by field.

$ fsxrun -f ah1q.f -top tb_ah1q -trace-retire ah1q_rtl.trace
$ vwiz-cli isscmp ah1q_rtl.trace ah1q_golden.trace

RVVIP_RETIRE format : order pc insn priv rd_we rd rd_data
ISSCMP golden=5018 rtl=33 aligned=1 skipped=13 compared=20
result : MATCH  (20/20 retirements, 20/20 register writebacks)

Alignment is the whole problem

The reference boots from its own reset vector at 0x1000 and runs a bootrom before it reaches the payload, and it runs the program to completion. The RTL simulation stops after a fixed number of cycles. On the first paired run that meant 5,018 golden commits against 33 RTL retirements, with the payload's first instruction appearing only at golden line 13.

Compare those by sequence number and you pit the reference bootrom against the RTL's first instruction, and report a divergence at index 0 that is an artefact of the harness, not a bug in the core. The golden stream is advanced to the RTL's entry program counter first, and only the overlap is compared. The exit codes stay distinct: identical, divergence, and comparison-could-not-run are three different outcomes, and collapsing them is how a broken harness passes as a green build.

Golden ISS commit log reset 0x1000 → bootrom → payload    5,018 commits RTL retirement monitor entry PC → fixed cycle budget    33 retirements align on entry PC, compare overlap

A compressed instruction is not a divergence

The first comparison run flagged a divergence at retirement 3:

golden : 0x0000431d
rtl    : 0x00700313

Decoded, those are c.li x6, 7 and addi x6, x0, 7, the same operation. The reference logs the compressed 16-bit halfword it fetched; the RTL monitor logs the decompressed 32-bit equivalent while advancing the program counter by 2. Before writing a line of code against that theory, we measured it across the window: 33/33 program counters identical, 33/33 register writebacks identical, with exactly 8 differing encodings that were all this case. Twenty-five plus eight is thirty-three.

Trusting the tool's first answer would have meant reporting a CPU bug to the core team that did not exist. The fix was to expand the compressed forms with a full RVC decoder for the integer C extension, and to return "unverified" rather than a plausible guess for float C forms, reserved encodings and HINTs. The comparison also prints what it could not check: the retirement trace carries no memory fields, so the reference's memory-effect records are counted as unverified on every run.

The blind test

Every check above proves the comparator can flag a mismatch we planted. It does not prove it catches a bug nobody described. So we took a package we could not see into: one RTL build with an injected mutation, its golden trace, and no disclosure of what changed or where. Hashes verified, comparator run, no hints.

ISSCMP golden=5018 rtl=9 aligned=1 skipped=5 compared=9
DIVERGENCE at retirement 8 (pc)
  golden : pc=0x000000008000001a
  rtl    : pc=0x0000000080000054
Retirement compare stopping at the injected divergence with the program counter mismatch highlighted
The compare halts at retirement 8. The program counter that should be 0x8000001a is 0x80000054, the address of the test's failure label.

Reading the result

The divergence is reported at retirement 8, but the fault is at retirement 7. Reconstructing the operands around it:

idxpcinstructiongoldenRTL
50x80000010add s0, t1, t2s0 = 0xcs0 = 0xc
60x80000014li s1, 12s1 = 0xcs1 = 0xc
70x80000016bne s0, s1, +62not takentaken
8--0x8000001a0x80000054 <fail>

The branch was taken with its two operands equal. The target is correct: 0x80000016 plus 62 is 0x80000054, which the disassembly confirms is the label fail. The target arithmetic is sound and the decision is wrong. The core jumped into the test's own failure handler, which is why the run ends after 9 retirements.

Narrowing it to one bypass

Two defect classes fit a branch taken on equal operands:

  • Broken EX-stage forwarding into the branch comparator. Here s1 is written by the immediately preceding instruction and consumed by the branch, the textbook distance-1 hazard. With that bypass missing, the comparator sees a stale s1 against a correct s0 and takes the branch on wrong data.
  • Inverted branch condition, bne evaluating as beq.

The trace separates them. s0 is written two instructions before the branch. If the bypass were broken at both distances, the comparator would see 0 against 0, bne would not be taken, and the test would have passed. The failure requires a stale s1 with a correct s0: the distance-1 path broken while distance-2 is intact. That is a forwarding fault, not an inverted condition. The output does not say "something is wrong"; it says which retirement, which operand, and which bypass.

The software view: the same divergence cross-probed to the function, the instruction, and the waveform interval that produced it.

What it caught, and what it does not claim

Four defects surfaced during this work, none of them by a passing test, all of them by looking at the output. The comparison is worth exactly what it inspects, so it prints its gaps: compressed forms it could not expand stay counted as unverified, and memory effects the retirement trace does not carry are never reported as matched. A comparator that quietly skips half the state while printing MATCH is worse than one that checks less and says so.

The testbench says the firmware finished.
Co-simulation debug says whether the pipeline was right the whole way.
#RISCV #cosimulation #verification #FSiMX #debug

Download FSiMX Studio

FSiMX Studio is available now. It ships as a Docker bundle from the WIOWIZ website — download it, build the image, and run the same examples, waveforms and coverage you saw in this article. The native FSiMX engine, vWIZ-Wave and vWIZ-Coverage, in one window.

Download FSiMX Studio on wiowiz.ai →

FSiMX Studio: compile, run, waveform debug and coverage closure in one window.
FSiMX Studio on wiowiz.ai/fsimx-studio.html — one persistent session for compile, run, waveform debug and coverage closure.

 

#FSiMXStudio #FSiMX #verification

Our Approach

We're building systems that think about specifications the way engineers do.

We build our own in-house EDA with an intelligence layer across it. Our stack covers the full flow,
from spec to comprehensive sign-off, on tools we build and control.

Walk-in ones, walk-in zeros