From 7a54742d83b3b97314dcc35210818f8574ee02b3 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 00:39:41 +0700 Subject: [PATCH] fix(t27b): fold a constant rst_n/en tie-off out of the clocked process (Closes #10046) A module that ties the implicit rst_n or en to true (`const rst_n : bool = true;`) was written as `wire rst_n; assign rst_n = 1'b1;` and the always block still read it (`or negedge rst_n`, `if (!rst_n)`, `else if (en)`). A test bench that writes every handle at start-up (cocotb dut_init) deposits 0 on that net, the constant driver never fires again, and the design sits in reset: CVDP moving_average_0001 failed G1 for that reason alone. verilog_plan.t27 records the rule (tie_folds): a tie of rst_n or en to true is folded out; a tie to false and a constant clk stay a wire the process reads. verilog_emit.t27 writes `always @(posedge clk)` without the reset branch when rst_n is tied on, drops `if (en)` when en is tied on, and writes no net for a folded tie (every other read of a constant is already a literal). Registers keep their reset values from `initial`. Tests: a_constant_rst_n_and_en_are_folded_out_of_the_always_block, and the negative control an_rst_n_port_keeps_its_async_reset_and_a_false_en_keeps_its_wire. gen/rust regenerated with t27c gen-rust; VerilogEmit, VerilogPlan and AstShape (verilog_plan is in its closure) resealed. All on the Railway lab. Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/t27b_T27bAstShape.json | 6 +- .trinity/seals/t27b_T27bVerilogEmit.json | 20 +-- .trinity/seals/t27b_T27bVerilogPlan.json | 14 +- gen/rust/tri/t27b/verilog_emit.rs | 116 +++++++++++--- gen/rust/tri/t27b/verilog_plan.rs | 4 + specs/tri/t27b/verilog_emit.t27 | 184 ++++++++++++++++++++--- specs/tri/t27b/verilog_plan.t27 | 17 +++ 7 files changed, 305 insertions(+), 56 deletions(-) diff --git a/.trinity/seals/t27b_T27bAstShape.json b/.trinity/seals/t27b_T27bAstShape.json index 81bcad4740..f10a59d35e 100644 --- a/.trinity/seals/t27b_T27bAstShape.json +++ b/.trinity/seals/t27b_T27bAstShape.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+cc19d1fcd", + "built_by": "t27c-bootstrap@0.5.2+f787afb", "closure": [ { "spec": "specs/tri/t27b/ast_scan.t27", @@ -7,7 +7,7 @@ }, { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:6178c276f6928f211b1cb65f470a93a5fcfbb11119bad7a26030072d777e8e70" + "spec_hash": "sha256:a52dbb4524173c54e19d205ee72b8a1544da978df82cc15a92341c7cad9ba428" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -18,7 +18,7 @@ "module": "T27bAstShape", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-11T10:43:29Z", + "sealed_at": "2026-10-17T17:33:08Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:fef40e3e7885265523c48d4a936c033bbc929b80dbf3ec06f79332dd63834134", "spec_path": "specs/tri/t27b/ast_shape.t27", diff --git a/.trinity/seals/t27b_T27bVerilogEmit.json b/.trinity/seals/t27b_T27bVerilogEmit.json index d77f2a085e..b53fe770c2 100644 --- a/.trinity/seals/t27b_T27bVerilogEmit.json +++ b/.trinity/seals/t27b_T27bVerilogEmit.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+cc19d1fcd", + "built_by": "t27c-bootstrap@0.5.2+f787afb", "closure": [ { "spec": "specs/tri/t27b/text_escape.t27", @@ -7,27 +7,27 @@ }, { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:6178c276f6928f211b1cb65f470a93a5fcfbb11119bad7a26030072d777e8e70" + "spec_hash": "sha256:a52dbb4524173c54e19d205ee72b8a1544da978df82cc15a92341c7cad9ba428" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:83ed268b2738067a1d1010e8eb36c4074d0aebe3b9151be66794e47af3d90710", - "gen_hash_rust": "sha256:b711c621b7719f9446912c05d02e7bd0e03f41bee71472a2fcb91f98c6c75392", - "gen_hash_verilog": "sha256:484bc265025e26176a53aceccf7b23f64ced954fcc3c2d46911c6469b53b7bfc", - "gen_hash_zig": "sha256:0803344590f689a6601e8ff7f60c76da2162437623e69262f29e46f1bb1fb360", + "gen_hash_c": "sha256:e898398e1e6f1541b034d7f93b45eca4341c961f5b37ba1e154c02b3272ece5b", + "gen_hash_rust": "sha256:b218d53be59a034da8a84565c204e96647bcb1b7af9bd400d448149a9904ae31", + "gen_hash_verilog": "sha256:17511cc822f43fc37f070e9d2178ffa1ff1f6ef1300134a38cb9bdcab4e03d49", + "gen_hash_zig": "sha256:a654b907033a49164f3eb19c4592c1cf58ce7d7b29230819258712cdb7a00e41", "module": "T27bVerilogEmit", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-11T10:43:31Z", + "sealed_at": "2026-10-17T17:33:08Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:0cc23245474d6ce568497b119e5cd719673c7487c3544817af8ac49c7f960aa6", + "spec_hash": "sha256:62dfd9b10d4cbcb90a55ca2c76c712c9b94ffd8a416020ac1f8ff980b930ca60", "spec_path": "specs/tri/t27b/verilog_emit.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 5, - "total": 5, + "passed": 7, + "total": 7, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/t27b_T27bVerilogPlan.json b/.trinity/seals/t27b_T27bVerilogPlan.json index 5b5a698bcf..5e92056a97 100644 --- a/.trinity/seals/t27b_T27bVerilogPlan.json +++ b/.trinity/seals/t27b_T27bVerilogPlan.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+67cbf8284", + "built_by": "t27c-bootstrap@0.5.2+f787afb", "closure": [ { "spec": "specs/tri/t27b/ast_walk.t27", @@ -7,16 +7,16 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:07009024c2ece655f0c4b8547b2973a7e0453d3b2518cdf00cb8d9cf5455f7fd", - "gen_hash_rust": "sha256:94d020775bc80ba2eadfa1548da37b070cb08f54ee766f294cef05a5f10752cf", - "gen_hash_verilog": "sha256:238ce5f1e1849efe809f4e5a3804c7407a42fd37a1304e801c1ecb50c7412822", - "gen_hash_zig": "sha256:91d40b10511d2e8569c8e2eea0dfb53bc4ab3ac2b3a8c83b88b2108c57f8bf4c", + "gen_hash_c": "sha256:88edabc42fca646e9a444d9f82370d78ecb67a5afa554f949f57fb4eb573a7f4", + "gen_hash_rust": "sha256:b02233d200b6cad6d160305768928911509d98199f0aee9d208bf9f14a1e9065", + "gen_hash_verilog": "sha256:c490c6a07fb43b4ae5fd69949d9725baf10e9332277667b7365ce040d605280d", + "gen_hash_zig": "sha256:30a577020d433ae2de867124d570b67a3f4799d03909c83c38f3f723a33d3a55", "module": "T27bVerilogPlan", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-10T23:14:12Z", + "sealed_at": "2026-10-17T17:33:05Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:6178c276f6928f211b1cb65f470a93a5fcfbb11119bad7a26030072d777e8e70", + "spec_hash": "sha256:a52dbb4524173c54e19d205ee72b8a1544da978df82cc15a92341c7cad9ba428", "spec_path": "specs/tri/t27b/verilog_plan.t27", "tests": { "failed": 0, diff --git a/gen/rust/tri/t27b/verilog_emit.rs b/gen/rust/tri/t27b/verilog_emit.rs index 5a74d9e7e4..8992e27097 100644 --- a/gen/rust/tri/t27b/verilog_emit.rs +++ b/gen/rust/tri/t27b/verilog_emit.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 5 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 7 test(s), 0 invariant(s), 0 function(s) with no body. // This backend emits declarations only. The spec's checks live in // the Zig and Verilog outputs; do not read this file as verified. @@ -628,6 +628,24 @@ pub fn has_net(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], name: &'sta return false; } +pub fn folded_tie(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], c: usize) -> bool { + if ((kind(nodes, c) != K_CONST_DECL) || (item_class(nodes, text, meta, ptext, c) != C_CONST)) { + return false; + } + return tie_folds(&text[(lo(nodes, c, F_NAME)) as usize..(hi(nodes, c, F_NAME)) as usize], fold_val(nodes, text, meta, ptext, 0, (c + 1), 0)); +} + +pub fn tied_on(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], name: &'static str) -> bool { + let mut c: usize = 1; + while (c < end(nodes, 0)) { + if ((kind(nodes, c) == K_CONST_DECL) && field_eq(nodes, text, c, F_NAME, name)) { + return folded_tie(nodes, text, meta, ptext, c); + } + c = end(nodes, c); + } + return false; +} + pub fn declares_ports(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8]) -> bool { let mut c: usize = 1; while (c < end(nodes, 0)) { @@ -2187,7 +2205,7 @@ pub fn module_text(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: & q = put_name(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); q = put_text(out, q, ";\n"); } - if (((cl == C_CONST) && implicit_port(&text[(lo(nodes, c, F_NAME)) as usize..(hi(nodes, c, F_NAME)) as usize])) && (oc != NO_NODE)) { + if ((((cl == C_CONST) && implicit_port(&text[(lo(nodes, c, F_NAME)) as usize..(hi(nodes, c, F_NAME)) as usize])) && (oc != NO_NODE)) && !(folded_tie(nodes, text, meta, ptext, c))) { q = put_text(out, q, " wire "); q = put_name(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); q = put_text(out, q, ";\n"); @@ -2215,7 +2233,7 @@ pub fn module_text(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: & q = (r2 as _); q = put_text(out, q, ";\n"); } - if (((cl1 == C_CONST) && implicit_port(&text[(lo(nodes, c1, F_NAME)) as usize..(hi(nodes, c1, F_NAME)) as usize])) && (oc != NO_NODE)) { + if ((((cl1 == C_CONST) && implicit_port(&text[(lo(nodes, c1, F_NAME)) as usize..(hi(nodes, c1, F_NAME)) as usize])) && (oc != NO_NODE)) && !(folded_tie(nodes, text, meta, ptext, c1))) { q = put_text(out, q, " assign "); q = put_name(out, q, text, lo(nodes, c1, F_NAME), hi(nodes, c1, F_NAME)); q = put_text(out, q, " = "); @@ -2276,29 +2294,49 @@ pub fn clocked(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8] return r1; } q = (r1 as _); - q = put_text(out, q, " always @(posedge clk or negedge rst_n) begin\n if (!rst_n) begin\n"); - let mut c: usize = 1; - while (c < end(nodes, 0)) { - if ((item_class(nodes, text, meta, ptext, c) == C_VAR) && !(ident_is(nodes, text, (c + 1), b"undefined"))) { - let t: i64 = sym_type(nodes, text, meta, ptext, 0, sym_of(SYM_TOP, c), 0); - q = put_text(out, q, " "); - q = put_name(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); - q = put_text(out, q, " <= "); - q = put_lit(out, q, fold_val(nodes, text, meta, ptext, 0, (c + 1), 0), t); - q = put_text(out, q, ";\n"); + let rf: bool = tied_on(nodes, text, meta, ptext, "rst_n"); + let ef: bool = tied_on(nodes, text, meta, ptext, "en"); + let mut ind: usize = 12; + if rf { + q = put_text(out, q, " always @(posedge clk) begin\n"); + if ef { + ind = 8; + } else { + q = put_text(out, q, " if (en) begin\n"); + } + } else { + q = put_text(out, q, " always @(posedge clk or negedge rst_n) begin\n if (!rst_n) begin\n"); + let mut c: usize = 1; + while (c < end(nodes, 0)) { + if ((item_class(nodes, text, meta, ptext, c) == C_VAR) && !(ident_is(nodes, text, (c + 1), b"undefined"))) { + let t: i64 = sym_type(nodes, text, meta, ptext, 0, sym_of(SYM_TOP, c), 0); + q = put_text(out, q, " "); + q = put_name(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, " <= "); + q = put_lit(out, q, fold_val(nodes, text, meta, ptext, 0, (c + 1), 0), t); + q = put_text(out, q, ";\n"); + } + c = end(nodes, c); + } + if ef { + q = put_text(out, q, " end else begin\n"); + } else { + q = put_text(out, q, " end else if (en) begin\n"); } - c = end(nodes, c); } - q = put_text(out, q, " end else if (en) begin\n"); if gd { - q = put_text(out, q, " t27_oc_done = 1'b0;\n"); + q = put_ind(out, q, ind); + q = put_text(out, q, "t27_oc_done = 1'b0;\n"); } - let r2: i64 = stmts(out, q, nodes, text, meta, ptext, work, oc, M_CLOCK, oc, (oc + 1), 12, 0, gd); + let r2: i64 = stmts(out, q, nodes, text, meta, ptext, work, oc, M_CLOCK, oc, (oc + 1), ind, 0, gd); if (r2 < 0) { return r2; } q = (r2 as _); - q = put_text(out, q, " end\n end\n"); + if (ind == 12) { + q = put_text(out, q, " end\n"); + } + q = put_text(out, q, " end\n"); let rc: i64 = (q as _); return rc; } @@ -2525,6 +2563,44 @@ pub fn holds(out: &[u8], n: usize, w: &'static str) -> bool { return false; } +pub fn tied_counter(nb: &mut [u8], mb: &mut [u8], rv_in: bool, rv_lo: usize, rv_hi: usize, ev_lo: usize, ev_hi: usize) -> () { + tnode(nb, 0, K_MODULE, 13, 0, 1); + tnode(nb, 1, K_CONST_DECL, 3, 1, 2); + vset(nb, 1, F_TYPE, 2, 4); + tnode(nb, 2, K_EXPR_LITERAL, 3, 0, 0); + vset(nb, 2, F_VALUE, 4, 5); + tnode(nb, 3, K_CONST_DECL, 5, 15, 20); + vset(nb, 3, F_TYPE, 20, 24); + if rv_in { + tnode(nb, 4, K_EXPR_IDENTIFIER, 5, rv_lo, rv_hi); + } else { + tnode(nb, 4, K_EXPR_LITERAL, 5, 0, 0); + vset(nb, 4, F_VALUE, rv_lo, rv_hi); + } + tnode(nb, 5, K_CONST_DECL, 7, 28, 30); + vset(nb, 5, F_TYPE, 20, 24); + tnode(nb, 6, K_EXPR_LITERAL, 7, 0, 0); + vset(nb, 6, F_VALUE, ev_lo, ev_hi); + tnode(nb, 7, K_FN_DECL, 13, 7, 15); + tnode(nb, 8, K_STMT_ASSIGN, 13, 0, 0); + tnode(nb, 9, K_EXPR_IDENTIFIER, 10, 1, 2); + tnode(nb, 10, K_EXPR_BINARY, 13, 0, 0); + vset(nb, 10, F_OP, 6, 7); + tnode(nb, 11, K_EXPR_IDENTIFIER, 12, 1, 2); + tnode(nb, 12, K_EXPR_LITERAL, 13, 0, 0); + vset(nb, 12, F_VALUE, 5, 6); + let mut i: usize = 0; + while (i < 13) { + tmeta(mb, i, 0, 0); + i = (i + 1); + } + tmeta(mb, 1, FLAG_MUT, 2); + tmeta(mb, 3, 0, 3); + tmeta(mb, 5, 0, 4); + tmeta(mb, 7, 0, 5); + tmeta(mb, 8, 0, 5); +} + pub const B_DOT: u8 = 46; pub const FIELDS: usize = 8; @@ -3459,6 +3535,10 @@ pub fn steps_up(nodes: &[u8], text: &[u8], s: usize, va: usize, vb: usize) -> i6 return cc; } +pub fn tie_folds(s: &[u8], v: u64) -> bool { + return ((same(s, "rst_n") || same(s, "en")) && (v != 0)); +} + pub fn trip_bound(op: &[u8], kok: bool, k: u64, sok: bool, step: u64) -> i64 { if (((!(kok) || !(sok)) || (step == 0)) || (k > 4611686018427387904)) { return NO_BOUND; diff --git a/gen/rust/tri/t27b/verilog_plan.rs b/gen/rust/tri/t27b/verilog_plan.rs index 5ea87d5748..8ddf9551a4 100644 --- a/gen/rust/tri/t27b/verilog_plan.rs +++ b/gen/rust/tri/t27b/verilog_plan.rs @@ -601,6 +601,10 @@ pub fn implicit_port(s: &[u8]) -> bool { return ((same(s, "clk") || same(s, "rst_n")) || same(s, "en")); } +pub fn tie_folds(s: &[u8], v: u64) -> bool { + return ((same(s, "rst_n") || same(s, "en")) && (v != 0)); +} + pub const ROLE_BAD: u8 = 0; pub const ROLE_PORT: u8 = 1; diff --git a/specs/tri/t27b/verilog_emit.t27 b/specs/tri/t27b/verilog_emit.t27 index 2ff7483153..9dffb29de0 100644 --- a/specs/tri/t27b/verilog_emit.t27 +++ b/specs/tri/t27b/verilog_emit.t27 @@ -571,6 +571,25 @@ pub fn has_net(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []c return false; } +/// Is top-level node c a constant tie of `rst_n` or `en` that is folded out of the clocked process +/// (verilog_plan.t27's tie_folds, #10046)? No net of that name is then written. +pub fn folded_tie(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, c: usize) -> bool { + if (kind(nodes, c) != K_CONST_DECL or item_class(nodes, text, meta, ptext, c) != C_CONST) { return false; } + return tie_folds(text[lo(nodes, c, F_NAME) .. hi(nodes, c, F_NAME)], fold_val(nodes, text, meta, ptext, 0, c + 1, 0)); +} + +/// Is the implicit port `name` tied to a constant that is folded out of the clocked process? +pub fn tied_on(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, name: str) -> bool { + var c : usize = 1; + while (c < end(nodes, 0)) { + if (kind(nodes, c) == K_CONST_DECL and field_eq(nodes, text, c, F_NAME, name)) { + return folded_tie(nodes, text, meta, ptext, c); + } + c = end(nodes, c); + } + return false; +} + /// Does the module declare its outputs: does it mark a var or a net `pub`, or instantiate an extern (a module /// t27c's gen-verilog cannot write, so its rule does not bind it)? A module that does names exactly its pub /// vars and nets as outputs, none if it has none; any other module has every var as an output (t27c's rule). @@ -1949,8 +1968,9 @@ pub fn module_text(out: []u8, at: usize, nodes: []const u8, text: []const u8, me q = put_name(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); q = put_text(out, q, ";\n"); } - if (cl == C_CONST and implicit_port(text[lo(nodes, c, F_NAME) .. hi(nodes, c, F_NAME)]) and oc != NO_NODE) { - // a constant clk, rst_n or en replaces that port: a wire tied to its value + if (cl == C_CONST and implicit_port(text[lo(nodes, c, F_NAME) .. hi(nodes, c, F_NAME)]) and oc != NO_NODE and !folded_tie(nodes, text, meta, ptext, c)) { + // a constant clk, rst_n or en replaces that port: a wire tied to its value, unless the + // clocked process folds it away (an rst_n or en tied to true reads nowhere, #10046) q = put_text(out, q, " wire "); q = put_name(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); q = put_text(out, q, ";\n"); @@ -1974,7 +1994,7 @@ pub fn module_text(out: []u8, at: usize, nodes: []const u8, text: []const u8, me q = @intCast(r2); q = put_text(out, q, ";\n"); } - if (cl1 == C_CONST and implicit_port(text[lo(nodes, c1, F_NAME) .. hi(nodes, c1, F_NAME)]) and oc != NO_NODE) { + if (cl1 == C_CONST and implicit_port(text[lo(nodes, c1, F_NAME) .. hi(nodes, c1, F_NAME)]) and oc != NO_NODE and !folded_tie(nodes, text, meta, ptext, c1)) { q = put_text(out, q, " assign "); q = put_name(out, q, text, lo(nodes, c1, F_NAME), hi(nodes, c1, F_NAME)); q = put_text(out, q, " = "); @@ -2019,6 +2039,8 @@ pub fn module_text(out: []u8, at: usize, nodes: []const u8, text: []const u8, me /// Writes on_clock: its locals and flags as module registers, then /// `always @(posedge clk or negedge rst_n) if (!rst_n) else if (en) `. +/// An rst_n tied to true drops `or negedge rst_n` and the reset branch, an en tied to true drops +/// `if (en)` (verilog_plan.t27's tie_folds, #10046); the registers keep their reset values from `initial`. pub fn clocked(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, oc: usize) -> i64 { var q : usize = put_text(out, at, "\n // on_clock (line "); q = put_dec(out, q, line_of(meta, oc) as u64); @@ -2028,25 +2050,37 @@ pub fn clocked(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: const r1 : i64 = decls(out, q, nodes, text, meta, ptext, oc, M_CLOCK, 4); if (r1 < 0) { return r1; } q = @intCast(r1); - q = put_text(out, q, " always @(posedge clk or negedge rst_n) begin\n if (!rst_n) begin\n"); - var c : usize = 1; - while (c < end(nodes, 0)) { - if (item_class(nodes, text, meta, ptext, c) == C_VAR and !ident_is(nodes, text, c + 1, "undefined")) { - const t : i64 = sym_type(nodes, text, meta, ptext, 0, sym_of(SYM_TOP, c), 0); - q = put_text(out, q, " "); - q = put_name(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); - q = put_text(out, q, " <= "); - q = put_lit(out, q, fold_val(nodes, text, meta, ptext, 0, c + 1, 0), t); - q = put_text(out, q, ";\n"); + const rf : bool = tied_on(nodes, text, meta, ptext, "rst_n"); + const ef : bool = tied_on(nodes, text, meta, ptext, "en"); + var ind : usize = 12; + if (rf) { + q = put_text(out, q, " always @(posedge clk) begin\n"); + if (ef) { ind = 8; } else { q = put_text(out, q, " if (en) begin\n"); } + } else { + q = put_text(out, q, " always @(posedge clk or negedge rst_n) begin\n if (!rst_n) begin\n"); + var c : usize = 1; + while (c < end(nodes, 0)) { + if (item_class(nodes, text, meta, ptext, c) == C_VAR and !ident_is(nodes, text, c + 1, "undefined")) { + const t : i64 = sym_type(nodes, text, meta, ptext, 0, sym_of(SYM_TOP, c), 0); + q = put_text(out, q, " "); + q = put_name(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, " <= "); + q = put_lit(out, q, fold_val(nodes, text, meta, ptext, 0, c + 1, 0), t); + q = put_text(out, q, ";\n"); + } + c = end(nodes, c); } - c = end(nodes, c); + if (ef) { q = put_text(out, q, " end else begin\n"); } else { q = put_text(out, q, " end else if (en) begin\n"); } + } + if (gd) { + q = put_ind(out, q, ind); + q = put_text(out, q, "t27_oc_done = 1'b0;\n"); } - q = put_text(out, q, " end else if (en) begin\n"); - if (gd) { q = put_text(out, q, " t27_oc_done = 1'b0;\n"); } - const r2 : i64 = stmts(out, q, nodes, text, meta, ptext, work, oc, M_CLOCK, oc, oc + 1, 12, 0, gd); + const r2 : i64 = stmts(out, q, nodes, text, meta, ptext, work, oc, M_CLOCK, oc, oc + 1, ind, 0, gd); if (r2 < 0) { return r2; } q = @intCast(r2); - q = put_text(out, q, " end\n end\n"); + if (ind == 12) { q = put_text(out, q, " end\n"); } + q = put_text(out, q, " end\n"); const rc : i64 = @intCast(q); return rc; } @@ -2343,6 +2377,120 @@ test a_clocked_counter_lowers_to_a_register_and_an_always_block { tmeta(mb[0 .. 144], 1, FLAG_MUT, 2); } +/// The counter of the test above with its rst_n and en declared at the top, as nodes 3 to 6: +/// `const rst_n : bool = ; const en : bool = ;`. Node 4 is a literal at rv_lo..rv_hi, or the +/// identifier `undefined` (an input port) when rv_in; node 6 is a literal at ev_lo..ev_hi. +fn tied_counter(nb: []u8, mb: []u8, rv_in: bool, rv_lo: usize, rv_hi: usize, ev_lo: usize, ev_hi: usize) -> void { + // text: M 0..1, x 1..2, u8 2..4, 0 4..5, 1 5..6, + 6..7, on_clock 7..15, rst_n 15..20, bool 20..24, + // true 24..28, en 28..30, undefined 30..39, false 39..44 + tnode(nb, 0, K_MODULE, 13, 0, 1); + tnode(nb, 1, K_CONST_DECL, 3, 1, 2); + vset(nb, 1, F_TYPE, 2, 4); + tnode(nb, 2, K_EXPR_LITERAL, 3, 0, 0); + vset(nb, 2, F_VALUE, 4, 5); + tnode(nb, 3, K_CONST_DECL, 5, 15, 20); + vset(nb, 3, F_TYPE, 20, 24); + if (rv_in) { + tnode(nb, 4, K_EXPR_IDENTIFIER, 5, rv_lo, rv_hi); + } else { + tnode(nb, 4, K_EXPR_LITERAL, 5, 0, 0); + vset(nb, 4, F_VALUE, rv_lo, rv_hi); + } + tnode(nb, 5, K_CONST_DECL, 7, 28, 30); + vset(nb, 5, F_TYPE, 20, 24); + tnode(nb, 6, K_EXPR_LITERAL, 7, 0, 0); + vset(nb, 6, F_VALUE, ev_lo, ev_hi); + tnode(nb, 7, K_FN_DECL, 13, 7, 15); + tnode(nb, 8, K_STMT_ASSIGN, 13, 0, 0); + tnode(nb, 9, K_EXPR_IDENTIFIER, 10, 1, 2); + tnode(nb, 10, K_EXPR_BINARY, 13, 0, 0); + vset(nb, 10, F_OP, 6, 7); + tnode(nb, 11, K_EXPR_IDENTIFIER, 12, 1, 2); + tnode(nb, 12, K_EXPR_LITERAL, 13, 0, 0); + vset(nb, 12, F_VALUE, 5, 6); + var i : usize = 0; + while (i < 13) { + tmeta(mb, i, 0, 0); + i = i + 1; + } + tmeta(mb, 1, FLAG_MUT, 2); + tmeta(mb, 3, 0, 3); + tmeta(mb, 5, 0, 4); + tmeta(mb, 7, 0, 5); + tmeta(mb, 8, 0, 5); +} + +test a_constant_rst_n_and_en_are_folded_out_of_the_always_block { + // module M { var x : u8 = 0; const rst_n : bool = true; const en : bool = true; + // fn on_clock() { x = x + 1; } } (#10046) + const tx : str = "Mxu801+on_clockrst_nbooltrueenundefinedfalse"; + var nb : [936]u8 = undefined; + var mb : [208]u8 = undefined; + tied_counter(nb[0 .. 936], mb[0 .. 208], false, 24, 28, 24, 28); + var wk : [96]u8 = undefined; + var i : usize = 0; + while (i < 96) { + wk[i] = 0; + i = i + 1; + } + var out : [4096]u8 = undefined; + const r : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "", wk[0 .. 96], out[0 .. 4096]); + assert r > 0; + const n : usize = @intCast(r); + assert holds(out[0 .. 4096], n, "input wire clk"); + assert holds(out[0 .. 4096], n, "always @(posedge clk) begin"); + assert holds(out[0 .. 4096], n, "x <= {x + 8'd1};"); + assert holds(out[0 .. 4096], n, "x = 8'd0;"); + // the process reads neither tie, and no net, port or reset branch is left that names one + assert !holds(out[0 .. 4096], n, "rst_n"); + assert !holds(out[0 .. 4096], n, "(en)"); + assert !holds(out[0 .. 4096], n, "wire en"); + assert !holds(out[0 .. 4096], n, "assign en"); + assert !holds(out[0 .. 4096], n, "x <= 8'd0;"); + assert !holds(out[0 .. 4096], n, "else"); + // rst_n alone tied to true: no reset branch, but the enable is still read + tied_counter(nb[0 .. 936], mb[0 .. 208], false, 24, 28, 39, 44); + i = 0; + while (i < 96) { + wk[i] = 0; + i = i + 1; + } + const r2 : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "", wk[0 .. 96], out[0 .. 4096]); + assert r2 > 0; + const n2 : usize = @intCast(r2); + assert holds(out[0 .. 4096], n2, "always @(posedge clk) begin"); + assert holds(out[0 .. 4096], n2, " if (en) begin"); + assert !holds(out[0 .. 4096], n2, "rst_n"); +} + +test an_rst_n_port_keeps_its_async_reset_and_a_false_en_keeps_its_wire { + // negative control of the fold: module M { var x : u8 = 0; const rst_n : bool = undefined; + // const en : bool = false; fn on_clock() { x = x + 1; } }: rst_n is the async active-low input + // port, and an en tied to false is still a net the process reads (it is never enabled) + const tx : str = "Mxu801+on_clockrst_nbooltrueenundefinedfalse"; + var nb : [936]u8 = undefined; + var mb : [208]u8 = undefined; + tied_counter(nb[0 .. 936], mb[0 .. 208], true, 30, 39, 39, 44); + var wk : [96]u8 = undefined; + var i : usize = 0; + while (i < 96) { + wk[i] = 0; + i = i + 1; + } + var out : [4096]u8 = undefined; + const r : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "", wk[0 .. 96], out[0 .. 4096]); + assert r > 0; + const n : usize = @intCast(r); + assert holds(out[0 .. 4096], n, "input wire rst_n"); + assert holds(out[0 .. 4096], n, "always @(posedge clk or negedge rst_n) begin"); + assert holds(out[0 .. 4096], n, "if (!rst_n) begin"); + assert holds(out[0 .. 4096], n, "x <= 8'd0;"); + assert holds(out[0 .. 4096], n, "end else if (en) begin"); + assert holds(out[0 .. 4096], n, "wire en;"); + assert holds(out[0 .. 4096], n, "assign en = 1'b0;"); + assert !holds(out[0 .. 4096], n, "input wire en"); +} + test a_call_of_an_undeclared_fn_is_refused_by_name { // module M { var mclk : bool = false; fn on_clock() { mclk = STARTUPE2(false); } } const tx : str = "Mmclkboolfalseon_clockSTARTUPE2"; diff --git a/specs/tri/t27b/verilog_plan.t27 b/specs/tri/t27b/verilog_plan.t27 index 655813964b..69a71d6392 100644 --- a/specs/tri/t27b/verilog_plan.t27 +++ b/specs/tri/t27b/verilog_plan.t27 @@ -24,6 +24,14 @@ ; - A NET is any other module-level `const` whose value is not a compile-time constant: `const clk : bool = ; gclk.O;`. A net named `clk`, `rst_n` or `en` replaces that implicit port of a clocked module. ; - A CONSTANT is a module-level `const` with a compile-time value; every use is folded to a sized literal. +; A CONSTANT named `clk`, `rst_n` or `en` ties that implicit port of a clocked module off (#10046). A tie +; of `rst_n` or `en` to `true` is folded out of the clocked process: no `or negedge rst_n` and no +; `if (!rst_n)` reset branch for rst_n, no `if (en)` for en, and no net of that name, since every other +; use is a literal already (tie_folds). The registers still start at their reset values (`initial`). A +; tie to `false` (held in reset, never enabled) and a tied `clk` stay a wire with an `assign`, because +; the process still reads them. Why: a test bench that writes every handle at start-up (cocotb's +; `dut_init` writes 0) deposits 0 on a tied net, and its constant driver never fires again, so the +; design sat in reset (CVDP moving_average_0001). ; - An INPUT is a module-level `const` whose value is `undefined`: a value this module does not define, which ; the hardware gives it (#8826). `const clk : bool = undefined;` names the implicit clk port, so an ; instance's connection can say `clk`; any other name is one more input port. @@ -527,6 +535,13 @@ pub fn implicit_port(s: []const u8) -> bool { return same(s, "clk") or same(s, "rst_n") or same(s, "en"); } +/// Is a constant tie of implicit port `s` to value v folded out of the clocked process (#10046)? Only +/// `rst_n` or `en` tied to `true`: the reset branch can never run and the enable always holds. A tie to +/// `false` and a constant `clk` stay a wire the process reads. +pub fn tie_folds(s: []const u8, v: u64) -> bool { + return (same(s, "rst_n") or same(s, "en")) and v != 0; +} + /// The role of an extern's parameter named `name` of type t. pub const ROLE_BAD : u8 = 0; pub const ROLE_PORT : u8 = 1; @@ -843,6 +858,8 @@ test a_parameter_binds_to_an_instance_output_by_name { assert !binds(s, 0, 8, s, 9, 14, 14, 17); assert binds("bscan_TCK", 0, 9, s, 9, 14, 14, 17); assert implicit_port("clk") and implicit_port("rst_n") and implicit_port("en") and !implicit_port("clk2"); + assert tie_folds("rst_n", 1) and tie_folds("en", 1); + assert !tie_folds("rst_n", 0) and !tie_folds("en", 0) and !tie_folds("clk", 1) and !tie_folds("reset", 1); assert port_name_end("clk_", 0, 4) == 3 and port_name_end("clk", 0, 3) == 3 and port_name_end("_", 0, 1) == 1; assert port_name_end("a__", 0, 3) == 2; }