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
  • Memories, Boot and Security21
    • Memory Subsystem Integration
    • ECC and Error Handling
    • External Memory Controller Integration
    • Boot ROM and Boot Modes
    • eFuse and OTP
    • Secure Boot and Root of Trust
    • Security Architecture
      • The AxPROT Partition
      • Lifecycle States and Debug Locking
      • The Integrator Security Checklist
  • 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

←Security Architecture
  1. Topics
  2. SoC Design
  3. Memories, Boot and Security
  4. Security Architecture
  5. The AxPROT Partition
On this page

    Part 8 of 14, lesson 19 of 21Part 8 of 14/Memories, Boot and Security/Lesson 19 of 21All levels

    The AxPROT Partition

    One signal bit, two states, two secure-only slaves, and the master attribute that can undo them.

    Why it matters

    This lesson has one job: make the partition real in gates and responses. NanoSoC carries the whole scheme in one signal: AxPROT bit 1, where 0 means secure and 1 means non-secure. Two slaves admit secure only: SRAM1 and the reset controller. One master, the SBR, defaults to non-secure. Every other slave answers either state. A non-secure access to a secure-only slave must be refused with SLVERR, never answered and never hung, and the refusal must not reveal more than the policy.

    The core idea

    Enforcement sits at two levels. The interconnect filter watches AxPROT beside the address decode: a non-secure access to SRAM1 selects the default error path and returns SLVERR while raising the secure fault, exactly as the decode lesson's module shows. The APB slot decoder repeats the check per slot because the DMA has no protection port of its own: the reset-controller slot refuses non-secure accesses with PSLVERR, which the bridge presents as SLVERR. The SBR is the weak point. Its CTRL non-secure bit (bit 2) resets to 1, so an uncleared SBR master is refused by SRAM1 at the first beat with STATUS error set, the miswired-attribute bug the integration section plants. That is the fail-closed half. The other half is the escape: the SBR register slot is a non-secure slot, so non-secure software can itself write CTRL.NS = 0. From then on the SBR issues secure transfers on that software's behalf and can read and write SRAM1, which the filter was supposed to keep from non-secure software. This is a confused deputy: the filter protects SRAM1 only against masters whose attribute software cannot choose. The rule is that a master's security attribute may be programmed only by an access of equal or higher privilege. On NanoSoC that means CTRL.NS must be writable from secure accesses only: either put it in a secure-only register page, or have the slot decoder refuse a non-secure write that would clear bit 2 (a non-secure write that sets it is harmless and START must stay usable). The guard module below is the second option; the filter module is the base policy. Both are compiling teaching models.

    nano_axprot_filter.sv
    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
    // NanoSoC teaching values: AxPROT filter skeleton.
    // prot_ns = AxPROT[1]: 1 means non-secure. Secure-only slaves refuse
    // non-secure accesses with SLVERR; everything else passes through.
    module nano_axprot_filter (
      input  logic       prot_ns,
      input  logic       sel_sram1,
      input  logic       sel_rstpwr,
      output logic       allow_sram1,
      output logic       allow_rstpwr,
      output logic       secure_fault,
      output logic [1:0] err_resp
    );
      assign allow_sram1  = sel_sram1 & ~prot_ns;
      assign allow_rstpwr = sel_rstpwr & ~prot_ns;
      assign secure_fault = (sel_sram1 | sel_rstpwr) & prot_ns;
      assign err_resp     = 2'b10; // SLVERR
    endmodule
    
    // Guard for the SBR CTRL register (slot offset 0x0C, bit 2 = NS).
    // prot_ns is the attribute the decoder receives with the access (APB3 has
    // no PPROT, so it travels as a sideband from the bridge).
    // A non-secure write that would CLEAR NS is refused with PSLVERR and does
    // not reach the register; a non-secure write that sets NS, or a START
    // write that keeps NS = 1, passes.
    module nano_sbr_ns_guard (
      input  logic        psel_sbr,
      input  logic        pwrite,
      input  logic        prot_ns,
      input  logic [11:0] paddr,
      input  logic [31:0] pwdata,
      output logic        write_ok,
      output logic        pslverr
    );
      logic hits_ctrl, clears_ns;
      assign hits_ctrl = (paddr[7:0] == 8'h0C);
      assign clears_ns = ~pwdata[2];
      assign pslverr   = psel_sbr & pwrite & hits_ctrl & prot_ns & clears_ns;
      assign write_ok  = psel_sbr & pwrite & ~pslverr;
    endmodule

    The script has two parts. First it walks the address decode table of the memory map (the source of truth: base, size and the secure-only flag of each slave, the same table the filter is generated from) and compares every slave in both states, plus one probe in each of the four address holes, against an expected table written out by hand, cell by cell. A mutated decode table, with the secure flag dropped from the reset controller, must be caught. Second it models the SBR escape: non-secure software writes CTRL.NS = 0 and then copies out of SRAM1, once on v1 and once with the guard.

    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
    decode = [  # (slave, base, size, secure_only): the memory map
        ("ROM", 0x0000_0000, 0x4000, False), ("SRAM0", 0x2000_0000, 0x1_0000, False),
        ("SRAM1", 0x2001_0000, 0x1_0000, True), ("DMA", 0x4000_0000, 0x1000, False),
        ("SBR", 0x4000_1000, 0x1000, False), ("UART", 0x4000_2000, 0x1000, False),
        ("SPI", 0x4000_3000, 0x1000, False), ("GPIO", 0x4000_4000, 0x1000, False),
        ("INTC", 0x4000_5000, 0x1000, False), ("RSTPWR", 0x4000_6000, 0x1000, True),
    ]
    def respond(addr, ns, table):
        for name, base, size, secure_only in table:
            if base <= addr < base + size:
                return "SLVERR" if (secure_only and ns) else "OK"
        if 0x4000_0000 <= addr <= 0x4000_FFFF:
            return "SLVERR"  # hole H3: the APB decoder's default slave
        return "DECERR"      # holes H1, H2, H4: the interconnect's default slave
    expected = {  # (slave, secure access, non-secure access), written by hand
        "ROM": ("OK", "OK"), "SRAM0": ("OK", "OK"), "SRAM1": ("OK", "SLVERR"),
        "DMA": ("OK", "OK"), "SBR": ("OK", "OK"), "UART": ("OK", "OK"), "SPI": ("OK", "OK"),
        "GPIO": ("OK", "OK"), "INTC": ("OK", "OK"), "RSTPWR": ("OK", "SLVERR"),
    }
    def mismatches(table):
        bad = []
        for name, base, size, secure_only in decode:
            probe = base + 0x10
            got = (respond(probe, 0, table), respond(probe, 1, table))
            if got != expected[name]:
                bad.append((name, got))
        return bad
    assert mismatches(decode) == [], mismatches(decode)
    holes = {0x0000_4000: "DECERR", 0x2002_0000: "DECERR", 0x4000_7000: "SLVERR", 0x4001_0000: "DECERR"}
    for addr, want in holes.items():
        assert respond(addr, 0, decode) == respond(addr, 1, decode) == want, hex(addr)
    mutated = [(n, b, s, False if n == "RSTPWR" else so) for n, b, s, so in decode]
    assert [name for name, _ in mismatches(mutated)] == ["RSTPWR"]
    print("access matrix: 20 cases and 4 holes agree with the hand-written expectation; a mutated table is caught")
    
    class Sbr:
        def __init__(self, guard):
            self.ns, self.guard = 1, guard   # CTRL.NS resets to 1
        def write_ctrl(self, value, writer_ns):
            if self.guard and writer_ns and not (value >> 2) & 1:
                return "PSLVERR"
            self.ns = (value >> 2) & 1
            return "OK"
        def copy_from_sram1(self):
            return respond(0x2001_0000, self.ns, decode)
    v1 = Sbr(guard=False)
    assert v1.copy_from_sram1() == "SLVERR"               # fail-closed reset
    assert v1.write_ctrl(0x0, writer_ns=1) == "OK"        # non-secure software clears NS
    assert v1.copy_from_sram1() == "OK", "the escape: SRAM1 read through the SBR"
    fixed = Sbr(guard=True)
    assert fixed.write_ctrl(0x0, writer_ns=1) == "PSLVERR"
    assert fixed.copy_from_sram1() == "SLVERR"
    assert fixed.write_ctrl(0x1, writer_ns=1) == "PSLVERR"  # a START write that clears NS is refused too
    assert fixed.write_ctrl(0x5, writer_ns=1) == "OK"       # START with NS kept at 1 still works
    assert fixed.write_ctrl(0x0, writer_ns=0) == "OK" and fixed.copy_from_sram1() == "OK"  # secure software may clear it
    print("v1: non-secure CTRL.NS = 0 opens SRAM1; guarded: refused, secure software unaffected")
    print("PASS: matrix matches, mutation caught, SBR escape reproduced on v1 and closed by the guard")

    What the review looks for

    • The partition table names every slave in both states with the responder and response of each refusal.

    • Secure filtering is visible beside each decoder with its own fault output, never folded silently into a select.

    • The SBR non-secure default is documented with the driver rule (secure software clears the bit before touching SRAM1) and with the hardware rule that only a secure access may write it.

    Verify and debug

    Walk the whole matrix from both states in simulation: twenty accesses, two SLVERRs, eighteen successes, with the secure fault asserted exactly twice. Then leave the SBR non-secure bit at its reset value and run a DMA copy out of SRAM1: the first beat must return SLVERR and set the SBR status error with the DMA staying busy. Finally run the negative test for the escape: from non-secure software write CTRL.NS = 0, check the write is refused (or has no effect), then start a DMA copy out of SRAM1 and confirm it still fails; the same test must pass (the copy succeeds) when the write comes from secure software. If a secure-only slave answers non-secure software, the filter enable for that slot is at reset default.

    Security attribute dropped across a bridge, or chosen by the wrong software: a converter that does not propagate AxPROT lets a non-secure master reach a secure slave, and a master whose attribute is a non-secure register (the SBR on v1) does the same by design. Symptom: secure software fails or non-secure software succeeds where it must not. Caught by sideband connectivity checks plus directed tests from both states, including the test that tries to change a master's attribute from the non-secure side. Fix: carry the attribute end to end, filter at the slave, and make every attribute register secure-writable only.

    If asked what a non-secure read of SRAM1 returns, answer in three parts: who refuses, with what code, and what software does next. The answer is the interconnect filter, SLVERR, and the fault handler, and the address being mapped is why it is SLVERR and not DECERR.

    Check yourself The SBR non-secure bit resets to non-secure rather than secure. Why is that default deliberate?

    Because fail-open defaults hide missing configuration: with a secure default, forgotten software would silently gain secure rights. The non-secure default fails closed, forcing secure software to claim its rights explicitly, and the SLVERR on the first SRAM1 beat proves the claim was forgotten. A fail-closed reset value is not enough on its own, though: if non-secure software can write the bit, it can claim the rights too, so the write path needs the secure-only guard.

    Related topics

    • Next: Lifecycle States and Debug Locking

    • Verification: SoC and Software-Driven Tests

    Previous

    172 / 289

    Next: Lifecycle States and Debug Locking

    Up next

    Up nextLifecycle States and Debug Locking

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • The Integrator Security Checklist