journal 2026-04-28

Project 02 hardened — first flops, plus a viewer pipeline rebuild

project-02librelanehardensky130systemverilogviewers

Long session straight after the P01 harden landed. Project 02 is the first sequential design on the ladder — 8-bit counter, 8-bit Fibonacci LFSR (max-length, taps 8/6/5/4), 1-bit PWM, 2-bit mode mux. ~50 lines of RTL.

Three things landed: the project itself, a switch to SystemVerilog, and a full rebuild of the layout/3D viewer pipeline.

RTL pass on the first compile

Wrote the testbench in six phases (reset, 1024-cycle exhaustive run, counter wrap check, exact PWM duty, 255-state LFSR coverage, mode mux). All passed first compile.

$ make test PROJECT=02_counter_pwm_lfsr
PASS: counter (4 wraps), LFSR (255 states), PWM (256/1024 high), mode mux all OK.
02_counter_pwm_lfsr              PASS

Two things made first-compile pass not a fluke:

Switched the project ladder to SystemVerilog

Project 01 was plain Verilog 2001 to keep the toolchain happy. P02 introduces flops, and the SV always_ff / always_comb discipline catches sequential-vs-combinational mistakes at compile time — worth the upgrade for everything from here forward.

The visible code reads the same. The compiler just gets stricter.

Hardened, with a real-flow lesson

LibreLane ran end-to-end at 100 MHz on sky130A. Result: 1946 std cells, +1.24 ns setup ws, +0.12 ns hold ws, 0 DRC, 0 LVS, 0 antenna — and 11 max-slew violations in the slow PVT corner.

That’s the first non-zero-violation harden in this project. I didn’t fix it. It’s a good artifact: a real-flow violation in a real run on a small design, useful to point at in the lesson page rather than chase. Possible mitigations are listed there for the inevitable “would tightening MAX_TRANSITION fix this?” question later.

Cell count exploded vs P01 (81 → 1946). Most of the difference is not the new flops (16 of them); it’s the clock tree. CTS inserted a balanced buffer fan-out so every flop’s clock arrives within tens of picoseconds of every other. Real flops + real CTS is where digital design actually starts costing silicon.

Viewer pipeline: full rebuild

The naïve approach from P01 (inline 9 MB SVG into the page, plus a 15 MB GLB on <model-viewer> which I tried first) does not scale. Three rounds of pivots:

  1. SVG inline → SVG with viewBox transform — pan/zoom worked but the browser’s cached bitmap looked fuzzy at any zoom that wasn’t 1×.
  2. SVG → canvas 2D with parsed polygon JSON — re-rasterizes every frame at the current viewport, sharp at every zoom. Pan went from ~5 fps (SVG, ~187 ms / frame) to 60 fps (canvas, ~16 ms / frame). ~11× speedup.
  3. inline GLB → meshopt-compressed GLB. gltfpack -cc -kn compresses geometry losslessly; -kn keeps named nodes so the Three.js layer panel still groups polygons by sky130 layer name. P02 went 15 MB → 1.9 MB. Three.js needs MeshoptDecoder wired up; one extra import.

Side effects of this work:

Centering / layer-panel fights

Both viewers had their own version of the same bug: a layer panel overlapping the canvas, hijacking pan-drag, and the camera fitting to a pre-load placeholder rather than the actual model.

(The 2D viewer also had a y-axis bug from the SVG-flip handling that hid most of the chip; I didn’t catch it until much later when the full-width layout exposed it. Fixed in a later commit.)

Now what