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.
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)
# ---- this lesson starts here ----
def chain(xtal=24, r=1, n=32, d_cpu=4, d_sys=8):
vco = xtal * n / r
assert 600 <= vco <= 1200 # the teaching VCO range
return vco, vco / d_cpu, vco / d_sys, xtal
assert chain() == (768.0, 192.0, 96.0, 24) and chain(n=30) != chain()
assert abs(T_PER - 41.6667) < 0.001 and abs(256 * T_PER / 1000 - 10.667) < 0.01
# id, from, to, signal, width in bits, mechanism, flops on the receiving side
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)]
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 # one bit: two flops, no fewer
else:
good = mech in ("fifo", "reqack") # many bits: a FIFO or a handshake, never a synchroniser per bit
if not good:
bad.append(x)
return bad
assert len(XING) == 10 and audit(XING) == []
def with_row(i, row):
return XING[:i] + [row] + XING[i + 1:]
assert audit(with_row(4, ("X5", "clk_sys", "clk_per", "irq_dma", 1, "direct", 0))) == ["X5"] # bug 5
assert audit(with_row(2, ("X3", "clk_sys", "clk_per", "apb_req", 32, "2ff", 2))) == ["X3"] # a bus with a bit synchroniser
assert audit(with_row(5, ("X6", "clk_per", "clk_cpu", "irq_cpu", 1, "2ff", 1))) == ["X6"] # one flop is not a synchroniser
print("PASS: 768/192/96/24 MHz chain; 10 crossings audited by width; bare X5, bus on 2ff and 1-flop X6 all flagged")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)
# ---- this lesson starts here ----
# each net: (what it waits for, how long after that), from the R1 to R6 table
PLAN = {"rst_per_n": ("por", 1026 * T_PER), # 1024 oscillator cycles plus a 2-flop sync
"rst_sys_n": ("gates", 18 * T_SYS), # 16-cycle hold plus a 2-flop sync in clk_sys
"rst_cpu_n": ("rst_sys_n", 8 * T_SYS + 2 * T_CPU)} # 8-cycle gap plus a 2-flop sync in clk_cpu
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 # PLL start and lock (260) and gates open (2)
for net in ("rst_sys_n", "rst_cpu_n"):
after, wait = plan[net]
t[net] = t[after] + wait
return t
def fetch_lost(t):
# the first fetch reaches the crossbar 1 clk_cpu + 3 clk_sys after the CPU leaves reset
return t["rst_cpu_n"] + T_CPU + 3 * T_SYS < t["rst_sys_n"]
def order_ok(t):
return t["rst_per_n"] <= t["rst_sys_n"] and t["rst_cpu_n"] >= t["rst_sys_n"] + 8 * T_SYS
good = release_times(PLAN)
assert abs(good["rst_cpu_n"] / 1000 - 53.948) < 0.01
assert abs(good["rst_sys_n"] - good["gates"] - 187.5) < 0.1 and abs(good["rst_cpu_n"] - good["rst_sys_n"] - 93.75) < 0.1
assert order_ok(good) and not fetch_lost(good)
bug = release_times(dict(PLAN, rst_cpu_n=("gates", 2 * T_CPU))) # bug 2: released in the same step as rst_sys_n
assert not order_ok(bug) and fetch_lost(bug)
assert abs(bug["rst_sys_n"] - bug["rst_cpu_n"] - 177.1) < 0.1
print("PASS: CPU released at 53.948 us, 93.75 ns after the fabric; same-step release breaks the order and loses the fetch")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)
# ---- this lesson starts here ----
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)]
def clamp_audit(clamped):
return [s for s in signals() if s not in clamped]
def sleep_effects(missing):
fx = set()
if any(s.startswith("irq.") for s in missing):
fx.add("spurious irq_cpu, sleep and wake loop")
if any(s.startswith("sram0.") for s in missing):
fx.add("X on the SRAM0 macro port")
return fx
ALL = set(signals())
assert len(ALL) == 114 and clamp_audit(ALL) == []
assert {g: sum(w for _, w in v) for g, v in GROUPS.items()} == {"apb_cdc": 47, "sram0": 52, "rom": 13, "irq": 2}
no_irq = {s for s in ALL if not s.startswith("irq.")} # bug 6
assert len(no_irq) == 112 and clamp_audit(no_irq) == ["irq.irq_dma[0]", "irq.irq_sbr[0]"]
assert sleep_effects(clamp_audit(no_irq)) == {"spurious irq_cpu, sleep and wake loop"}
assert sleep_effects(clamp_audit(ALL - {"sram0.wdata[7]"})) == {"X on the SRAM0 macro port"} and sleep_effects([]) == set()
down = [("isolate", 2), ("gate_clocks", 2), ("assert_resets", 2), ("open_switch", 64)]
assert [n for n, _ in down] == ["isolate", "gate_clocks", "assert_resets", "open_switch"]
assert abs(sum(c for _, c in down) * T_PER - 2916.7) < 1.0
def wake():
t = {"pgood": (64 + 8) * T_PER}
t["clocks_open"] = t["pgood"] + (256 + 2) * T_PER
t["iso_release"] = t["clocks_open"] + 4 * T_SYS
t["rst_sys_n"] = t["iso_release"] + 18 * T_SYS
t["rst_cpu_n"] = t["rst_sys_n"] + 8 * T_SYS + 2 * T_CPU
return t
w = wake()
assert abs(w["rst_cpu_n"] - 14072.9) < 1.0
assert w["clocks_open"] < w["iso_release"] < w["rst_sys_n"] < w["rst_cpu_n"] # isolation drops while resets are still asserted
def apb_access(clk_en, wdog):
for cycle in range(1, 2000):
if clk_en and cycle >= 3:
return "OK" # a DMA access takes 3 clocks
if wdog and cycle >= wdog:
return "PSLVERR" # the bridge watchdog gives up
return "HANG"
def clk_en_write(busy, old, new):
return old if busy else new # refuse to gate a block that is BUSY
assert apb_access(True, None) == "OK"
assert apb_access(False, None) == "HANG" # bug 10: idle DMA gated, no watchdog
assert apb_access(False, 256) == "PSLVERR" and abs(256 * T_SYS / 1000 - 2.667) < 0.001
assert clk_en_write(True, 0x3, 0x0) == 0x3 and clk_en_write(False, 0x3, 0x0) == 0x0
assert apb_access(clk_en_write(False, 0x3, 0x0) & 1 == 1, None) == "HANG" # the BUSY rule alone does not fix the idle case
print("PASS: 114 clamps audited; 112 flagged with a wake loop; wake 14.073 us; gated idle DMA hangs without the 256-cycle watchdog")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.