Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

Last updated: 2026-08-09

## gen-verilog: signed-aware ordered comparison (Refs #1948)

- Verilog makes an ordered comparison (`< <= > >=`) UNSIGNED if either operand is unsigned, so a signed i8 (`trend = -16 = 8'hF0`) meeting an unsigned const read as 240 -- `trend > THRESHOLD` diverged from Rust/Zig/C (signed-int promotion)
- When at least one operand is signed, emit in the signed domain with C-promotion semantics: signed operand -> `$signed(x)` (sign-extends into the wider signed context), unsigned operand -> `$signed({1'b0, x})` (zero-extend one bit, stays non-negative). Unsigned/unsigned pairs untouched
- tri-net corpus: link_quality_monitor flips to passing (last codegen RT divergence); full 105-spec icarus gate: 0 regressions. Only rti_security remains out -- a spec-side assertion arithmetic bug, not codegen
- FROZEN_HASH resealed

## gen-verilog: test-block call temps re-materialized after a rebinding (Refs #1948)

- A call-return temp is CSE'd by call TEXT, but a test block mutates its bindings between statements (`st = on_ack(st);` repeated). Caching the temp across a reassignment reused a STALE value, so the state never advanced -- every step tested the pre-mutation snapshot
Expand Down
60 changes: 55 additions & 5 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5287,6 +5287,23 @@ impl VerilogCodegen {
/// W539: infer the scalar bit width and signedness of an expression in the
/// Icarus-lowerable subset. Returns None for non-scalar or unresolvable
/// expressions (structs, arrays, strings, etc.).
/// W565: emit one operand of a signed-aware ordered comparison. A signed
/// operand is wrapped in `$signed(...)` (Verilog sign-extends it into the
/// wider signed comparison context); an unsigned operand is zero-extended by
/// one bit and reinterpreted as signed, `$signed({1'b0, ...})`, so its value
/// stays non-negative and the comparison follows C integer-promotion rules.
fn emit_verilog_signed_rel_operand(&mut self, node: &Node, is_signed: bool) {
if is_signed {
self.write("$signed(");
self.gen_verilog_expr(node);
self.write(")");
} else {
self.write("$signed({1'b0, ");
self.gen_verilog_expr(node);
self.write("})");
}
}

fn expr_width_signed(&self, node: &Node) -> Option<(u32, bool)> {
match node.kind {
NodeKind::ExprLiteral => {
Expand Down Expand Up @@ -9763,11 +9780,44 @@ impl VerilogCodegen {
">>" => ">>",
other => other,
};
self.write("(");
self.gen_verilog_expr(&node.children[0]);
self.write(&format!(" {} ", op));
self.gen_verilog_expr(&node.children[1]);
self.write(")");
// W565: signed-aware ordered comparison. Verilog makes an
// ordered comparison UNSIGNED if either operand is unsigned,
// so a signed i8 (e.g. trend = -16 = 8'hF0) meeting an
// unsigned const reads as 240 and the test diverges from
// Rust/Zig/C (which promote both to a signed int). When at
// least one operand is signed, compare in the signed domain
// with C-promotion semantics: the signed operand is wrapped
// in $signed() (Verilog sign-extends it into the wider signed
// context) and the unsigned operand is zero-extended one bit,
// $signed({1'b0, x}), so it stays non-negative.
let ordered_rel = matches!(op, "<" | "<=" | ">" | ">=");
let rel_signed = ordered_rel
&& match (
self.expr_width_signed(&node.children[0]),
self.expr_width_signed(&node.children[1]),
) {
(Some((_, ls)), Some((_, rs))) => ls || rs,
_ => false,
};
if rel_signed {
let (_, ls) = self
.expr_width_signed(&node.children[0])
.unwrap_or((0, false));
let (_, rs) = self
.expr_width_signed(&node.children[1])
.unwrap_or((0, false));
self.write("(");
self.emit_verilog_signed_rel_operand(&node.children[0], ls);
self.write(&format!(" {} ", op));
self.emit_verilog_signed_rel_operand(&node.children[1], rs);
self.write(")");
} else {
self.write("(");
self.gen_verilog_expr(&node.children[0]);
self.write(&format!(" {} ", op));
self.gen_verilog_expr(&node.children[1]);
self.write(")");
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
512b60aa836b260e2cbb2b8ae3b33fb1b329eb68764535df8e21f18bfacade73
274e2e4be8dd8bc505e656cdb24542f0534c0857bf50214b5c92e6a29595353b
7 changes: 7 additions & 0 deletions docs/NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

Last updated: 2026-08-09

## gen-verilog: signed-aware ordered comparison (Refs #1948)

- Verilog makes an ordered comparison (`< <= > >=`) UNSIGNED if either operand is unsigned. A signed `i8` (e.g. `trend = -16 = 8'hF0`) meeting an unsigned const read as 240, so `trend > THRESHOLD` diverged from Rust/Zig/C (which promote both to a signed int)
- When at least one operand is signed, the comparison is now emitted in the signed domain with C-promotion semantics: the signed operand is wrapped `$signed(x)` (Verilog sign-extends it into the wider signed context) and the unsigned operand is zero-extended one bit `$signed({1'b0, x})` so it stays non-negative. Unsigned/unsigned pairs are untouched
- tri-net corpus: link_quality_monitor's `degradation_detection` flips to passing (the last *codegen* runtime divergence); full 105-spec icarus gate: 0 regressions
- FROZEN_HASH resealed

## gen-verilog: test-block call temps re-materialized after a rebinding (Refs #1948)

- A call-return temp is CSE'd by call TEXT, but a test block mutates its bindings between statements (`st = on_ack(st);` repeated). Caching the temp across a reassignment reused a STALE value, so the modelled state never advanced -- every step re-tested the pre-mutation snapshot. Verilog-path-only (Rust/Zig/C evaluate the calls directly)
Expand Down
Loading