A DMA engine issues a 256-byte burst starting at 0x0FC0. The first 64 bytes belong to a buffer in one 4 KB page; the remaining 192 bytes run into the next page, which the operating system gave to a different program, and which the address-translation unit maps to a completely different physical location. The engine's burst describes one contiguous range. Physically, it is two.
Why do protocols forbid bursts from crossing certain boundaries, and what should a requester do with a transfer that would?
A burst gives one start address and lets the target compute the rest (lesson 5.8), so the obvious rule is that any burst, of any length, from any address, is fine. But every agent that looks at the address only once, at the start of the burst, assumes the whole burst lives where the start lives. The decoder sends the whole burst to the target that owns the start address; an address-translation unit translates the start address and adds offsets. Both are wrong the moment the burst crosses into a region with a different owner or a different translation.
Pick a boundary size that every agent can rely on: targets are placed on multiples of it, and address translation works in pages at least that large. Forbid any burst from crossing it. Then the start address alone decides where the whole burst goes, and every agent can decode and translate once per burst. 4 KB is the common choice because it is the smallest common memory-management page.
A burst of n beats of size bytes covers [start, start + n·size). It crosses if its first and last bytes are in different pages. The splitter issues one burst per page.
"""Find INCR bursts that would cross a 4 KB boundary, and split them.
A burst of n beats of `size` bytes from `start` covers [start, start + n*size).
It crosses a boundary if its first and last bytes lie in different 4 KB pages.
A splitter issues one burst per page instead.
"""
PAGE = 4096
def crosses(start, n, size):
return start // PAGE != (start + n * size - 1) // PAGE
def split(start, n, size):
pieces, a, left = [], start, n
while left:
room = (PAGE - a % PAGE) // size # whole beats left in this page
take = min(left, room)
pieces.append((a, take))
a, left = a + take * size, left - take
return pieces
for start, n, size in ((0x0F00, 16, 16), (0x0FC0, 16, 16), (0x1FF8, 4, 8)):
parts = split(start, n, size)
desc = " + ".join(f"{k} beat{'s' if k > 1 else ''} at 0x{a:04X}" for a, k in parts)
print(f"INCR{n} x {size} B at 0x{start:04X}: {'crosses' if crosses(start, n, size) else 'fits'} -> {desc}")
assert sum(k for _, k in parts) == n
assert all(not crosses(a, k, size) for a, k in parts)
print("PASS: every piece stays inside one 4 KB page and the pieces cover the original burst")It prints:
INCR16 x 16 B at 0x0F00: fits -> 16 beats at 0x0F00
INCR16 x 16 B at 0x0FC0: crosses -> 4 beats at 0x0FC0 + 12 beats at 0x1000
INCR4 x 8 B at 0x1FF8: crosses -> 1 beat at 0x1FF8 + 3 beats at 0x2000
PASS: every piece stays inside one 4 KB page and the pieces cover the original burstThe first burst ends exactly at 0x0FFF, so it fits. The second is the opening's DMA burst: 4 beats fit before 0x1000, the other 12 start a new burst there. The third shows that even a short burst crosses if it starts near the end of a page.
Agent | Works per | What a crossing burst would break |
|---|---|---|
Address decoder | Target region (placed on 4 KB multiples) | The tail of the burst goes to the wrong target |
Address translation (IOMMU or MMU) | Page (4 KB or larger) | The tail is translated with the wrong page's mapping: data lands in another program's memory |
Protection checks | Page or region | The tail escapes the permission check made for the start |
Targets with internal pages (some memories) | Row or page | Extra work, or wrong behaviour, if a burst spans two internal pages |
Requesters pay: a DMA engine must split transfers at boundaries. Because a 4 KB page holds many bursts, the cost is small: at most one extra burst per page crossed.
Protocol | Boundary rule |
|---|---|
AXI | No burst may cross a 4 KB address boundary. A 256-beat INCR burst therefore fits only if its size and start address keep it inside one 4 KB page. |
DCLink (step 4) | A burst that would run past the end of memory (0x00FFF) is refused with an error completion. |
A DMA engine (illustrative) split transfers at 4 KB boundaries correctly for writes but not for reads, because the read path had been written later by a different engineer. On systems with an IOMMU, a read that crossed a page fetched its tail from the wrong physical page, and the bug appeared only when buffers happened to straddle pages, which depended on memory fragmentation. A boundary-checker assertion on the bus would have caught it the first time it happened.
It covers 0x1F90 to 0x200F, so yes: split into 7 beats from 0x1F90 and 1 beat from 0x2000.
It translates the start address's page; the tail would be written to the physical memory behind the wrong page, possibly another program's, with no error.
Protocols forbid bursts from crossing page boundaries so every agent can decode, translate and check once per burst.
4 KB is the common boundary because it is the smallest common page.
Requesters split transfers at boundaries: at most one extra burst per page crossed.