Repository navigation
gen-verilog: on_clock with a return type emits an assignment to an implicitly-declared identifier under default_nettype none #2364
Description
Found while trying to close #2266 at the spec layer. Built from master cf1265312486e02ddf6b7375814a82301130bed6.
Defect
on_clock lowers to an always @(posedge clk or negedge rst_n) block, not to a Verilog function — so there is no identifier of that name to receive a return value. But gen_verilog_fn_body lowers return / a trailing call expression to an assignment to self.current_fn_name, which for this path is the literal string on_clock. The result is an assignment to an identifier that was never declared.
Reproduction
Patch specs/fpga/uart.t27:224 from
fn on_comb(data: u8) -> bool { return uart_tx_send(data); }
to
fn on_clock(data: u8) -> void { uart_tx_send(data); }
t27c gen-verilog specs/fpga/uart.t27 emits:
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
end else if (en) begin
on_clock <= uart_tx_send(data);
end
end
on_clock is declared nowhere in the module. yosys 0.63:
Warning: wire '\on_clock' is assigned in a block at fixA.v:175.13-175.43.
fixA.v:175: Warning: Identifier `\on_clock' is implicitly declared.
The file opens with `default_nettype none, under which an implicit net declaration is not supposed to be legal at all; yosys downgrades it to a warning, so it passes silently.
Using -> bool with an explicit return produces the same thing with a blocking assign (on_clock = uart_tx_send(data);).
Suggested shape
Either refuse an on_clock that declares a return type (it has no port to drive — the boundary for a clocked entry point is an exposed var, not a return value), or bind the value to a declared local. Refusing is the stricter side and matches the W699/T190b precedent already in this file.
Relationship to other issues
Independent of #2363, which is about assignments being dropped from the same block. Both were hit while attempting the spec-level repair for #2266.
Boundary
specs/fpga/uart.t27
User Scenarios
- Given a T27 spec containing
fn on_clock(data: u8) -> void { uart_tx_send(data); }, whent27c gen-verilogcompiles it, then the emitted Verilog must not assign to an undeclared identifieron_clockinside thealways @(posedge clk ...)block. - Given a T27 spec containing
fn on_clock(data: u8) -> bool { return uart_tx_send(data); }, whent27c gen-verilogcompiles it, then the compiler must reject the spec with a clear diagnostic rather than emitting an assignment to an implicitly-declared net.
Requirements
- FR-001: The compiler MUST reject any
fn on_clockthat declares a non-voidreturn type with a diagnostic at the call site. - FR-002: The compiler MUST reject any
fn on_clockthat declares-> voidbut contains a trailing expression (implicit return) with a diagnostic at the call site. - FR-003: The compiler MUST accept
fn on_clock(...) -> void { /* statements only, no trailing expr */ }and lower it to a clockedalwaysblock with no assignment toon_clock.
Success Criteria
t27c gen-verilog specs/fpga/uart.t27exits 0 and the emitted Verilog contains no assignment toon_clockinside thealways @(posedge clk ...)block.- Compiling a spec with
fn on_clock(...) -> bool { ... }produces a compiler error (non-zero exit) and the error message references the return type onon_clock. - Compiling a spec with
fn on_clock(...) -> void { expr; }(trailing expression) produces a compiler error (non-zero exit) and the error message references the trailing expression. - 3 tests in the test suite cover these three cases and all pass.
Activity
gHashTag commentedon Aug 22, 2026
Note from #2363, which is now closed by #2380 (merged,
2d2eafe79).The blocker under this issue is gone:
on_clockno longer drops a body statement whose RHS does not mention the assigned var. The cause wasdead_store_elim— it runs per function body, treats a simple-identifier LHS as a pure write, and deleted the store because nothing later in the body read the name. A module-levelvaris not function-local; the Verilog backend emits it as anoutput regport.This issue is unaffected and stays open. The
on_clock <= uart_tx_send(data);shape reported here comes fromgen_verilog_fn_body's tail-expression /returnlowering assigning toself.current_fn_name, which for the clocked path is the literal stringon_clock. That is a different code path from the one #2363 touched, and #2380 changed nothing in it — deliberately, to keep the diff to the defect it was reproducing.One detail that may matter to whoever takes this: the reproduction quoted here shows an EMPTY reset arm
The reset arm is emitted from the module's scalar
vardeclarations, independently of the body, so that emptiness is about the spec'svars, not about the drop #2363 described. #2363's signature is the opposite one: a populated reset arm with an emptyenarm.gHashTag commentedon Sep 23, 2026
A boundary drafted from the paths this issue already names, so a bee can take it. Nothing is reserved until this is in the issue body - the Queen reads the body, not comments. Check the paths first: a wrong boundary reserves files the work does not own.
Boundary
specs/fpga/uart.t27docs: the coordination entry this branch needs to land
gen-verilog: on_clock with a return type emits an assignment to an im…
gHashTag commentedon Oct 4, 2026
Reopening: the compiler defect this issue names is still live on master.
942a001 (#4651), whose message closed this issue, changed the spec rather than the compiler. It pasted this issue's reproduction into
specs/fpga/uart.t27, replacingfn on_comb(data: u8) -> bool { return uart_tx_send(data); }with anon_clockbody. On master,gen-verilogof that spec still emits an assignment to an undeclaredon_clock, and iverilog saysuart.v:175: Could not find variable on_clock in ZeroDSP_UART. That is the second of the two errors the FPGA elaboration ratchet counted (178 vs baseline 176) since 2026-09-22.PR #5948 (Closes #5908) restores the
on_combline, souartis back to 0 errors. It does not fix the compiler, so this issue has no fix yet. A fix needs abootstrap/change and a test that runsgen-verilogon anon_clockwith a return type underdefault_nettype none.Refs #5906
gHashTag commentedon Oct 5, 2026
Review of the bee branch
queen-2364(review-jam sweep, lane A).Not accepted, and not releasable to a bee. The branch adds nothing: master already carries the reproduction,
fn on_clock(data: u8) -> void { uart_tx_send(data); }. The defect is still live. On the Railway t27c lab at master 13e212a,t27c gen-verilog specs/fpga/uart.t27 | grep -cE '^ *on_clock <?= 'prints1: an assignment to the undeclared identifieron_clock.What a correct answer needs. A change to
gen_verilog_fn_bodyinbootstrap/that does one of two things: refuse anon_clockwith a return value or trailing call, or bind the value to a declared local. That is hand-written Rust. Under the only-t27 rule it needs the owner'sowner-approved-foreignlabel before anyone may write it. A spec-only answer would only hide the defect by removing the reproduction fromuart.t27. Escalating to the owner; I left the criteria unchanged.fix(fpga): restore uart on_comb, record clean adapter, name ternary_i…
Add test cases for on_clock function validation