Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • The NanoSoC Tour
  • Reading Path
  • SoC Glossary10
  • SoC Metrics Reference
  • SoC Cheatsheet
  • What SoC Integration Is12
  • Architecture and Budgets21
  • Memory Maps and Address Decode26
    • Designing the Address Map
    • Implementing Address Decode
    • Decode Errors and Default Slaves
    • Register Maps and Banks
    • Security and Privilege Regions
    • The Software View and the Hardware-Software Contract
      • Generated Headers and the Map Document
      • Endianness, Access Width and Ordering
      • Errata and Software Workarounds
    • IP-XACT and Automated Assembly
    • Memory-Map Verification
  • Interconnect and Bus Fabrics26
  • Clock and Reset Architecture24
  • Power Architecture21
  • Interrupts, DMA and Peripherals15
  • Memories, Boot and Security21
  • IP Integration and Handoff21
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←The Software View and the Hardware-Software Contract
  1. Topics
  2. SoC Design
  3. Memory Maps and Address Decode
  4. The Software View and the Hardware-Software Contract
  5. Endianness, Access Width and Ordering
On this page

    Part 3 of 14, lesson 19 of 26Part 3 of 14/Memory Maps and Address Decode/Lesson 19 of 26All levels

    Endianness, Access Width and Ordering

    Little-endian words, full-word register writes and device ordering: the rules every NanoSoC driver obeys.

    Why it matters

    Three driver bugs repeat on every chip: a byte-wise write to a register that only accepts full words, a byte-swapped value read from a peripheral, and an interrupt that arrives before the write it announces has landed. All three are contract violations, not hardware faults, and all three are prevented by rules short enough to print on one page. This lesson states NanoSoC's page.

    The core idea

    NanoSoC is little-endian and word-oriented, and the worked example is worth memorising: storing the word 0x1122_3344 at 0x2000_0000 leaves the byte at 0x2000_0000 reading 0x44, the byte at 0x2000_0001 reading 0x33, and the halfword at 0x2000_0002 reading 0x1122. Peripheral registers are 32-bit and word aligned, and the APB carries no byte strobes and no protection signals, which has a hard consequence: the AXI-to-APB bridge cannot express a partial write, so it drops the master's write strobes and the APB slave sees a full 32-bit write whose other three bytes are whatever the master happened to drive on those lanes. A byte store to DMA KEY therefore does not update one byte; it overwrites all four with lane garbage. The bridge policy must be stated, not discovered: partial-strobe writes to a slot are a software error to be caught in review and test, and the preferred hardware behaviour is to answer them with SLVERR rather than silently widen them; either way it must be written in the contract. In practice: every register write is a full-word read-modify-write in software, never read-modify-write on a register that mixes write-1-to-clear status with configuration, and never let the compiler merge or split register accesses, which is why driver code uses volatile accesses of exactly the register width.

    Rule

    NanoSoC form

    Why

    Endianness

    Little-endian everywhere on the fabric

    One convention at the bridge; no swizzle anywhere

    Register access width

    Exactly 32-bit loads and stores to slots

    APB has no byte strobes; partial writes do not exist

    Field updates

    Read-modify-write, never on mixed status registers

    A blind writeback can clear status or retrigger actions

    Ordering to devices

    Device-type in the CPU memory attributes: non-cacheable, non-bufferable, in-order

    Two accesses to the device complete in program order

    Completion check

    Read back after clearing an interrupt source

    Proves the clear reached the device; does not by itself drain the interrupt synchronisers (see text)

    Ordering deserves the worked numbers, because the rule software most often gets wrong is what a readback proves. Device-type regions are non-bufferable and in-order, so when a write to a device register completes, the slave has accepted it: there are no posted writes hiding in the fabric on this path. A readback of the same block (125 ns for a DMA or SBR register) therefore proves the clear reached the device. It does not prove the CPU has stopped seeing the interrupt, because the level still has to travel from the DMA on clk_sys through a two-flop synchroniser into clk_per, the interrupt controller's output register, and a second two-flop synchroniser into clk_cpu: 135.4 ns at best and 182.3 ns at worst, counted from the moment the clear lands in the DMA. The script below puts both timelines on one axis. The decomposition of the write is illustrative: it assumes a write costs the same 125 ns as a read, with the clear landing at the end of the APB access.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    T_CPU = 1000 / 192   # ns
    T_SYS = 1000 / 96    # ns
    
    # CPU write to a DMA register, assumed to cost the same as the 125 ns read.
    land = 1 * T_CPU + (3 + 1 + 1 + 3) * T_SYS   # issue, crossing, xbar, bridge accept, APB access
    write_done = 24 * T_CPU                       # response is back at the CPU
    readback_done = write_done + 24 * T_CPU       # then the 125 ns readback
    
    best = land + 26 * T_CPU    # level seen at the CPU, best case (135.4 ns after landing)
    worst = land + 35 * T_CPU   # level seen at the CPU, worst case (182.3 ns after landing)
    
    assert round(land, 1) == 88.5 and round(write_done, 1) == 125.0
    assert round(readback_done, 1) == 250.0
    assert round(best, 1) == 224.0 and round(worst, 1) == 270.8
    stale_after_readback = max(0.0, worst - readback_done)
    assert round(stale_after_readback, 1) == 20.8   # one spurious entry is still possible
    assert best < readback_done < worst             # the readback does not settle the question
    print("PASS: readback returns at 250.0 ns; the old level can persist to 270.8 ns")

    So the readback returns at about 250 ns, while the CPU can still see the old level until about 224 ns at best and 271 ns at worst: for up to roughly 21 ns after the handler believes the source is cleared, one more, spurious, entry is possible. The correct closing sequence is: clear the source, read the block back to prove the clear landed, then re-read the interrupt controller's PENDING register, which sits on clk_per, until the bit reads zero. That read takes 281.25 ns, longer than the remaining synchroniser path, so the CPU's own level has dropped by the time it returns. The alternative is to tolerate and dismiss one spurious entry that finds nothing pending. Where a CPU provides explicit barrier instructions, they belong at the data boundaries instead: after filling a DMA buffer and before ringing the doorbell.

    What the review looks for

    • Driver code uses generated-width volatile accesses to registers; no byte pointers into slots anywhere.

    • Every interrupt clear is followed by a readback of the same block and then a PENDING re-read, or the handler is written to tolerate one spurious entry.

    • Buffer-then-doorbell sequences for DMA are separated by the ordering the fabric actually guarantees, stated in the driver guide.

    Verify and debug

    Test the rules, do not assume them: store 0x1122_3344 in SRAM0 and read the bytes and halfwords back against the worked example, attempt a byte write to DMA KEY at the bus monitor and confirm the slave sees a full-word write, and clear an interrupt source under load to observe the one tolerated spurious entry arriving within the synchroniser window. If a driver works in simulation and fails on the bench, width and ordering are the first two suspects, ahead of the hardware.

    Assuming a readback makes a level interrupt vanish instantly. The clear has reached the device, but the level is still travelling through the synchronisers: the readback returns at about 250 ns on the write's own timeline, the old level can persist to about 271 ns. Handlers that re-enable on readback alone take one spurious entry; handlers that then find nothing pending and panic turn it into a storm. Wait out the synchroniser path in PENDING, or tolerate the extra entry by design.

    If asked for the software rules of a memory map, give width, endianness and ordering before anything else, then the readback pattern. Interviewers are listening for the fact that APB without strobes makes every write a full word.

    Check yourself A driver updates one bit of GPIO DIR with a byte store to save time. Name two things wrong with that.

    First, slot accesses are full 32-bit words: the APB has no byte strobes and the bridge drops the write strobes, so the byte store becomes a full-word write that overwrites the other three bytes with whatever the master drove on those lanes (or is refused with SLVERR if the bridge policy says so). Second, even as a word read-modify-write it must not touch a mixed register blindly; DIR is plain read-write so it is safe here, but the habit fails on status registers.

    Related topics

    • Generated Headers and the Map Document

    • Next: Errata and Software Workarounds

    • RTL: The Firmware Side, a C Driver

    Previous

    57 / 289

    Next: Errata and Software Workarounds

    Up next

    Up nextErrata and Software Workarounds

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error