The DMA finishes a job and raises irq_dma, the CPU takes the interrupt and checksums the destination buffer in SRAM1. Which event is allowed to start that checksum? On NanoSoC the DMA has no AXI port. The SBR reads the source into the DMA's input stream and writes the DMA's output stream to SRAM1 as master M1, straight through nano_xbar: no axi2apb bridge is on that path. irq_dma rises in the same cycle in which the DMA's last output beat first appears, before the SBR write engine has issued its last write and long before the last write response (BRESP) has come back. The SBR's own DONE bit is set only after the last BRESP, and irq_sbr follows it. The data-complete event is therefore the SBR's DONE, not the DMA's.
Peripheral registers are device memory: non-cacheable, non-bufferable, strongly ordered, so accesses arrive in program order, and the axi2apb bridge returns B only after the APB access completes. Memory is normal memory: its writes may be buffered and merged, and AXI itself gives no ordering between a read and a write unless the master waits for the write response before issuing the read. The integrator owes software a rule that names the event after which data is visible: for a buffer written by the SBR, that is SBR STATUS.DONE (or irq_sbr), because DONE is set after the last BRESP. A later APB read of DMA STATUS goes through a different path (CPU, axi_cdc, xbar, axi2apb) and orders nothing against M1's writes.
Pair | Ordered? | Why |
|---|---|---|
CPU write to a DMA register, then CPU read of the same register | Yes | Device memory, one bridge, one access at a time; B returns after the APB access completes. |
CPU write to UART DATA, then read of UART STATUS | Yes | Same bridge and the same rule, through the apb_cdc handshake. |
CPU write to SRAM, then CPU read of the same address | Only if the CPU waits for B | AXI has no ordering between the write and read channels; the blocking NanoSoC CPU waits, a buffering master would not. |
SBR final write to SRAM1, then irq_dma at the CPU | Not guaranteed | irq_dma is raised before the last BRESP; it wins or loses the race by timing. |
SBR final write to SRAM1, then SBR DONE and irq_sbr | Yes | DONE is set only after the last BRESP, so the data is in SRAM1 before the event exists. |
The race can be modelled. After irq_dma rises, the last write response arrives at most 8 clk_sys cycles later (83.3 ns, the whole fill and drain allowance of the job formula), while the interrupt takes at least 135.4 ns to reach the CPU. Today the data wins comfortably, so a handler that waits on irq_dma passes every test. That is a timing margin of 5 clk_sys cycles, not a guarantee: a deeper write buffer, a register slice pair on the write path or a slower destination that adds 5 or more clocks removes it, and the handler then reads stale data intermittently. A handler that waits on irq_sbr is correct for any write-path delay.
# NanoSoC teaching values: which event proves the SBR's writes are in SRAM1?
T_SYS = 1e9 / 96e6 # ns
T_PER = 1e9 / 24e6
T_CPU = 1e9 / 192e6
IRQ_BEST = 2 * T_PER + 1 * T_PER + 2 * T_CPU # 135.4 ns from the interrupt source to the CPU
JOB_TAIL = 8 * T_SYS # upper bound on irq_dma to the last BRESP: the job's fill and drain allowance
def times(event, extra_write_clks):
# (time the CPU can see the event, time the last write is complete), ns after irq_dma rises
t_data = JOB_TAIL + extra_write_clks * T_SYS
if event == "irq_dma":
return IRQ_BEST, t_data # raised without waiting for the last BRESP
if event == "irq_sbr":
return t_data + IRQ_BEST, t_data # SBR DONE is set after the last BRESP
raise ValueError(event)
def data_ready(event, extra):
t_cpu, t_data = times(event, extra)
return t_data < t_cpu
assert abs(IRQ_BEST - 135.4) < 0.05
for extra in (0, 2, 4):
assert data_ready("irq_dma", extra) # today: the data happens to win
assert not data_ready("irq_dma", 6) # a longer write path loses the race
assert not data_ready("irq_dma", 20)
for extra in range(0, 40):
assert data_ready("irq_sbr", extra) # correct whatever the write path costs
print("PASS: irq_dma wins only by timing (4 spare clocks), irq_sbr is correct for any write delay")The integration spec names the point of serialisation for every master-to-slave path, not just the protocol default: for NanoSoC, the slave for memory and the single-access bridge for registers.
Every interrupt that announces data states which event proves the data is written: the SBR's DONE for buffers the SBR writes, never an upstream block's interrupt.
Device versus normal attributes in the map match the ordering assumptions of every driver.
Write the write-then-interrupt test for the SBR path: run a job, wait for irq_sbr, then check the buffer with no extra delay. Then prove the test can fail: add a write-path delay of 6 clk_sys cycles to the SRAM1 write channel and check that a handler waiting on irq_dma now sees stale words while one waiting on irq_sbr still passes. If a hand-written handler fails intermittently, it is waiting on the wrong event. Randomise write-path timing while interrupts fire; an ordering bug scales with timing pressure, which is why directed tests with the default timing miss it.
irq_dma rises when the last word leaves the DMA, before the SBR has the last write response. The data wins only because the write path is shorter than the 135.4 ns interrupt path. DMA DONE says the last word left the DMA; SBR DONE says the last write response arrived.