diff --git a/.trinity/seals/conformance_T27bConformanceVerilogWideInout.json b/.trinity/seals/conformance_T27bConformanceVerilogWideInout.json new file mode 100644 index 0000000000..a3a1b0c732 --- /dev/null +++ b/.trinity/seals/conformance_T27bConformanceVerilogWideInout.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+a86b691b1", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:50a0075bdd13f6e9572ca66ac245bd675da8a8d3ee1da84e3518ea73a780adfd", + "gen_hash_rust": "sha256:bbb25f000ed2c0b24a09a3866c33e6b9b33f81a2f52426eb568e87996a6c5e3b", + "gen_hash_verilog": "sha256:118df03c79a82e30b43f16a1555d924e18d930f359ecce1112cfc835031fca25", + "gen_hash_zig": "sha256:0191fe8e0eb70d2b745bca90be258f2aa5755e5b3f29a9b38323884f94f69d78", + "module": "T27bConformanceVerilogWideInout", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T03:40:39Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:6f40fe75af01583676dbf42fdc00b1291a5f4e69bd939eeb408d46a8da85bc8c", + "spec_path": "specs/tri/t27b/conformance/verilog_wide_inout.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 3, + "total": 3, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27b_T27bVerilogEmit.json b/.trinity/seals/t27b_T27bVerilogEmit.json index d581e90647..f66efd15d9 100644 --- a/.trinity/seals/t27b_T27bVerilogEmit.json +++ b/.trinity/seals/t27b_T27bVerilogEmit.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+063da04", + "built_by": "t27c-bootstrap@0.5.2+a86b691b1", "closure": [ { "spec": "specs/tri/t27b/emit_kit; // byte writers, to_front, line_near, resolve and symbols, test trees.t27", @@ -11,27 +11,27 @@ }, { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:c7eb1749b322a318ec71a2544172783a77361cf9a2083b88fcc7afd3c5a44708" + "spec_hash": "sha256:e83cfc469873ff2317ab1ec4afcf775e576028e572adc232052dbfc46ee26507" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:d4a0c1d16b2e75c1d7028eae7133c8af8359b1e90ada94289435881e2c31a2dc", - "gen_hash_rust": "sha256:daa741c306509d8baabb4130273593065066784a63697e693d5287bca45ce7d8", - "gen_hash_verilog": "sha256:81f1f5903b2800ed2d684e7ceb986eb7106e8ec9f6978d329afe7313b1565a3c", - "gen_hash_zig": "sha256:abf0fbf7e8028b15f30438cae0a89bb32a2b5257576e7f1d2f1027958b1d6d6f", + "gen_hash_c": "sha256:3b1d0af3d223ece81d76f46e7a17a4b7be4870207c15fb7b8fa0e277a8735f82", + "gen_hash_rust": "sha256:1fe1d2a06eef20f16e92c0d521185806db1cbb60e5779335e0f37eb6a6689f7f", + "gen_hash_verilog": "sha256:587cc7d8f59751c39692c49ffe252663cfe9a204f24725325f92d628cf1aed3b", + "gen_hash_zig": "sha256:09c7cbba8fe1c04837f3561924bffefff20a77e1d1ee03a36fe900da1d52f3ff", "module": "T27bVerilogEmit", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-17T18:12:17Z", + "sealed_at": "2026-10-18T03:40:42Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:b28baac32b8d291619dfe2664fe4167287f6705ec901fa0e87200ee95523a84d", + "spec_hash": "sha256:4fadad47612defd88ad1e3e3160aac802ac5f9e0a122fc33f058105923a0c903", "spec_path": "specs/tri/t27b/verilog_emit.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 8, - "total": 8, + "passed": 9, + "total": 9, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/t27b_T27bVerilogPlan.json b/.trinity/seals/t27b_T27bVerilogPlan.json index 51eaafd264..5ba03ea3b7 100644 --- a/.trinity/seals/t27b_T27bVerilogPlan.json +++ b/.trinity/seals/t27b_T27bVerilogPlan.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+063da04", + "built_by": "t27c-bootstrap@0.5.2+a86b691b1", "closure": [ { "spec": "specs/tri/t27b/ast_walk.t27", @@ -7,23 +7,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:5b61c4481375289a5498e39d2f7ea3ea430fdc8d100fc0d6faa3428f67919518", - "gen_hash_rust": "sha256:5feafbe5476ebc8fb7f3abfb05bd0225bc8fd14c3f3bc6b479421845a052d422", - "gen_hash_verilog": "sha256:62cec2367aab19770b0e706753ee7905e30050c2fb52b807bfcf909cc6c674e0", - "gen_hash_zig": "sha256:3d3bc805bd698fc8e6470c867572bcdedf3d090f9f59bc5c8b1f1b3b760f2637", + "gen_hash_c": "sha256:5a6a8b00e27811871acf66e81b547b3ba1e7a81b2388f0faae2487575c151f01", + "gen_hash_rust": "sha256:828a636e0d7dd3c1814667902d86436433ab6cc6e58e4ed163ee1b4a431b4c37", + "gen_hash_verilog": "sha256:b053716c93b085c72fbefadead49d3371ec4c11f3fa8cc9affc711770ad6174f", + "gen_hash_zig": "sha256:29602f8d57a5df9a641adb65e9b96f119bf74b018f97d57e1d621e6f68ec1c14", "module": "T27bVerilogPlan", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-17T18:12:18Z", + "sealed_at": "2026-10-18T03:40:41Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:c7eb1749b322a318ec71a2544172783a77361cf9a2083b88fcc7afd3c5a44708", + "spec_hash": "sha256:e83cfc469873ff2317ab1ec4afcf775e576028e572adc232052dbfc46ee26507", "spec_path": "specs/tri/t27b/verilog_plan.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 16, - "total": 16, + "passed": 17, + "total": 17, "vacuous": 0 }, "toolchain": { diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index c95fff6bf1..87c12a66c8 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -1380,6 +1380,7 @@ {"path": "specs/tri/t27b/conformance/verilog_odd_cast.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/conformance/verilog_prim.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/conformance/verilog_use.t27", "t27b": "pass"}, + {"path": "specs/tri/t27b/conformance/verilog_wide_inout.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/conformance/verilog_widths.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/conformance/void_bind.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/conformance/wide_int_const.t27", "t27b": "pass"}, diff --git a/gen/rust/tri/t27b/verilog_emit.rs b/gen/rust/tri/t27b/verilog_emit.rs index 305e503502..0956b24566 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: 8 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 9 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. @@ -400,6 +400,9 @@ pub fn item_class(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], c: usize if is_mut(meta, c) { return C_VAR; } + if is_inout(nodes, text, meta, c) { + return C_INOUT; + } if is_input(nodes, text, c) { return C_INPUT; } @@ -736,7 +739,11 @@ pub fn ex(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], pte return fail(out, nodes, meta, text, i, "module var read inside a fn", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } } else { - return fail(out, nodes, meta, text, i, "name", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + if (cl == C_INOUT) { + return fail(out, nodes, meta, text, i, "inout read anywhere but as an instance's port", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } else { + return fail(out, nodes, meta, text, i, "name", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); + } } } } @@ -1748,6 +1755,23 @@ pub fn ports(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], } ci = end(nodes, ci); } + let mut co: usize = 1; + while (co < end(nodes, 0)) { + if (item_class(nodes, text, meta, ptext, co) == C_INOUT) { + let iot: i64 = sym_type(nodes, text, meta, ptext, 0, sym_of(SYM_TOP, co), 0); + if !(ty_scalar(iot)) { + return fail(out, nodes, meta, text, co, "inout that is no integer or bool", lo(nodes, co, F_NAME), hi(nodes, co, F_NAME)); + } + if !(first) { + q = put_text(out, q, ","); + } + q = put_text(out, q, "\n inout wire "); + q = put_range(out, q, iot); + q = put_name(out, q, text, lo(nodes, co, F_NAME), hi(nodes, co, F_NAME)); + first = false; + } + co = end(nodes, co); + } let dp: bool = declares_ports(nodes, text, meta, ptext); let mut c: usize = 1; while (c < end(nodes, 0)) { @@ -1830,11 +1854,11 @@ pub fn put_instance(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: let pb: usize = param_name_hi(meta, ptext, fi, kx); let role: u8 = param_role(&ptext[(pa) as usize..(pb) as usize], param_type(nodes, text, meta, ptext, fi, kx)); if (role == ROLE_BAD) { - return fail(out, nodes, meta, text, call, "extern parameter of a type with no role", pa, pb); + return fail(out, nodes, meta, ptext, call, "extern parameter of a type with no role", pa, pb); } if (role == ROLE_ATTR) { if !(fold_ok(nodes, text, meta, ptext, 0, a, 0)) { - return fail(out, nodes, meta, text, call, "attribute that is no constant", pa, pb); + return fail(out, nodes, meta, ptext, call, "attribute that is no constant", pa, pb); } if (fold_val(nodes, text, meta, ptext, 0, a, 0) == 1) { if (nattr == 0) { @@ -1894,23 +1918,57 @@ pub fn put_instance(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: let pa3: usize = param_name_lo(meta, ptext, fi, kx); let pb3: usize = param_name_hi(meta, ptext, fi, kx); let pt3: i64 = param_type(nodes, text, meta, ptext, fi, kx); + let mut nl: usize = 1; if (param_role(&ptext[(pa3) as usize..(pb3) as usize], pt3) == ROLE_PORT) { if (nport > 0) { q = put_text(out, q, ","); } q = put_text(out, q, "\n ."); - q = put_bytes(out, q, ptext, pa3, port_name_end(ptext, pa3, pb3)); - q = put_text(out, q, "("); - let r2: i64 = ex(out, q, nodes, text, meta, ptext, work, 0, M_TOP, a, pt3); - if (r2 < 0) { - return r2; + let lp: i64 = lane_at(ptext, pa3, pb3); + if (lp == NO_LANE) { + q = put_bytes(out, q, ptext, pa3, port_name_end(ptext, pa3, pb3)); + q = put_text(out, q, "("); + let r2: i64 = port_arg(out, q, nodes, text, meta, ptext, work, a, pt3); + if (r2 < 0) { + return r2; + } + q = (r2 as _); + q = put_text(out, q, ")"); + } else { + let lpu: usize = (lp as _); + if (lane_index(ptext, lpu, pb3) != 0) { + return fail(out, nodes, meta, ptext, call, "lane that does not follow its lane 0 in order", pa3, pb3); + } + while (((kx + nl) < np) && is_lane_of(ptext, param_name_lo(meta, ptext, fi, (kx + nl)), param_name_hi(meta, ptext, fi, (kx + nl)), ptext, pa3, lpu, (nl as u64))) { + if (param_type(nodes, text, meta, ptext, fi, (kx + nl)) != pt3) { + return fail(out, nodes, meta, ptext, call, "lane of another type than its lane 0", param_name_lo(meta, ptext, fi, (kx + nl)), param_name_hi(meta, ptext, fi, (kx + nl))); + } + nl = (nl + 1); + } + q = put_bytes(out, q, ptext, pa3, lpu); + q = put_text(out, q, "({"); + let mut j: usize = nl; + while (j > 0) { + j = (j - 1); + if ((j + 1) < nl) { + q = put_text(out, q, ", "); + } + let r3: i64 = port_arg(out, q, nodes, text, meta, ptext, work, sibling(nodes, a, j), pt3); + if (r3 < 0) { + return r3; + } + q = (r3 as _); + } + q = put_text(out, q, "})"); } - q = (r2 as _); - q = put_text(out, q, ")"); nport = (nport + 1); } - a = end(nodes, a); - kx = (kx + 1); + let mut s: usize = 0; + while (s < nl) { + a = end(nodes, a); + kx = (kx + 1); + s = (s + 1); + } } if (sd != NO_NODE) { let sdj: usize = (sd as _); @@ -1920,14 +1978,52 @@ pub fn put_instance(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: q = put_text(out, q, ","); } q = put_text(out, q, "\n ."); - q = put_bytes(out, q, text, lo(nodes, fo, F_NAME), hi(nodes, fo, F_NAME)); - q = put_text(out, q, "("); - q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); - q = put_text(out, q, "__"); - q = put_bytes(out, q, text, lo(nodes, fo, F_NAME), hi(nodes, fo, F_NAME)); - q = put_text(out, q, ")"); + let flo: usize = lo(nodes, fo, F_NAME); + let fhi: usize = hi(nodes, fo, F_NAME); + let fl: i64 = lane_at(text, flo, fhi); + let mut nf: usize = 1; + if (fl == NO_LANE) { + q = put_bytes(out, q, text, flo, fhi); + q = put_text(out, q, "("); + q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, "__"); + q = put_bytes(out, q, text, flo, fhi); + q = put_text(out, q, ")"); + } else { + let flu: usize = (fl as _); + if (lane_index(text, flu, fhi) != 0) { + return fail(out, nodes, meta, text, c, "lane that does not follow its lane 0 in order", flo, fhi); + } + let ft: i64 = type_code(text, lo(nodes, fo, F_TYPE), hi(nodes, fo, F_TYPE)); + let mut fx: usize = end(nodes, fo); + while ((fx < end(nodes, sdj)) && is_lane_of(text, lo(nodes, fx, F_NAME), hi(nodes, fx, F_NAME), text, flo, flu, (nf as u64))) { + if (type_code(text, lo(nodes, fx, F_TYPE), hi(nodes, fx, F_TYPE)) != ft) { + return fail(out, nodes, meta, text, c, "lane of another type than its lane 0", lo(nodes, fx, F_NAME), hi(nodes, fx, F_NAME)); + } + nf = (nf + 1); + fx = end(nodes, fx); + } + q = put_bytes(out, q, text, flo, flu); + q = put_text(out, q, "({"); + let mut jf: usize = nf; + while (jf > 0) { + jf = (jf - 1); + if ((jf + 1) < nf) { + q = put_text(out, q, ", "); + } + let fj: usize = sibling(nodes, fo, jf); + q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, "__"); + q = put_bytes(out, q, text, lo(nodes, fj, F_NAME), hi(nodes, fj, F_NAME)); + } + q = put_text(out, q, "})"); + } nport = (nport + 1); - fo = end(nodes, fo); + let mut sf: usize = 0; + while (sf < nf) { + fo = end(nodes, fo); + sf = (sf + 1); + } } } q = put_text(out, q, ");\n"); @@ -1935,6 +2031,34 @@ pub fn put_instance(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: return ri; } +pub fn sibling(nodes: &[u8], a: usize, j: usize) -> usize { + let mut s: usize = a; + let mut k: usize = 0; + while (k < j) { + s = end(nodes, s); + k = (k + 1); + } + return s; +} + +pub fn port_arg(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], a: usize, t: i64) -> i64 { + if (kind(nodes, a) == K_EXPR_IDENTIFIER) { + let d: i64 = find_decl(nodes, text, lo(nodes, a, F_NAME), hi(nodes, a, F_NAME)); + if (d != NO_NODE) { + let du: usize = (d as _); + if (item_class(nodes, text, meta, ptext, du) == C_INOUT) { + if (sym_type(nodes, text, meta, ptext, 0, sym_of(SYM_TOP, du), 0) != t) { + return fail(out, nodes, meta, text, a, "inout of another type than the port it is handed to", lo(nodes, a, F_NAME), hi(nodes, a, F_NAME)); + } + let q: usize = put_name(out, at, text, lo(nodes, a, F_NAME), hi(nodes, a, F_NAME)); + let rq: i64 = (q as _); + return rq; + } + } + } + return ex(out, at, nodes, text, meta, ptext, work, 0, M_TOP, a, t); +} + pub fn param_value(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], a: usize, t: i64) -> i64 { let mut v: usize = a; if (kind(nodes, v) == K_EXPR_IDENTIFIER) { @@ -2954,6 +3078,8 @@ pub const C_EXTERN: u8 = 5; pub const C_FN: u8 = 6; +pub const C_INOUT: u8 = 11; + pub const C_INPUT: u8 = 10; pub const C_INSTANCE: u8 = 3; @@ -2972,6 +3098,8 @@ pub const FLAG_PUB: usize = 2; pub const KIND_NAMES: [&'static str; 35] = ["Module", "UseDecl", "ConstDecl", "EnumDecl", "EnumVariant", "StructDecl", "FnDecl", "InvariantBlock", "TestBlock", "BenchBlock", "ExprLiteral", "ExprIdentifier", "ExprEnumValue", "ExprCall", "ExprFieldAccess", "ExprSwitch", "ExprBinary", "ExprUnary", "ExprReturn", "ExprIndex", "ExprIf", "ExprStructLit", "ExprArrayLiteral", "ExprTuple", "ExprCast", "ExprRange", "StmtLocal", "StmtAssign", "StmtIf", "StmtWhile", "StmtFor", "StmtForRange", "StmtBreak", "StmtContinue", "StmtExpr"]; +pub const MAX_LANES: u64 = 64; + pub const MAX_UNROLL: i64 = 4096; pub const MAX_WIDTH: i64 = 64; @@ -2980,6 +3108,8 @@ pub const META: usize = 16; pub const NO_BOUND: i64 = -1; +pub const NO_LANE: i64 = -1; + pub const OP_ARITH: u8 = 1; pub const OP_BAD: u8 = 0; @@ -3209,6 +3339,10 @@ pub fn is_extern(nodes: &[u8], i: usize) -> bool { return ((kind(nodes, i) == K_FN_DECL) && (end(nodes, i) == (i + 1))); } +pub fn is_inout(nodes: &[u8], text: &[u8], meta: &[u8], i: usize) -> bool { + return (is_input(nodes, text, i) && is_pub(meta, i)); +} + pub fn is_input(nodes: &[u8], text: &[u8], i: usize) -> bool { if (((kind(nodes, i) != K_CONST_DECL) || ((i + 1) >= end(nodes, i))) || (end(nodes, (i + 1)) != end(nodes, i))) { return false; @@ -3231,6 +3365,18 @@ pub fn is_instance(nodes: &[u8], text: &[u8], meta: &[u8], i: usize) -> bool { return is_extern(nodes, fi); } +pub fn is_lane_of(s: &[u8], a: usize, b: usize, t: &[u8], c: usize, d: usize, k: u64) -> bool { + let p: i64 = lane_at(s, a, b); + if (p == NO_LANE) { + return false; + } + let pu: usize = (p as _); + if (((pu - a) != (d - c)) || !(has_at(s, a, pu, &t[(c) as usize..(d) as usize]))) { + return false; + } + return (lane_index(s, pu, b) == k); +} + pub fn is_mut(meta: &[u8], i: usize) -> bool { return ((meta_word(meta, i, 0) & FLAG_MUT) == FLAG_MUT); } @@ -3255,6 +3401,44 @@ pub fn kind_label(k: u8) -> &'static str { return "node"; } +pub fn lane_at(s: &[u8], a: usize, b: usize) -> i64 { + let mut j: usize = b; + while (((j > a) && (s[((j - 1)) as usize] >= 48)) && (s[((j - 1)) as usize] <= 57)) { + j = (j - 1); + } + if ((j == b) || ((b - j) > 2)) { + return NO_LANE; + } + if (((b - j) == 2) && (s[(j) as usize] == 48)) { + return NO_LANE; + } + if (j < (a + 3)) { + return NO_LANE; + } + if ((s[((j - 1)) as usize] != 95) || (s[((j - 2)) as usize] != 95)) { + return NO_LANE; + } + if (lane_digits(s, j, b) >= MAX_LANES) { + return NO_LANE; + } + let r: i64 = ((j - 2) as _); + return r; +} + +pub fn lane_digits(s: &[u8], a: usize, b: usize) -> u64 { + let mut v: u64 = 0; + let mut j: usize = a; + while (j < b) { + v = ((v * 10) + ((s[(j) as usize] - 48) as u64)); + j = (j + 1); + } + return v; +} + +pub fn lane_index(s: &[u8], p: usize, b: usize) -> u64 { + return lane_digits(s, (p + 2), b); +} + pub fn line_of(meta: &[u8], i: usize) -> usize { return meta_word(meta, i, 1); } diff --git a/gen/rust/tri/t27b/verilog_plan.rs b/gen/rust/tri/t27b/verilog_plan.rs index 4ece161f37..371b4b6e4e 100644 --- a/gen/rust/tri/t27b/verilog_plan.rs +++ b/gen/rust/tri/t27b/verilog_plan.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 16 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 17 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. @@ -512,6 +512,12 @@ pub fn is_input(nodes: &[u8], text: &[u8], i: usize) -> bool { return ident_is(nodes, text, (i + 1), b"undefined"); } +pub const C_INOUT: u8 = 11; + +pub fn is_inout(nodes: &[u8], text: &[u8], meta: &[u8], i: usize) -> bool { + return (is_input(nodes, text, i) && is_pub(meta, i)); +} + pub fn is_extern(nodes: &[u8], i: usize) -> bool { return ((kind(nodes, i) == K_FN_DECL) && (end(nodes, i) == (i + 1))); } @@ -633,6 +639,60 @@ pub fn port_name_end(ps: &[u8], a: usize, b: usize) -> usize { return b; } +pub const NO_LANE: i64 = -1; + +pub const MAX_LANES: u64 = 64; + +pub fn lane_at(s: &[u8], a: usize, b: usize) -> i64 { + let mut j: usize = b; + while (((j > a) && (s[((j - 1)) as usize] >= 48)) && (s[((j - 1)) as usize] <= 57)) { + j = (j - 1); + } + if ((j == b) || ((b - j) > 2)) { + return NO_LANE; + } + if (((b - j) == 2) && (s[(j) as usize] == 48)) { + return NO_LANE; + } + if (j < (a + 3)) { + return NO_LANE; + } + if ((s[((j - 1)) as usize] != 95) || (s[((j - 2)) as usize] != 95)) { + return NO_LANE; + } + if (lane_digits(s, j, b) >= MAX_LANES) { + return NO_LANE; + } + let r: i64 = ((j - 2) as _); + return r; +} + +pub fn lane_digits(s: &[u8], a: usize, b: usize) -> u64 { + let mut v: u64 = 0; + let mut j: usize = a; + while (j < b) { + v = ((v * 10) + ((s[(j) as usize] - 48) as u64)); + j = (j + 1); + } + return v; +} + +pub fn lane_index(s: &[u8], p: usize, b: usize) -> u64 { + return lane_digits(s, (p + 2), b); +} + +pub fn is_lane_of(s: &[u8], a: usize, b: usize, t: &[u8], c: usize, d: usize, k: u64) -> bool { + let p: i64 = lane_at(s, a, b); + if (p == NO_LANE) { + return false; + } + let pu: usize = (p as _); + if (((pu - a) != (d - c)) || !(has_at(s, a, pu, &t[(c) as usize..(d) as usize]))) { + return false; + } + return (lane_index(s, pu, b) == k); +} + pub fn binds(ps: &[u8], a: usize, b: usize, text: &[u8], ia: usize, ib: usize, fa: usize, fb: usize) -> bool { let n: usize = (ib - ia); let m: usize = (fb - fa); @@ -1001,6 +1061,11 @@ pub fn put32(nodes: &mut [u8], at: usize, v: usize) -> () { nodes[((at + 3)) as usize] = ((v / 16777216) as _); } +pub fn set(nodes: &mut [u8], i: usize, f: usize, a: usize, b: usize) -> () { + put32(nodes, (((i * REC) + 8) + (f * 8)), a); + put32(nodes, (((i * REC) + 12) + (f * 8)), b); +} + pub fn u32_at(nodes: &[u8], at: usize) -> usize { let b0: usize = (nodes[(at) as usize] as usize); let b1: usize = (nodes[((at + 1)) as usize] as usize); diff --git a/specs/tri/t27b/conformance/verilog_wide_inout.t27 b/specs/tri/t27b/conformance/verilog_wide_inout.t27 new file mode 100644 index 0000000000..ae7ce22214 --- /dev/null +++ b/specs/tri/t27b/conformance/verilog_wide_inout.t27 @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/tri/t27b/conformance/verilog_wide_inout.t27 -- the shape of the LiteDRAM core for `t27b verilog` +; (gHashTag/t27#10170, option A): an extern whose data ports are 256 bits wide and whose DDR3 data bus is a pin +; the module passes through. The emitter's widths stop at 64 (verilog_plan.t27's MAX_WIDTH), and it had no inout +; port, so no top around the core could be written in t27 at all. +; The source form (specs/tri/t27b/verilog_plan.t27): +; - a LANE SET: extern parameters wdata__0 .. wdata__3 of one type, adjacent and numbered from 0, are ONE port +; `.wdata({wdata__3, wdata__2, wdata__1, wdata__0})`, lane 0 in the low bits; result fields rdata__0 .. +; rdata__3 likewise, each lane its own wire core__rdata__N; +; - an INOUT: a `pub const` whose value is `undefined` is an `inout wire` port of the module, and may only be +; handed by name to an instance's port of its own type; +; - on_clock's parameter `core_rdata__2` is the instance core's output rdata__2, not a port. +; The 256 and the 32 are LiteDRAM's native port and the AX7203's DDR3 bus (2x MT41J256M16). +; The reference never calls the extern: Zig analyses a declaration only when a test reaches it, so the tests +; below drive on_clock with the lanes the core would give. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27bConformanceVerilogWideInout; + +/// The outputs of the core this design reads: four 64-bit lanes of read data and a ready. +pub struct WideCoreOut { rdata__0: u64, rdata__1: u64, rdata__2: u64, rdata__3: u64, ready: bool } + +/// A core with a 256-bit write port (four lanes), a 32-bit bidirectional data bus and a write enable. +pub fn WIDE_CORE(DATA_WIDTH: comptime_int, dq: u32, wdata__0: u64, wdata__1: u64, wdata__2: u64, wdata__3: u64, we: bool) -> WideCoreOut {} + +/// The DDR3 data bus: a pin of this module, passed through to the core. +pub const ddr3_dq : u32 = undefined; + +pub var beat : u64 = 0; +pub var sum : u64 = 0; + +const core : WideCoreOut = WIDE_CORE(256, ddr3_dq, beat, beat + 1, beat + 2, beat + 3, true); + +// Every var is read before it is written, so the test's order and the clock edge agree. +fn on_clock(core_rdata__0: u64, core_rdata__1: u64, core_rdata__2: u64, core_rdata__3: u64, core_ready: bool) { + if (core_ready) { sum = sum ^ core_rdata__0 ^ core_rdata__1 ^ core_rdata__2 ^ core_rdata__3; } + beat = beat + 4; +} + +test a_ready_beat_folds_all_four_lanes { + beat = 0; + sum = 0; + on_clock(1, 2, 4, 8, true); + assert_eq(sum, 15); + assert_eq(beat, 4); +} + +test a_beat_that_is_not_ready_folds_nothing { + beat = 8; + sum = 5; + on_clock(1, 2, 4, 8, false); + assert_eq(sum, 5); + assert_eq(beat, 12); +} + +test each_lane_lands_in_the_sum_once { + sum = 0; + on_clock(256, 0, 0, 0, true); + on_clock(0, 0, 0, 256, true); + assert_eq(sum, 0); + on_clock(0, 0, 65536, 0, true); + assert_eq(sum, 65536); +} diff --git a/specs/tri/t27b/verilog_emit.t27 b/specs/tri/t27b/verilog_emit.t27 index b0f07270d3..3fdc9cca02 100644 --- a/specs/tri/t27b/verilog_emit.t27 +++ b/specs/tri/t27b/verilog_emit.t27 @@ -352,6 +352,7 @@ pub fn item_class(nodes: []const u8, text: []const u8, meta: []const u8, ptext: if (k == K_TEST_BLOCK) { return C_TEST; } if (k == K_CONST_DECL) { if (is_mut(meta, c)) { return C_VAR; } + if (is_inout(nodes, text, meta, c)) { return C_INOUT; } if (is_input(nodes, text, c)) { return C_INPUT; } if (is_instance(nodes, text, meta, c)) { return C_INSTANCE; } if (c + 1 < end(nodes, c) and is_const_expr(nodes, text, meta, ptext, 0, c + 1)) { return C_CONST; } @@ -672,6 +673,9 @@ pub fn ex(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []con if (mode == M_FUNC or mode == M_TBFUNC or mode == M_TEST) { return fail(out, nodes, meta, text, i, "net or input read outside the module scope and on_clock", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } } else if (cl == C_VAR) { if (mode == M_FUNC or mode == M_TBFUNC) { return fail(out, nodes, meta, text, i, "module var read inside a fn", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } + } else if (cl == C_INOUT) { + // an inout is handed to an instance's port by name (port_arg), and is read nowhere else + return fail(out, nodes, meta, text, i, "inout read anywhere but as an instance's port", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } else { return fail(out, nodes, meta, text, i, "name", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } @@ -1544,6 +1548,20 @@ pub fn ports(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: [] } ci = end(nodes, ci); } + // inouts: every `pub const ... = undefined`, a pin handed to an instance's port + var co : usize = 1; + while (co < end(nodes, 0)) { + if (item_class(nodes, text, meta, ptext, co) == C_INOUT) { + const iot : i64 = sym_type(nodes, text, meta, ptext, 0, sym_of(SYM_TOP, co), 0); + if (!ty_scalar(iot)) { return fail(out, nodes, meta, text, co, "inout that is no integer or bool", lo(nodes, co, F_NAME), hi(nodes, co, F_NAME)); } + if (!first) { q = put_text(out, q, ","); } + q = put_text(out, q, "\n inout wire "); + q = put_range(out, q, iot); + q = put_name(out, q, text, lo(nodes, co, F_NAME), hi(nodes, co, F_NAME)); + first = false; + } + co = end(nodes, co); + } const dp : bool = declares_ports(nodes, text, meta, ptext); var c : usize = 1; while (c < end(nodes, 0)) { @@ -1620,9 +1638,9 @@ pub fn put_instance(out: []u8, at: usize, nodes: []const u8, text: []const u8, m const pa : usize = param_name_lo(meta, ptext, fi, kx); const pb : usize = param_name_hi(meta, ptext, fi, kx); const role : u8 = param_role(ptext[pa .. pb], param_type(nodes, text, meta, ptext, fi, kx)); - if (role == ROLE_BAD) { return fail(out, nodes, meta, text, call, "extern parameter of a type with no role", pa, pb); } + if (role == ROLE_BAD) { return fail(out, nodes, meta, ptext, call, "extern parameter of a type with no role", pa, pb); } if (role == ROLE_ATTR) { - if (!fold_ok(nodes, text, meta, ptext, 0, a, 0)) { return fail(out, nodes, meta, text, call, "attribute that is no constant", pa, pb); } + if (!fold_ok(nodes, text, meta, ptext, 0, a, 0)) { return fail(out, nodes, meta, ptext, call, "attribute that is no constant", pa, pb); } if (fold_val(nodes, text, meta, ptext, 0, a, 0) == 1) { if (nattr == 0) { q = put_text(out, q, " (* "); @@ -1669,7 +1687,8 @@ pub fn put_instance(out: []u8, at: usize, nodes: []const u8, text: []const u8, m 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, " ("); - // ports: inputs, then outputs + // ports: inputs, then outputs; a lane set (verilog_plan.t27's lane_at) is one port, its lanes + // concatenated highest first var nport : usize = 0; a = arg0; kx = 0; @@ -1677,19 +1696,47 @@ pub fn put_instance(out: []u8, at: usize, nodes: []const u8, text: []const u8, m const pa3 : usize = param_name_lo(meta, ptext, fi, kx); const pb3 : usize = param_name_hi(meta, ptext, fi, kx); const pt3 : i64 = param_type(nodes, text, meta, ptext, fi, kx); + var nl : usize = 1; if (param_role(ptext[pa3 .. pb3], pt3) == ROLE_PORT) { if (nport > 0) { q = put_text(out, q, ","); } q = put_text(out, q, "\n ."); - q = put_bytes(out, q, ptext, pa3, port_name_end(ptext, pa3, pb3)); - q = put_text(out, q, "("); - const r2 : i64 = ex(out, q, nodes, text, meta, ptext, work, 0, M_TOP, a, pt3); - if (r2 < 0) { return r2; } - q = @intCast(r2); - q = put_text(out, q, ")"); + const lp : i64 = lane_at(ptext, pa3, pb3); + if (lp == NO_LANE) { + q = put_bytes(out, q, ptext, pa3, port_name_end(ptext, pa3, pb3)); + q = put_text(out, q, "("); + const r2 : i64 = port_arg(out, q, nodes, text, meta, ptext, work, a, pt3); + if (r2 < 0) { return r2; } + q = @intCast(r2); + q = put_text(out, q, ")"); + } else { + const lpu : usize = @intCast(lp); + if (lane_index(ptext, lpu, pb3) != 0) { return fail(out, nodes, meta, ptext, call, "lane that does not follow its lane 0 in order", pa3, pb3); } + // the set: the parameters after this one that are its lanes 1, 2, ..., of its type + while (kx + nl < np and is_lane_of(ptext, param_name_lo(meta, ptext, fi, kx + nl), param_name_hi(meta, ptext, fi, kx + nl), ptext, pa3, lpu, nl as u64)) { + if (param_type(nodes, text, meta, ptext, fi, kx + nl) != pt3) { return fail(out, nodes, meta, ptext, call, "lane of another type than its lane 0", param_name_lo(meta, ptext, fi, kx + nl), param_name_hi(meta, ptext, fi, kx + nl)); } + nl = nl + 1; + } + q = put_bytes(out, q, ptext, pa3, lpu); + q = put_text(out, q, "({"); + var j : usize = nl; + while (j > 0) { + j = j - 1; + if (j + 1 < nl) { q = put_text(out, q, ", "); } + const r3 : i64 = port_arg(out, q, nodes, text, meta, ptext, work, sibling(nodes, a, j), pt3); + if (r3 < 0) { return r3; } + q = @intCast(r3); + } + q = put_text(out, q, "})"); + } nport = nport + 1; } - a = end(nodes, a); - kx = kx + 1; + // past this parameter and the other lanes of its set + var s : usize = 0; + while (s < nl) { + a = end(nodes, a); + kx = kx + 1; + s = s + 1; + } } if (sd != NO_NODE) { const sdj : usize = @intCast(sd); @@ -1697,14 +1744,46 @@ pub fn put_instance(out: []u8, at: usize, nodes: []const u8, text: []const u8, m while (fo < end(nodes, sdj)) { if (nport > 0) { q = put_text(out, q, ","); } q = put_text(out, q, "\n ."); - q = put_bytes(out, q, text, lo(nodes, fo, F_NAME), hi(nodes, fo, F_NAME)); - q = put_text(out, q, "("); - q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); - q = put_text(out, q, "__"); - q = put_bytes(out, q, text, lo(nodes, fo, F_NAME), hi(nodes, fo, F_NAME)); - q = put_text(out, q, ")"); + const flo : usize = lo(nodes, fo, F_NAME); + const fhi : usize = hi(nodes, fo, F_NAME); + const fl : i64 = lane_at(text, flo, fhi); + var nf : usize = 1; + if (fl == NO_LANE) { + q = put_bytes(out, q, text, flo, fhi); + q = put_text(out, q, "("); + q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, "__"); + q = put_bytes(out, q, text, flo, fhi); + q = put_text(out, q, ")"); + } else { + const flu : usize = @intCast(fl); + if (lane_index(text, flu, fhi) != 0) { return fail(out, nodes, meta, text, c, "lane that does not follow its lane 0 in order", flo, fhi); } + const ft : i64 = type_code(text, lo(nodes, fo, F_TYPE), hi(nodes, fo, F_TYPE)); + var fx : usize = end(nodes, fo); + while (fx < end(nodes, sdj) and is_lane_of(text, lo(nodes, fx, F_NAME), hi(nodes, fx, F_NAME), text, flo, flu, nf as u64)) { + if (type_code(text, lo(nodes, fx, F_TYPE), hi(nodes, fx, F_TYPE)) != ft) { return fail(out, nodes, meta, text, c, "lane of another type than its lane 0", lo(nodes, fx, F_NAME), hi(nodes, fx, F_NAME)); } + nf = nf + 1; + fx = end(nodes, fx); + } + q = put_bytes(out, q, text, flo, flu); + q = put_text(out, q, "({"); + var jf : usize = nf; + while (jf > 0) { + jf = jf - 1; + if (jf + 1 < nf) { q = put_text(out, q, ", "); } + const fj : usize = sibling(nodes, fo, jf); + q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, "__"); + q = put_bytes(out, q, text, lo(nodes, fj, F_NAME), hi(nodes, fj, F_NAME)); + } + q = put_text(out, q, "})"); + } nport = nport + 1; - fo = end(nodes, fo); + var sf : usize = 0; + while (sf < nf) { + fo = end(nodes, fo); + sf = sf + 1; + } } } q = put_text(out, q, ");\n"); @@ -1712,6 +1791,35 @@ pub fn put_instance(out: []u8, at: usize, nodes: []const u8, text: []const u8, m return ri; } +/// The node j siblings after node a (a itself for j = 0). +pub fn sibling(nodes: []const u8, a: usize, j: usize) -> usize { + var s : usize = a; + var k : usize = 0; + while (k < j) { + s = end(nodes, s); + k = k + 1; + } + return s; +} + +/// Writes the expression an instance's port of type t is connected to: an inout by its name, when its type is +/// the port's (the one place an inout is read); anything else as ex writes it at the module scope. +pub fn port_arg(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, a: usize, t: i64) -> i64 { + if (kind(nodes, a) == K_EXPR_IDENTIFIER) { + const d : i64 = find_decl(nodes, text, lo(nodes, a, F_NAME), hi(nodes, a, F_NAME)); + if (d != NO_NODE) { + const du : usize = @intCast(d); + if (item_class(nodes, text, meta, ptext, du) == C_INOUT) { + if (sym_type(nodes, text, meta, ptext, 0, sym_of(SYM_TOP, du), 0) != t) { return fail(out, nodes, meta, text, a, "inout of another type than the port it is handed to", lo(nodes, a, F_NAME), hi(nodes, a, F_NAME)); } + const q : usize = put_name(out, at, text, lo(nodes, a, F_NAME), hi(nodes, a, F_NAME)); + const rq : i64 = @intCast(q); + return rq; + } + } + } + return ex(out, at, nodes, text, meta, ptext, work, 0, M_TOP, a, t); +} + /// Writes the value of an extern's Verilog parameter: a string literal quoted, a float literal as written, an /// integer folded. A name of a top-level const is read through. pub fn param_value(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, a: usize, t: i64) -> i64 { @@ -2472,3 +2580,88 @@ test a_declared_clock_period_rides_on_the_wire_of_the_output_it_names { const n3 : usize = @intCast(0 - r3); assert holds(out[0 .. 4096], n3, "unsupported construct clock period of zero `g_O_PERIOD_PS` at line 7"); } + +test a_lane_set_is_one_port_and_an_inout_is_handed_through { + // module W { struct R { r__0: u8, r__1: u8 } fn E(d__0: u8, d__1: u8, q: u8) -> R {} + // pub const p : u8 = undefined; const k : R = E(1, 2, p); } + // text: W 0..1, R 1..2, r__0 2..6, r__1 6..10, u8 10..12, E 12..13, p 13..14, undefined 14..23, k 23..24, + // 1 24..25, 2 25..26 + const tx : str = "WRr__0r__1u8Epundefinedk12"; + var nb : [864]u8 = undefined; + tnode(nb[0 .. 864], 0, K_MODULE, 12, 0, 1); + tnode(nb[0 .. 864], 1, K_STRUCT_DECL, 4, 1, 2); + tnode(nb[0 .. 864], 2, K_EXPR_IDENTIFIER, 3, 2, 6); + vset(nb[0 .. 864], 2, F_TYPE, 10, 12); + tnode(nb[0 .. 864], 3, K_EXPR_IDENTIFIER, 4, 6, 10); + vset(nb[0 .. 864], 3, F_TYPE, 10, 12); + tnode(nb[0 .. 864], 4, K_FN_DECL, 5, 12, 13); + vset(nb[0 .. 864], 4, F_RET, 1, 2); + tnode(nb[0 .. 864], 5, K_CONST_DECL, 7, 13, 14); + vset(nb[0 .. 864], 5, F_TYPE, 10, 12); + tnode(nb[0 .. 864], 6, K_EXPR_IDENTIFIER, 7, 14, 23); + tnode(nb[0 .. 864], 7, K_CONST_DECL, 12, 23, 24); + tnode(nb[0 .. 864], 8, K_EXPR_CALL, 12, 12, 13); + tnode(nb[0 .. 864], 9, K_EXPR_LITERAL, 10, 0, 0); + vset(nb[0 .. 864], 9, F_VALUE, 24, 25); + tnode(nb[0 .. 864], 10, K_EXPR_LITERAL, 11, 0, 0); + vset(nb[0 .. 864], 10, F_VALUE, 25, 26); + tnode(nb[0 .. 864], 11, K_EXPR_IDENTIFIER, 12, 13, 14); + var mb : [192]u8 = undefined; + var i : usize = 0; + while (i < 12) { + tmeta(mb[0 .. 192], i, 0, i + 1); + i = i + 1; + } + tmeta(mb[0 .. 192], 5, FLAG_PUB, 6); + // E's parameter lines are the params text [0, 21) + vput32(mb[0 .. 192], 4 * META + 12, 21); + var wk : [100]u8 = undefined; + i = 0; + while (i < 100) { + wk[i] = 0; + i = i + 1; + } + var out : [4096]u8 = undefined; + const r : i64 = emit(nb[0 .. 864], tx, mb[0 .. 192], "d__0:u8\nd__1:u8\nq:u8\n", wk[0 .. 100], out[0 .. 4096]); + assert r > 0; + const n : usize = @intCast(r); + // p is a pin of the module, no output; each lane its own wire; each set one port, lane 0 in the low bits + assert holds(out[0 .. 4096], n, "\n inout wire [7:0] p\n);\n"); + assert !holds(out[0 .. 4096], n, "output"); + assert holds(out[0 .. 4096], n, " wire [7:0] k__r__0;\n wire [7:0] k__r__1;\n"); + assert holds(out[0 .. 4096], n, " E k (\n .d({8'd2, 8'd1}),\n .q(p),\n .r({k__r__1, k__r__0}));\n"); + // anywhere but at an instance's port, an inout is refused by name + const re : i64 = ex(out[0 .. 4096], 0, nb[0 .. 864], tx, mb[0 .. 192], "d__0:u8\nd__1:u8\nq:u8\n", wk[0 .. 100], 0, M_TOP, 11, 8); + assert re < 0; + const ne : usize = @intCast(0 - re); + assert holds(out[4096 - MSG_ROOM .. 4096], ne, "unsupported construct inout read anywhere but as an instance's port `p` at line 12"); + // a lane after a gap is refused, and so is a lane of another type, and an inout handed to a port of another + i = 0; + while (i < 100) { + wk[i] = 0; + i = i + 1; + } + const r2 : i64 = emit(nb[0 .. 864], tx, mb[0 .. 192], "d__0:u8\nd__2:u8\nq:u8\n", wk[0 .. 100], out[0 .. 4096]); + assert r2 < 0; + const n2 : usize = @intCast(0 - r2); + assert holds(out[0 .. 4096], n2, "unsupported construct lane that does not follow its lane 0 in order `d__2` at line 9"); + vput32(mb[0 .. 192], 4 * META + 12, 22); + i = 0; + while (i < 100) { + wk[i] = 0; + i = i + 1; + } + const r3 : i64 = emit(nb[0 .. 864], tx, mb[0 .. 192], "d__0:u8\nd__1:u16\nq:u8\n", wk[0 .. 100], out[0 .. 4096]); + assert r3 < 0; + const n3 : usize = @intCast(0 - r3); + assert holds(out[0 .. 4096], n3, "unsupported construct lane of another type than its lane 0 `d__1` at line 9"); + i = 0; + while (i < 100) { + wk[i] = 0; + i = i + 1; + } + const r4 : i64 = emit(nb[0 .. 864], tx, mb[0 .. 192], "d__0:u8\nd__1:u8\nq:u16\n", wk[0 .. 100], out[0 .. 4096]); + assert r4 < 0; + const n4 : usize = @intCast(0 - r4); + assert holds(out[0 .. 4096], n4, "unsupported construct inout of another type than the port it is handed to `p` at line 12"); +} diff --git a/specs/tri/t27b/verilog_plan.t27 b/specs/tri/t27b/verilog_plan.t27 index a8d2407351..0363fa1fd1 100644 --- a/specs/tri/t27b/verilog_plan.t27 +++ b/specs/tri/t27b/verilog_plan.t27 @@ -35,6 +35,16 @@ ; - 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. +; - An INOUT is an INPUT marked `pub`: `pub const ddram_dq : u32 = undefined;` (gHashTag/t27#10170). It is +; a pin driven from both sides, an `inout wire` port, and the module only hands it to an instance's port by +; name (`.ddram_dq(ddram_dq)`); any other read of it is refused by name, since the module cannot say who +; drives it. `pub` already means "a port" on a var or a net; on an input it adds the outward direction. +; - A LANE SET is how a port wider than MAX_WIDTH is spelled (#10170): an extern's parameters `d__0`, +; `d__1`, ... `d__N` (or its result struct's fields), of one type, adjacent and in order from 0, are the +; one port `d`, connected to the concatenation of the lanes, highest first: `.d({x3, x2, x1, x0})`. A +; value t27 holds stays at most 64 bits; only the connection is wide. A lane that does not follow its lane 0 +; in order (a gap, a set that does not start at 0), and a lane of another type than its lane 0, are +; refused by name (lane_at). ; - A CLOCK DECLARATION is a CONSTANT named `__PERIOD_PS` (#8839): that instance output is a ; clock of that period, in picoseconds. It is written as the attribute `(* t27_period_ps = N *)` on the ; output's wire, which `t27c silicon` turns into nextpnr's create_clock (specs/verified/clock_constraints.t27), @@ -58,6 +68,7 @@ ; (output wire); when the module declares no `pub` var or net at all and instantiates no extern, every var ; is an output reg, as in t27c's gen-verilog. So a module declares its ports, and every other var is an ; internal register; a top whose only outputs are a primitive's (a BSCANE2's TDO) has no port at all. +; The inouts, written after the inputs, are the INOUTs; they declare no output. ; The use closure. The module carries, as Verilog functions, exactly the fns its hardware reaches: on_clock, ; the nets and the instances' arguments, and every fn those call, transitively. `use` has already spliced ; each imported fn into the file (bootstrap/src/use_resolve.rs, as in every t27c path), so an imported fn @@ -458,6 +469,15 @@ pub fn is_input(nodes: []const u8, text: []const u8, i: usize) -> bool { return ident_is(nodes, text, i + 1, "undefined"); } +/// `pub const` whose value is `undefined`: an inout port, a pin the module passes to an instance's port. +pub const C_INOUT : u8 = 11; + +/// Is top-level node i a `pub const` whose one child is the name `undefined` (an inout)? An input marked +/// `pub` is driven from both sides: the module hands it to an instance and never reads or drives it itself. +pub fn is_inout(nodes: []const u8, text: []const u8, meta: []const u8, i: usize) -> bool { + return is_input(nodes, text, i) and is_pub(meta, i); +} + /// Is top-level node i a fn with an empty body (an extern module)? pub fn is_extern(nodes: []const u8, i: usize) -> bool { return kind(nodes, i) == K_FN_DECL and end(nodes, i) == i + 1; @@ -573,6 +593,56 @@ pub fn port_name_end(ps: []const u8, a: usize, b: usize) -> usize { return b; } +/// Lanes. A port wider than MAX_WIDTH (LiteDRAM's 256-bit native port) is declared as a LANE SET: the +/// extern's parameters `d__0`, `d__1`, ... (or its result struct's fields) of one type, adjacent and in +/// order from 0, are the one port `d`, connected to the concatenation of the lanes, highest lane first: +/// `.d({x3, x2, x1, x0})`. Every value t27 holds stays at most 64 bits; only the connection is wide. +/// No lane: the name does not end in `__` and a lane number. +pub const NO_LANE : i64 = -1; +/// The most lanes a set has (64 lanes of u64: a 4096-bit port). +pub const MAX_LANES : u64 = 64; + +/// Where the lane suffix of the name s[a .. b] starts: at the `__` before its last run of digits, when at +/// least one byte of name comes before it and the run is one digit, or two with no leading zero, below +/// MAX_LANES; NO_LANE otherwise. +pub fn lane_at(s: []const u8, a: usize, b: usize) -> i64 { + var j : usize = b; + while (j > a and s[j - 1] >= 48 and s[j - 1] <= 57) { j = j - 1; } + if (j == b or b - j > 2) { return NO_LANE; } + if (b - j == 2 and s[j] == 48) { return NO_LANE; } + if (j < a + 3) { return NO_LANE; } + if (s[j - 1] != 95 or s[j - 2] != 95) { return NO_LANE; } + if (lane_digits(s, j, b) >= MAX_LANES) { return NO_LANE; } + const r : i64 = @intCast(j - 2); + return r; +} + +/// The value of the decimal digits s[a .. b]. +pub fn lane_digits(s: []const u8, a: usize, b: usize) -> u64 { + var v : u64 = 0; + var j : usize = a; + while (j < b) { + v = v * 10 + (s[j] - 48) as u64; + j = j + 1; + } + return v; +} + +/// The lane number of the name s[a .. b], which lane_at found at p. +pub fn lane_index(s: []const u8, p: usize, b: usize) -> u64 { + return lane_digits(s, p + 2, b); +} + +/// Is the name s[a .. b] lane k of the set whose base is t[c .. d]? +pub fn is_lane_of(s: []const u8, a: usize, b: usize, t: []const u8, c: usize, d: usize, k: u64) -> bool { + const p : i64 = lane_at(s, a, b); + if (p == NO_LANE) { return false; } + const pu : usize = @intCast(p); + // the bases are equal: of one length, and one a prefix of the other (`same` takes a str, has_at a slice) + if (pu - a != d - c or !has_at(s, a, pu, t[c .. d])) { return false; } + return lane_index(s, pu, b) == k; +} + /// Is on_clock's parameter ps[a .. b] the output text[fa .. fb] of the instance named text[ia .. ib]: is it /// the instance's name, `_`, then the output's name? pub fn binds(ps: []const u8, a: usize, b: usize, text: []const u8, ia: usize, ib: usize, fa: usize, fb: usize) -> bool { @@ -898,6 +968,33 @@ test a_parameter_binds_to_an_instance_output_by_name { assert port_name_end("a__", 0, 3) == 2; } +test a_lane_set_is_named_by_its_base_and_numbered_from_zero { + // "wdata__0", "wdata__12": lanes 0 and 12 of the base "wdata" + assert lane_at("wdata__0", 0, 8) == 5 and lane_index("wdata__0", 5, 8) == 0; + assert lane_at("wdata__12", 0, 9) == 5 and lane_index("wdata__12", 5, 9) == 12; + assert lane_at("xwdata__3y", 1, 9) == 6 and lane_index("xwdata__3y", 6, 9) == 3; + // a base that ends in `_` keeps it + assert lane_at("d___1", 0, 5) == 2; + // no digits, one `_`, no base, a leading zero, three digits, or MAX_LANES and above: no lane + assert lane_at("wdata__", 0, 7) == NO_LANE; + assert lane_at("wdata_0", 0, 7) == NO_LANE; + assert lane_at("__0", 0, 3) == NO_LANE; + assert lane_at("d__03", 0, 5) == NO_LANE; + assert lane_at("d__100", 0, 6) == NO_LANE; + assert lane_at("d__64", 0, 5) == NO_LANE and lane_at("d__63", 0, 5) == 1; + assert lane_at("d__1x", 0, 5) == NO_LANE; + assert lane_at("data", 0, 4) == NO_LANE and lane_at("d0", 0, 2) == NO_LANE; + // lane k of the base "rdata", and nothing else + assert is_lane_of("rdata__2", 0, 8, "xrdata", 1, 6, 2); + assert !is_lane_of("rdata__2", 0, 8, "xrdata", 1, 6, 1); + assert !is_lane_of("wdata__2", 0, 8, "xrdata", 1, 6, 2); + assert !is_lane_of("rdata", 0, 5, "xrdata", 1, 6, 0); + assert !is_lane_of("rdat__0", 0, 7, "xrdata", 1, 6, 0); + // a longer base that starts with the set's base is another set + assert !is_lane_of("rdatax__2", 0, 9, "xrdata", 1, 6, 2); + assert lane_digits("1234", 0, 4) == 1234 and lane_digits("", 0, 0) == 0; +} + test a_clock_period_is_declared_by_the_name_of_an_instance_output { // "slow_O_PERIOD_PS" against instance "slow" (16..20) and output "O" (20..21) const s : str = "slow_O_PERIOD_PSslowOslowxO_PERIOD_PS_PERIOD_PS"; @@ -1092,6 +1189,18 @@ test hardware_only_consts_are_found_to_a_fixed_point { vset(buf[0 .. 864], 7, F_NAME, 15, 24); assert is_input(buf[0 .. 864], PT2, 6) and is_hw_const(buf[0 .. 864], PT2, 6, 0); assert !is_input(buf[0 .. 864], PT2, 1); + // the same input marked `pub` is an inout; a `pub` const that is no input is none + var mt : [192]u8 = undefined; + w = 0; + while (w < 192) { + mt[w] = 0; + w = w + 1; + } + assert !is_inout(buf[0 .. 864], PT2, mt[0 .. 192], 6); + mt[96] = 2; + mt[16] = 2; + assert is_inout(buf[0 .. 864], PT2, mt[0 .. 192], 6); + assert !is_inout(buf[0 .. 864], PT2, mt[0 .. 192], 1); vnode(buf[0 .. 864], 7, K_EXPR_LITERAL, 8); // two consts that name each other, and nothing else, are no hardware: the depth bound stops the walk vset(buf[0 .. 864], 2, F_NAME, 7, 11);