No. 10 / project of 147 on the ladder

Tiny Tapeout port

introduces — TT pin frame, fixed module signature, ena gating, uio bidirectional

harden statelast run2026-04-29
cells125non-filler
slack7.15ns setup
area30000 (die) / 26290 (core)μm²
signoff
  • DRCPASS
  • LVSPASS
  • antennaPASS

Adapts project 02 (counter / PWM / LFSR) into the Tiny Tapeout user-project pin frame. The TT harness has a fixed module signature: 8 dedicated inputs, 8 dedicated outputs, 8 bidirectional pins, plus clk / rst_n / ena. This project’s wrapper module drops P02’s RTL into that frame.

Status: Hardened. Standalone harden at 50 MHz on a 200 × 150 µm die — 125 non-filler cells, 7.15 ns of setup slack, zero DRC/LVS/ antenna violations. The chip is essentially P02’s tiny datapath (counter / PWM / LFSR ≈ 50 cells of real logic) plus the wrapper overhead — ena gating, uio_oe tie-off, output muxing.

This is not the actual TT submission. TT runs its own CI-driven flow against fixed tile sizes and the shuttle’s chip- level pin-mux RTL. What lives here is the wrapper plus a sanity- check harden — confirm the pin shape synthesizes cleanly before pushing to a TT shuttle.

layout · sky130A x= μm y= μm
drag · scroll to zoom · double-click to fit · 1 1:1 · f fit 200 × 150 µm die · sky130A · 50 MHz · TT pin frame · met1+met2+met3 only
3d · sky130A · z×10
drag · scroll · right-drag pan · double-click recenter · R reset metal stack only · z exaggerated 10× · meshopt-compressed

What’s new vs. P02

P02’s logic is unchanged. The new pieces are entirely about the pinout:

  • Fixed module signature. TT’s shuttle RTL drops the wrapper into a pre-routed user slot. The signature can’t change.
  • Bidirectional pins. uio_* is a tristate triplet (uio_in, uio_out, uio_oe). Each bit can be input or output at synthesis time via uio_oe[i]. This project uses uio as input-only and ties uio_oe to all-zero.
  • ena pin. Held high while the project is selected by the TT mux on the shuttle die. We honour it as a synchronous enable on top of rst_n; with ena=0 the outputs are forced to 0 even with the design running internally.

Pin map

TT pinroledirectionnotes
ui_in[1:0]modein00=count, 01=lfsr, 10=pwm, 11=zero
ui_in[7:2]unusedintied off
uio_in[7:0]thresholdinPWM duty threshold
uo_out[7:0]outoutmode-selected 8-bit observable
uio_outunusedoutalways 0
uio_oeunusedoutalways 0 (uio input-only here)
enaenableinactive-high; mutes uo_out when low
clkclockinsystem clock from TT shim
rst_nresetinactive-low

Architecture

ui_in / uio_in / clk / rst_n / ena mode / threshold / clk / rst_n out / count_o / lfsr_o / pwm_o / tick uo_out TT shuttlepin frame tt_um_librelane_p02wrapper p02_top(verbatim P02) ena gate
The wrapper is a thin shim. ena gates uo_out (so the TT mux can park us by holding ena=0); the inner p02_top is identical to project 02.

RTL

projects/10_tiny_tapeout/src/top.sv system-verilog
// Project 10: Tiny Tapeout port.
//
// This is a thin wrapper that adapts project 02 (counter + PWM + LFSR)
// into the Tiny Tapeout user-project pin frame. The TT harness has a
// fixed module signature with 8 dedicated inputs, 8 dedicated outputs,
// 8 bidirectional pins, plus clk / rst_n / ena. Whatever wrapper we
// write has to match exactly; the TT shuttle's chip-level RTL drops it
// into a pre-routed slot.
//
// Pin map (this design's choices):
//
//   ui_in[1:0]   mode   — selects what drives `out` (00=count, 01=lfsr, 10=pwm, 11=zero)
//   ui_in[7:2]   unused
//   uio_in[7:0]  threshold — PWM duty threshold
//   uo_out[7:0]  out       — mode-selected 8-bit observable
//   uio_oe       all 0     — uio is input-only here
//   uio_out      all 0     — unused
//   ena          active-high enable from TT shim
//   clk          system clock from TT shim
//   rst_n        active-low reset from TT shim (P02 uses the same convention)
//
// What this teaches that earlier projects didn't:
//   - **The TT harness module signature** is fixed and non-negotiable.
//     Every TT user-project module must be `tt_um_*` with these exact
//     ports. The shuttle's chip-level RTL relies on it.
//   - **Bidirectional pins.** `uio_*` is a tristate triplet:
//     `uio_oe[i]=1` makes that bit an output (driving `uio_out[i]`);
//     `uio_oe[i]=0` makes it an input (sampling `uio_in[i]`). Most
//     submissions pick a direction per bit at synthesis-time.
//   - **The `ena` pin** is held high while the project is selected by
//     the TT mux on the shuttle die. We honour it as a synchronous
//     enable on top of `rst_n`.
//
// Note this project does NOT do the actual TT submission. The TT flow
// has its own GitHub-Actions-driven harden against TT's standard tile
// sizes (1×2, 2×2, etc.). What lives in this repo is the wrapper +
// a librelane config that hardens it standalone so we can confirm the
// shape is right before pushing to a TT shuttle.

