patch notes · #326–27 September

#3 · Silent wrongs stop the build

In numbers. A silent wrong is a design that builds a bitstream and does not do what the simulation showed: expect.tsv rows with bit=pass and netlist=fail. Before #3: 17. After: 2 (35, an MMCM, and 97, a block RAM: their netlists have no simulation model, the circuits are right). Of the 15 others, one became right (33, a PMOD pin tied to 'z and read back) and 14 stop with an ERROR that names the student's file and line and says how to fix it; no .bit is written. Three probes that used to fail now pass all four stages (26 an array of instances, 33, 56 a file-scope enum), so the Vivado-supported probes passing all four stages went 29/111 → 32/111 and all four stages 30/132 → 33/133 (one probe added, 95b). Synthesis 69/132 → 57/133: the silent wrongs now stop there. Known gaps 87 → 79. test/run.sh: 178 passed before, 187 passed, 0 failed, 79 known gaps after (9 min 10 s). dewfpga bit on the blink example: 4.6 s before, 4.1 s after (three yosys runs instead of one: a preprocessor dump, elaboration, synthesis; measured once each, warm).

What the student sees. test/sv/39_undeclared_name (a typo, summ for sum), dewfpga bit, before:

design.sv:4: Warning: Identifier `\summ' is implicitly declared.
Warning: Wire top.\summ is used but has no driver.
pnr ok: 0 LUT, 0 FF, no clocked paths, timing not applicable   (full log: top.log)
top.bit  2.2 MB
exit 0

after:

design.sv:4: Warning: Identifier `\summ' is implicitly declared.
ERROR: design.sv:4: summ is not declared anywhere and nothing drives it: yosys made it a 1-bit wire stuck at x and built the design without it (dewfpga sim refuses this code). Declare it, or check the spelling (a typo of a declared name).
exit 2

05_two_block_driver (one register written from two always blocks) printed 22 lines of yosys warnings and top.bit 2.2 MB, exit 0; now, after the first warning:

ERROR: design.sv:3 and design.sv:4: cnt is written from two always blocks (design.sv:3 and design.sv:4). Drive each signal from one always block: yosys resolved the conflict to a constant, so the board would not do what the simulation showed.

and no bitstream. 93_two_roots_file_name (the plain counter and the board's top in one file named counter.sv) built the plain counter, counter.bit 2.2 MB, exit 0, without a word; now:

ERROR: 2 modules could be the top (nothing instantiates them): counter, top. Name it:  dewfpga bit counter
exit 1

What changed, one mechanism per class. Product files only: templates/Makefile (111 → 252 lines), templates/check_xdc.py (192 → 318), bin/dewfpga (162 → 164).

Tried and did not work. -noautowire (above). yosys check -assert for the two-block case: it also counts the "no driver" warnings legal designs print. connect -unset for the 'z pin: it rewires the read too and the LED reads x. A first .mem grep pattern with a literal ( broke make 3.81 ("unterminated call to function sort"). Parsing top.il in the awk's BEGIN block read an empty file (the pipe starts awk before yosys writes it); it is read lazily now. hierarchy -check in the elaboration run refused the UNISIM primitives (34 BUFG, 35 MMCM: "not part of the design"); the check is the synthesis run's, which has the cell library. The first two-block rule fired on 52's tri bus with "(line unknown)"; a conflict without an always-block line is not this error.

Break rounds. Two, each a student (up to 10 new designs against the claim) and a regression reader (the diff, the 31 protect probes, the legal neighbour of every new rule), then a fix agent per task and an integration; then a judge was added to the script for the next updates (this run was made before it). Round 1: 9 reports, 8 in scope (constant start value from localparam int refused; $isunknown in a block comment and under `ifndef SYNTHESIS; an interface port on its own line refused; the typedef scan firing on a comment; import pkg::* reported as a typo; always_ff + assign printing "(line unknown)"; sim's two-roots message saying dewfpga bit). Round 2: 5 reports, 5 in scope (the typedef scan missing an `included .svh; $finish under `ifndef SYNTHESIS; a package file sorting after its user; a struct member taken for a type name; a second "(line unknown)" line in a submodule). All reproduced and fixed, each with the legal neighbour re-run. Held in both rounds: undeclared names in a .v submodule, hierarchical names two levels deep and into a generate block, two always_ff on the same edge in a parameterised submodule, an active-low async load in a .v file, $isunknown in an included function, start-value spellings in reg, typedef'd and parameterised forms, two roots across .v and .sv, two always_ff writing different bits of one vector (builds), a reset branch loading a parameter (builds), dotted names of a packed struct (builds).

Tests. test/sv/run.sh on the 16 target rows: each row's stages and its today quotes were rewritten to what the product prints (the runner fails a quote no stage printed). Two runner checks changed because the product changed: "a new silent wrong fails with any yosys" plants its wrong row on 97 (it used 05, which the product now refuses), and the personal-path scan skips the gitignored .claude/ folder (the workflow's worktrees live there). The full suite ran three times in the main session: the first run found the 34/35/52 regressions above and the 26/56 gains; the second failed only on 22b's stale quote (its message changed: the interface scan now reads every .sv in the folder, not only the module files, so an interface in its own file is classed as an interface, and the row quotes what yosys then prints); the third is the line quoted in the numbers.

Found and left to later updates.

CI. The first CI run of #3 was red on 95b: the message named the two files in the other order (design_old.sv:2 and design.sv:1). The CLI's file list came from the shell glob, whose order follows the locale (on the CI runner _ sorts before .), so the tools read the files in a different order on different machines and messages named them differently. The list is sorted with LC_ALL=C now, and the twice-defined message sorts its pair; the second CI run is the one that counts.

Orchestration, for the record. One workflow: 3 build agents in their own worktrees, one integrator, 2 breakers × 2 rounds, up to 3 fix agents per round, 14 agents in 3 h 0 min, then the harness killed the last integrator (a 20-probe run is silent for more than 3 minutes; it had already applied both fixes). Lessons written into the script: at most 5 probes per call, fix agents copy the integrated product into their worktree first (each spent 10 minutes discovering that their worktree was at HEAD), one scratch folder per agent (two overwrote each other's repro), a judge with a reproduced criticism before the close.


previous: #2 132 SystemVerilog probes run through the product, every stage checked
next: #4 What Vivado accepts compiles: yosys-slang as the second reader
This entry in the full list
The commands this entry names: dewfpga sim · dewfpga bit · dewfpga clean
Every SystemVerilog construct measured so far, next to what Vivado does: the probes, stage by stage