Implementing an IO pad ring#

Every build in the other tutorials stops at a rectangle of standard cells. A part you can put on a board needs more than that: it needs pads to bond to, supplies that reach them, and a ring of cells around the edge tying the two together.

This tutorial builds one. The design is examples/padring – a RISC-V processor with an SRAM inside a complete sky130 pad ring – and it runs to GDSII:

cd examples/padring
./padring.py

Note

This needs a local toolchain (Yosys, OpenROAD, OpenSTA and KLayout) and takes around ten minutes. The pad ring is generated by lambdalib, which installs alongside lambdapdk, so there is nothing extra to fetch.

What a ring is made of#

Four things have to line up, and they live in different places:

Piece

Where it lives

The pad cells

RTL. A dependency on lambdalib’s generator, plus a CELLMAP saying which cell sits where on each side.

The interface

RTL. A top level whose ports are the pads.

Placement

A TCL script, attached to the floorplanning task.

Power

Two more TCL scripts: the grid, and the global connections that tie the core’s supplies to the ring’s.

The interface#

The top level is the module that becomes the die, and its ports are the pads:

The die’s pins#
module picorv32_top (
    // Supplies. Core and IO are separate domains.
    inout vdd,
    inout vss,
    inout vddio,
    inout vssio,

    // Clock and reset in.
    input clk,
    input resetn,

    // Processor status out.
    output trap,

    // Memory bus, brought out so the processor can be watched on a bench.
    output        mem_valid,
    output        mem_la_read,
    output        mem_la_write,
    output [ 3:0] mem_la_wstrb,
    output [29:0] mem_addr
);

Two conventions are worth copying. The ports are named for what they carry, not for where they sit in the ring – nobody debugging a board wants to work out which bit of no_pad is the clock. And each one declares the direction it is really used in: the cells in the ring are all bidirectional, but a pin on the finished part is not, and only the supplies are genuinely inout.

Underneath, the logic never touches a pad cell. It drives four buses per side – dout out, din back in, oen and ie for direction, and a technology-specific configuration bus – which is what lets the same processor be wrapped in another technology’s pads without editing it.

Describing the ring#

CELLMAP lists one side, cell by cell, in the order they abut. Each entry is 80 bits packing the cell type, the pin it carries, and its power section:

Four supplies then ten signal pads, per side#
    localparam [80*NCELLS-1:0] CELLMAP = {
        {16'h0000, 16'h0000, LA_VSS, 16'h0000, PIN_NONE},
        {16'h0000, 16'h0000, LA_VDD, 16'h0000, PIN_NONE},
        {16'h0000, 16'h0000, LA_VDDIO, 16'h0000, PIN_NONE},
        {16'h0000, 16'h0000, LA_VSSIO, 16'h0000, PIN_NONE},
        {16'h0000, 16'h0000, LA_BIDIR, 16'h0000, 16'd0},
        {16'h0000, 16'h0000, LA_BIDIR, 16'h0000, 16'd1},
        {16'h0000, 16'h0000, LA_BIDIR, 16'h0000, 16'd2},
        {16'h0000, 16'h0000, LA_BIDIR, 16'h0000, 16'd3},
        {16'h0000, 16'h0000, LA_BIDIR, 16'h0000, 16'd4},
        {16'h0000, 16'h0000, LA_BIDIR, 16'h0000, 16'd5},
        {16'h0000, 16'h0000, LA_BIDIR, 16'h0000, 16'd6},
        {16'h0000, 16'h0000, LA_BIDIR, 16'h0000, 16'd7},
        {16'h0000, 16'h0000, LA_BIDIR, 16'h0000, 16'd8},
        {16'h0000, 16'h0000, LA_BIDIR, 16'h0000, 16'd9}
    };

All four sides reuse this map here. A real part rarely has such tidy symmetry – different sides carry different pin counts, and each gets its own map.

Placing it#

The RTL settled which cells exist. A TCL script settles where they go, and the order of operations matters because each step depends on the last: make the IO rows, place the pads into them, place the corners, fill the gaps, connect the ring by abutment, then add the bond pads.

Pads go into rows in CELLMAP order#
# North and south run left to right; east and west are built bottom to top,
# so their declaration order is reversed relative to the row.
place_pads -row IO_NORTH {*}[padring_side_insts north]
place_pads -row IO_SOUTH {*}[padring_side_insts south]
place_pads -row IO_WEST {*}[lreverse [padring_side_insts west]]
place_pads -row IO_EAST {*}[lreverse [padring_side_insts east]]

connect_by_abutment is the step people miss. The supply rails and the configuration signals pass between neighbouring cells through touching pins rather than through routed wire, so it has to come after the fill has closed the gaps – otherwise the ring is a ring of islands.

See also

Every command in that script belongs to OpenROAD, not to SiliconCompiler, and each takes more arguments than this example uses. OpenROAD’s pad module documents make_io_sites, place_pads, place_corners, place_io_fill, connect_by_abutment and place_bondpad; its pdn module documents the power grid commands in the next section.

Wiring it to the flow#

None of those scripts do anything until a task is told to read them. These are task variables rather than schema keys, because each configures one step:

Attaching the physical filesets#
    InitFloorplanTask.find_task(project).add_openroad_padringfileset("padring.sky130")

    PowerGridTask.find_task(project).add_openroad_powergridfileset("padring", "pdn.sky130")

    for task in APRTask.find_task(project):
        task.add_openroad_globalconnectfileset("padring", "globalconns.sky130")

Note

The pad ring hook takes only a fileset name, while the power grid and global connect hooks also take the library that owns the fileset.

The power grid then needs one addition an ordinary design does not: the core’s rings are tied out to the supply pads with -connect_to_pads, so the core is fed through the ring rather than through pins on the die edge.

How big does the die have to be?#

The ring sets a floor: fourteen cells plus two corners need about 1520 µm a side. A die sized by that is pad limited – set by how many pins must fit around the edge rather than by the logic inside.

Here the memory pushes it further. sky130’s SRAM macro is 1041 µm wide at any depth and needs a placement channel around it, so with a 250 µm margin to clear the pads this die lands at 1900 µm.

What to look at#

Open the result with project.show(), or the GDS directly, and check the ring before anything else:

../../_images/padring_layout.png

The finished die. Ten signal pads and four supply pads on each side, corners turning the ring, IO fill closing the gaps between cells, and bond pads over the top. Inside, the SRAM macro is the wide block across the lower core and the processor is the dense region above it.#

  • Four populated sides. The floorplan log reports what it placed – Placed 14 pads in IO_NORTH and so on. A ring that is missing pads on one side usually means the selection in the placement script stopped matching the instance names the RTL generates.

  • Continuous supplies. The fill cells should leave no gaps, and the rails should run unbroken through the corners.

  • Bond pads sitting over their cells, offset to clear the passivation.

Warning

This example stops short of a tapeout in one respect worth naming: the supply pads carry ESD clamp pins (DRN_HVC, SRC_BDY_HVC) which are left unconnected, and the power grid step says so. Where those clamps belong depends on the chip’s ESD strategy and on foundry guidance, so the example does not guess. A real part has to resolve them.

Next#