Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • The NanoSoC Tour
  • Reading Path
  • SoC Glossary10
  • SoC Metrics Reference
  • SoC Cheatsheet
  • What SoC Integration Is12
  • Architecture and Budgets21
  • Memory Maps and Address Decode26
  • Interconnect and Bus Fabrics26
  • Clock and Reset Architecture24
  • Power Architecture21
  • Interrupts, DMA and Peripherals15
    • Interrupt Controller Integration
    • Interrupt Routing and Latency
    • DMA Integration
    • Peripheral Subsystem
    • Pad Muxing and GPIO
      • Pin-Mux Tables and Function Select
      • Reset States, Pulls and GPIO Direction
      • Package, Ball-Out and Simultaneous Switching
  • Memories, Boot and Security21
  • IP Integration and Handoff21
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←Pad Muxing and GPIO
  1. Topics
  2. SoC Design
  3. Interrupts, DMA and Peripherals
  4. Pad Muxing and GPIO
  5. Pin-Mux Tables and Function Select
On this page

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

    Pin-Mux Tables and Function Select

    One pad table: which functions each pad offers, who owns the pad when, the EN-follows rule and the checker that proves no conflict.

    Why it matters

    This lesson has one job: teach the pad table as the single source of truth for what each pad does. NanoSoC's sixteen GPIO pads are shared: P0 and P1 carry the UART, P2 to P5 carry the SPI flash bus, and P6 to P15 are GPIO only. The two peripherals never compete for a pad, but the GPIO block does compete with each of them, and a table that is wrong in one place puts two drivers on one board net. This is the classic pin-mux conflict, caught by a checker run against the ball-out and fixed by a single pad database that generates the RTL mux, the software header and the board netlist.

    The core idea

    The table has one row per pad and, for each shared pad, one peripheral function. There is no pad-control register and no function-select field: NanoSoC has none in its register map. Instead the rule is one sentence. If the peripheral's CTRL.EN bit is 1, the pad belongs to the peripheral; otherwise it is a GPIO, with direction from GPIO.DIR, value from DATA_OUT and level read back in DATA_IN. At reset every EN is 0, so every pad is a GPIO input. The pulls are fixed in the pad cell, not programmable. Because the select is the enable bit software writes anyway, the mux needs no extra programming step, but it also means the select can change at any time the driver writes CTRL, which the next lesson turns into a discipline.

    Two classes of pad bypass the mux entirely. RESET_N and the two crystal pads are dedicated: a muxed reset is a boot risk, and a muxed crystal is an oscillator that never starts. The power and ground pads are not signals at all. Everything else is the table below. The UART and SPI signals are counted once, as pads: the 19 signal pads are 16 GPIO pads, the reset and the crystal pair, with no extra pad for any peripheral.

    Pad

    Reset function (EN = 0)

    Function when the peripheral is enabled

    Direction when enabled

    Owner bit

    P0

    GPIO0

    uart_tx

    output

    UART.CTRL.EN

    P1

    GPIO1

    uart_rx

    input

    UART.CTRL.EN

    P2

    GPIO2

    spi_sck

    output

    SPI.CTRL.EN

    P3

    GPIO3

    spi_mosi

    output

    SPI.CTRL.EN

    P4

    GPIO4

    spi_miso

    input

    SPI.CTRL.EN

    P5

    GPIO5

    spi_cs_n (the inverse of SPI.CTRL.CS)

    output

    SPI.CTRL.EN

    P6 to P15

    GPIO6 to GPIO15

    none: GPIO only

    per GPIO.DIR

    none

    NanoSoC has no JTAG, TEST_MODE or BOOT_MODE pins and no boot straps: boot is over SPI only, so there is no debug tap on any pad and no pad function other than the ones in the table. The shape of the table, one row per pad with reset function, enabled function and the owner bit, is the transferable part. The pad control logic sits in the always-on domain like the peripherals, so pad outputs keep their state in SLEEP.

    nano_pad_mux_cell.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // NanoSoC teaching values: the mux on one shared pad (P0 to P5).
    // en is the owning peripheral's CTRL.EN bit; there is no select register.
    // Outputs: the peripheral drives the pad when en = 1, the GPIO block otherwise.
    module nano_pad_mux_cell (
      input  logic en,          // UART.CTRL.EN or SPI.CTRL.EN
      input  logic per_is_out,  // 1 for uart_tx, spi_sck, spi_mosi, spi_cs_n; 0 for the inputs
      input  logic per_out,     // value the peripheral drives
      input  logic per_idle,    // idle level seen by a peripheral input while en = 0
      input  logic gpio_dir,    // GPIO.DIR bit, 1 = output
      input  logic gpio_out,    // GPIO.DATA_OUT bit
      input  logic pad_in,      // pad level after the pad cell
      output logic pad_out,
      output logic pad_oe,
      output logic gpio_in,     // GPIO.DATA_IN bit
      output logic per_in       // peripheral input (uart_rx, spi_miso), unsynchronised
    );
      assign pad_out = en ? per_out : gpio_out;
      assign pad_oe  = en ? per_is_out : gpio_dir;
      // The GPIO block still reads the pad level (teaching choice: DATA_IN is honest).
      assign gpio_in = pad_in;
      // A disabled peripheral sees its idle level, never a live pad.
      assign per_in  = en ? pad_in : per_idle;
    endmodule

    Worked example: the conflict checker

    The script below encodes what each pad offers and a candidate table, then checks the rules that catch every conflict: no pad carries two peripheral functions, every function the board needs (the UART pair and the four SPI signals) is placed exactly once, and every pad is given only a function it offers. It also applies the EN-follows rule so you can see the owner of every pad in three situations. Then it proves it is awake by injecting a double placement, a missing SPI signal and an unoffered function.

    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
    # What each pad offers beyond GPIO (None = GPIO only), and who owns it.
    OFFER = {"P0": "uart_tx", "P1": "uart_rx", "P2": "spi_sck",
             "P3": "spi_mosi", "P4": "spi_miso", "P5": "spi_cs_n"}
    OWNER = {"uart_tx": "UART", "uart_rx": "UART", "spi_sck": "SPI",
             "spi_mosi": "SPI", "spi_cs_n": "SPI", "spi_miso": "SPI"}
    REQUIRED = sorted(OWNER)
    PLAN = {f"P{i}": OFFER.get(f"P{i}") for i in range(16)}   # the NanoSoC table
    
    def check(plan):
        # plan maps a pad to a list of peripheral functions placed on it
        for f in REQUIRED:
            users = [p for p, fs in plan.items() if f in fs]
            assert len(users) == 1, f"{f} placed {len(users)} times"
        for p, fs in plan.items():
            assert len(fs) <= 1, f"{p} carries {len(fs)} functions"
            for f in fs:
                assert OFFER.get(p) == f, f"{p} does not offer {f}"
    
    def listed(plan):
        return {p: ([f] if f else []) for p, f in plan.items()}
    
    def owner_of(pad, en):
        # the EN-follows rule: no register, only the peripheral's enable bit
        f = OFFER.get(pad)
        return f if f and en[OWNER[f]] else "GPIO"
    
    check(listed(PLAN))
    assert len(PLAN) == 16 and sum(1 for f in PLAN.values() if f) == 6
    reset = {"UART": 0, "SPI": 0}
    assert all(owner_of(p, reset) == "GPIO" for p in PLAN)
    assert owner_of("P0", {"UART": 1, "SPI": 0}) == "uart_tx"
    assert owner_of("P2", {"UART": 1, "SPI": 0}) == "GPIO"      # SPI off: P2 stays a GPIO
    assert owner_of("P5", {"UART": 0, "SPI": 1}) == "spi_cs_n"
    assert owner_of("P9", {"UART": 1, "SPI": 1}) == "GPIO"      # P6 to P15 never change
    print("16 pads, 6 shared; every pad is GPIO at reset; the owner follows EN only")
    
    bad = listed(PLAN); bad["P6"] = ["uart_tx"]                    # a second place for uart_tx
    try:
        check(bad); raise SystemExit("checker missed the double placement")
    except AssertionError as e:
        assert str(e) == "uart_tx placed 2 times", str(e); print(f"caught: {e}")
    bad = listed(PLAN); bad["P4"] = []                             # spi_miso has no pad
    try:
        check(bad); raise SystemExit("checker missed the missing function")
    except AssertionError as e:
        assert str(e) == "spi_miso placed 0 times", str(e); print(f"caught: {e}")
    bad = listed(PLAN); bad["P3"] = ["spi_cs_n"]; bad["P5"] = []   # spi_cs_n on a pad that offers spi_mosi
    try:
        check(bad); raise SystemExit("checker missed the unoffered function")
    except AssertionError as e:
        assert "placed 0 times" in str(e) or "does not offer" in str(e); print(f"caught: {e}")
    print("PASS: NanoSoC table conflict-free with six functions placed once; three injected faults caught")

    What the review looks for

    • One pad database generates the RTL mux, the software header and the board netlist; the review quotes the database version.

    • No pad carries two peripheral functions, and a function that is absent from the table is a missing function, not a don't care: the checker output is attached.

    • The EN-follows rule is written once, in the programming guide, with the idle level of each shared pad; a design that adds a pad-control register has changed the register map and the review must say so.

    • A reader of the table can see that RESET_N and the crystal pads are dedicated, and that no pad is a JTAG, test or boot pin.

    Verify and debug

    Generate all three consumers from the table and diff them against the checked-in files: any hand edit shows up as a diff. On the bench, enable each peripheral in turn and walk its pads with a scope: a pad that moves under two drivers, or a peripheral signal that does not appear on its pad, is a table error, not a board error. If the UART works and the SPI is dead, check that SPI.CTRL.EN is really 1 and that the board wiring follows P2 to P5, not a different pad order.

    Pin-mux conflict or package ball-out error: two functions drive the same net, or the package pin map disagrees with the board, and the symptom lands on the wrong ball or as noise on the interface. Caught by a pad-mux checker run against the ball-out and the board netlist; fixed by a single pad database reviewed with the board team.

    If asked to fit many functions on few pins, start with the table shape, then the owner of each shared pad and how it changes, then the dedicated pads, then the checker. The reset-state column is the answer that shows seniority; offer it before being asked.

    Check yourself The board wires its console to P4 and P5 while NanoSoC puts the UART on P0 and P1. Which artefact is wrong?

    Neither block is wrong: the board netlist was built from the wrong pad table. NanoSoC has one pad plan, so the fix is the board netlist, regenerated from the pad database, not a patch to the RTL or a second pad plan; and a table generated from one source cannot disagree with the netlist.

    Related topics

    • Next: Reset States, Pulls and GPIO Direction

    • SPI and GPIO Integration

    • PD: IO Ring Architecture

    Previous

    148 / 289

    Next: Reset States, Pulls and GPIO Direction

    Up next

    Up nextReset States, Pulls and GPIO Direction

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Package, Ball-Out and Simultaneous Switching