This guide shows you how to add silicon art — a decorative doodle, logo, or message — to your Tiny Tapeout project. When the chip is manufactured, your artwork appears as metal shapes on the die, visible under a microscope.
You do not need to understand the full ASIC flow to follow this guide. If you already have a working Tiny Tapeout repository, you can add art in a few steps.
At a high level, silicon art works like placing a pre-drawn sticker on your chip layout:
New to Tiny Tapeout? Start with the workshop guide to set up a repository and get a green GDS action before adding art.
Before you begin, you should have:
Silicon art is added as a macro — a fixed block of layout that gets placed on your tile.
| File | What it is |
|---|---|
| GDS | The actual geometry: rectangles and polygons on specific metal layers. This is what becomes visible silicon. |
| LEF | A simplified description of the macro’s size and boundaries. The place-and-route tools need this to know how much space your art occupies. |
Your macro GDS must also include a PR boundary rectangle on a dedicated layer (prBoundary). PR stands for place and route — this shape tells the tools the legal footprint of your macro (its width and height on the tile). It is not decorative art; the LEF SIZE should match this rectangle.
Both files must describe the same cell name (e.g. my_logo). That name must also match the Verilog module you declare later.
Coordinates are in micrometers (µm). Tile size depends on your shuttle PDK and how many tiles you bought — check tile_sizes.yaml in tt-support-tools for exact numbers. As a guide:
| Shuttle | 1×1 tile (µm) | 2×2 tile (µm) |
|---|---|---|
| SKY | 161 × 112 | 335 × 226 |
| IHP | 202 × 155 | 420 × 314 |
Keep your art smaller than the usable area inside your tile — margins and routing take some space.
Create a folder for your macro files in your repository root (next to src/, not inside it). The folder name is up to you — macros/, gds/, and lef/ are all used in Tiny Tapeout projects. This guide uses macros/:
your-repo/
├── macros/
│ ├── my_logo.gds
│ └── my_logo.lef
├── src/
│ ├── config.json
│ └── project.v
└── ...
If you use different folder names, update the paths in config.json accordingly (see Step 2).
The cell name inside the GDS file must match the macro name in config.json, the name in the LEF file, and the Verilog module name. If they differ, the build will fail with confusing errors. In this guide we use my_logo everywhere.
Open src/config.json. This file already contains many settings for the ASIC build — you will add the entries below to the existing JSON object (don’t replace the whole file).
Add a MACROS section that points to your files and says where to place them:
"MACROS": {
"my_logo": {
"instances": {
"logo": {
"location": [30, 10],
"orientation": "N"
}
},
"gds": ["dir::../macros/my_logo.gds"],
"lef": ["dir::../macros/my_logo.lef"]
}
}
What each part means:
[30, 10] and adjust after viewing the GDS previewN, S, E, W, FN, FS, FE, or FWdir:: prefix tells the tools to look relative to the config file locationSilicon art is decorative — it has no inputs, outputs, or power connections. Without the settings below, the build will report errors about disconnected modules and pins:
"IGNORE_DISCONNECTED_MODULES": [
"my_logo", "logo"
],
"ERROR_ON_DISCONNECTED_PINS": false
Add both the macro name (my_logo) and the instance name (logo) to IGNORE_DISCONNECTED_MODULES.
If your tile contains only silicon art and little or no functional logic, you may also need:
"ERROR_ON_PDN_VIOLATIONS": false,
"RUN_PDN": false,
"RUN_LVS": false
These settings are used in working art projects on weaving01 (IHP) and crossstitching01 (SKY), alongside the macro and connectivity settings above.
Open src/project.v. You need two additions.
1. Declare a blackbox module for your art (place this before your main tt_um_* module):
(* blackbox *) (* keep *)
module my_logo ();
endmodule
The blackbox attribute tells the tools “this module has no internal logic — the real geometry comes from the GDS file.” The keep attribute prevents the optimizer from removing it.
2. Instantiate the macro inside your top-level tt_um_* module (not outside it):
module tt_um_example (
// ... ports ...
);
// your existing logic here
(* keep *)
my_logo logo();
endmodule
Here my_logo is the module name and logo is the instance name (matching what you used in config.json). If you also have functional logic on the tile, your project.v will contain both your normal design and the my_logo logo(); line.
Commit and push your changes. The GDS GitHub Action will run automatically (or trigger it manually from the Actions tab).
When it finishes:
A green GDS action means your art integrated successfully. You can now submit your project as usual via app.tinytapeout.com.
Depending on your layout and macro position, power distribution lines may overlap your artwork. The Verilog templates ship with "FP_PDN_VPITCH": 38.87 — leave that at the default unless you have a specific reason to change it.
If power stripes overlap your art, try adjusting the horizontal offset first:
FP_PDN_VPITCH — spacing between vertical power stripes (µm). The template default is 38.87 for current IHP and SKY shuttles.FP_PDN_VOFFSET — horizontal offset of the first power stripe (µm). Tweak this in small steps and re-run the GDS action until the power grid clears your art in the preview.Your art will be covered by the top metal layer. This layer is reserved for Tiny Tapeout power distribution. Any gaps left are covered by metal fill. Fill shapes are added across the chip to keep metal density uniform during manufacturing.
On Tiny Tapeout 6 (SKY130), a decapped die looks like this — the vertical stripes are top-level power rails and fill, with individual project tiles visible underneath only as faint variations:
![]()
That makes it hard to see your artwork (or anyone else’s logic) with the naked eye or a basic microscope. Tiny Tapeout has a partnership with Texplained: they remove the top metal layer and capture high-resolution microscope images of the die underneath. Here is the same TT06 chip after delayering — the individual project blocks and routing are clearly visible:
![]()
If you look closely, you will find a skull silicon art hidden among the tiles. Here is a close-up of that design after delayering:
![]()
You can explore the full TT06 imaging data in the interactive chip viewer — zoom and pan across the entire shuttle to find your tile and any silicon art on it.
If you don’t have art prepared in a layout editor, you can generate GDS files with Python. The gdstk library is a good choice.
Two complete working generators are available as reference:
The walkthrough below distils the important parts of each.
pip install gdstk
This script follows the same structure as the weaving01 generator: art on TopMetal1, a PR boundary, then GDS + LEF output.
import gdstk
CELL_NAME = "my_logo"
# IHP SG13G2 layers (see table below)
ART_LAYER = 126 # TopMetal1
BOUNDARY_LAYER, BOUNDARY_DATATYPE = 189, 4 # prBoundary
def write_lef(filename, cell_name, bounds):
"""Write a minimal LEF file. bounds = (xmin, ymin, xmax, ymax)."""
width = bounds[2] - bounds[0]
height = bounds[3] - bounds[1]
with open(filename, "w") as f:
f.write(f"# LEF file generated for {cell_name}\n")
f.write("VERSION 5.8 ;\n")
f.write("NAMESCASESENSITIVE ON ;\n")
f.write("DIVIDERCHAR \"/\" ;\n")
f.write("BUSBITCHARS \"[]\" ;\n")
f.write("UNITS\n")
f.write(" DATABASE MICRONS 1000 ;\n")
f.write("END UNITS\n\n")
f.write(f"MACRO {cell_name}\n")
f.write(" CLASS BLOCK ;\n")
f.write(f" FOREIGN {cell_name} 0 0 ;\n")
f.write(f" SIZE {width:.3f} BY {height:.3f} ;\n")
f.write(" SYMMETRY X Y ;\n")
f.write(f"END {cell_name}\n")
lib = gdstk.Library()
cell = lib.new_cell(CELL_NAME)
cell_width, cell_height = 120, 80
# PR boundary — required for macro placement
cell.add(gdstk.rectangle(
(0, 0), (cell_width, cell_height),
layer=BOUNDARY_LAYER, datatype=BOUNDARY_DATATYPE,
))
# Simple art: one TopMetal1 rectangle
cell.add(gdstk.rectangle((10, 10), (50, 30), layer=ART_LAYER))
cell.write_svg("../macros/my_logo.svg")
lib.write_gds("../macros/my_logo.gds")
bounds = (0, 0, cell_width, cell_height)
write_lef("../macros/my_logo.lef", CELL_NAME, bounds)
Run the script from a macro_generation/ folder (or adjust the output paths), then continue from Step 2.
This script follows the same structure as the crossstitching01 generator: art on met4, a PR boundary, then GDS + LEF output.
import gdstk
CELL_NAME = "my_logo"
# SKY130 layers (see table below)
ART_LAYER, ART_DATATYPE = 71, 20 # met4.drawing
BOUNDARY_LAYER, BOUNDARY_DATATYPE = 235, 4 # prBoundary
def write_lef(filename, cell_name, bounds):
"""Write a minimal LEF file. bounds = (xmin, ymin, xmax, ymax)."""
width = bounds[2] - bounds[0]
height = bounds[3] - bounds[1]
with open(filename, "w") as f:
f.write(f"# LEF file generated for {cell_name}\n")
f.write("VERSION 5.8 ;\n")
f.write("NAMESCASESENSITIVE ON ;\n")
f.write("DIVIDERCHAR \"/\" ;\n")
f.write("BUSBITCHARS \"[]\" ;\n")
f.write("UNITS\n")
f.write(" DATABASE MICRONS 1000 ;\n")
f.write("END UNITS\n\n")
f.write(f"MACRO {cell_name}\n")
f.write(" CLASS BLOCK ;\n")
f.write(f" FOREIGN {cell_name} 0 0 ;\n")
f.write(f" SIZE {width:.3f} BY {height:.3f} ;\n")
f.write(" SYMMETRY X Y ;\n")
f.write(f"END {cell_name}\n")
lib = gdstk.Library()
cell = lib.new_cell(CELL_NAME)
cell_width, cell_height = 80, 60
# PR boundary — required for macro placement
cell.add(gdstk.rectangle(
(0, 0), (cell_width, cell_height),
layer=BOUNDARY_LAYER, datatype=BOUNDARY_DATATYPE,
))
# Simple art: one met4 rectangle
cell.add(gdstk.rectangle(
(10, 10), (50, 30),
layer=ART_LAYER, datatype=ART_DATATYPE,
))
cell.write_svg("../macros/my_logo.svg")
lib.write_gds("../macros/my_logo.gds")
bounds = (0, 0, cell_width, cell_height)
write_lef("../macros/my_logo.lef", CELL_NAME, bounds)
Run the script from a macro_generation/ folder (or adjust the output paths), then continue from Step 2.
Open the SVG preview to check your art before running the full GDS build. It’s much faster to iterate on shapes in Python than to wait for the GitHub Action each time.
Avoid ellipse, text, and other helpers that produce curved or arbitrary geometry — they may look fine in the SVG preview but fail DRC.
Large IHP macros may fail density checks without fill shapes underneath the art. One example for a fill pattern can be found in the weaving01 generator. It adds a grid of active and poly rectangles for this purpose — see the layer numbers in the table below. Small art macros like the minimal example above may not need fill at all.
Each shuttle uses specific GDS layer and datatype numbers. Using the wrong values means your art won’t appear on the expected metal layer, or the precheck may reject the file.
The values below match the working generators for each PDK:
| Purpose | Layer | Datatype | Notes |
|---|---|---|---|
| Art (TopMetal1) | 126 | 0 | Minimum width/spacing ~1.64 µm |
| PR boundary | 189 | 4 | Place-and-route footprint; required in the macro GDS |
| Active filler | 1 | 22 | For density on larger macros |
| Poly rect filler | 5 | 22 | Paired with active filler |
| Purpose | Layer | Datatype | Notes |
|---|---|---|---|
| Art (met4) | 71 | 20 | Highest metal in the standard TT digital flow |
| PR boundary | 235 | 4 | Place-and-route footprint; required in the macro GDS |
| Problem | Things to check |
|---|---|
| GDS action fails with “macro not found” | Cell name in GDS, LEF, config.json, and project.v must all match exactly |
| Art doesn’t appear in preview | Wrong layer/datatype for your shuttle; open the GDS in a viewer like KLayout to confirm shapes exist |
| Disconnected pin / module errors | Add macro and instance names to IGNORE_DISCONNECTED_MODULES; set "ERROR_ON_DISCONNECTED_PINS": false |
| Art overlaps power stripes | Adjust FP_PDN_VOFFSET and/or move the macro location |
| IHP DRC / density failures on large art | Add the fill pattern under your macro |
| SKY DRC failures on met4 art | Check minimum width/spacing rules for met4 |
If you’re stuck, ask in the Tiny Tapeout Discord — many participants have added silicon art before and can help debug placement issues.