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
  • IP Integration and Handoff21
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC19
    • Capstone Brief
    • Stage 1: Memory Map and Interconnect
    • Stage 2: Clocks, Resets, Power
    • Stage 3: Interrupts and Boot
    • Stage 4: Verification and Signoff
    • Solutions
      • Root Causes, Fixes and Evidence
      • Stage 1 Worked Checks
      • Stage 2 Worked Checks
      • Stage 3 Worked Checks
      • Stage 4 Worked Checks
Loading lesson

←Solutions
  1. Topics
  2. SoC Design
  3. Capstone: Integrate NanoSoC
  4. Solutions
  5. Stage 4 Worked Checks
On this page

    Part 14 of 14, lesson 19 of 19Part 14 of 14/Capstone: Integrate NanoSoC/Lesson 19 of 19All levels

    Stage 4 Worked Checks

    Worked answer: runnable checks and explicit fault injections after the challenge stages.

    These are the worked solutions. Compare the inputs and injected faults with your written hypothesis before running each block. Each block runs independently and prints one PASS line after its clean-data and failing-data assertions.

    Worked check 1

    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
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    MAP = [
        ("BOOTROM", 0x00000000, 0x4000),
        ("SRAM0", 0x20000000, 0x10000),
        ("SRAM1", 0x20010000, 0x10000),
        ("DMA", 0x40000000, 0x1000),
        ("SBR", 0x40001000, 0x1000),
        ("UART", 0x40002000, 0x1000),
        ("SPI", 0x40003000, 0x1000),
        ("GPIO", 0x40004000, 0x1000),
        ("INTC", 0x40005000, 0x1000),
        ("RSTPWR", 0x40006000, 0x1000),
    ]
    SECURE_ONLY = {"SRAM1", "RSTPWR"}
    SLOTS = ["DMA", "SBR", "UART", "SPI", "GPIO", "INTC", "RSTPWR"]
    T_CPU, T_SYS, T_PER = 1e9 / 192e6, 1e9 / 96e6, 1e9 / 24e6
    def end_of(base, size):
        return base + size - 1
    def overlaps(entries=MAP):
        bad = []
        xs = sorted(entries, key=lambda e: e[1])
        for a, b in zip(xs, xs[1:]):
            if b[1] < a[1] + a[2]:
                bad.append((a[0], b[0]))
        return bad
    def misaligned(entries=MAP):
        return [n for n, b, s in entries if b % s != 0]
    def decode(addr, slots=SLOTS):
        if addr >> 14 == 0:
            return "BOOTROM"
        if addr >> 16 == 0x2000:
            return "SRAM0"
        if addr >> 16 == 0x2001:
            return "SRAM1"
        if addr >> 16 == 0x4000:
            slot = (addr >> 12) & 0xF
            return slots[slot] if slot < 7 else "H3"
        if addr < 0x20000000:
            return "H1"
        if addr < 0x40000000:
            return "H2"
        return "H4"
    def response(addr, secure, slots=SLOTS):
        t = decode(addr, slots)
        if t in ("H1", "H2", "H4"):
            return "DECERR"
        if t == "H3":
            return "SLVERR"
        if t in SECURE_ONLY and not secure:
            return "SLVERR"
        return "OK"
    def sustained(peak, beats):
        return peak * beats / (beats + 1)
    import hashlib
    # ---- stage checkers: every one is a function of the design data d ----
    ORACLE = {"irq_pwr": 0, "irq_dma": 1, "irq_sbr": 2, "irq_uart": 3, "irq_spi": 4, "irq_gpio": 5}
    REG = {0x00: "CTRL", 0x04: "STATUS", 0x08: "LEN", 0x0C: "KEY", 0x10: "COUNT"}
    GROUPS = {"apb_cdc": [("paddr", 12), ("pwdata", 32), ("psel", 1), ("penable", 1), ("pwrite", 1)],
              "sram0": [("addr", 14), ("wdata", 32), ("be", 4), ("ce", 1), ("we", 1)],
              "rom": [("addr", 12), ("ce", 1)], "irq": [("irq_dma", 1), ("irq_sbr", 1)]}
    def signals():
        return [g + "." + n + "[" + str(i) + "]" for g, items in GROUPS.items() for n, w in items for i in range(w)]
    XING = [("X1", "clk_cpu", "clk_sys", "axi_req", 32, "fifo", 2), ("X2", "clk_sys", "clk_cpu", "axi_resp", 32, "fifo", 2),
            ("X3", "clk_sys", "clk_per", "apb_req", 32, "reqack", 2), ("X4", "clk_per", "clk_sys", "apb_resp", 32, "reqack", 2),
            ("X5", "clk_sys", "clk_per", "irq_dma", 1, "2ff", 2), ("X6", "clk_per", "clk_cpu", "irq_cpu", 1, "2ff", 2),
            ("X7", "clk_per", "clk_sys", "rst_sys_n", 1, "rstsync", 2), ("X8", "clk_per", "clk_cpu", "rst_cpu_n", 1, "rstsync", 2),
            ("X9", "pad", "clk_per", "gpio_in", 1, "2ff", 2), ("X10", "clk_per", "clk_sys", "pgood", 1, "2ff", 2)]
    PLAN = {"rst_per_n": ("por", 1026 * T_PER), "rst_sys_n": ("gates", 18 * T_SYS), "rst_cpu_n": ("rst_sys_n", 8 * T_SYS + 2 * T_CPU)}
    AREA = [0.150, 0.025, 0.015, 0.040, 0.012, 0.010, 0.006, 0.004, 0.100, 0.010,
            0.100, 0.012, 0.008, 0.008, 0.006, 0.004, 0.015, 0.060]
    POWER = [(77.10, 5.64), (11.25, 5.64), (0.16, 1.24)]
    def design():
        return {"map": list(MAP), "slots": list(SLOTS), "sel_mask": {}, "paddr": (0, 7), "xing": list(XING), "plan": dict(PLAN),
                "irq": dict(ORACLE), "irq_sync": (2, 1, 2), "clamped": set(signals()), "wdog": 256, "sbr_ctrl": 0x4,
                "boot_clears_ns": True, "ns_secure_only": False, "lens": (64, 64), "driver": "good", "cause_first": True,
                "burst": 16, "link_width": 4, "fabric_mhz": 96, "banks": ("SRAM0", "SRAM1"), "clk": (24, 1, 32, 4, 8),
                "area": list(AREA), "power": [tuple(p) for p in POWER]}
    def selects(d):
        sel = {n: (i << 12, d["sel_mask"].get(n, 0xF000)) for i, n in enumerate(d["slots"])}
        return sel
    def hits(sel, addr):
        return [n for n, (v, m) in sel.items() if addr >> 16 == 0x4000 and (addr & 0xFFFF) & m == v]
    def map_ok(d):
        sel = selects(d)
        slot_ok = all(hits(sel, 0x40000000 + (i << 12) + o) == [n] for i, n in enumerate(SLOTS) for o in (0, 0xC, 0xFFC))
        return (overlaps(d["map"]) == [] and misaligned(d["map"]) == [] and slot_ok
                and all(decode(b, d["slots"]) == n for n, b, s in d["map"]))
    def dma_read(off, lo, hi):
        if hi - lo != 7:
            return "WIDTH"
        return REG.get((off >> lo) & 0xFF, "PSLVERR")
    def paddr_ok(d):
        lo, hi = d["paddr"]
        return [dma_read(o, lo, hi) for o in sorted(REG)] == ["CTRL", "STATUS", "LEN", "KEY", "COUNT"]
    def audit(rows):
        bad = []
        for x, src, dst, sig, width, mech, flops in rows:
            if sig.startswith("rst"):
                good = mech == "rstsync" and flops >= 2
            elif width == 1:
                good = mech == "2ff" and flops >= 2
            else:
                good = mech in ("fifo", "reqack")
            if not good:
                bad.append(x)
        return bad
    def xing_ok(d):
        return len(d["xing"]) == 10 and audit(d["xing"]) == []
    def release_times(plan):
        t = {"por": 0.0}
        t["rst_per_n"] = plan["rst_per_n"][1]
        t["gates"] = t["rst_per_n"] + 262 * T_PER
        for net in ("rst_sys_n", "rst_cpu_n"):
            after, wait = plan[net]
            t[net] = t[after] + wait
        return t
    def fetch_lost(t):
        return t["rst_cpu_n"] + T_CPU + 3 * T_SYS < t["rst_sys_n"]
    def release_ok(d):
        t = release_times(d["plan"])
        return t["rst_per_n"] <= t["rst_sys_n"] and t["rst_cpu_n"] >= t["rst_sys_n"] + 8 * T_SYS and not fetch_lost(t)
    def intc_raw(wiring, active):
        return sum(1 << wiring[s] for s in active)
    def conn_test(w):
        return [s for s in ORACLE if intc_raw(w, [s]) != 1 << ORACLE[s]]
    def latency(sync_per, ctrl, sync_cpu):
        best = sync_per * T_PER + ctrl * T_PER + sync_cpu * T_CPU
        return best, best + T_PER + T_CPU
    def irq_ok(d):
        best, worst = latency(*d["irq_sync"])
        return conn_test(d["irq"]) == [] and abs(best - 135.4) < 0.1 and abs(worst - 182.3) < 0.1
    def clamp_ok(d):
        return [s for s in signals() if s not in d["clamped"]] == []
    def apb_access(clk_en, wdog):
        for cycle in range(1, 2000):
            if clk_en and cycle >= 3:
                return "OK"
            if wdog and cycle >= wdog:
                return "PSLVERR"
        return "HANG"
    def gate_ok(d):
        return apb_access(False, d["wdog"]) == "PSLVERR" and apb_access(True, None) == "OK"
    def sbr_ctrl_write(ctrl, wdata, writer_secure, ns_secure_only=False):
        if ns_secure_only and not writer_secure:
            wdata = (wdata & ~0x4) | (ctrl & 0x4)
        return wdata & 0x6
    def sbr_reaches_sram1(ctrl):
        return response(0x20010000, (ctrl >> 2) & 1 == 0) == "OK"
    def secure_ok(d):
        ctrl = d["sbr_ctrl"]
        if d["boot_clears_ns"]:
            ctrl = sbr_ctrl_write(ctrl, 0x0, True, d["ns_secure_only"])
        return sbr_reaches_sram1(ctrl)
    def partition_closed(d):
        return not sbr_reaches_sram1(sbr_ctrl_write(d["sbr_ctrl"], 0x0, False, d["ns_secure_only"]))
    class Dma:
        def __init__(self):
            self.busy = self.done = self.err = self.irq_en = False
            self.key = self.len = self.count = self.stalls = 0
        def irq(self):
            return self.irq_en and (self.done or self.err)
        def write_key(self, v):
            self.key = self.key if self.busy else v
        def write_len(self, v):
            self.len = self.len if self.busy else v & 0xFFFF
        def ctrl_write(self, v):
            self.irq_en = bool(v & 2)
            if v & 1 and not self.busy:
                if self.len == 0:
                    self.done = True
                else:
                    self.busy, self.done, self.count = True, False, 0
        def status_write(self, v):
            self.done = self.done and not v & 2
            self.err = self.err and not v & 4
        def accept(self, last):
            if not self.busy:
                self.stalls += 1
                return False
            self.count += 1
            if last and self.count < self.len:
                self.err, self.busy = True, False
            elif self.count == self.len:
                self.done, self.busy = True, False
            return True
    def stream(dma, n):
        return not all(dma.accept(i == n - 1) for i in range(n))
    def job(n_dma, n_sbr, key=0x5A):
        d = Dma()
        d.write_key(key)
        d.write_len(n_dma)
        d.ctrl_write(0x3)
        return d, stream(d, n_sbr)
    def driver_ok(count, sbr_len, dma_len):
        return count == sbr_len == dma_len and dma_len > 0
    def len_ok(d):
        n_dma, n_sbr = d["lens"]
        dma, sbr_busy = job(n_dma, n_sbr)
        return dma.done and not dma.err and not sbr_busy and driver_ok(dma.count, n_sbr, n_dma)
    class Good:
        def start(self, dma): dma.ctrl_write(0x3)
        def rekey(self, dma, key, finish):
            if dma.busy: finish(dma)
            dma.write_key(key)
        def clear(self, dma): dma.status_write(0x6)
    class Naive:
        def start(self, dma): dma.ctrl_write(0x1)
        def rekey(self, dma, key, finish): dma.write_key(key)
        def clear(self, dma): dma.status_write(0x2)
    def contract_problems(drv):
        found = []
        a = Dma(); a.write_key(0xAA); a.write_len(4); a.ctrl_write(0x3); a.accept(False)
        drv.rekey(a, 0x55, lambda x: [x.accept(i == 2) for i in range(3)])
        if a.key != 0x55: found.append("stale_key")
        b = Dma(); b.write_len(8); b.ctrl_write(0x3); b.accept(True)
        drv.clear(b)
        if b.irq(): found.append("irq_stuck")
        c = Dma(); c.write_len(1); drv.start(c)
        if not c.irq_en: found.append("irq_disabled")
        return found
    def contract_ok(d):
        return contract_problems({"good": Good, "naive": Naive}[d["driver"]]()) == []
    def link_table(d):
        peak = d["link_width"] * d["fabric_mhz"]
        s = sustained(peak, d["burst"])
        return {"cpu_fill": 16 / (24 * T_CPU) * 1e3, "fabric": s, "sram_bank_shared": s / 2, "apb_fast": peak / 5,
                "apb_slow": 4 / 208.33e-9 / 1e6}
    def stream_rate(d):
        t = link_table(d)
        bank = t["sram_bank_shared"] if d["banks"][0] == d["banks"][1] else t["fabric"]
        path = {"sbr_read": t["fabric"], "src_bank": bank, "dma_in": t["fabric"], "dma_out": t["fabric"],
                "dst_bank": bank, "sbr_write": t["fabric"]}
        low = min(path.values())
        return low, sorted(n for n, v in path.items() if v - low < 0.05)
    def links_ok(d):
        t = link_table(d)
        rate, tied = stream_rate(d)
        want = {"cpu_fill": 128.0, "fabric": 361.4, "sram_bank_shared": 180.7, "apb_fast": 76.8, "apb_slow": 19.2}
        return all(abs(t[k] - v) < 0.05 for k, v in want.items()) and abs(rate - 361.4) < 0.05 and abs(2 * rate - 722.8) < 0.1
    def clocks_ok(d):
        xtal, r, n, dc, ds = d["clk"]
        vco = xtal * n / r
        return 600 <= vco <= 1200 and (vco / dc, vco / ds, xtal) == (192.0, 96.0, 24) and abs(256 * T_PER / 1000 - 10.667) < 0.01
    def boot_steps(cause, cause_first=True):
        hw = [("osc", 1024 * T_PER), ("rst_per", 2 * T_PER), ("pll", 260 * T_PER), ("gates", 2 * T_PER),
              ("rst_sys", 18 * T_SYS), ("rst_cpu", 8 * T_SYS + 2 * T_CPU), ("fetch", 24 * T_CPU)]
        rd, clr = ("read_cause", 54 * T_CPU), ("clear_sram0", 1024 * T_SYS)
        if cause == "WAKE":
            return hw + ([rd] if cause_first else [clr, rd])
        rest = [("uart_init", 3 * 54 * T_CPU), ("banner", 160 / (24e6 / 208) * 1e9),
                ("dma_selftest", 5 * 125 + 7 * 125 + 72 * T_SYS + 26 * T_CPU + 6000), ("spi_load", 8196 * 8 / 6e6 * 1e9),
                ("checksum", 2048 * 18 * T_CPU)]
        return hw + ([rd, clr] if cause_first else [clr, rd]) + rest
    def boot_ok(d):
        cold = sum(ns for _, ns in boot_steps("POR", d["cause_first"]))
        wake = [n for n, _ in boot_steps("WAKE", d["cause_first"])]
        return abs(cold / 1000 - 12580.917) < 0.01 and "clear_sram0" not in wake
    # ---- the ten bugs: each injector breaks one fact of the design data ----
    def inj1(d):
        d["map"] = [("DMA", 0x40000000, 0x2000) if r[0] == "DMA" else r for r in d["map"]]
        d["sel_mask"]["DMA"] = 0xE000
    def inj2(d): d["plan"]["rst_cpu_n"] = ("gates", 2 * T_CPU)
    def inj3(d): d["irq"]["irq_dma"] = 6
    def inj4(d): d["paddr"] = (2, 9)
    def inj5(d): d["xing"][4] = ("X5", "clk_sys", "clk_per", "irq_dma", 1, "direct", 0)
    def inj6(d): d["clamped"] -= {s for s in signals() if s.startswith("irq.")}
    def inj7(d): d["boot_clears_ns"] = False
    def inj8(d): d["lens"] = (64, 32)
    def inj9(d): d["driver"] = "naive"
    def inj10(d): d["wdog"] = None
    BUGS = {1: ("map", map_ok, inj1), 2: ("release", release_ok, inj2), 3: ("irqs", irq_ok, inj3), 4: ("paddr", paddr_ok, inj4),
            5: ("crossings", xing_ok, inj5), 6: ("clamps", clamp_ok, inj6), 7: ("secure", secure_ok, inj7),
            8: ("lengths", len_ok, inj8), 9: ("contract", contract_ok, inj9), 10: ("gate", gate_ok, inj10)}
    
    # ---- this lesson starts here ----
    
    def breaks(fn, inj):
        d = design()
        inj(d)
        return not fn(d)
    OWNER = {1: 1, 4: 1, 2: 2, 5: 2, 6: 2, 10: 2, 3: 3, 7: 3, 8: 3, 9: 3}
    assert sorted(BUGS) == list(range(1, 11)) and sorted(OWNER) == list(range(1, 11))
    for bug, (name, fn, inj) in BUGS.items():
        assert fn(design()), name                      # the test passes on the clean design
        assert breaks(fn, inj), name                   # and fails once its bug is injected
        d = design()
        inj(d)
        failed = {b for b, (n, f, i) in BUGS.items() if not f(d)}
        assert failed == {bug}, (bug, failed)          # no other test notices: each result identifies its bug
    assert not breaks(lambda d: True, BUGS[3][2])      # a test that cannot fail is refused by the same rule
    per_stage = [sum(1 for v in OWNER.values() if v == s) for s in (1, 2, 3)]
    assert per_stage == [2, 4, 4] and all(per_stage)
    print("PASS: matrix covers 10 of 10 bugs; each test passes clean and fails only its own bug; stages own 2, 4 and 4")

    Worked check 2

    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
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    MAP = [
        ("BOOTROM", 0x00000000, 0x4000),
        ("SRAM0", 0x20000000, 0x10000),
        ("SRAM1", 0x20010000, 0x10000),
        ("DMA", 0x40000000, 0x1000),
        ("SBR", 0x40001000, 0x1000),
        ("UART", 0x40002000, 0x1000),
        ("SPI", 0x40003000, 0x1000),
        ("GPIO", 0x40004000, 0x1000),
        ("INTC", 0x40005000, 0x1000),
        ("RSTPWR", 0x40006000, 0x1000),
    ]
    SECURE_ONLY = {"SRAM1", "RSTPWR"}
    SLOTS = ["DMA", "SBR", "UART", "SPI", "GPIO", "INTC", "RSTPWR"]
    T_CPU, T_SYS, T_PER = 1e9 / 192e6, 1e9 / 96e6, 1e9 / 24e6
    def end_of(base, size):
        return base + size - 1
    def overlaps(entries=MAP):
        bad = []
        xs = sorted(entries, key=lambda e: e[1])
        for a, b in zip(xs, xs[1:]):
            if b[1] < a[1] + a[2]:
                bad.append((a[0], b[0]))
        return bad
    def misaligned(entries=MAP):
        return [n for n, b, s in entries if b % s != 0]
    def decode(addr, slots=SLOTS):
        if addr >> 14 == 0:
            return "BOOTROM"
        if addr >> 16 == 0x2000:
            return "SRAM0"
        if addr >> 16 == 0x2001:
            return "SRAM1"
        if addr >> 16 == 0x4000:
            slot = (addr >> 12) & 0xF
            return slots[slot] if slot < 7 else "H3"
        if addr < 0x20000000:
            return "H1"
        if addr < 0x40000000:
            return "H2"
        return "H4"
    def response(addr, secure, slots=SLOTS):
        t = decode(addr, slots)
        if t in ("H1", "H2", "H4"):
            return "DECERR"
        if t == "H3":
            return "SLVERR"
        if t in SECURE_ONLY and not secure:
            return "SLVERR"
        return "OK"
    def sustained(peak, beats):
        return peak * beats / (beats + 1)
    import hashlib
    # ---- stage checkers: every one is a function of the design data d ----
    ORACLE = {"irq_pwr": 0, "irq_dma": 1, "irq_sbr": 2, "irq_uart": 3, "irq_spi": 4, "irq_gpio": 5}
    REG = {0x00: "CTRL", 0x04: "STATUS", 0x08: "LEN", 0x0C: "KEY", 0x10: "COUNT"}
    GROUPS = {"apb_cdc": [("paddr", 12), ("pwdata", 32), ("psel", 1), ("penable", 1), ("pwrite", 1)],
              "sram0": [("addr", 14), ("wdata", 32), ("be", 4), ("ce", 1), ("we", 1)],
              "rom": [("addr", 12), ("ce", 1)], "irq": [("irq_dma", 1), ("irq_sbr", 1)]}
    def signals():
        return [g + "." + n + "[" + str(i) + "]" for g, items in GROUPS.items() for n, w in items for i in range(w)]
    XING = [("X1", "clk_cpu", "clk_sys", "axi_req", 32, "fifo", 2), ("X2", "clk_sys", "clk_cpu", "axi_resp", 32, "fifo", 2),
            ("X3", "clk_sys", "clk_per", "apb_req", 32, "reqack", 2), ("X4", "clk_per", "clk_sys", "apb_resp", 32, "reqack", 2),
            ("X5", "clk_sys", "clk_per", "irq_dma", 1, "2ff", 2), ("X6", "clk_per", "clk_cpu", "irq_cpu", 1, "2ff", 2),
            ("X7", "clk_per", "clk_sys", "rst_sys_n", 1, "rstsync", 2), ("X8", "clk_per", "clk_cpu", "rst_cpu_n", 1, "rstsync", 2),
            ("X9", "pad", "clk_per", "gpio_in", 1, "2ff", 2), ("X10", "clk_per", "clk_sys", "pgood", 1, "2ff", 2)]
    PLAN = {"rst_per_n": ("por", 1026 * T_PER), "rst_sys_n": ("gates", 18 * T_SYS), "rst_cpu_n": ("rst_sys_n", 8 * T_SYS + 2 * T_CPU)}
    AREA = [0.150, 0.025, 0.015, 0.040, 0.012, 0.010, 0.006, 0.004, 0.100, 0.010,
            0.100, 0.012, 0.008, 0.008, 0.006, 0.004, 0.015, 0.060]
    POWER = [(77.10, 5.64), (11.25, 5.64), (0.16, 1.24)]
    def design():
        return {"map": list(MAP), "slots": list(SLOTS), "sel_mask": {}, "paddr": (0, 7), "xing": list(XING), "plan": dict(PLAN),
                "irq": dict(ORACLE), "irq_sync": (2, 1, 2), "clamped": set(signals()), "wdog": 256, "sbr_ctrl": 0x4,
                "boot_clears_ns": True, "ns_secure_only": False, "lens": (64, 64), "driver": "good", "cause_first": True,
                "burst": 16, "link_width": 4, "fabric_mhz": 96, "banks": ("SRAM0", "SRAM1"), "clk": (24, 1, 32, 4, 8),
                "area": list(AREA), "power": [tuple(p) for p in POWER]}
    def selects(d):
        sel = {n: (i << 12, d["sel_mask"].get(n, 0xF000)) for i, n in enumerate(d["slots"])}
        return sel
    def hits(sel, addr):
        return [n for n, (v, m) in sel.items() if addr >> 16 == 0x4000 and (addr & 0xFFFF) & m == v]
    def map_ok(d):
        sel = selects(d)
        slot_ok = all(hits(sel, 0x40000000 + (i << 12) + o) == [n] for i, n in enumerate(SLOTS) for o in (0, 0xC, 0xFFC))
        return (overlaps(d["map"]) == [] and misaligned(d["map"]) == [] and slot_ok
                and all(decode(b, d["slots"]) == n for n, b, s in d["map"]))
    def dma_read(off, lo, hi):
        if hi - lo != 7:
            return "WIDTH"
        return REG.get((off >> lo) & 0xFF, "PSLVERR")
    def paddr_ok(d):
        lo, hi = d["paddr"]
        return [dma_read(o, lo, hi) for o in sorted(REG)] == ["CTRL", "STATUS", "LEN", "KEY", "COUNT"]
    def audit(rows):
        bad = []
        for x, src, dst, sig, width, mech, flops in rows:
            if sig.startswith("rst"):
                good = mech == "rstsync" and flops >= 2
            elif width == 1:
                good = mech == "2ff" and flops >= 2
            else:
                good = mech in ("fifo", "reqack")
            if not good:
                bad.append(x)
        return bad
    def xing_ok(d):
        return len(d["xing"]) == 10 and audit(d["xing"]) == []
    def release_times(plan):
        t = {"por": 0.0}
        t["rst_per_n"] = plan["rst_per_n"][1]
        t["gates"] = t["rst_per_n"] + 262 * T_PER
        for net in ("rst_sys_n", "rst_cpu_n"):
            after, wait = plan[net]
            t[net] = t[after] + wait
        return t
    def fetch_lost(t):
        return t["rst_cpu_n"] + T_CPU + 3 * T_SYS < t["rst_sys_n"]
    def release_ok(d):
        t = release_times(d["plan"])
        return t["rst_per_n"] <= t["rst_sys_n"] and t["rst_cpu_n"] >= t["rst_sys_n"] + 8 * T_SYS and not fetch_lost(t)
    def intc_raw(wiring, active):
        return sum(1 << wiring[s] for s in active)
    def conn_test(w):
        return [s for s in ORACLE if intc_raw(w, [s]) != 1 << ORACLE[s]]
    def latency(sync_per, ctrl, sync_cpu):
        best = sync_per * T_PER + ctrl * T_PER + sync_cpu * T_CPU
        return best, best + T_PER + T_CPU
    def irq_ok(d):
        best, worst = latency(*d["irq_sync"])
        return conn_test(d["irq"]) == [] and abs(best - 135.4) < 0.1 and abs(worst - 182.3) < 0.1
    def clamp_ok(d):
        return [s for s in signals() if s not in d["clamped"]] == []
    def apb_access(clk_en, wdog):
        for cycle in range(1, 2000):
            if clk_en and cycle >= 3:
                return "OK"
            if wdog and cycle >= wdog:
                return "PSLVERR"
        return "HANG"
    def gate_ok(d):
        return apb_access(False, d["wdog"]) == "PSLVERR" and apb_access(True, None) == "OK"
    def sbr_ctrl_write(ctrl, wdata, writer_secure, ns_secure_only=False):
        if ns_secure_only and not writer_secure:
            wdata = (wdata & ~0x4) | (ctrl & 0x4)
        return wdata & 0x6
    def sbr_reaches_sram1(ctrl):
        return response(0x20010000, (ctrl >> 2) & 1 == 0) == "OK"
    def secure_ok(d):
        ctrl = d["sbr_ctrl"]
        if d["boot_clears_ns"]:
            ctrl = sbr_ctrl_write(ctrl, 0x0, True, d["ns_secure_only"])
        return sbr_reaches_sram1(ctrl)
    def partition_closed(d):
        return not sbr_reaches_sram1(sbr_ctrl_write(d["sbr_ctrl"], 0x0, False, d["ns_secure_only"]))
    class Dma:
        def __init__(self):
            self.busy = self.done = self.err = self.irq_en = False
            self.key = self.len = self.count = self.stalls = 0
        def irq(self):
            return self.irq_en and (self.done or self.err)
        def write_key(self, v):
            self.key = self.key if self.busy else v
        def write_len(self, v):
            self.len = self.len if self.busy else v & 0xFFFF
        def ctrl_write(self, v):
            self.irq_en = bool(v & 2)
            if v & 1 and not self.busy:
                if self.len == 0:
                    self.done = True
                else:
                    self.busy, self.done, self.count = True, False, 0
        def status_write(self, v):
            self.done = self.done and not v & 2
            self.err = self.err and not v & 4
        def accept(self, last):
            if not self.busy:
                self.stalls += 1
                return False
            self.count += 1
            if last and self.count < self.len:
                self.err, self.busy = True, False
            elif self.count == self.len:
                self.done, self.busy = True, False
            return True
    def stream(dma, n):
        return not all(dma.accept(i == n - 1) for i in range(n))
    def job(n_dma, n_sbr, key=0x5A):
        d = Dma()
        d.write_key(key)
        d.write_len(n_dma)
        d.ctrl_write(0x3)
        return d, stream(d, n_sbr)
    def driver_ok(count, sbr_len, dma_len):
        return count == sbr_len == dma_len and dma_len > 0
    def len_ok(d):
        n_dma, n_sbr = d["lens"]
        dma, sbr_busy = job(n_dma, n_sbr)
        return dma.done and not dma.err and not sbr_busy and driver_ok(dma.count, n_sbr, n_dma)
    class Good:
        def start(self, dma): dma.ctrl_write(0x3)
        def rekey(self, dma, key, finish):
            if dma.busy: finish(dma)
            dma.write_key(key)
        def clear(self, dma): dma.status_write(0x6)
    class Naive:
        def start(self, dma): dma.ctrl_write(0x1)
        def rekey(self, dma, key, finish): dma.write_key(key)
        def clear(self, dma): dma.status_write(0x2)
    def contract_problems(drv):
        found = []
        a = Dma(); a.write_key(0xAA); a.write_len(4); a.ctrl_write(0x3); a.accept(False)
        drv.rekey(a, 0x55, lambda x: [x.accept(i == 2) for i in range(3)])
        if a.key != 0x55: found.append("stale_key")
        b = Dma(); b.write_len(8); b.ctrl_write(0x3); b.accept(True)
        drv.clear(b)
        if b.irq(): found.append("irq_stuck")
        c = Dma(); c.write_len(1); drv.start(c)
        if not c.irq_en: found.append("irq_disabled")
        return found
    def contract_ok(d):
        return contract_problems({"good": Good, "naive": Naive}[d["driver"]]()) == []
    def link_table(d):
        peak = d["link_width"] * d["fabric_mhz"]
        s = sustained(peak, d["burst"])
        return {"cpu_fill": 16 / (24 * T_CPU) * 1e3, "fabric": s, "sram_bank_shared": s / 2, "apb_fast": peak / 5,
                "apb_slow": 4 / 208.33e-9 / 1e6}
    def stream_rate(d):
        t = link_table(d)
        bank = t["sram_bank_shared"] if d["banks"][0] == d["banks"][1] else t["fabric"]
        path = {"sbr_read": t["fabric"], "src_bank": bank, "dma_in": t["fabric"], "dma_out": t["fabric"],
                "dst_bank": bank, "sbr_write": t["fabric"]}
        low = min(path.values())
        return low, sorted(n for n, v in path.items() if v - low < 0.05)
    def links_ok(d):
        t = link_table(d)
        rate, tied = stream_rate(d)
        want = {"cpu_fill": 128.0, "fabric": 361.4, "sram_bank_shared": 180.7, "apb_fast": 76.8, "apb_slow": 19.2}
        return all(abs(t[k] - v) < 0.05 for k, v in want.items()) and abs(rate - 361.4) < 0.05 and abs(2 * rate - 722.8) < 0.1
    def clocks_ok(d):
        xtal, r, n, dc, ds = d["clk"]
        vco = xtal * n / r
        return 600 <= vco <= 1200 and (vco / dc, vco / ds, xtal) == (192.0, 96.0, 24) and abs(256 * T_PER / 1000 - 10.667) < 0.01
    def boot_steps(cause, cause_first=True):
        hw = [("osc", 1024 * T_PER), ("rst_per", 2 * T_PER), ("pll", 260 * T_PER), ("gates", 2 * T_PER),
              ("rst_sys", 18 * T_SYS), ("rst_cpu", 8 * T_SYS + 2 * T_CPU), ("fetch", 24 * T_CPU)]
        rd, clr = ("read_cause", 54 * T_CPU), ("clear_sram0", 1024 * T_SYS)
        if cause == "WAKE":
            return hw + ([rd] if cause_first else [clr, rd])
        rest = [("uart_init", 3 * 54 * T_CPU), ("banner", 160 / (24e6 / 208) * 1e9),
                ("dma_selftest", 5 * 125 + 7 * 125 + 72 * T_SYS + 26 * T_CPU + 6000), ("spi_load", 8196 * 8 / 6e6 * 1e9),
                ("checksum", 2048 * 18 * T_CPU)]
        return hw + ([rd, clr] if cause_first else [clr, rd]) + rest
    def boot_ok(d):
        cold = sum(ns for _, ns in boot_steps("POR", d["cause_first"]))
        wake = [n for n, _ in boot_steps("WAKE", d["cause_first"])]
        return abs(cold / 1000 - 12580.917) < 0.01 and "clear_sram0" not in wake
    # ---- the ten bugs: each injector breaks one fact of the design data ----
    def inj1(d):
        d["map"] = [("DMA", 0x40000000, 0x2000) if r[0] == "DMA" else r for r in d["map"]]
        d["sel_mask"]["DMA"] = 0xE000
    def inj2(d): d["plan"]["rst_cpu_n"] = ("gates", 2 * T_CPU)
    def inj3(d): d["irq"]["irq_dma"] = 6
    def inj4(d): d["paddr"] = (2, 9)
    def inj5(d): d["xing"][4] = ("X5", "clk_sys", "clk_per", "irq_dma", 1, "direct", 0)
    def inj6(d): d["clamped"] -= {s for s in signals() if s.startswith("irq.")}
    def inj7(d): d["boot_clears_ns"] = False
    def inj8(d): d["lens"] = (64, 32)
    def inj9(d): d["driver"] = "naive"
    def inj10(d): d["wdog"] = None
    BUGS = {1: ("map", map_ok, inj1), 2: ("release", release_ok, inj2), 3: ("irqs", irq_ok, inj3), 4: ("paddr", paddr_ok, inj4),
            5: ("crossings", xing_ok, inj5), 6: ("clamps", clamp_ok, inj6), 7: ("secure", secure_ok, inj7),
            8: ("lengths", len_ok, inj8), 9: ("contract", contract_ok, inj9), 10: ("gate", gate_ok, inj10)}
    # ---- manifest and waivers ----
    def manifest(d):
        xtal, r, n, dc, ds = d["clk"]
        cold = sum(ns for _, ns in boot_steps("POR", d["cause_first"]))
        return {"map": (len(d["map"]), len(overlaps(d["map"]))), "map_hash": hashlib.sha256(repr(d["map"]).encode()).hexdigest()[:8],
                "clocks": (xtal * n / r / dc, xtal * n / r / ds, xtal), "release_us": round(release_times(d["plan"])["rst_cpu_n"] / 1000, 3),
                "clamps": len(d["clamped"]), "irq_inputs": tuple(d["irq"].get(s) for s in ORACLE), "paddr": d["paddr"],
                "boot_ms": round(cold / 1e6, 3), "bugs": len(BUGS),
                "area_mm2": round(sum(d["area"]), 3), "util_pct": round(sum(d["area"]) * 1.12 / 0.940 * 100, 1),
                "power_mw": tuple(round(dyn + leak, 2) for dyn, leak in d["power"])}
    SIGNED = {"map": (10, 0), "map_hash": "a418a427", "clocks": (192.0, 96.0, 24), "release_us": 53.948, "clamps": 114,
              "irq_inputs": (0, 1, 2, 3, 4, 5), "paddr": (0, 7), "boot_ms": 12.581, "bugs": 10,
              "area_mm2": 0.585, "util_pct": 69.7, "power_mw": (82.74, 16.89, 1.4)}
    TODAY, LIMIT, REV = 200, 90, 7
    def fresh(w):
        return (TODAY - w["raised"] <= LIMIT and TODAY <= w["expires"] and w["ev_rev"] == REV and bool(w["owner"]))
    WAIVERS = [{"id": "W1-alias-images", "owner": "integration", "raised": 188, "expires": 280, "ev_rev": 7},
               {"id": "W2-h3-reserved", "owner": "integration", "raised": 170, "expires": 260, "ev_rev": 7}]
    
    # ---- this lesson starts here ----
    
    assert manifest(design()) == SIGNED                             # today's design generates exactly the signed record
    d = design()
    d["map"][3] = ("DMA", 0x40000000, 0x2000)                       # change the map and do not re-sign
    now = manifest(d)
    assert {k for k in SIGNED if now[k] != SIGNED[k]} == {"map", "map_hash"}
    d = design()
    d["clamped"].pop()                                              # one clamp lost
    assert {k for k in SIGNED if manifest(d)[k] != SIGNED[k]} == {"clamps"}
    w = WAIVERS[0]
    assert all(fresh(x) for x in WAIVERS)
    assert not fresh(dict(w, raised=100))                           # older than the 90-day limit (age 100)
    assert not fresh(dict(w, expires=190))                          # past its expiry day
    assert not fresh(dict(w, ev_rev=6))                             # evidence recorded for an older design revision
    assert not fresh(dict(w, owner=""))                             # nobody owns it
    stale = {"id": "W3-old-gpio-pulse", "owner": "integration", "raised": 80, "expires": 260, "ev_rev": 5}
    assert not fresh(stale)
    print("PASS: manifest of 12 fields regenerated and equal to the signed record; map and clamp drift named; 2 waivers current, each conjunct refused")

    Worked check 3

    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
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    MAP = [
        ("BOOTROM", 0x00000000, 0x4000),
        ("SRAM0", 0x20000000, 0x10000),
        ("SRAM1", 0x20010000, 0x10000),
        ("DMA", 0x40000000, 0x1000),
        ("SBR", 0x40001000, 0x1000),
        ("UART", 0x40002000, 0x1000),
        ("SPI", 0x40003000, 0x1000),
        ("GPIO", 0x40004000, 0x1000),
        ("INTC", 0x40005000, 0x1000),
        ("RSTPWR", 0x40006000, 0x1000),
    ]
    SECURE_ONLY = {"SRAM1", "RSTPWR"}
    SLOTS = ["DMA", "SBR", "UART", "SPI", "GPIO", "INTC", "RSTPWR"]
    T_CPU, T_SYS, T_PER = 1e9 / 192e6, 1e9 / 96e6, 1e9 / 24e6
    def end_of(base, size):
        return base + size - 1
    def overlaps(entries=MAP):
        bad = []
        xs = sorted(entries, key=lambda e: e[1])
        for a, b in zip(xs, xs[1:]):
            if b[1] < a[1] + a[2]:
                bad.append((a[0], b[0]))
        return bad
    def misaligned(entries=MAP):
        return [n for n, b, s in entries if b % s != 0]
    def decode(addr, slots=SLOTS):
        if addr >> 14 == 0:
            return "BOOTROM"
        if addr >> 16 == 0x2000:
            return "SRAM0"
        if addr >> 16 == 0x2001:
            return "SRAM1"
        if addr >> 16 == 0x4000:
            slot = (addr >> 12) & 0xF
            return slots[slot] if slot < 7 else "H3"
        if addr < 0x20000000:
            return "H1"
        if addr < 0x40000000:
            return "H2"
        return "H4"
    def response(addr, secure, slots=SLOTS):
        t = decode(addr, slots)
        if t in ("H1", "H2", "H4"):
            return "DECERR"
        if t == "H3":
            return "SLVERR"
        if t in SECURE_ONLY and not secure:
            return "SLVERR"
        return "OK"
    def sustained(peak, beats):
        return peak * beats / (beats + 1)
    import hashlib
    # ---- stage checkers: every one is a function of the design data d ----
    ORACLE = {"irq_pwr": 0, "irq_dma": 1, "irq_sbr": 2, "irq_uart": 3, "irq_spi": 4, "irq_gpio": 5}
    REG = {0x00: "CTRL", 0x04: "STATUS", 0x08: "LEN", 0x0C: "KEY", 0x10: "COUNT"}
    GROUPS = {"apb_cdc": [("paddr", 12), ("pwdata", 32), ("psel", 1), ("penable", 1), ("pwrite", 1)],
              "sram0": [("addr", 14), ("wdata", 32), ("be", 4), ("ce", 1), ("we", 1)],
              "rom": [("addr", 12), ("ce", 1)], "irq": [("irq_dma", 1), ("irq_sbr", 1)]}
    def signals():
        return [g + "." + n + "[" + str(i) + "]" for g, items in GROUPS.items() for n, w in items for i in range(w)]
    XING = [("X1", "clk_cpu", "clk_sys", "axi_req", 32, "fifo", 2), ("X2", "clk_sys", "clk_cpu", "axi_resp", 32, "fifo", 2),
            ("X3", "clk_sys", "clk_per", "apb_req", 32, "reqack", 2), ("X4", "clk_per", "clk_sys", "apb_resp", 32, "reqack", 2),
            ("X5", "clk_sys", "clk_per", "irq_dma", 1, "2ff", 2), ("X6", "clk_per", "clk_cpu", "irq_cpu", 1, "2ff", 2),
            ("X7", "clk_per", "clk_sys", "rst_sys_n", 1, "rstsync", 2), ("X8", "clk_per", "clk_cpu", "rst_cpu_n", 1, "rstsync", 2),
            ("X9", "pad", "clk_per", "gpio_in", 1, "2ff", 2), ("X10", "clk_per", "clk_sys", "pgood", 1, "2ff", 2)]
    PLAN = {"rst_per_n": ("por", 1026 * T_PER), "rst_sys_n": ("gates", 18 * T_SYS), "rst_cpu_n": ("rst_sys_n", 8 * T_SYS + 2 * T_CPU)}
    AREA = [0.150, 0.025, 0.015, 0.040, 0.012, 0.010, 0.006, 0.004, 0.100, 0.010,
            0.100, 0.012, 0.008, 0.008, 0.006, 0.004, 0.015, 0.060]
    POWER = [(77.10, 5.64), (11.25, 5.64), (0.16, 1.24)]
    def design():
        return {"map": list(MAP), "slots": list(SLOTS), "sel_mask": {}, "paddr": (0, 7), "xing": list(XING), "plan": dict(PLAN),
                "irq": dict(ORACLE), "irq_sync": (2, 1, 2), "clamped": set(signals()), "wdog": 256, "sbr_ctrl": 0x4,
                "boot_clears_ns": True, "ns_secure_only": False, "lens": (64, 64), "driver": "good", "cause_first": True,
                "burst": 16, "link_width": 4, "fabric_mhz": 96, "banks": ("SRAM0", "SRAM1"), "clk": (24, 1, 32, 4, 8),
                "area": list(AREA), "power": [tuple(p) for p in POWER]}
    def selects(d):
        sel = {n: (i << 12, d["sel_mask"].get(n, 0xF000)) for i, n in enumerate(d["slots"])}
        return sel
    def hits(sel, addr):
        return [n for n, (v, m) in sel.items() if addr >> 16 == 0x4000 and (addr & 0xFFFF) & m == v]
    def map_ok(d):
        sel = selects(d)
        slot_ok = all(hits(sel, 0x40000000 + (i << 12) + o) == [n] for i, n in enumerate(SLOTS) for o in (0, 0xC, 0xFFC))
        return (overlaps(d["map"]) == [] and misaligned(d["map"]) == [] and slot_ok
                and all(decode(b, d["slots"]) == n for n, b, s in d["map"]))
    def dma_read(off, lo, hi):
        if hi - lo != 7:
            return "WIDTH"
        return REG.get((off >> lo) & 0xFF, "PSLVERR")
    def paddr_ok(d):
        lo, hi = d["paddr"]
        return [dma_read(o, lo, hi) for o in sorted(REG)] == ["CTRL", "STATUS", "LEN", "KEY", "COUNT"]
    def audit(rows):
        bad = []
        for x, src, dst, sig, width, mech, flops in rows:
            if sig.startswith("rst"):
                good = mech == "rstsync" and flops >= 2
            elif width == 1:
                good = mech == "2ff" and flops >= 2
            else:
                good = mech in ("fifo", "reqack")
            if not good:
                bad.append(x)
        return bad
    def xing_ok(d):
        return len(d["xing"]) == 10 and audit(d["xing"]) == []
    def release_times(plan):
        t = {"por": 0.0}
        t["rst_per_n"] = plan["rst_per_n"][1]
        t["gates"] = t["rst_per_n"] + 262 * T_PER
        for net in ("rst_sys_n", "rst_cpu_n"):
            after, wait = plan[net]
            t[net] = t[after] + wait
        return t
    def fetch_lost(t):
        return t["rst_cpu_n"] + T_CPU + 3 * T_SYS < t["rst_sys_n"]
    def release_ok(d):
        t = release_times(d["plan"])
        return t["rst_per_n"] <= t["rst_sys_n"] and t["rst_cpu_n"] >= t["rst_sys_n"] + 8 * T_SYS and not fetch_lost(t)
    def intc_raw(wiring, active):
        return sum(1 << wiring[s] for s in active)
    def conn_test(w):
        return [s for s in ORACLE if intc_raw(w, [s]) != 1 << ORACLE[s]]
    def latency(sync_per, ctrl, sync_cpu):
        best = sync_per * T_PER + ctrl * T_PER + sync_cpu * T_CPU
        return best, best + T_PER + T_CPU
    def irq_ok(d):
        best, worst = latency(*d["irq_sync"])
        return conn_test(d["irq"]) == [] and abs(best - 135.4) < 0.1 and abs(worst - 182.3) < 0.1
    def clamp_ok(d):
        return [s for s in signals() if s not in d["clamped"]] == []
    def apb_access(clk_en, wdog):
        for cycle in range(1, 2000):
            if clk_en and cycle >= 3:
                return "OK"
            if wdog and cycle >= wdog:
                return "PSLVERR"
        return "HANG"
    def gate_ok(d):
        return apb_access(False, d["wdog"]) == "PSLVERR" and apb_access(True, None) == "OK"
    def sbr_ctrl_write(ctrl, wdata, writer_secure, ns_secure_only=False):
        if ns_secure_only and not writer_secure:
            wdata = (wdata & ~0x4) | (ctrl & 0x4)
        return wdata & 0x6
    def sbr_reaches_sram1(ctrl):
        return response(0x20010000, (ctrl >> 2) & 1 == 0) == "OK"
    def secure_ok(d):
        ctrl = d["sbr_ctrl"]
        if d["boot_clears_ns"]:
            ctrl = sbr_ctrl_write(ctrl, 0x0, True, d["ns_secure_only"])
        return sbr_reaches_sram1(ctrl)
    def partition_closed(d):
        return not sbr_reaches_sram1(sbr_ctrl_write(d["sbr_ctrl"], 0x0, False, d["ns_secure_only"]))
    class Dma:
        def __init__(self):
            self.busy = self.done = self.err = self.irq_en = False
            self.key = self.len = self.count = self.stalls = 0
        def irq(self):
            return self.irq_en and (self.done or self.err)
        def write_key(self, v):
            self.key = self.key if self.busy else v
        def write_len(self, v):
            self.len = self.len if self.busy else v & 0xFFFF
        def ctrl_write(self, v):
            self.irq_en = bool(v & 2)
            if v & 1 and not self.busy:
                if self.len == 0:
                    self.done = True
                else:
                    self.busy, self.done, self.count = True, False, 0
        def status_write(self, v):
            self.done = self.done and not v & 2
            self.err = self.err and not v & 4
        def accept(self, last):
            if not self.busy:
                self.stalls += 1
                return False
            self.count += 1
            if last and self.count < self.len:
                self.err, self.busy = True, False
            elif self.count == self.len:
                self.done, self.busy = True, False
            return True
    def stream(dma, n):
        return not all(dma.accept(i == n - 1) for i in range(n))
    def job(n_dma, n_sbr, key=0x5A):
        d = Dma()
        d.write_key(key)
        d.write_len(n_dma)
        d.ctrl_write(0x3)
        return d, stream(d, n_sbr)
    def driver_ok(count, sbr_len, dma_len):
        return count == sbr_len == dma_len and dma_len > 0
    def len_ok(d):
        n_dma, n_sbr = d["lens"]
        dma, sbr_busy = job(n_dma, n_sbr)
        return dma.done and not dma.err and not sbr_busy and driver_ok(dma.count, n_sbr, n_dma)
    class Good:
        def start(self, dma): dma.ctrl_write(0x3)
        def rekey(self, dma, key, finish):
            if dma.busy: finish(dma)
            dma.write_key(key)
        def clear(self, dma): dma.status_write(0x6)
    class Naive:
        def start(self, dma): dma.ctrl_write(0x1)
        def rekey(self, dma, key, finish): dma.write_key(key)
        def clear(self, dma): dma.status_write(0x2)
    def contract_problems(drv):
        found = []
        a = Dma(); a.write_key(0xAA); a.write_len(4); a.ctrl_write(0x3); a.accept(False)
        drv.rekey(a, 0x55, lambda x: [x.accept(i == 2) for i in range(3)])
        if a.key != 0x55: found.append("stale_key")
        b = Dma(); b.write_len(8); b.ctrl_write(0x3); b.accept(True)
        drv.clear(b)
        if b.irq(): found.append("irq_stuck")
        c = Dma(); c.write_len(1); drv.start(c)
        if not c.irq_en: found.append("irq_disabled")
        return found
    def contract_ok(d):
        return contract_problems({"good": Good, "naive": Naive}[d["driver"]]()) == []
    def link_table(d):
        peak = d["link_width"] * d["fabric_mhz"]
        s = sustained(peak, d["burst"])
        return {"cpu_fill": 16 / (24 * T_CPU) * 1e3, "fabric": s, "sram_bank_shared": s / 2, "apb_fast": peak / 5,
                "apb_slow": 4 / 208.33e-9 / 1e6}
    def stream_rate(d):
        t = link_table(d)
        bank = t["sram_bank_shared"] if d["banks"][0] == d["banks"][1] else t["fabric"]
        path = {"sbr_read": t["fabric"], "src_bank": bank, "dma_in": t["fabric"], "dma_out": t["fabric"],
                "dst_bank": bank, "sbr_write": t["fabric"]}
        low = min(path.values())
        return low, sorted(n for n, v in path.items() if v - low < 0.05)
    def links_ok(d):
        t = link_table(d)
        rate, tied = stream_rate(d)
        want = {"cpu_fill": 128.0, "fabric": 361.4, "sram_bank_shared": 180.7, "apb_fast": 76.8, "apb_slow": 19.2}
        return all(abs(t[k] - v) < 0.05 for k, v in want.items()) and abs(rate - 361.4) < 0.05 and abs(2 * rate - 722.8) < 0.1
    def clocks_ok(d):
        xtal, r, n, dc, ds = d["clk"]
        vco = xtal * n / r
        return 600 <= vco <= 1200 and (vco / dc, vco / ds, xtal) == (192.0, 96.0, 24) and abs(256 * T_PER / 1000 - 10.667) < 0.01
    def boot_steps(cause, cause_first=True):
        hw = [("osc", 1024 * T_PER), ("rst_per", 2 * T_PER), ("pll", 260 * T_PER), ("gates", 2 * T_PER),
              ("rst_sys", 18 * T_SYS), ("rst_cpu", 8 * T_SYS + 2 * T_CPU), ("fetch", 24 * T_CPU)]
        rd, clr = ("read_cause", 54 * T_CPU), ("clear_sram0", 1024 * T_SYS)
        if cause == "WAKE":
            return hw + ([rd] if cause_first else [clr, rd])
        rest = [("uart_init", 3 * 54 * T_CPU), ("banner", 160 / (24e6 / 208) * 1e9),
                ("dma_selftest", 5 * 125 + 7 * 125 + 72 * T_SYS + 26 * T_CPU + 6000), ("spi_load", 8196 * 8 / 6e6 * 1e9),
                ("checksum", 2048 * 18 * T_CPU)]
        return hw + ([rd, clr] if cause_first else [clr, rd]) + rest
    def boot_ok(d):
        cold = sum(ns for _, ns in boot_steps("POR", d["cause_first"]))
        wake = [n for n, _ in boot_steps("WAKE", d["cause_first"])]
        return abs(cold / 1000 - 12580.917) < 0.01 and "clear_sram0" not in wake
    # ---- the ten bugs: each injector breaks one fact of the design data ----
    def inj1(d):
        d["map"] = [("DMA", 0x40000000, 0x2000) if r[0] == "DMA" else r for r in d["map"]]
        d["sel_mask"]["DMA"] = 0xE000
    def inj2(d): d["plan"]["rst_cpu_n"] = ("gates", 2 * T_CPU)
    def inj3(d): d["irq"]["irq_dma"] = 6
    def inj4(d): d["paddr"] = (2, 9)
    def inj5(d): d["xing"][4] = ("X5", "clk_sys", "clk_per", "irq_dma", 1, "direct", 0)
    def inj6(d): d["clamped"] -= {s for s in signals() if s.startswith("irq.")}
    def inj7(d): d["boot_clears_ns"] = False
    def inj8(d): d["lens"] = (64, 32)
    def inj9(d): d["driver"] = "naive"
    def inj10(d): d["wdog"] = None
    BUGS = {1: ("map", map_ok, inj1), 2: ("release", release_ok, inj2), 3: ("irqs", irq_ok, inj3), 4: ("paddr", paddr_ok, inj4),
            5: ("crossings", xing_ok, inj5), 6: ("clamps", clamp_ok, inj6), 7: ("secure", secure_ok, inj7),
            8: ("lengths", len_ok, inj8), 9: ("contract", contract_ok, inj9), 10: ("gate", gate_ok, inj10)}
    # ---- manifest and waivers ----
    def manifest(d):
        xtal, r, n, dc, ds = d["clk"]
        cold = sum(ns for _, ns in boot_steps("POR", d["cause_first"]))
        return {"map": (len(d["map"]), len(overlaps(d["map"]))), "map_hash": hashlib.sha256(repr(d["map"]).encode()).hexdigest()[:8],
                "clocks": (xtal * n / r / dc, xtal * n / r / ds, xtal), "release_us": round(release_times(d["plan"])["rst_cpu_n"] / 1000, 3),
                "clamps": len(d["clamped"]), "irq_inputs": tuple(d["irq"].get(s) for s in ORACLE), "paddr": d["paddr"],
                "boot_ms": round(cold / 1e6, 3), "bugs": len(BUGS),
                "area_mm2": round(sum(d["area"]), 3), "util_pct": round(sum(d["area"]) * 1.12 / 0.940 * 100, 1),
                "power_mw": tuple(round(dyn + leak, 2) for dyn, leak in d["power"])}
    SIGNED = {"map": (10, 0), "map_hash": "a418a427", "clocks": (192.0, 96.0, 24), "release_us": 53.948, "clamps": 114,
              "irq_inputs": (0, 1, 2, 3, 4, 5), "paddr": (0, 7), "boot_ms": 12.581, "bugs": 10,
              "area_mm2": 0.585, "util_pct": 69.7, "power_mw": (82.74, 16.89, 1.4)}
    TODAY, LIMIT, REV = 200, 90, 7
    def fresh(w):
        return (TODAY - w["raised"] <= LIMIT and TODAY <= w["expires"] and w["ev_rev"] == REV and bool(w["owner"]))
    WAIVERS = [{"id": "W1-alias-images", "owner": "integration", "raised": 188, "expires": 280, "ev_rev": 7},
               {"id": "W2-h3-reserved", "owner": "integration", "raised": 170, "expires": 260, "ev_rev": 7}]
    # ---- every stage check, the test of the tests, and the full run ----
    def checks(d):
        r = {name: fn(d) for name, fn, _ in BUGS.values()}
        r["links"], r["clocks"], r["boot"] = links_ok(d), clocks_ok(d), boot_ok(d)
        r["manifest"] = manifest(d) == SIGNED
        r["waivers"] = all(fresh(w) for w in d.get("waivers", WAIVERS))
        return r
    def breaks(fn, inj):
        d = design()
        inj(d)
        return not fn(d)
    def matrix():
        return all(fn(design()) and breaks(fn, inj) for _, fn, inj in BUGS.values())
    def run(d):
        r = checks(d)
        r["matrix"] = matrix()
        return {k for k, v in r.items() if not v}
    
    # ---- this lesson starts here ----
    
    assert run(design()) == set()
    def with_(**kw):
        d = design()
        d.update(kw)
        return d
    def swapped():
        d = design()
        d["slots"][0], d["slots"][1] = "SBR", "DMA"
        return d
    def bug(n):
        d = design()
        BUGS[n][2](d)
        return d
    faults = {"bug 1 8 KB DMA window": (bug(1), {"map", "manifest"}),
              "bug 2 CPU released with the fabric": (bug(2), {"release", "manifest"}),
              "bug 3 irq_dma on input 6": (bug(3), {"irqs", "manifest"}),
              "bug 4 paddr[9:2]": (bug(4), {"paddr", "manifest"}),
              "bug 5 bare crossing": (bug(5), {"crossings"}),
              "bug 6 irq clamps missing": (bug(6), {"clamps", "manifest"}),
              "bug 7 NS never cleared": (bug(7), {"secure"}),
              "bug 8 lengths 64 and 32": (bug(8), {"lengths"}),
              "bug 9 naive driver": (bug(9), {"contract"}),
              "bug 10 no watchdog": (bug(10), {"gate"}),
              "DMA and SBR slots swapped": (swapped(), {"map"}),
              "burst 4": (with_(burst=4), {"links"}),
              "both buffers in SRAM0": (with_(banks=("SRAM0", "SRAM0")), {"links"}),
              "16-bit links": (with_(link_width=2), {"links"}),
              "an area row dropped": (with_(area=AREA[:-1]), {"manifest"}),
              "interrupt sync of one flop": (with_(irq_sync=(1, 1, 2)), {"irqs"}),
              "PLL multiply 30": (with_(clk=(24, 1, 30, 4, 8)), {"clocks", "manifest"}),
              "cause read after the clear": (with_(cause_first=False), {"boot"}),
              "stale waiver": (with_(waivers=WAIVERS + [{"id": "W3-old-gpio-pulse", "owner": "integration", "raised": 80, "expires": 260, "ev_rev": 5}]), {"waivers"})}
    for name, (d, expect) in faults.items():
        assert run(d) == expect, (name, run(d))
    OPEN = [] if partition_closed(design()) else ["SBR slot is non-secure: non-secure software can clear CTRL.NS"]
    fixed = design()
    fixed["ns_secure_only"] = True
    assert len(OPEN) == 1 and partition_closed(fixed) and run(fixed) == set()
    print("PASS: NanoSoC integration manifest consistent")

    The final PASS establishes consistency of the Python model and manifest. Record the known SBR attribute weakness and all implementation evidence still required in the readiness statement. The model reproduces an open partition in v1 and demonstrates a proposed secure-write guard; a model PASS alone does not close that finding.

    Related topics

    • Solution key and closure evidence

    Previous

    284 / 294

    Next: Glossary: Address and decode

    Up next

    Up nextGlossary: Address and decode

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Root Causes, Fixes and Evidence
    • Stage 1 Worked Checks
    • Stage 2 Worked Checks