A classic bring-up failure, shown here as a scenario: a crystal wired to ordinary digital pads with fast buffers sees the wrong loading, the oscillator runs badly or not at all, and the PLL never locks. NanoSoC avoids it by giving the crystal two dedicated analog pads. Pad type is not a detail the library team sorts out later; the wrong family on a pin breaks the function, and the integrator is the one who assigns the family.
Pads come in three families. Digital pads carry logic signals in and out: the sixteen GPIO pads and the reset pin on NanoSoC. Each has a driver on the way out, a receiver on the way in (with a Schmitt trigger where the signal is slow or noisy), a weak pull and protection devices that belong to the discharge strategy of the protection topic. Analog pads carry signals that must never see a digital buffer: the two crystal pads, which connect the oscillator to the board crystal with controlled loading. They have no digital driver or receiver; the oscillator amplifier belongs to the analog pad cell or the PLL wrapper, depending on the library. Supply pads carry power and ground: the core and I/O supply pairs that feed the whole chip, with no logic function at all, sized by current and by switching noise rather than by signal count.
Every NanoSoC signal is counted on exactly one pad. The UART and the SPI do not have pads of their own: UART TX and RX use P0 and P1, and the SPI signals use P2 to P5, all of them pads that are plain GPIO until the peripheral's enable bit is set. That is why the inventory below has 19 signal pads rather than one pad per signal name. NanoSoC v1 also has no JTAG, test-mode or boot-mode pin; the chip boots from the SPI flash alone. Every pad count in this section is a NanoSoC teaching value, defined once in the NanoSoC pad plan and used unchanged by every later lesson.
Family | Pads | Count | Notes |
|---|---|---|---|
Digital: GPIO | P0 to P15 | 16 | Bidirectional, weak pull, I/O supply. P0 to P5 are shared with the UART and the SPI. |
Digital: reset | RESET_N | 1 | Input with Schmitt trigger and weak pull-up. |
Analog | XTAL_IN, XTAL_OUT | 2 | No digital driver; loading set with the oscillator; referenced to the core supply. |
Supply: core | VDD_CORE x4, VSS_CORE x4 | 8 | Sized by current in the pad-count topic. One 0.9 V rail: there is no separate always-on supply pad, because the header switch is on the die. |
Supply: I/O | VDD_IO x2, VSS_IO x2 | 4 | Separate from the core pads so pad switching current stays off the core rail. Sized by switching noise, not current. |
Total | 31 | 19 signal (16 + 1 + 2) plus 12 supply. |
Two separations do quiet work. Core and I/O supplies are distinct pad pairs on distinct package nets, because the pad drivers dump their switching current into the I/O supply and the core logic must not see it. The two grounds meet on the die through a diode pair in the protection ring, a point the protection topic returns to. And the analog pads sit between supply pads with no fast switcher beside them, because a crystal oscillator disturbed by a neighbouring GPIO edge is a PLL that never locks.
The script below takes the pad ring itself — 31 names in clockwise order — derives the family totals from the names, and checks them against the plan: 16 GPIO, one reset, two crystal, four core pairs, two I/O pairs. It then runs the same check on a ring with one supply pad removed and on a ring with a name repeated, and requires both to be rejected. Every later lesson in the section uses the same ring.
RING = ["VDD_CORE0", "VSS_CORE0", "XTAL_IN", "XTAL_OUT", "VSS_CORE1", "VDD_CORE1", "RESET_N", "GPIO0",
"GPIO1", "GPIO2", "GPIO3", "VDD_IO0", "VSS_IO0", "GPIO4", "GPIO5", "GPIO6",
"GPIO7", "VSS_CORE2", "VDD_CORE2", "GPIO8", "GPIO9", "GPIO10", "VDD_IO1", "VSS_IO1",
"GPIO11", "GPIO12", "GPIO13", "GPIO14", "GPIO15", "VSS_CORE3", "VDD_CORE3"]
def rollup(ring):
assert len(ring) == len(set(ring)), "a pad name appears twice"
n = lambda prefix: sum(1 for p in ring if p.startswith(prefix))
return {"gpio": n("GPIO"), "reset": n("RESET_N"), "xtal": n("XTAL"),
"vdd_core": n("VDD_CORE"), "vss_core": n("VSS_CORE"),
"vdd_io": n("VDD_IO"), "vss_io": n("VSS_IO")}
def check(ring):
c = rollup(ring)
signal = c["gpio"] + c["reset"] + c["xtal"]
supply = c["vdd_core"] + c["vss_core"] + c["vdd_io"] + c["vss_io"]
assert c["gpio"] == 16 and c["reset"] == 1 and c["xtal"] == 2, c
assert (c["vdd_core"], c["vss_core"], c["vdd_io"], c["vss_io"]) == (4, 4, 2, 2), c
assert (signal, supply, signal + supply) == (19, 12, 31), (signal, supply)
return signal, supply
assert check(RING) == (19, 12)
dropped = [p for p in RING if p != "VDD_IO1"]
for bad in (dropped, RING + ["GPIO3"]):
try:
check(bad)
raise SystemExit("a broken ring was accepted")
except AssertionError:
pass
print("PASS: 19 signal + 12 supply = 31 pads; a dropped supply pad and a repeated name are rejected")Every external signal has a row with a family, and no row says a decision is pending.
Analog pads are identified with their loading assumptions, not left as ordinary digital pads.
Core and I/O supplies are separate nets from pad to board, with no shared ball.
No signal is counted twice: a shared function (UART on a GPIO pad) appears in the pad's row, not as a second pad.
Diff the inventory against the top-level netlist port list: every port that leaves the die must appear exactly once, and every inventory row must appear in the netlist. A port with no row is an unowned pin; a row with no port is a ball connected to nothing. On the bench, the crystal symptom above is diagnosed by scoping the oscillator waveform: a clipped or double-frequency trace points at wrong pad loading before it points at the PLL.
Because the oscillator is an analog circuit whose loading sets its behaviour: an analog pad presents the specified loading and no digital structures, while a disabled digital driver still leaves its receiver, protection and parasitics on the pin.