`default_nettype none

module tt_um_librelane_p02 (
    input  wire [7:0] ui_in,
    output wire [7:0] uo_out,
    input  wire [7:0] uio_in,
    output wire [7:0] uio_out,
    output wire [7:0] uio_oe,
    input  wire       ena,
    input  wire       clk,
    input  wire       rst_n
);

  // Map the TT pins onto P02's `top` signature.
  //
  // P02 expects:
  //   input  [1:0]  mode
  //   input  [7:0]  threshold
  //   output [7:0]  out
  //   output [7:0]  count_o
  //   output [7:0]  lfsr_o
  //   output        pwm_o
  //   output        tick
  //
  // We expose `out` on uo_out. The other observable signals (count_o,
  // lfsr_o, pwm_o, tick) are dropped — TT only gives us 8 outputs.
  // `mode` cycles through them so a host-side test sequence can sample
  // each.

  // Gate everything on `ena`. Holding ena=0 lets the TT shim park us
  // (the design still draws power for the regs but its outputs are
  // tied to 0).
  wire effective_rst_n = rst_n & ena;

  wire [7:0] out;
  wire [7:0] count_o;
  wire [7:0] lfsr_o;
  wire       pwm_o;
  wire       tick;

  p02_top u_inner (
    .clk        (clk),
    .rst_n      (effective_rst_n),
    .mode       (ui_in[1:0]),
    .threshold  (uio_in),
    .out        (out),
    .count_o    (count_o),
    .lfsr_o     (lfsr_o),
    .pwm_o      (pwm_o),
    .tick       (tick)
  );

  assign uo_out  = ena ? out : 8'h00;
  assign uio_out = 8'h00;             // never drive any uio output
  assign uio_oe  = 8'h00;             // all uio bits are inputs

  // Tieoffs for unused signals — keep verilator quiet.
  wire _unused = &{1'b0, ui_in[7:2], count_o, lfsr_o, pwm_o, tick};

endmodule


// =====================================================================
// p02_top — verbatim copy of project 02's `top` module, renamed so
// it doesn't collide with TT's wrapper or any other project on the
// chip. (TT shuttles can have hundreds of `top` modules; flat names
// are mandatory.)
//
// Functionality is unchanged from project 02. See
// projects/02_counter_pwm_lfsr/src/top.sv for the original commentary.
// =====================================================================
module p02_top (
    input  logic        clk,
    input  logic        rst_n,
    input  logic [1:0]  mode,
    input  logic [7:0]  threshold,
    output logic [7:0]  out,
    output logic [7:0]  count_o,
    output logic [7:0]  lfsr_o,
    output logic        pwm_o,
    output logic        tick
);

  // ---- counter ----
  logic [7:0] count;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) count <= 8'h00;
    else        count <= count + 8'h01;
  end

  // ---- LFSR ----
  logic [7:0] lfsr;
  logic       lfsr_fb;
  assign lfsr_fb = lfsr[7] ^ lfsr[5] ^ lfsr[4] ^ lfsr[3];
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) lfsr <= 8'hFF;
    else        lfsr <= {lfsr[6:0], lfsr_fb};
  end

  // ---- PWM ----
  assign pwm_o = (count < threshold);

  // ---- tick ----
  assign tick = (count == 8'hFF);

  // ---- output mux ----
  always_comb begin
    unique case (mode)
      2'b00:   out = count;
      2'b01:   out = lfsr;
      2'b10:   out = {8{pwm_o}};
      2'b11:   out = 8'h00;
      default: out = 8'h00;
    endcase
  end

  assign count_o = count;
  assign lfsr_o  = lfsr;

endmodule

`default_nettype wire

How to actually submit

The wrapper here is the user-logic half of a TT submission. The other half — the chip-level pin mux, the shuttle’s GDS template, the CI flow — lives in TT’s own infrastructure. The submission process is:

  1. Create a TT user-project repo from the template at tt-template.
  2. Drop the wrapper into the template’s src/ directory (rename the module to match your project handle).
  3. Edit the template’s info.yaml with project metadata.
  4. Push to GitHub; TT’s CI runs the official harden against the TT shuttle’s chip-level config and uploads the GDS to the shuttle’s own collection.

What just happened?

We took a small hardened design and adapted it to a fixed pin frame. The lesson is that real-world tape-outs always come with a harness: a TT shuttle, a Caravel chip, a custom dev-board socket — something above the user logic that fixes the I/O. The user logic is the interesting part; the wrapper is the receipt that lets it ride to the fab on someone else’s mask set.

The ladder ends here. P01–P09 took us from a plain combinational ALU to a multi-cycle RV32I-min CPU; P10 shows how any of those designs gets onto an actual silicon wafer through the smallest practical fab path.

See also