Quick Cheat Sheet
The failures that show up most often on register-access and clock-control blocks. Skim this before you go signal-by-signal.
| Protocol | Symptom | Likely fix |
|---|---|---|
| AXI | Waveform freezes with AWVALID/ARVALID high, nothing moves | Trace READY backwards through the interconnect — almost always a full FIFO or an arbiter that never grants this master's ID |
| AXI | Read data looks right but lands in the wrong register | Check RID against the ARID of the request you think you're looking at — you're probably reading a different outstanding transaction |
| APB | PENABLE never rises after PSEL | SETUP-to-ACCESS state transition is missing in the bridge/master FSM — one-cycle bug, check the state register directly |
| APB | Register write "doesn't take" — PREADY asserts, value never changes | Address decode landed on a different offset. Compare PADDR against your register map, not against what the driver intended to send |
| AHB | One slave inserts a wait state and the whole bus stalls | Expected — AHB is single-bus, HREADY is shared. Confirm it's the target slave holding HREADYOUT low, not a sibling |
| CDC | A status bit glitches for exactly one destination-clock cycle | Missing or under-staged synchronizer. Check flop count between source logic and the first destination-domain register, not the waveform value itself |
AXI4 / AXI4-Lite
Five independent channels, each with its own VALID/READY handshake, tied together only by IDs and ordering rules. Most AXI debug time goes into figuring out which outstanding transaction a signal value belongs to — not what the signal means.
The five channels
| Channel | Direction | Key signals | Carries |
|---|---|---|---|
| AW — write address | M→S | AWID, AWADDR, AWLEN, AWSIZE, AWBURST, AWVALID/READY | Where and how a write burst will land |
| W — write data | M→S | WDATA, WSTRB, WLAST, WVALID/READY | The burst payload, one beat per cycle |
| B — write response | S→M | BID, BRESP, BVALID/READY | Whether the write committed cleanly |
| AR — read address | M→S | ARID, ARADDR, ARLEN, ARSIZE, ARBURST, ARVALID/READY | Where a read burst starts |
| R — read data | S→M | RID, RDATA, RRESP, RLAST, RVALID/READY | The returned burst, one beat per cycle |
Reading the handshake: a transfer happens on the clock edge where VALID and READY are both high — not when either one first asserts. Once a master raises VALID it is not allowed to lower it again until that edge fires, so a VALID pulse that drops before READY ever rises is a protocol violation, not a cancelled request. If your testbench has an assertion-based protocol checker, let it flag this class of bug instead of eyeballing every pulse.
Common bug signatures
| Waveform symptom | Likely cause | Where to look |
|---|---|---|
| AWVALID/ARVALID high for many cycles, READY never comes | Downstream FIFO full, or the interconnect arbiter never grants this master's outstanding ID | Walk backward through the interconnect one hop at a time; correlate READY with the sibling master that is being granted |
| BRESP / RDATA arrives for the wrong request | BID/RID doesn't match the AWID/ARID you're tracking — transactions completed out of order, which AXI explicitly allows across different IDs | Filter or color the waveform by ID and follow one ID end-to-end instead of by time order |
| Write appears to corrupt neighboring bytes | WSTRB doesn't match AWSIZE, or a narrow transfer isn't aligned the way the slave assumes | Compare WSTRB bit pattern against AWSIZE and the low address bits for that beat |
| Burst response comes one beat short or long | WLAST/RLAST asserted on the wrong beat relative to AWLEN/ARLEN | Count W/R beats against AxLEN+1 explicitly — AxLEN is "beats minus one," the classic off-by-one |
| Second write to the same register silently reuses stale data | Master reissued AWID before the prior transaction with that ID returned its BRESP, breaking ID ordering guarantees | Check that ID is retired (BRESP/RRESP seen) before the same ID is reused for a new address phase |
| Access to a register just past a page silently corrupts an unrelated address | A burst was allowed to cross a 4KB boundary, which AXI forbids and most interconnects don't check | Check AWADDR/ARADDR + burst size × length against the enclosing 4KB window at the request boundary |
APB (APB4)
This is almost certainly what your control/status registers sit behind. It's the simplest of the three protocols, which is exactly why bugs here are usually not protocol bugs at all — they're address decode, timing, or clock-domain-crossing bugs wearing an APB costume.
Signals and the two-phase transfer
| Signal | Meaning |
|---|---|
| PSEL[x] | One-hot select for the addressed slave — asserted for the whole transfer |
| PENABLE | Low in SETUP (cycle 1), high through ACCESS (cycle 2 onward) — this is what distinguishes the two phases |
| PADDR / PWRITE / PWDATA | Must be stable from SETUP through the end of ACCESS |
| PREADY | Slave holds this low to insert wait states; transfer completes on the edge where PSEL & PENABLE & PREADY are all high |
| PRDATA / PSLVERR | Valid on the same edge the transfer completes |
| PSTRB | Byte lane enables for writes (APB4+) |
In the waveform: one flat cycle of PSEL alone (SETUP), then PSEL+PENABLE held together until PREADY closes it out (ACCESS). If a sensor block needs several cycles for a conversion to finish, that latency shows up entirely as PREADY held low — the bus is simply waiting, which is correct behavior, not a hang, up to whatever timeout your environment enforces.
Common bug signatures
| Waveform symptom | Likely cause | Where to look |
|---|---|---|
| PSEL asserts, PENABLE never follows | Master/bridge FSM has a broken SETUP→ACCESS transition | Look at the driving state register directly, not the output signal — usually a one-state, one-cycle bug |
| Write reports success but the register never changes | Address decode routed the transfer to the wrong PSEL, or the register offset doesn't match the map | Diff PADDR against your register spec offset-by-offset, not against what you assume the driver sent |
| Two PSEL lines high at once | Overlapping address decode ranges between two slaves | Check the decoder's range boundaries against both slaves' address maps for an overlap or off-by-one |
| PREADY never returns | Slave FSM waiting on an internal "done" pulse (e.g. conversion complete) that never arrives, often because that pulse lives in a different clock domain and was never synchronized in | Trace the internal done/ready signal feeding PREADY back to its source domain — see the clock-domain-crossing section |
| PRDATA looks stale by one transfer | Register read muxed off a pipeline stage that lags the address by a cycle | Check the read-data mux selects with the same PADDR that was valid in SETUP, not the current cycle's PADDR |
AHB / AHB-Lite
Less common in new designs but still turns up on legacy peripheral bridges. The defining trait to hold onto: it's pipelined but single-bus — only one transfer's address phase and one transfer's data phase are in flight at a time, and a wait state from any slave stalls everyone.
Signals and pipelining
| Signal | Meaning |
|---|---|
| HTRANS | IDLE / BUSY / NONSEQ / SEQ — NONSEQ starts a new burst, SEQ continues one |
| HADDR / HSIZE / HBURST / HWRITE | Address phase, one cycle ahead of the matching data phase |
| HWDATA / HRDATA | Data phase, overlapping the next transfer's address phase |
| HREADY (in) / HREADYOUT (out) | HREADYOUT from the selected slave becomes HREADY broadcast to everyone — a wait state is global, not local |
| HRESP | OKAY or ERROR; an ERROR response is a two-cycle protocol, not one |
Common bug signatures
| Waveform symptom | Likely cause | Where to look |
|---|---|---|
| Entire bus stalls, not just one slave's transfer | This is expected AHB behavior — HREADY is shared. Confirm it's actually your slave holding HREADYOUT low and not a sibling on the same bus | Check HSEL for the stalling slave alongside its HREADYOUT, not just the merged HREADY |
| Slave samples the wrong address after a wait state | Address wasn't held/re-registered correctly across the HREADY-low cycles | Confirm the slave only advances its captured HADDR on cycles where HREADY was high |
| Bus appears to hang permanently after an error | HRESP ERROR wasn't held for its required two cycles (first with HREADY low, then high) — a downstream master's FSM built for the two-cycle sequence gets stuck waiting for the second phase | Check HRESP and HREADY together across the error response, not HRESP alone |
| Burst reads land at the wrong addresses | HTRANS/HBURST sequencing broken — SEQ asserted without a preceding NONSEQ, or wrap boundary miscalculated | Walk HTRANS cycle by cycle against HBURST type; verify wrap alignment against HSIZE |
Clock-Domain Crossing (CDC)
This is where clock-management debug actually lives. A register interface typically runs on a bus clock, but a PLL, glitch-free clock mux, and dividers operate on reference and generated clocks that don't share an edge with it. Sensor blocks add a second crossing: a conversion or free-running measurement source produces a "done" pulse on its own clock, which then has to reach the register-interface domain intact.
A clean-looking waveform can still be a CDC bug. RTL simulation won't show metastability — a signal that crossed clock domains with no synchronizer at all can still look like a normal, glitch-free toggle in the waveform. The real check is structural: count the flop stages between the source-domain register and the first destination-domain register, and confirm there's no combinational logic in between. A CDC lint or formal check will catch what a waveform review alone cannot.
Crossing patterns you'll actually see
| Pattern | Where it shows up | What "correct" looks like on the waveform |
|---|---|---|
| 2-flop (or 3-flop) synchronizer | Single status/control bits — e.g. a PLL lock indicator into the register-access domain | The bit changes value cleanly aligned to a destination-clock edge, one cycle after the intermediate flop, never mid-cycle |
| Gray-coded pointer / async FIFO | Any multi-bit value crossing domains, including config words written into a clock-generation domain | Pointer bits never change more than one bit at a time between sampled values — if multiple bits change together across a crossing, that's the smoking gun for a missing gray-code stage |
| Request/acknowledge (4-phase) handshake | A "conversion done" pulse crossing from a sensor's clock into the domain that reads back the result | Req asserts and stays asserted until ack returns; a req that pulses and drops before an ack could possibly have propagated is undersized for the destination clock rate |
| Glitch-free clock mux (clock-management specific) | Switching an output clock between reference/PLL/bypass sources | The mux control sequence gates the old clock's output low, waits enough cycles for the switch logic to settle in both domains, then enables the new clock — look for that gate-then-settle-then-enable ordering explicitly, not just the final clock waveform |
Common bug signatures
| Waveform symptom | Likely cause | Where to look |
|---|---|---|
| Status bit glitches for exactly one destination-clock cycle then reverts | Missing synchronizer, or a synchronizer with only one flop stage instead of two | Count registers between source and destination domain in the RTL, don't infer it from the waveform shape |
| Multi-bit config value briefly reads as a value that was never written | A whole bus was double-flopped as a group instead of gray-coded or handshaked — different bits arrive on different destination cycles | Overlay the individual bits of the bus and check whether they all change on the same destination-clock edge |
| A "conversion done" pulse is sometimes missed entirely | Source-domain pulse is narrower than one destination-clock period, so the destination sampling flop can miss it outright | Compare pulse width in the source domain against the destination clock period; look for a toggle-based or stretched-pulse crossing instead of a raw one-cycle pulse |
| Output clock glitches briefly during a clock-source switch | New clock enabled before the old one was fully gated, or before both domains agreed the switch was safe | Check the gate/settle/enable sequence on the mux control signals against both source clocks' edges around the switch point |
Waveform Debugger Tactics
Most RTL debug environments let you work above the pin level once you set them up that way. The gap between "scrolling raw signals" and "reading transactions" is almost entirely setup, done once per block and reused every session.
- Attach a protocol checker, don't eyeball VALID/READY. If your testbench has AXI/APB/AHB verification IP with assertion-based protocol checking, its violation reports are ground truth for handshake and ordering bugs — use them to point you at the cycle, then open the waveform there.
- Group signals into buses before you start. Bundle AWID/AWADDR/AWLEN/... into one composite "AW" row, same for W, B, AR, R (or PADDR/PWRITE/PSEL/PENABLE for APB). Reading decoded bus values beats scanning twenty individual traces.
- Filter or color by ID on AXI. With multiple outstanding transactions in flight, isolate one AWID/ARID at a time and follow it from address phase to response — don't try to read every ID's activity at once.
- Use transaction-level / protocol-aware views over raw signal view when available. Many debug environments can decode bus activity into transaction rows (address, data, ID, response) — much faster than reconstructing a burst by hand from beats.
- Cross-probe from the waveform to RTL source. When a value looks wrong, jump straight from that signal to the driving always block instead of guessing which process wrote it.
- Pull up an auto-extracted FSM view for sequencers. Particularly useful for a clock-switch sequencer or any conversion-state machine in a sensor block — overlay the current state on the waveform instead of decoding a state-encoding value by hand.
- Overlay both clocks explicitly at a CDC boundary. Put the source and destination clocks in the same waveform pane as the crossing signal so you can see the actual edge relationship, not just infer it.
- Save the signal-group and bookmark setup per block. Register-access and clock-control debug sessions repeat the same waveform layout constantly — save it once so the next bug starts from a readable view, not a blank signal list.
Stuck? Work It in This Order
When you don't yet know which protocol layer the bug is in, this order tends to find it fastest.
- Confirm the handshake actually completed. Find the exact edge where VALID/READY (or PSEL/PENABLE/PREADY, or HTRANS/HREADY) were all simultaneously satisfied. If you can't find that edge, you have a handshake bug, not a data bug — stop looking at data signals.
- Confirm you're looking at the right transaction. On AXI, match ID before you trust any data or response value. It's easy to debug a real bug on the wrong outstanding transaction.
- Confirm the address, not just the data. A large share of "wrong value" bugs on register-interface blocks are address decode or offset bugs, not data-path bugs. Check PADDR/AWADDR/ARADDR/HADDR against the register map before debugging logic.
- If it crosses clocks, check flop count before waveform shape. RTL simulation can't show you metastability. A structural CDC check (lint/formal) tells you more than staring at the trace.
- If it's intermittent, check timing relative to the slower clock. Pulses that are fine at one clock ratio can be missed at another — especially relevant if an internal clock rate can vary with operating conditions such as temperature.
Signal names and protocol behavior follow the Arm AMBA AXI4, APB, and AHB protocol specifications. Debug tactics are described generically — exact navigation and terminology vary by tool, so check your own debug environment's documentation for precise steps.