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. Reset States, Pulls and GPIO Direction
On this page

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

    Reset States, Pulls and GPIO Direction

    What every pad does before software wakes up: input by default, pulls that match the idle level, the direction discipline and what happens while EN toggles.

    Why it matters

    This lesson has one job: define what the pads do in the seconds before the first configuration write, and what they do while a peripheral is being turned on. At reset release the chip has opinions about sixteen pads and the board has opinions about the same nets, and if both drive, the symptom is physical: contention current, a failed boot or a damaged driver. The classic pad-direction bug is a bidirectional pad driving at reset while the board device also drives, and the fix is a reset-state table with input by default and defined pulls, enforced before any functional test runs.

    The core idea

    Every GPIO pad resets to an input with its output driver off: GPIO.DIR resets to all zeros and DATA_OUT to zero, so even the first configured output starts by driving low. Every shared peripheral is disabled at reset, so every shared pad is a GPIO input. Each pad carries a weak fixed pull (illustratively 50 kilohm) chosen from the idle level of the function the pad takes when enabled. P0 uart_tx and P1 uart_rx pull up: UART lines idle high, so a pull-down on RX reads as a continuous break and a pull-down on TX shows the far end a break while the UART is disabled. P5 spi_cs_n pulls up, because chip select is active low and a pull-down would select the flash from power-up, so the flash would see the first clock edges in the middle of a command. P2 spi_sck pulls down because SPI mode 0 idles low, P3 spi_mosi pulls down as a quiet default, and P4 spi_miso pulls down so that a missing flash reads 0 and never floats. P6 to P15 pull down: quiet, and no interrupt because INT_EN resets to 0. RESET_N pulls up so that an open pin means run; the crystal pads have no pull. The rule the audit checks is one line: for every shared pad, the reset pull equals the idle level of the function the pad takes when enabled.

    Because the select is the enable bit, the transition matters, and it is the one thing the pull rule buys. At reset the pad sits at its pull level. When a driver writes EN = 1, the mux hands the pad to the peripheral in the same cycle, and the peripheral drives its idle level, which is the level the pull already held: the wire does not move and the far end sees nothing. This is why the pulls are chosen from the idle levels, and it dictates the order of the driver. The first instruction a driver executes on a shared pad must not be a GPIO write: DIR for P0 to P5 stays 0 and DATA_OUT stays 0 until the peripheral is finished with. Configure the peripheral first (baud divider, mode, interrupt enables) with EN still 0, and write EN = 1 last, so the pad changes owner exactly once and with no level change. For SPI, CS resets to 0, so spi_cs_n is 1 at the moment of EN, matching the pull-up; setting CS = 1 is the deliberate act that drops the pad.

    Glitches while EN toggles come in three forms. First, a GPIO driver left on the pad: if DIR bit 0 is 1 and DATA_OUT bit 0 is 0 when EN rises, P0 is driven low and then handed to a UART whose idle level is 1, so the far end sees a start edge. Second, clearing EN while the peripheral is active: the pad falls back to a GPIO input and the pull takes over, so a UART frame is cut short and then idles high, and spi_cs_n returns high through the pull-up and ends the transfer in whatever state the flash was in; stop the transfer first, then clear EN. Third, EN and the data written in the same access: for a peripheral that may start on EN, write the data after EN so the first bit is not lost. These are driver rules, and they exist because there is no pad-control register to hold the pad in a neutral state in between.

    Direction discipline after reset has three further rules. Configure direction before enabling an interrupt on the pad, or an output driving its own level self-triggers, as the GPIO lesson showed. Never leave a bidirectional pad driven from both sides: the board schematic and the pad table are reviewed together, pad by pad, with one owner signing that each net has exactly one driver in every mode. And remember that NanoSoC has no boot straps and no test-mode pad: no pad is sampled as configuration, so the pulls exist for the board, not for a boot decision.

    Worked example: the reset-state audit

    The script below encodes the reset-state table for all sixteen pads and audits the rules that prevent contention: every pad resets to an input, every pad has a defined pull, and the pull matches the idle level of the function the pad takes when enabled. It then models the EN rising edge, to show that a clean pad does not move and a pad with a leftover GPIO driver does, and proves it is awake by catching an output-at-reset, a pull-down on spi_cs_n, a pull-down on uart_tx and an EN glitch.

    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
    # Idle level of the function a shared pad takes when its peripheral is enabled.
    FUNC = {"P0": ("uart_tx", 1), "P1": ("uart_rx", 1), "P2": ("spi_sck", 0),
            "P3": ("spi_mosi", 0), "P4": ("spi_miso", None), "P5": ("spi_cs_n", 1)}
    PULL = {"P0": "up", "P1": "up", "P2": "down", "P3": "down", "P4": "down", "P5": "up"}
    
    def make_table():
        t = []
        for i in range(16):
            pad = f"P{i}"
            func, idle = FUNC.get(pad, ("GPIO", None))
            pull = PULL.get(pad, "down")
            t.append({"pad": pad, "func": func, "idle": idle, "reset_dir": "input", "pull": pull})
        return t
    
    LEVEL = {"up": 1, "down": 0}
    
    def audit(table):
        problems = []
        for p in table:
            if p["reset_dir"] != "input":
                problems.append(f"{p['pad']} drives at reset")
            if p["pull"] not in LEVEL:
                problems.append(f"{p['pad']} floating: no pull")
            elif p["idle"] is not None and LEVEL[p["pull"]] != p["idle"]:
                problems.append(f"{p['pad']} pull {p['pull']} fights idle level of {p['func']}")
        return problems
    
    def enable_edge(p, dir_bit=0, data_out=0):
        # level on an open pad just before EN rises, and just after the mux hands over
        before = data_out if dir_bit else LEVEL[p["pull"]]
        after = p["idle"] if p["idle"] is not None else before   # MISO is an input: no change
        return before, after
    
    assert audit(make_table()) == []
    t = make_table()
    pulls = {p["pad"]: p["pull"] for p in t}
    assert [q for q, v in pulls.items() if v == "up"] == ["P0", "P1", "P5"]
    assert all(v == "down" for q, v in pulls.items() if q not in ("P0", "P1", "P5"))
    for p in t:
        b, a = enable_edge(p)
        assert b == a, f"{p['pad']} moves when EN rises"
    print("16 pads input at reset; P0, P1, P5 pull up, the other 13 pull down; EN rising moves no pad")
    
    bad = make_table(); bad[3]["reset_dir"] = "output"
    assert audit(bad) == ["P3 drives at reset"]
    bad = make_table(); bad[5]["pull"] = "down"             # selects the flash at reset
    assert audit(bad) == ["P5 pull down fights idle level of spi_cs_n"]
    bad = make_table(); bad[0]["pull"] = "down"             # a break while UART.EN = 0
    assert audit(bad) == ["P0 pull down fights idle level of uart_tx"]
    b, a = enable_edge(t[0], dir_bit=1, data_out=0)          # driver left DIR bit 0 high
    assert (b, a) == (0, 1)                                  # a falling-then-rising start edge
    print("PASS: clean table passes; output-at-reset, spi_cs_n and uart_tx pull-downs and an EN glitch caught")

    What the review looks for

    • The reset-state table lists direction, the function the pad takes when enabled and a pull that matches that function's idle level for every pad; no cell may say to be decided.

    • The board schematic review signs that each net has exactly one driver in every mode, with the table as the reference.

    • The programming guide says that EN is written last and that DIR and DATA_OUT stay at their reset values on P0 to P5 while the peripheral owns the pad.

    • The pull on each pad is fixed in the pad cell: the review confirms no software step is expected to set it.

    Verify and debug

    On first silicon, probe every pad at reset release before loading software: inputs with pulls sit at their pull level, nothing drives. If a pad sits mid-rail, its pull is missing or fighting the board; if a pad drives, its direction default is wrong. Then enable each peripheral on a scope and watch the pad at the moment EN is written: a clean enable shows no edge at all. On NanoSoC, a flash that misbehaves from power-up points first at the spi_cs_n pull, and a flash that fails only when the driver sets EN points at DIR and DATA_OUT left over from a GPIO test. A chip that reboots at random after a quiet period points at the RESET_N pull-up.

    Pad direction, pull and drive defaults wrong: a bidirectional pad drives at reset while the board device also drives, causing contention current and boot failure. Caught by the pad-ring review and a pin-state table for reset; fixed by input by default with defined pulls, enabling the driver only when firmware configures it. On NanoSoC the commonest form is spi_cs_n copied from the row above with a pull-down, so the flash is selected from power-up; it passes in RTL simulation because there is no pad model.

    If asked what a pad does at reset, answer input, pulled to the idle level of the function it takes when enabled, driver off. Then say what happens when the peripheral is enabled: nothing on the wire, because the pull equals the idle level. The contention-current mechanism is the detail that convinces: two drivers, one net, ohms of resistance between supplies.

    Check yourself A driver tests P0 as a GPIO output driving 0, then enables the UART without clearing DIR bit 0. What does the far end see?

    P0 is driven low until EN rises, then the mux hands the pad to uart_tx, whose idle level is 1: the line rises, and the far end sees the low period as a break and the rise as the start of idle. The fix is in the driver, not the pad: clear DIR bit 0 and leave DATA_OUT at 0 before EN, so the pad sits at its pull level and does not move when EN rises.

    Related topics

    • Pin-Mux Tables and Function Select

    • Next: Package, Ball-Out and Simultaneous Switching

    • RTL: Clock, Reset, and Power Contract

    Previous

    149 / 289

    Next: Package, Ball-Out and Simultaneous Switching

    Up next

    Up nextPackage, Ball-Out and Simultaneous Switching

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error