↑ index§4 Writeups

Reverse Engineering an ASIC

Written 2026-08-10 · Revised 2026-09-17

Event
Jane Street ASIC Reverse Engineer Challenge
Result
Solved!!
Stack
KLayout, Verilog, Magic, SPICE, Python

Problem

We’ve designed an ASIC, and we’re giving you its final mask: all of its metal, routing, and active transistor layers, along with some sample inputs and outputs.

Your job is to reverse engineer it. First, recover a netlist from the layout. Then figure out the circuit’s true purpose. And then comes the puzzle within the puzzle: once you understand what the chip does, use it to tease out the output it’s looking for, and find the string value that’s your final answer.

So what we actually get is a .gds (just polygons on layers), the Liberty file for the standard cells, and one recorded run as a VCD. No Verilog, no schematic.

Solution

Approach

Having no experience in reverse engineering ASIC but some limited experience in Verilog, the first step was looking through the warmup round. Fortunately, it pretty much gave away all the answers– the challenge statement was very clear about tools built in the warmup being directly applicable on the actual round. The warmup also ships the original Verilog, which the real round doesn’t, so I used it to check my extraction before pointing the same code at the real chip. The steps are as follows:

  1. Use KLayout to extract a netlist, then compare with the Liberty cell definitions; emit an output as Verilog, while omitting all non-logic cells.

  2. Test for equivalence with the VCD and with the unformatted Verilog netlist (in the case of the warmup).

  3. Reverse-engineer the solution logic and find the correct input sequence

  4. Search for easter eggs!

Most of the time went into 1 and 2. Once I trusted the model, 3 was quick.

Details

Netlist extraction

KLayout has a Python package that can directly read .gds files and convert them into a netlist. You have to tell it which layers are metal, which are vias, and where the cell boundaries are; after that it figures out which pins are connected. Using the Liberty pin database, we only extract signal pins (VIAs get joined, power-only cells are dropped, and power pins are dropped) and check that all cells are assigned the correct number of pins. I added the pin count check after getting a few cells with a missing pin on the warmup– it meant my layer setup was wrong, and it’s much easier to catch that here than later. Emit the whole discovered netlist as a Verilog module containing all the sky_130 cells as well as wires. Now, we have a clean Verilog representation of the digital logic, with the actual logic behind the Liberty definitions. None of the wires have meaningful names, but every cell instance does, so we know exactly what each one computes.

Testing Pt. 1

Read the VCD file. The clock period is 10ns, and enable is held high for 121 cycles. Afterwards, O carries ASCII which decodes to TRY AGAIN. So the sample run is a wrong answer. Good for testing, useless for figuring out the right input. Then, I wrote a testbench that runs the generated Verilog through verilator with the Liberty definitions while feeding in the exact same input sequence, and the output was byte-for-byte equivalent. Thus, my example was functionally equivalent to the generated solution. For further checking, I later installed MAGIC and ngspice, using those tools to perform a parasitic extraction and simulated a couple more inputs against the SPICE extracted model from the GDS, which came out to be the same as well. I wanted this second check because it doesn’t touch my Verilog at all– it’s transistors extracted straight from the geometry. If both agree, I’m fairly sure the extraction is right.

Figuring out what the chip does

We know, from the VCD, that the black box does this: given an input stream I and holding enable for 121 cycles, drop enable and read from O over some amount of cycles. 121 is , which I noted and then ignored for a while. I traced as many layers back as I could by hand, which allowed me to partially understand what was happening. All the logic came from the Liberty definitions, which had direct boolean operations. Hand tracing got old fast (thousands of gates, no names), but it was enough to see the overall shape. Essentially, the chip is a FSM with many AND and INV conditions driving success, and the output generator block is largely separated from the logic driving success. This was the useful part: I could ignore the output block completely and only ask what makes success go high. I then wrote a small AST parser that collects the boolean expressions and flops into both Python bools and symbolic expressions. Here, there’s another layer of verifying a couple results against MAGIC, but it turns out that everything matches. The parser starts at success, swaps each cell for its Liberty boolean, and follows wires back until it hits an input bit or a flop. Running the Python version on the VCD input gave False, which matched TRY AGAIN, so the parser was reading the circuit right. To solve this massive chain of boolean expressions, the tool I used was z3, which was just assuming each I is an independent free variable then solving. In 0.3s, a solution was found (since this is a SAT-like problem). I never had to understand the formula– just hand it over and ask for any assignment that makes it true.

After simulating the correct input that the solver returned through all of our different simulation methods, the answer is:

(* TWO STARS *)

Easter eggs

I also found several artifacts which could be classified as Easter eggs. First of all, by inspecting the VCD file, I found that $date was marked as Sat Dec 31 23:59:60 2016 which is an actual leap second (pretty cool!). Nothing generates a 60 in the seconds field by accident, so someone put it there. Next, the puzzle.gds file had a watermark which looked like an incomplete Jane Street logo (may or may not be an actual Easter egg, I found this by rendering each layer of the GDS individually when I was starting out as it was strange to have a group of polygons with no routing). Next, there’s a another Easter egg in the GDS file. I was drawn towards two mystery cells labeled INTERNAL_3 and INTERNAL_7, and after rendering they were placed on a single strip. If we take them to be shapes only, it forms a pattern that looks like Morse code. Indeed, it decodes out to "PER ARENAM AD ASTRA" which is Latin for "through the sand to the stars" (probably a play on words, since chips are made of silicon thus sand?). In hindsight the "stars" part was a hint too. Finally, there’s also Easter eggs from different inputs you can give the chip. An all-zero input will yield EMPTY SKY, while an all-ones input will yield BIG BANG. Interestingly, there’s also one last possible string which reads TWO NOT TOUCH, which I’ll discuss in the conclusion. Since the output block was already separated out, listing every string it can produce was easy– it only has a few states.

Conclusion

Actually, in my writeup I skipped a key detail– what the chip is actually built to do. If you write out the input manually, it’s an 11x11 row-major grid, with 1 denoting a filled cell and 0 denoting an empty cell. That’s the 121– it’s a picture, not a number. The fact that this SAT input is unique was suspicious, so I made Claude write out a script that can probe my extracted silicon against 121 one-hot inputs, and it found 11 enumerated "regions" in the grid. Each one-hot input fills exactly one cell, and you can see which internal signals react, so cells that react together are in the same region. 11 regions, 11 rows, 11 columns. Thus, we can conclude that this is actually an n-queens-like solution verifier– there can only be two stars per row, column, and region, with no two cells adjacent/diagonal, which also provides context to why the answer is TWO STARS. All those AND/INV chains I was staring at were just these rules in gates: count each row, count each column, count each region, check every pair of neighbours. success is the AND of all of it. This also makes TWO NOT TOUCH make sense– when all the other constraints are fulfilled, if there are touching filled cells then the program will remind the user that no two cells may touch. In fact, if you search up the term "TWO NOT TOUCH", this is actually the name of the puzzle! It’s the NYT version of Star Battle: two stars per row, column, and region, no two touching, even diagonally. So to conclude, our answer is

j / kmove cursor over links (counts work: 5j)
gg / Gfirst / last link
Enter / lopen link under cursor
hback
/filter entries
Escclear filter and cursor, close
j / kmove cursor over links
Enter / lopen link under cursor
d / uscroll half page
gg / Gfirst / last link
] / [next / previous heading
hback
ttoggle dark mode
?this help