Bus Transaction Tracing (AHB / APB)

Overview

jacquard cosim can decode on-chip bus transactions and emit them in a compact, transaction-level form — one row per transfer, rather than raw per-cycle waveforms. You declare the bus interfaces to watch in sim_config.json; cosim observes their pins on the GPU each tick and runs the protocol decode on the CPU, writing decoded transactions to a CSV file.

This is observe-only: the tracer watches signals the design already drives, it never drives anything. It adds no measurable simulation overhead when no buses are configured.

ProtocolStatus
APB3Supported
AHB-LitePlanned (pipelined address/data pairing, burst tracking)
AHB5Planned (AHB-Lite + security / exclusive signals)

The design rationale lives in ADR 0013; the roadmap is in plans/bus-transaction-tracing.md.

Bus tracing is the structured, protocol-aware counterpart to --trace-signals, which surfaces raw internal nets in the output VCD. Use --trace-signals when you want waveforms of individual wires; use bus tracing when you want decoded READ 0x40 => 0x1 records.

Configuring a bus

Add a bus_traces array to sim_config.json. Each entry names one bus interface:

{
    "netlist_path": "build/soc.gv",
    "clock_gpio": 0,
    "reset_gpio": 1,
    "num_cycles": 100000,
    "clock_period_ps": 40000,

    "bus_traces": [
        {
            "name": "dmi",
            "protocol": "apb3",
            "prefix": "soc.dm.",
            "addr_bits": 9,
            "data_bits": 32
        }
    ]
}
FieldRequiredMeaning
nameyesLabel for this bus in the CSV bus column.
protocolyesapb3 (or ahb-lite / ahb5 once supported).
prefixyesHierarchical net-name prefix; standard pin names are appended (see below). May be "" for top-level pins.
addr_bitsno (default 32)Address bus width.
data_bitsno (default 32)Data bus width.
signalsnoPer-pin net-name overrides (see Pin resolution).

Pin names

By default each protocol pin is resolved as {prefix}{pin}. For APB3:

Logical pinDefault netNotes
psel{prefix}pselrequired
penable{prefix}penablerequired
pwrite{prefix}pwritedirection
paddr{prefix}paddr[i]addr_bits wide
pwdata{prefix}pwdata[i]data_bits wide
prdata{prefix}prdata[i]data_bits wide
pready{prefix}preadyoptional — unresolved is treated as always-ready (1)
pslverr{prefix}pslverroptional — unresolved is treated as no-error (0)

So a bus with "prefix": "soc.dm." looks for soc.dm.psel, soc.dm.paddr[0], …, soc.dm.prdata[31].

If your design's pins don't follow that convention, remap individual logical pins with signals:

{
    "name": "periph",
    "protocol": "apb3",
    "prefix": "soc.apb.",
    "signals": {
        "psel": "soc.apb_decode.sel_periph",
        "prdata": "soc.apb_mux.readback"
    }
}

Running

cargo run -r --features metal --bin jacquard -- cosim \
    build/soc.gv \
    --config sim_config.json \
    --bus-trace-csv bus.csv

At startup each bus logs whether it resolved:

bus-trace `dmi` (APB3): psel/penable resolved, addr 9/9 bits, pready=true pslverr=true

and at the end:

bus-trace: decoded 12 transaction(s) across 1 bus(es)
bus-trace: wrote 12 transaction(s) to bus.csv

CSV output

tick,bus,protocol,dir,addr,data,resp,burst
24,dmi,apb3,WR,0x10,0xCAFEBABE,OK,
30,dmi,apb3,RD,0x10,0xCAFEBABE,OK,
ColumnMeaning
tickCosim edge at which the transfer completed. One clock cycle = 2 edges (rising + falling) for a single-domain design.
busThe configured bus name.
protocolapb3 / ahb-lite / ahb5.
dirWR or RD.
addrTransfer address (hex).
datapwdata for writes, prdata for reads (hex).
respOK or ERR (from pslverr / hresp).
burstAHB burst position beat/len (empty for APB).

Pin resolution

For the GPU to read a bus pin each tick, that net must (1) exist in the post-synthesis netlist under a resolvable name and (2) survive into the simulation's output state. Two consequences:

  • Names must survive synthesis. The resolver uses the same multi-candidate matcher as --trace-signals, so Yosys-flattened (\soc.dm.psel), scalar-expanded (soc.dm.paddr[3]), and structurally-hierarchical names all work. But synthesis is free to rename or delete combinational nets. The robust pattern is to make the bus signals registers (their DFF Q outputs keep their names), or to annotate the RTL nets with (* keep *).

  • Constant-folded bits read as 0 — correctly. If a design only ever drives, say, addresses 0x00 and 0x04, synthesis folds every address bit except paddr[2] to a constant. The startup log then shows e.g. addr 1/8 bits. This is expected: the tracer reconstructs the full value correctly because the dropped bits are genuinely 0.

pready / pslverr are allowed to be absent. A common case is an always-ready slave that ties pready high — it folds to a constant, fails to resolve, and the tracer correctly treats the bus as always-ready.

Worked example

tests/apb_trace/ is a self-contained, synthesizable APB3 system used as the CI regression. Its master issues a fixed program — two writes then two reads — to a register-file slave, and check.py asserts the decoded CSV. See tests/apb_trace/README.md.

yosys -s tests/apb_trace/synth.tcl          # (from tests/apb_trace/)
cargo run -r --features metal --bin jacquard -- cosim \
    tests/apb_trace/apb_trace_synth.gv \
    --config tests/apb_trace/sim_config.json \
    --top-module apb_trace \
    --max-clock-edges 200 \
    --bus-trace-csv apb.csv
python3 tests/apb_trace/check.py apb.csv

Troubleshooting

SymptomCause / fix
psel/penable did not resolve … this bus will not captureThe prefix is wrong, or the nets were optimized away. Find the real names with uv run netlist-graph search <netlist> psel, then fix prefix or add signals overrides.
Zero transactions decodedGate never asserted. Check that psel/penable resolve (startup log) and that the bus is actually exercised within --max-clock-edges.
Address or data always 0paddr/pwdata/prdata nets didn't resolve (renamed/folded). Confirm with netlist-graph search; mark the RTL nets (* keep *) and re-synthesize.
Reads return stale/wrong dataThe slave must present prdata during the ACCESS phase. Register prdata so its value is stable when psel & penable are high.

Limitations

  • APB3 only for now; AHB-Lite / AHB5 and annotated-VCD output are the next phases (see the plan).
  • Up to 4 buses per run, addresses/data up to 32 bits.
  • Cosim is Metal-only today, so bus tracing is Metal-only.
  • The legacy hardcoded Wishbone trace (a separate, SoC-specific path) is unaffected; folding it onto this general mechanism is a planned follow-up.