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
    • IP Collateral and Versioning
    • Wrappers and Adapters
    • Integration Lint and Connectivity Checks
      • Connectivity Tables as the Source of Truth
      • A Python Connectivity Checker
      • Port Widths, Tie-Offs and Unconnected Ports
    • Top-Level SDC and Constraint Merge
    • DFT Integration
    • Debug Infrastructure
    • Safety and Fault Architecture
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←Integration Lint and Connectivity Checks
  1. Topics
  2. SoC Design
  3. IP Integration and Handoff
  4. Integration Lint and Connectivity Checks
  5. A Python Connectivity Checker
On this page

    Part 9 of 14, lesson 8 of 21Part 9 of 14/IP Integration and Handoff/Lesson 8 of 21All levels

    A Python Connectivity Checker

    Widths, drivers, ties and sidebands checked mechanically: the working integrator's script.

    Why it matters

    The ID-width bug, the dropped protection sideband, the silently tied address bit: none of them needs a testbench, and all of them survive every testbench that does not happen to tickle them. On NanoSoC the DMA's 8-bit address port behind a 12-bit slot decoder is exactly this kind of net. A connectivity checker reads the top-level description as data and asserts the structural contracts directly, in milliseconds, on every commit. The script in this lesson is small enough to audit and strict enough to matter, and it proves each check by breaking the design on purpose and requiring the right complaint.

    The core idea

    The checker takes three inputs: the instance port list with widths and directions, the net list stating which ports each net joins, and the tie list stating every constant-driven input with its reason. Four checks do the work. Every net's ports must agree on width unless the pair is a reviewed slice. Every input must have exactly one driver (counted, not just present) unless the tie list claims it. Every tie-list entry must resolve to a real input port, so stale ties cannot accumulate, and a tied input must not also be driven. And every protection sideband leaving a master must arrive at a filter input, so a bridge that drops AxPROT fails loudly. The script runs the four over a miniature NanoSoC top, which must come out clean, and then over four mutated copies, each with one planted bug that the checker must name.

    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
    import copy
    PORTS = {  # instance.port: (direction, width); NanoSoC teaching values
        "u_cpu.AWADDR": ("out", 32), "u_xbar.AWADDR_M0": ("in", 32),
        "u_apbdec.PADDR_S0": ("out", 12), "u_dma.paddr": ("in", 8),
        "u_dma.irq": ("out", 1), "u_intc.irq1": ("in", 1),
        "u_intc.irq6": ("in", 1), "u_intc.irq7": ("in", 1),
        "u_cpu.AWPROT": ("out", 3),        # AWPROT[2:0]; NanoSoC filters on bit 1 (0 = secure)
        "u_xbar.AWPROT_M0": ("in", 3),
    }
    NETS = {"n_awaddr": ["u_cpu.AWADDR", "u_xbar.AWADDR_M0"],
            "n_paddr": ["u_apbdec.PADDR_S0", "u_dma.paddr"],   # 12 vs 8: reviewed slice
            "n_irq1": ["u_dma.irq", "u_intc.irq1"],
            "n_prot": ["u_cpu.AWPROT", "u_xbar.AWPROT_M0"]}
    TIES = {"u_intc.irq6": "reserved input, tied 0",
            "u_intc.irq7": "reserved input, tied 0"}
    SLICE_OK = {frozenset(("u_apbdec.PADDR_S0", "u_dma.paddr"))}
    FILTER_INPUTS = {"u_xbar.AWPROT_M0"}
    
    def check(ports, nets, ties):
        bad = []
        for net, members in nets.items():
            if len({ports[p][1] for p in members}) > 1 and frozenset(members) not in SLICE_OK:
                bad.append("width: " + net)
        drivers = {}
        for members in nets.values():
            outs = [p for p in members if ports[p][0] == "out"]
            for p in members:
                if ports[p][0] == "in":
                    drivers[p] = len(outs)
        for p, (direction, _w) in ports.items():
            n = drivers.get(p, 0)
            if direction == "in" and p in ties and n:
                bad.append("tied and driven: " + p)
            elif direction == "in" and p not in ties and n == 0:
                bad.append("undriven: " + p)
            elif direction == "in" and n > 1:
                bad.append("multi-driver: " + p)
        for t in ties:
            if t not in ports or ports[t][0] != "in":
                bad.append("stale tie: " + t)
        for p, (direction, _w) in ports.items():
            if direction == "out" and p.endswith(".AWPROT"):
                lands = any(p in m and (set(m) & FILTER_INPUTS) for m in nets.values())
                if not lands:
                    bad.append("sideband: " + p)
        return bad
    
    assert check(PORTS, NETS, TIES) == []                     # clean top
    
    m = copy.deepcopy(PORTS); m["u_xbar.AWPROT_M0"] = ("in", 2)          # old 2-bit width
    assert "width: n_prot" in check(m, NETS, TIES)
    m = copy.deepcopy(PORTS); m["u_dbg.irq"] = ("out", 1)                # second driver
    n = copy.deepcopy(NETS); n["n_irq1"].append("u_dbg.irq")
    assert "multi-driver: u_intc.irq1" in check(m, n, TIES)
    t = dict(TIES); t["u_intc.irq9"] = "no such input"                    # stale tie
    assert "stale tie: u_intc.irq9" in check(PORTS, NETS, t)
    n = copy.deepcopy(NETS); del n["n_prot"]                             # bridge drops AxPROT
    assert "sideband: u_cpu.AWPROT" in check(PORTS, n, TIES)
    print("PASS: clean top is green; width, second driver, stale tie and dropped sideband are each named")

    What the review looks for

    • The checker green on the current top, with the reviewed-slice list carrying a name and a date per entry, and the four planted-bug cases still failing when the checker is changed.

    • The tie list complete and reasoned; an empty tie list is a claim the review must believe, not a default.

    • Protection sidebands traced end to end for every master, through every bridge, to a named filter, at their full 3-bit AxPROT width.

    Verify and debug

    Extend the script before extending the testbench: each new interface adds its rows to the port and net lists and reruns in milliseconds. When the checker and the RTL disagree, the RTL wins the argument about what is built and the table wins the argument about what was intended; reconcile by changing one deliberately, never by editing the checker to look away. A checker that stops failing has either won or been tamed, and the review tells the difference by keeping the planted-bug cases in the script and running them on every build.

    AXI ID collision or width mismatch: a slave with too-narrow ID or a bridge that drops ID bits mixes responses under load. The structural half is caught here, by width agreement on every ID-bearing net; the behavioural half needs multi-master random traffic, which the verification part of the course owns.

    If asked what your checker asserts, answer the four: width agreement, exactly one driver per input, tie-list freshness, and sideband delivery to a filter, and say that each has a planted bug proving it can fail. Then say what it cannot: ordering, timing and behaviour, which is why lint gates simulation instead of replacing it.

    Check yourself The checker flags the 12-bit to 8-bit paddr net as a width problem. Fix the checker or the wiring?

    Neither: record the reviewed slice. The wiring is the intended alias A1, so the correct action is adding the pair to the reviewed-slice list with the wrapper as justification, which keeps the check strict everywhere else. The slice is not a tie: the four dropped bits are not an input of any block, so they never belong in the tie list.

    Related topics

    • Connectivity Tables as the Source of Truth

    • Next: Port Widths, Tie-Offs and Unconnected Ports

    • CDC and RDC: IP and Macro Boundaries

    Previous

    179 / 289

    Next: Port Widths, Tie-Offs and Unconnected Ports

    Up next

    Up nextPort Widths, Tie-Offs and Unconnected Ports

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error