Reproduce a score

From svgolf/score/PPA.md of the repository. The ranked score, the sky130 A0′ area and time, is a pure function of the design's text, the pinned flow and tool build, the pins and the task's scoring point and input drive; these steps give every recorded number again. Scoring in general: the rules.

A ranked number is a pure function of: the design's text; the pinned flow (score/srcgate.py, score/canon.ys, score/svgolf.ys, score/ge.json; for a clocked task score/svgolf_seq.ys and score/ge_seq.json), run by the pinned Yosys BUILD and its ABC (the build, not only the version: "Determinism (measured)", and the reference build below); score/ppa.py and score/ppa_pins.json; the pinned liberty; and the task's scoring point and input drive. A check result (and so a record) carries the hash of each: score.yosys_version, score.canon_script_sha256, score.script_sha256, score.ge_table_sha256, score.rtlil_sha256, and ppa.liberty_sha256, ppa.ppa_pins_sha256, ppa.ppa_script_sha256, ppa.netlist_sha256, ppa.repair.netlist_sha256, ppa.setup.

Tools and pinned files (the versions the goldens and tests/expected.json were recorded with; tests/run.py fails when the Yosys version or a script hash differs from tests/expected.json's flow):

whatversion or sha256role
Python3.12 (3.12.8 here), with pyslang (11.0.0 here)score/*.py; pyslang parses the source for the source gate
YosysYosys 0.53 (git sha1 53c22ab7c0ced80861c7536c5dae682c30fb5834, clang++ 17.0.0 -fPIC -O3); the binary of the rating build (Homebrew's yosys 0.53, macOS arm64) has sha256 011e094340da48932dd0af17da0846519af24cee7de9ae84c11cc78753ab7732the pinned flow: two processes, below
ABCthe yosys-abc beside that Yosys: UC Berkeley, ABC 1.01 (compiled May 6 2025 05:45:16); the binary here (Homebrew's yosys 0.53, macOS arm64) has sha256 769348bb154a26d5d97cd877d086893b3c6a1c8242343a505025bad62c7fa653 (another build has another hash and can give other numbers on some designs, measured 2026-11-05; the version line a record carries names the compiler)run only by Yosys's synth and abc -g passes; the ranked weighing and its repair are Python (ppa.py), no ABC
libertysky130_fd_sc_hd__tt_025C_1v80.lib, 12,800,135 bytes, ef34f999d582044cad9c1f4d5422504ae99702ad42baa432d75215476e5d23d5 (OpenROAD-flow-scripts 6c6fc7e2, URL in ppa_pins.json)fetched, never vendored: python3.12 score/ppa.py --fetch-liberty (size and sha256 checked) or SVGOLF_SKY130_LIB=/path/to/copy
score/canon.ysbbd11227d45dc1365f27597fd45c565fe721ba2648450588fc4fd983d00b367bYosys process 1
score/svgolf.ysea58219d8ef14c3bad265218ee266ccdb709ac9d4dc5258edc34d51ee83a0445Yosys process 2 (combinational)
score/svgolf_seq.ysc3874dbfc85921a87df6eacdfc6b2c9a0e2b34edd5738951e0d8bdb51f4558b2Yosys process 2 (clocked)
score/ge.json6a29196a3f4b58241f4c9af650c9141e4632b737214d563a11ba142a725c4beathe GE prices (the extra GE column)
score/ge_seq.json0a1aa1147b6916efa4a41e03aa3c8c70e68c1e3fd7a0e9c9a3cbc177edefd1f6the GE prices of a clocked task (ge.json's plus the flop's; its _derivation names the pins file it was derived under, so it changed with sky130_a0_v3's pins)
score/ppa_pins.jsond66a6086a37a97f5985266057051979fb6a8a3c557d3ef9c42acc1b5e41502d8 (sky130_a0_v3), recorded in every block as ppa_pins_sha256the gate-to-cell table, the timing setup and input drives, the repair, the tie cells

The reference build (the build ratings use, decided 2026-11-05; README.md, "Scoring", and score/reference_build.json): Homebrew's Yosys 0.53 for macOS arm64, compiled by clang++ 17 with -O3, with its yosys-abc (both sha256 above); every published number was made with it, the flow is deterministic on it, and another build can give other numbers on some designs ("Determinism (measured)"). It is not pinned to a slower portable build.

The pinned flow, step by step (what score.py does):

  1. The source gate, srcgate.py, refuses preprocessor directives, attributes, synthesis pragmas and unique/priority, and writes the CANONICAL text (comments stripped). Yosys reads that text, never the submitted file.

  2. Yosys process 1, yosys -q -l canon.log -s canon.ys: score/canon.ys without its comment lines, its placeholders filled (@SOURCES@: one read_verilog -sv "<canonical file>" per source; @TOP@: the task's top; @CHPARAM@: -chparam NAME VALUE per scoring parameter; @RTLIL@: the output). For the add reference at N = 16:

    read_verilog -sv "add_n.sv"
    hierarchy -check -top add_n -chparam N 16
    proc
    flatten -noscopeinfo
    hierarchy -top add_n
    setattr -unset src
    setattr -mod -unset src
    setattr -unset hdlname
    setattr -mod -unset hdlname
    rename -hide
    rename -enumerate
    write_rtlil "canon.il"
  3. Yosys process 2 (a fresh process: Yosys's results depend on names a process has interned), score/svgolf.ys:

    read_rtlil "canon.il"
    synth -flatten -top add_n
    abc -g AND,NAND,OR,NOR,XOR,XNOR,ANDNOT,ORNOT,MUX
    opt_clean
    tee -q -o "stat.json" stat -json
    tee -q -o "ltp.txt" ltp -noff
    write_verilog -noattr -noexpr "netlist.v"

    ABC runs twice, each time with Yosys 0.53's own default script (the log, grep "ABC: + " synth.log, prints both): inside synth, abc -fast, strash; dretime; map; then the abc -g pass, onto a generated gate library of the listed gates, strash; &get -n; &fraig -x; &put; scorr; dc2; dretime; strash; &get -n; &dch -f; &nf; &put. netlist.v is the SCORED netlist (the one postsynth simulates and check.py weighs); GE is the stat count priced by ge.json, the depth ltp -noff. A clocked task runs score/svgolf_seq.ys instead: the same with synth -nofsm (no FSM re-encoding, decided 2026-11-06), dfflegalize -cell $_DFF_P_ x after it and opt_clean -purge as the last clean-up (decided 2026-11-07: "Known gap" below), priced by ge_seq.json.

  4. The ranked weighing, ppa.py on netlist.v (Python only): each gate becomes ONE _1 cell (ppa_pins.json gates: NOT inv_1, NAND nand2_1, NOR nor2_1, AND and2_1, OR or2_1, XOR xor2_1, XNOR xnor2_1, MUX mux2_1, ANDNOT nor2b_1, ORNOT nand2b_1; each checked against the liberty's own function), a flop dfxtp_1, cut out of the timing graph; the netlist is REPAIRED by buffering only ("The repair and the legality check"); then the STA: every primary input that is read is the output of the task's input drive (ppa.setup.input_drive, default sky130_fd_sc_hd__buf_8) whose own input is an ideal 50 ps ramp at 0, every output bit a 10 fF load, no wires, every table read by bilinear interpolation inside its characterized grid only (a read outside it rejects the netlist), both edges, the worst arrival and the largest transition carried; delay_ps_sky130 is the latest output arrival, rounded to the ps (clocked: the latest output or flop D arrival plus the flop's setup, the clock ideal with a 50 ps transition); area_um2_sky130 the sum of the cell areas (exact: area_e4um2_sky130); the legality audit last.

Reproduce a recorded number. From svgolf/ of a checkout at the record's commit, with the tools above on PATH (tested 11/04 by running the two blocks below literally, in a scratch copy of svgolf/: both references and the hand run matched the goldens bit for bit; again after the per-edge loads):

python3.12 score/ppa.py --fetch-liberty
python3.12 score/ppa.py tasks/add/reference/add_n.sv --task tasks/add --no-mapped --json add_n.json --netlist add_n.scored.v --repaired-netlist add_n.repaired.v
python3.12 score/ppa.py tasks/aes_sbox/reference/sbox_bp.sv --task tasks/aes_sbox --no-mapped --json sbox_bp.json --netlist sbox_bp.scored.v --repaired-netlist sbox_bp.repaired.v

add_n.json then has "area_e4um2_sky130": 4604416, "delay_ps_sky130": 2611, "netlist_sha256": "3b7da549a07e…", "repair": {..., "netlist_sha256": "ac214a9f04d7…"} and "setup": {..., "input_drive": "sky130_fd_sc_hd__buf_8", ...}, and its score block "rtlil_sha256": "0699af350c19…": the golden add_ripple of score/tests/ppa/expected_ppa.json. sbox_bp.json has 8921056, 4297, 22493fba30d8…, b27773491834…: the golden sbox_bp. A clocked task needs --seq (python3.12 score/ppa.py tasks/matmul/reference/matmul_g1.sv --task tasks/matmul --seq: 87173.6064 um^2, period 10696 ps, 6 buffers on 5 nets, tests/expected.json). A record's own numbers come from check.py ... --ppa require, which weighs the very netlist its postsynth stage verified, by the same function; leaderboard.py verify weighs it again.

The same add_n numbers by hand, each step one tool and none of score.py:

mkdir hand && cd hand
python3.12 ../score/srcgate.py ../tasks/add/reference/add_n.sv --write add_n.sv
grep -v '^[[:space:]]*#' ../score/canon.ys | sed -e 's|@SOURCES@|read_verilog -sv "add_n.sv"|' -e 's|@TOP@|add_n|g' -e 's|@CHPARAM@| -chparam N 16|' -e 's|@RTLIL@|"canon.il"|' > canon.ys
grep -v '^[[:space:]]*#' ../score/svgolf.ys | sed -e 's|@RTLIL@|"canon.il"|' -e 's|@TOP@|add_n|g' -e 's|@STAT@|"stat.json"|' -e 's|@LTP@|"ltp.txt"|' -e 's|@NETLIST@|"netlist.v"|' > synth.ys
yosys -q -l canon.log -s canon.ys
yosys -q -l synth.log -s synth.ys
python3.12 ../score/ppa.py --netlist-in netlist.v --task ../tasks/add --repaired-netlist repaired.v --json hand.json
shasum -a 256 canon.il netlist.v repaired.v

The three hashes are the record's score.rtlil_sha256, ppa.netlist_sha256 and ppa.repair.netlist_sha256 (0699af35…, 3b7da549…, ac214a9f…), and hand.json's columns are add_n.json's. For another design: its canonical file(s), its top and its scoring parameters in the two sed lines, and its task in --task.

With OpenSTA. Time the REPAIRED netlist (--repaired-netlist) with one explicit instance of the task's input drive in front of every input (a buffer per input, whose own input gets set_input_transition 0.05), set_load 0.010 on every output and set_load 0 on every net (no wires), the clock ideal with a 0.05 ns transition: OpenSTA (openroad/orfs 26Q4) then gives ppa.py's arrivals to 1 fs per arc; set_driving_cell instead would make every input path earlier by the drive's zero-load delay ("The input drive"). score/test_ppa_opensta.py holds the script (OPENSTA_TCL) and OpenSTA's recorded numbers.

The ASAP7 view's numbers (decided 2026-11-09: a reader may see the golf scores on ASAP7; sky130 stays the default). The same command with --pdk asap7 weighs the same scored netlist on the five pinned ASAP7 liberty files (flow A0', asap7_a0_v1, "ASAP7: an alternative library" below); leaderboard/asap7/<task>.json keeps each design's numbers, and leaderboard.py asap7 --rescore weighs them all again and compares, bit for bit:

python3.12 score/ppa.py --pdk asap7 --fetch-liberty         # once: the five files into score/lib/asap7/ (git-ignored)
python3.12 score/ppa.py tasks/add/reference/add_n.sv --task tasks/add --pdk asap7 --no-mapped --json add_n_asap7.json
python3.12 leaderboard/leaderboard.py asap7 --rescore --task add

add_n_asap7.json has ppa.netlist_sha256 3b7da549… (the sky130 run's: the scored netlist does not depend on the library), area_e5um2_asap7 699840 (6.99840 um^2) and delay_ps_asap7 563, so area x time is 3.940 um2*ns.