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
  • Interconnect and Bus Fabrics26
  • Clock and Reset Architecture24
  • Power Architecture21
  • Interrupts, DMA and Peripherals15
    • Interrupt Controller Integration
    • Interrupt Routing and Latency
    • DMA Integration
      • Programming the DMA Registers
      • The Stream Bridge and Length Agreement
      • DMA Completion and Clock Gating
    • Peripheral Subsystem
    • Pad Muxing and GPIO
  • 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

←DMA Integration
  1. Topics
  2. SoC Design
  3. Interrupts, DMA and Peripherals
  4. DMA Integration
  5. Programming the DMA Registers
On this page

    Part 7 of 14, lesson 7 of 15Part 7 of 14/Interrupts, DMA and Peripherals/Lesson 7 of 15All levels

    Programming the DMA Registers

    The five registers, the exact start-poll-clear sequence, and the four contract quirks no driver can ignore.

    Why it matters

    This lesson has one job: teach the register sequence that moves 64 words without surprises. The DMA register block looks ordinary, five offsets in a 256-byte window, but its contract has four quirks that read as bugs until they are taught as rules: writes ignored while busy, a sticky error the start command does not clear, a control write that silently changes the interrupt enable, and a go-while-busy that fails without any error. Every one of them is quoted from the DMA's own RTL, and the second job a driver ever runs trips at least one of them.

    The core idea

    CTRL at 0x00 holds GO in bit 0 and IRQ_EN in bit 1. The write copies bit 1 into IRQ_EN every time, so starting with 0x1 clears the interrupt enable as a side effect; the correct start value with interrupts is 0x3. GO acts only when BUSY is 0, and a GO while busy is ignored with no error, not queued and not faulted. STATUS at 0x04 holds BUSY read-only, DONE and ERR as write-1-to-clear: writing 1 clears, writing 0 does nothing, and BUSY ignores writes. LEN at 0x08 and KEY at 0x0C are read-write but silently ignore writes while BUSY, again with no error response. COUNT at 0x10 counts accepted input words and is cleared by GO. A zero-length GO sets DONE at once with BUSY never rising; a normal GO with nonzero LEN raises BUSY, zeroes COUNT and clears DONE, but leaves ERR exactly as it was.

    Step

    Access

    Value

    Why

    1

    Write KEY (0x0C)

    any 32-bit key

    Must land while idle; busy writes are ignored.

    2

    Write LEN (0x08)

    64

    Word count; same value goes to the bridge.

    3

    Write CTRL (0x00)

    0x3

    GO with IRQ_EN kept on; 0x1 would mute the interrupt.

    4

    Poll STATUS (0x04)

    BUSY bit

    Or wait for the irq; the next lesson prices the wait.

    5

    Write STATUS (0x04)

    0x6

    Clear both DONE and ERR; clearing DONE alone leaves a stale ERR holding irq.

    Worked example: the contract as executable rules

    The model below implements the four quirks exactly as the RTL behaves, then runs the canonical 64-word job plus one probe per quirk: a busy LEN write that must not stick, a redundant GO that must not fault, a 0x1 start that mutes the interrupt, and a stale ERR that survives GO and holds irq until cleared. A last probe covers the same-edge race: a W1C of ERR landing on the edge where a new error is set loses, because the hardware set is the later assignment in the clocked block (set-wins). The UVM bench clocks the same RTL at 100 MHz; inside NanoSoC it runs at clk_sys = 96 MHz.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    class DMA:
        def __init__(self):
            self.irq_en = False
            self.busy = False
            self.done = False
            self.err = False
            self.len = 0
            self.key = 0
            self.count = 0
        @property
        def irq(self):
            return self.irq_en and (self.done or self.err)
        def write(self, addr, data):
            if addr == 0x00:  # CTRL: bit 1 copies into IRQ_EN every write
                self.irq_en = bool(data & 0x2)
                if (data & 0x1) and not self.busy:  # GO acts only when idle
                    self.busy = self.len != 0
                    self.count = 0
                    self.done = (self.len == 0)
            elif addr == 0x04:  # STATUS: W1C on bits 1 and 2
                if data & 0x2:
                    self.done = False
                if data & 0x4:
                    self.err = False
            elif addr == 0x08 and not self.busy:  # LEN ignored while busy
                self.len = data & 0xFFFF
            elif addr == 0x0C and not self.busy:  # KEY ignored while busy
                self.key = data & 0xFFFFFFFF
        def finish(self, early_tlast=False, clear=0):  # LEN-th beat, or early tlast
            # a STATUS write on the same edge is applied first; the hardware set
            # comes later in the same clocked block, so set wins
            if clear & 0x2:
                self.done = False
            if clear & 0x4:
                self.err = False
            if early_tlast:
                self.err, self.busy = True, False
            else:
                self.count, self.done, self.busy = self.len, True, False
    
    d = DMA()
    d.write(0x0C, 0xA5A5A5A5)
    d.write(0x08, 64)
    d.write(0x00, 0x3)          # GO with IRQ_EN on
    assert (d.busy, d.count, d.done) == (True, 0, False)
    d.write(0x08, 32)           # quirk 1: busy LEN write ignored, no error
    assert d.len == 64
    d.write(0x00, 0x3)          # quirk 2: GO while busy ignored, no error
    assert d.busy and d.count == 0
    d.finish()
    assert d.done and d.irq     # irq = IRQ_EN & (DONE | ERR)
    d.write(0x04, 0x2)          # clear DONE only
    assert not d.irq
    e = DMA()
    e.write(0x08, 64)
    e.write(0x00, 0x1)          # quirk 3: CTRL write copies IRQ_EN -> muted
    e.finish()
    assert e.done and not e.irq
    f = DMA()
    f.write(0x08, 64)
    f.write(0x00, 0x3)
    f.finish(early_tlast=True)  # quirk 4: ERR set, GO never clears it
    f.write(0x00, 0x3)
    assert f.err and f.irq       # stale ERR holds irq across the new job
    f.write(0x04, 0x6)
    assert not f.irq
    # set wins: a new early-tlast error on the same edge as a W1C of ERR survives
    g = DMA()
    g.write(0x08, 64)
    g.write(0x00, 0x3)
    g.finish(early_tlast=True)
    g.write(0x00, 0x3)                  # second job, stale ERR still set
    g.finish(early_tlast=True, clear=0x4)
    assert g.err and g.irq              # the clear lost to the new event
    print("PASS: 64-word job, all four contract quirks and set-wins behave")

    What the review looks for

    • The driver writes one length variable to both LEN registers, always writes 0x3 to start with interrupts, and always clears STATUS with 0x6.

    • No driver writes LEN or KEY without first reading BUSY as 0; the rule is in the coding standard because the hardware gives no error to catch violations.

    • The APB address wiring passes paddr bits 7 down to 0 as byte offsets, because the DMA decodes byte offsets 0x04 to 0x10, not word indices.

    Verify and debug

    Read back the reset values first: five reads that must all return zero, which is the first step of the boot ROM's DMA self-test. Then run one job of length 1 and confirm COUNT reads 1 with DONE set: the shortest job exercises the whole contract. If STATUS, LEN and KEY reads return PSLVERR, a COUNT read returns the STATUS value with no error, and only CTRL works, the integrator wired word addresses to a block that decodes byte offsets, the width-and-addressing bug: reconnect paddr bits 7 down to 0 and check every other narrow block the same way.

    Silently ignored writes while BUSY, and sticky ERR: the second job runs with the old KEY and no error anywhere; or after a failed job the irq never drops although DONE was cleared; or writing CTRL with 0x1 silently turns the interrupt off. Caught by error-injection tests and a driver that waits for BUSY 0, clears both DONE and ERR with 0x6, and always starts with 0x3.

    If asked to start a DMA job, give the five steps with the three magic values, 0x3, 0x6 and the shared length variable, and say what each quirk does to a driver that forgets. The byte-offset addressing trap is the follow-up that separates readers from skimmers.

    Check yourself Software writes LEN = 64, writes CTRL = 0x3, then immediately writes KEY before polling. What key does the job use?

    Whatever KEY held before, usually the reset value 0: the KEY write landed while BUSY was already 1 and was silently ignored. Program KEY before GO, never after.

    Related topics

    • Next: The Stream Bridge and Length Agreement

    • UVM: The DMA You Will Verify

    • RTL: FIFO and DMA Boundary

    Previous

    142 / 289

    Next: The Stream Bridge and Length Agreement

    Up next

    Up nextThe Stream Bridge and Length Agreement

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • DMA Completion and Clock Gating