The SBR streams a 64-word job whose source buffer starts at 0x2000_0FF0, 16 bytes below a 4 KB page boundary. If it issues one 16-beat read from that address, the burst would run 48 bytes past 0x2000_0FFF. Nothing on NanoSoC decodes differently at 0x2000_1000 (SRAM0 is 64 KB), so this particular burst would probably work. AXI forbids it anyway, because 4 KB is the smallest region a slave may own: a master may not assume that the next page belongs to the same slave. A fabric, a width converter or a later memory with 4 KB banks may split or misroute it, and the failure is intermittent, because it appears only when a buffer happens to straddle a page. The protocol checker flags it, and the SBR must split the burst itself.
A burst must not cross a 4 KB boundary. With 4-byte beats a 16-beat INCR burst covers 64 bytes and is legal anywhere except when it starts within the last 60 bytes below a page edge (a burst starting exactly 64 bytes below the edge ends at 0x...FFF and is legal). The SBR therefore limits every burst by two rules at once: at most 16 beats (the fabric maximum), and no more beats than remain before the next 4 KB boundary. An 8-beat read from 0x0FF0 becomes two 4-beat bursts: beats 0 to 3 end exactly at 0x0FFF and beats 4 to 7 form a new burst from 0x1000. WLAST or RLAST marks the last beat of each issued burst, not of the original request, and the response path treats the two halves as independent transactions with the same ID.
The waveform below is simulation output for the wave source of this topic: an 8-beat read split into two 4-beat bursts. One slot is one aclk cycle. The lanes carry handshakes and last flags only, no address or length, so the addresses 0x0FF0 and 0x1000 appear in the text, not in the picture.
How to read it: arvalid pulses in slot 0 while arready stays high; rvalid then stays high for four beats (slots 1 to 4) with rlast on the fourth. rvalid is low in slots 5 and 6, and in slot 6 arvalid pulses again for the second burst, whose four beats (slots 7 to 10) carry the second rlast. The second lane is a testbench marker, high from the second address to the end of the second burst, so the two halves are easy to tell apart. An unsplit eight-beat burst would show one arvalid pulse and a single rlast on the eighth beat; the rlast after beat four of each half is the sign of the split.
PAGE = 0x1000 # 4 KB
BEAT = 4 # bytes per beat on the 32-bit fabric
MAXLEN = 16 # longest INCR burst on this fabric
def split_burst(addr, total_beats):
# NanoSoC teaching values: the SBR splitter. Every burst obeys BOTH limits.
bursts = []
left = total_beats
while left > 0:
to_edge = (PAGE - addr % PAGE) // BEAT
n = min(left, MAXLEN, to_edge)
bursts.append((addr, n))
addr += n * BEAT
left -= n
return bursts
def legal(bursts, addr, total_beats):
# the protocol checker: contiguous, 1..16 beats, inside one page, nothing lost
nxt, beats = addr, 0
for a, n in bursts:
if a != nxt or not 1 <= n <= MAXLEN or a % PAGE + n * BEAT > PAGE:
return False
nxt, beats = a + n * BEAT, beats + n
return beats == total_beats
def first_cut_splitter(addr, total_beats):
# an earlier version: only the page edge limits the FIRST burst (the bug)
n = min(total_beats, (PAGE - addr % PAGE) // BEAT)
rest = split_burst(addr + n * BEAT, total_beats - n)
return [(addr, n)] + rest
cases = [
(0x0FF0, 8, [(0x0FF0, 4), (0x1000, 4)]),
(0x2000_0000, 64, [(0x2000_0000, 16), (0x2000_0040, 16), (0x2000_0080, 16), (0x2000_00C0, 16)]),
(0x2000_0F00, 64, [(0x2000_0F00, 16), (0x2000_0F40, 16), (0x2000_0F80, 16), (0x2000_0FC0, 16)]),
(0x2000_0FD0, 16, [(0x2000_0FD0, 12), (0x2000_1000, 4)]),
(0x2000_0FF0, 64, [(0x2000_0FF0, 4), (0x2000_1000, 16), (0x2000_1040, 16), (0x2000_1080, 16), (0x2000_10C0, 12)]),
]
for addr, n, want in cases:
assert split_burst(addr, n) == want, hex(addr)
# sweep every word-aligned start across two pages and several lengths
for off in range(0, 2 * PAGE, 4):
for n in (1, 3, 16, 17, 40, 64, 70):
assert legal(split_burst(0x2000_0000 + off, n), 0x2000_0000 + off, n)
# the checker can fail: the earlier splitter issues 64 beats where 16 is the limit
assert not legal(first_cut_splitter(0x2000_0000, 64), 0x2000_0000, 64)
assert not legal(first_cut_splitter(0x2000_0F00, 64), 0x2000_0F00, 64)
print("PASS: 5 worked splits, 2-page sweep legal, the 16-beat cap bug is caught")Every burst-issuing master and converter (on NanoSoC, the SBR) states its maximum burst length and its split rule, with both limits: 16 beats and the page edge.
The protocol checker flags any AxLEN that would cross a page, in every random test, not just directed ones.
WLAST and WSTRB handling is specified per issued burst, never per original request.
Constrain random tests to start bursts near page edges: unaligned starts plus lengths up to 16 beats hit every split case within seconds, and lengths above 16 prove the second limit. If data lands in the wrong page, dump the issued AxADDR and AxLEN pairs and check each against its page: the offender is the pair whose bytes cross the boundary. Remember unaligned starts are legal; only the crossing is forbidden.
Two: 48 bytes remain to the page edge, so 12 beats run to 0x2000_0FFF and the remaining 4 beats form a second burst from 0x2000_1000.