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 —
enagating,uio_oetie-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.
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 viauio_oe[i]. This project uses uio as input-only and tiesuio_oeto all-zero. enapin. Held high while the project is selected by the TT mux on the shuttle die. We honour it as a synchronous enable on top ofrst_n; withena=0the outputs are forced to 0 even with the design running internally.
Pin map
| TT pin | role | direction | notes |
|---|---|---|---|
ui_in[1:0] | mode | in | 00=count, 01=lfsr, 10=pwm, 11=zero |
ui_in[7:2] | unused | in | tied off |
uio_in[7:0] | threshold | in | PWM duty threshold |
uo_out[7:0] | out | out | mode-selected 8-bit observable |
uio_out | unused | out | always 0 |
uio_oe | unused | out | always 0 (uio input-only here) |
ena | enable | in | active-high; mutes uo_out when low |
clk | clock | in | system clock from TT shim |
rst_n | reset | in | active-low |
Architecture
RTL
// 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:
- Create a TT user-project repo from the template at tt-template.
- Drop the wrapper into the template’s
src/directory (rename the module to match your project handle). - Edit the template’s
info.yamlwith project metadata. - 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
- Project 02 → the design wrapped here.
- Tiny Tapeout — the actual programme.
- Project README