Jane Street's 2026 puzzle provides
puzzle.gds. GDSII is the file format normally used to send a physical chip layout to fabrication; it
stores shapes on named process layers rather than source code. The task is to recover the circuit and find a
serial input that raises success. This post describes the extraction, analysis, and verification
procedure. The recovered circuit checks an 11×11 Star Battle board.
Method
I treated the layout as the output of a compilation pipeline and reversed one stage at a time. Each stage produced a simpler representation and had a separate check:
mask polygons
-> standard-cell footprints geometry XOR, rotations, mirrors
-> pins and connected nets metal/via connectivity extraction
-> structural Verilog 728 functional instances
-> registers and datapaths graph passes to a fixed point
-> transition system yosys -> AIG
-> accepted serial input ABC bounded model checking
-> puzzle rules and regions simulation + GF(2) support probing
The warm-up in the challenge repository includes source Verilog, a synthesized netlist, DEF, and GDS for a known circuit. DEF describes placed cells and routed nets before they are converted into final mask geometry. I used these files to test cell matching and net extraction against known results: the extracted model recovered all 230 placed instances and all 84 signal-net partitions. The challenge says that the puzzle uses a very similar flow, so I still checked each assumption against the puzzle layout and traces.
The analysis scripts and selected intermediate artifacts are in the companion repository.
Identify the standard cells
The layout contains fixed-height rows of standard cells. A standard cell is a reusable physical implementation of a small function such as a NAND gate or flip-flop; synthesis tools place copies of them side by side. The following row contains a NAND, a compound AND-OR, an XOR, and a well-tap cell. The colors show poly, diffusion, local interconnect, and metal layers.
puzzle.gds · poly (red), diffusion (green), licon/li1/met1 stacked · dashed = cell outlinesThe GDS retains its cell hierarchy, so KLayout can group the polygons for each placed instance. I did not use the cell names for identification. For each definition, I merged the device layers, translated them to a common origin, and compared them with cells from sky130, an open 130 nm process design kit and standard-cell library. The comparison uses the geometric XOR area: any area present in only one of the two layouts remains in the result. I tested four rotations and their mirrored forms because placement rows can be flipped. An XOR area of zero is an exact geometric match.
xor2, as drawn and mirrored · the matcher tries all 8 orientations and takes the zero-XOR fitThis pass identified 69 specific standard-cell definitions, including drive-strength variants, with no ambiguous matches. Collapsing those variants left 67 base families: 60 combinational, three sequential, and four physical or tie-cell families. I then discarded the hierarchy and repeated the identification from geometry alone. The metal-1 rails gave 102 placement rows, and the 460 nm placement grid gave candidate cell boundaries. An exact-match tiler recovered all 722 placed logic cells.
Recover the cell functions and nets
A cell footprint identifies a library cell but does not by itself describe its behavior. For the first functional
model, I read each matched cell's Boolean output function from its Liberty file. Liberty is a standard text format that
records a cell's pins, logic functions, and timing data. I used z3 to compare those expressions with common
operators. If the two expressions cannot differ, the solver returns unsat and proves the equivalence.
xor2 X = (A&!B) | (!A&B) == A xor B proven (unsat)
xnor2 Y = (!A&!B) | (A&B) == not (A xor B) proven
nand2 Y = (!A) | (!B) == not (A and B) proven
nor2 Y = (!A&!B) == not (A or B) proven
a21o X = (A1&A2) | (B1) == (A1 and A2) or B1 proven
mux2 X = (A0&!S) | (A1&S) == S ? A1 : A0 proven
To check the result independently, I recovered the functions a second way, without Liberty. A transistor channel
is poly ∩ diffusion; diffusion minus poly gives source and drain; n-well separates PMOS from NMOS;
contacts and local interconnect connect the terminals. For each small cell, I enumerated every input pattern and
evaluated the conducting switch network. This produced complete truth tables for all 60 combinational cell
families, and all 60 matched Liberty. The same pass found three sequential storage-loop families and separated
four physical or tie-cell families. This transistor-level method still needs a technology layer map, but it does
not need the standard-cell function library.
KLayout's LayoutToNetlist then traced metal and vias to determine which pins share a net. A netlist is
simply a list of component instances and the wires connecting their pins. The emitted gate-level model contains
722 logic instances: 630 combinational gates and 92 flip-flops. Six constant cells bring the functional-instance
count to 728. Its ports are
clk, rst_n, enable, serial input I,
O[7:0], and success. Taps, decaps, and diodes were left out because they do not affect the
digital model.
A connectivity audit found one undriven net. KLayout had represented a physical pin as a labeled floating terminal and a separate unlabeled terminal touching the driven conductor. I added a general repair rule: if one instance has an unlabeled terminal on a driven net and a labeled input on an undriven net, merge the terminals and discard the floating one. All later analysis used the repaired model.
The repository includes a VCD, a timestamped trace of digital signal values, with sample inputs and recorded
outputs. Replaying those inputs through the extracted netlist reproduced all 22 output checks. On an invalid
input, the output spells TRY AGAIN one byte at a time. This replay was the end-to-end check for
extraction.
Recover the state-machine structure
The extracted Verilog is a flat collection of library instances connected by numbered nets. The following lines
are from that model, including the flip-flop that drives success:
module puzzle (clk, rst_n, enable, I, O, success);
input clk; input rst_n; input enable; input I;
output [7:0] O; output success;
sky130_fd_sc_hd__and2 i0 ( .A(n35), .B(n6), .X(n1) );
sky130_fd_sc_hd__o21a i1 ( .A1(n10), .A2(n1), .B1(n4), .X(n44) );
sky130_fd_sc_hd__inv i3 ( .A(n2), .Y(n87) );
sky130_fd_sc_hd__dfxtp i7 ( .CLK(n710), .D(n764), .Q(n274) );
sky130_fd_sc_hd__dfstp i10 ( .CLK(n568), .D(n614), .Q(n604), .SET_B(rst_n) );
...
sky130_fd_sc_hd__dfrtp i137 ( .CLK(n710), .D(n804),
.Q(success), .RESET_B(rst_n) );
Each flip-flop stores one state bit between clock edges. Names such as register, counter, and comparator are absent from the flat file, so I recovered larger structures from the connectivity graph before using a solver. The graph contains 92 flip-flops and 630 combinational gates.
recover.py runs local passes over the netlist graph and iterates them to a fixed point, which took
eight rounds. A pass records a candidate grouping and its supporting edges; it does not rewrite the graph. The
passes were:
- Control classes partition flip-flops by clock and reset roots.
- Tarjan strongly connected components find groups of flip-flops with feedback.
- Feedback groups without external data inputs identify autonomous counters or generators.
- Weakly connected components reachable from
Iidentify the input-mixing block. - Groups of state bits feeding the same downstream logic identify operands used by the output and success checks.
The resulting summary was:
=== concept recovery: 92 flops, 152 candidate concepts ===
control classes (reset / clock partition):
84 flops reset=0 -> zero-init state
4 flops reset=none -> free-running counter
4 flops reset=1 -> seed / one-init register
autonomous registers:
width 4 self-clocking counter, gated by i203
input-mixing registers:
width 85 coupled block; 58 bits directly sample serial I
operand buses (word-level reductions):
56 state bits -> D[i137] (the win comparator)
16 state bits -> each O[7] ... O[0] (16-entry byte ROM)
This gives a working model of the machine. An 85-flop coupled block receives I while
enable is active. A four-bit autonomous register acts as an address counter. The next-state cone of
the success flip-flop depends on 56 other state bits. Each output bit depends on 16 state bits; taken
together, the outputs implement a 16-entry byte ROM.
These structural groups give names to the state used in the next step. The 92 flip-flop outputs form the current
state; their D-pin logic defines the next state; and the serial input supplies one new value at each
clock edge. With those pieces identified, the circuit can be written as a transition system and queried over a
bounded sequence of clock steps.
Build and query the transition system
The extracted Verilog says which cells are connected, but Yosys also needs a behavioral definition for each sky130 cell. I generated small Verilog models from the Liberty functions. These are two of the definitions used for the formal model:
module sky130_fd_sc_hd__and2 (A, B, X);
input A, B; output X;
assign X = A & B;
endmodule
module sky130_fd_sc_hd__dfrtp (CLK, D, RESET_B, Q);
input CLK, D, RESET_B; output reg Q;
initial Q = 1'b0;
always @(posedge CLK or negedge RESET_B)
if (!RESET_B) Q <= 1'b0; else Q <= D;
endmodule
Linking these definitions with the extracted instances turns the flat netlist into an executable sequential
circuit. Let S_t be the 92 flip-flop outputs immediately after clock step t, and let
I_t be the serial input at that step. The combinational gates feeding every D pin define
the next-state function T. The success port is the Q output of one flip-flop,
so the output function G selects that bit from the current state:
S_0 = initial post-reset state
S_(t + 1) = T(S_t, I_t)
success_t = G(S_t)
This pair of functions and an initial state are the transition system. It discards physical details such as cell placement and wire shape, while keeping everything that can affect the state on the next clock edge.
I first wrote this relation explicitly with z3. The following is the central loop from the actual model. Each
dexpr[f] is the Boolean expression found by walking backward from flip-flop f's
D pin until reaching current-state bits or I:
S = m.init_state()
Ibits = [z3.Bool(f"I_{t}") for t in range(N)]
for t in range(N):
nxt = {f: z3.Bool(f"S{t+1}_{f}") for f in m.flops}
sub = [(m.qv[f], S[f]) for f in m.flops]
sub += [(m.iv, Ibits[t])]
for f in m.flops:
s.add(nxt[f] == z3.substitute(m.dexpr[f], *sub))
S = nxt
This code shows the unrolling directly, but the general SMT model became slow as the bound increased. For the
main search I used Yosys and ABC. The supplied VCD establishes the input protocol: reset the circuit, then load
one serial bit on each of 121 enabled clock cycles. The formal wrapper models the interval after reset, holds
enable high, leaves the serial bit unconstrained, and exposes success as the property
output:
module top (input clk, input i, output success);
wire [7:0] O;
puzzle dut (
.clk(clk), .rst_n(1'b1), .enable(1'b1), .I(i),
.O(O), .success(success)
);
endmodule
The clk port is present because the Verilog cell models are edge-triggered. After sequential lowering,
one AIG frame represents one register update; the counterexample data of interest is i. The cell
models contain the initial values used after reset. Yosys then flattened the wrapper and the 728-cell design,
normalized the flip-flops, and converted the combinational part to an AIG, an AND/inverter graph:
read_verilog -sv build/cells_yosys.v
read_verilog -sv build/puzzle_fixed.v
read_verilog -sv build/formal_aig.v
hierarchy -top top
prep -top top -flatten
async2sync
dffunmap
simplemap
aigmap
write_aiger -map build/puzzle.aim build/puzzle.aig
An AIG still contains state elements, but every combinational expression between them is represented using only
AND nodes and inverted edges. ABC can therefore ask, for each bound k, whether there are input bits
satisfying the following formula:
Init(S_0)
and T(S_0, I_0, S_1)
and T(S_1, I_1, S_2)
...
and T(S_(k-1), I_(k-1), S_k)
and success(S_k)
I ran that query with the following command. write_cex writes the counterexample trace, which in this
case is the input sequence we want:
yosys-abc -c "read build/puzzle.aig; print_stats; \
bmc3 -F 400 -T 150; write_cex -a build/win2.cex"
Yosys reduced the property cone to 79 state elements and 806 AND nodes; state and output logic unrelated to
success was removed. ABC proved that bounds 0 through 121 cannot reach the output and found
success=1 at frame 122. At that frame the SAT instance had 3,145 variables and 12,723 clauses.
I replayed the witness through the original sky130 simulation models and observed the same
success edge. This checks the result against the library cell models, independently of the simplified
AIG used for the search.
I then used z3 to block that witness and search for alternatives in the bounded model. The variables for all 121 load cycles were fixed. Two later input samples occur after the success state has been evaluated, so they do not affect the board and remained unconstrained. The 121-bit payload is therefore unique.
Identify the checked rules
The output messages explain what the 121 input bits represent. An all-zero input produces
EMPTY SKY, an all-one input produces BIG BANG, and an input with adjacent selected cells
produces TWO NOT TOUCH. The successful input raises success and produces:
(* TWO STARS *)
These diagnostics identify a two-star Star Battle: an 11×11 grid with two stars in every row, column, and irregular region, and no two stars touching horizontally, vertically, or diagonally. Serial input order maps directly to the grid, one row after another. The successful payload contains 22 stars.
Recover the regions
The row, column, and non-adjacency rules are now known, but the 11 irregular regions are still missing. They are
not exposed as simple groups of comparator inputs: the serial input is mixed into a coupled state register before
it reaches the success logic.
The useful signal came from the internal state instead. I simulated 121 inputs containing a single star, one at
each board position, and recorded which state bits in the success cone changed. Several bits act as
GF(2) parity accumulators. GF(2) is single-bit arithmetic in which addition is XOR, so such a bit flips once for
every selected cell in its support. Eleven of the measured supports were disjoint and together covered the
board:
region parity supports (cells): 4 5 6 7 8 8 9 11 14 21 28
---------------------------------------
sum = 121 -> a clean partition of the grid
The supports partition all 121 positions, and the successful payload has two stars in each support. An
independent Star Battle solver, given only these regions, returns the same grid and proves it unique. As a
separate check, I generated boards with the row and column counts held at two. For each board, I compared the
recovered region counts with the chip's diagnostic output: a TWO ... message means that the count
checks passed, while a different message identifies a count failure. The two classifications agreed.
The serial order and recovered supports give the following puzzle map:
Solving that recovered instance independently produces the same board as the formal witness:
success high, output (* TWO STARS *)Additional data in the files
The following details are not needed for the solve, but they appear to be intentional parts of the challenge.
A decorative GDS layer contains 36 rectangles in two widths with a 1:3 ratio. Interpreting the widths as Morse code gives:
PER ARENAM AD ASTRA
This changes per aspera ad astra, “through hardships to the stars,” to “through sand to the stars.” The VCD version string is “Leave no stone unturned.”
Reading each sample-input row as a 7-bit character gives “The night sky awaits.” The warm-up circuit checks
A + B == 496; 496 is a perfect number. The VCD timestamp is
Dec 31 23:59:60 2016, the leap second added at the end of that year.
Technology used
- KLayout: read the GDS hierarchy, compare polygons, and extract net connectivity through the metal and via layers.
- sky130 standard-cell data: provide reference layouts and Liberty functions for the direct extraction path.
- Python graph analysis: find feedback components, control classes, input-mixing registers, and shared fan-out cones in the flat netlist.
- Icarus Verilog: replay the supplied VCD against the extracted model and simulate targeted inputs.
- Yosys: lower the sequential gate-level model to an AIG transition system.
- ABC: bounded model checking for a trace that
reaches
success. - z3: prove selected Boolean equivalences and check whether the serial payload is unique.
- GF(2) support probing: recover the eleven region sets from controlled simulations of the success datapath.
The main verification points were 230/230 warm-up instances, 84/84 warm-up signal-net partitions, 69/69 cell definitions, 722/722 flattened logic instances, 60/60 geometry-derived truth tables, and 22/22 VCD output checks. The final input was re-simulated on the extracted sky130 model, and the recovered Star Battle instance was solved independently to confirm that it has one solution.
Thank you to Jane Street for publishing an interesting challenge and for including enough intermediate material in the warm-up to make the reverse-engineering process testable.