Hardware accelerated flag checker 2

Extracting a netlist back out of a fabricatable SKY130 layout with no PDK installed.

2026.09.20 NNS CTF 2026 105 pts Misc
FLAG NNS{fl4g_ver1f1ed_1n_SKY130_IC}

Challenge

A follow-up to “Hardware accelerated flag checker 1”, now delivered not as an already-synthesized Verilog netlist but as physical VLSI layout files:

  • flag_checker.gds (a self-contained GDSII layout, ~700 KB)
  • flag_checker.mag (the same design in Magic VLSI’s native format)

Placed and routed for the SKY130A open-source PDK (sky130_fd_sc_hd standard cell library) using real fabricatable geometry (metal, vias, local interconnect) - this is what actually gets sent to a foundry, several abstraction levels below the RTL/gate netlist we got in challenge 1. The task is the same as before (“recover the flag it checks for”), but the netlist must first be extracted back out of the physical layout.

Why not Magic + the real PDK

The intended-looking path (open the layout in Magic VLSI with the sky130A PDK installed, run extract to get a SPICE/gate netlist) needs Magic and the multi-hundred-MB open_pdks sky130 install - neither available and installing a full VLSI toolchain wasn’t practical here. Used the pip- installable klayout Python bindings instead, which needed no external PDK files: the GDS is self-contained (55 cells: the top design plus a full copy of every standard-cell footprint’s geometry, since the flow that produced it flattened the library cells’ physical views into the output GDS).

Approach

  1. Netlist extraction from the layout only, no PDK. Sky130’s GDS layer map is public (li1.drawing=67/20, mcon=67/44, met1.drawing=68/20, via1=68/44, met2=69/20, … stepping by one layer number and datatype 44 per via level up to met5=72/20). Used KLayout’s LayoutToNetlist: made a Region per drawing/via layer, connect()’d same-layer regions and adjacent metal/via pairs, then extract_netlist(). This produces, for free, one Circuit per placed cell TYPE plus a top “flag_checker” circuit containing 855 Subcircuit instances (matching the .mag file’s 855 use <cell> <inst> lines) connected by 164 top-level nets.

  2. Recovering pin names. Sky130 GDS cells carry their pin names as text labels on the li1.label layer (67/5) at each pin’s local coordinate (e.g. sky130_fd_sc_hd__nand2_2 has “A”, “B”, “Y” labelled inside its own cell geometry). For every placed instance, read its cell type’s local pin-label positions once, then applied the instance’s placement transform (inst.trans) to get each pin’s absolute layout position, and called LayoutToNetlist.probe_net(li1_region, point) to look up which extracted net that exact point belongs to - sidestepping KLayout’s automatic (and here unhelpful, single-anonymous-pin) hierarchical pin inference entirely. The same probing, using the met2.label layer, recovered the 10 top-level I/O nets by name: character[0..6], clk, reset_n, found_flag (identical port names to challenge 1).

  3. Gate-level functional models. 854 instances break down into 5 dfxtp_2 D-flip-flops (the state register, exactly as in challenge 1’s 5-bit s register), ~27 clkbuf/clkdlybuf4s25 buffers (used here not just for clock distribution but also to buffer the character/reset_n inputs and the found_flag output - initially filtered these out as “clock tree only”, which silently broke the whole simulation since all 10 top-level ports actually terminate directly on one of these buffer cells), and ~122 real combinational standard cells across 40+ distinct types (a21oi, o31ai, and4b, o2111a, xnor2, …). Rather than hand-derive each cell’s boolean formula, fetched the official sky130_fd_sc_hd__<cell>.functional.v Verilog models straight from the skywater-pdk-libs-sky130_fd_sc_hd GitHub repo - each one is a tiny, exact gate-primitive netlist (and/or/nor/nand/xnor/not/buf) with real pin names, which a small Python interpreter can execute directly. No guessing of the a21oi/o21ba/-style naming convention was needed.

  4. Simulating the FSM. With every instance’s pins mapped to net IDs and every cell type’s exact gate function known, wrote a worklist-based combinational evaluator: seed the 7 character bits, reset_n, and the 5 flip-flops’ current Q values as known net values, repeatedly evaluate any gate whose inputs are now all known, until the 5 D (next-state) nets and found_flag settle. This reproduces exactly the same style of automaton as challenge 1 (self-resetting on mismatch, folds back on a repeated flag-prefix character, found_flag asserts once state == 31).

  5. Recovering the flag. Same walk as challenge 1: starting from the all-zero state, for each of the 95 printable ASCII candidate bytes 32-126, simulate one step; among the resulting states, keep the one that reaches a genuinely new (not-yet-visited) 5-bit state - resolves the forward-edge vs. KMP-failure-edge ambiguity the same way challenge 1’s netlist did. Repeating this 31 times (5-bit state space, 0..31) walks straight through the automaton and stops the moment found_flag=1.

Flag

NNS{fl4g_ver1f1ed_1n_SKY130_IC}

#gdsii#vlsi#sky130#klayout