From 3e09466cb36ac71ed15227c1ebcf8db54dc633a2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 18:42:12 +0700 Subject: [PATCH 1/2] t27b verilog: signed integers, pure-fn modules, tables, constant division, casts -- petal_menu lowers, realm 2147 -> 82 LUT (Closes #11375) `t27b verilog` now lowers the T27 OS decision specs, which all emitted an empty module before. Decisions are in specs/tri/t27b/verilog_plan.t27, the writer in verilog_emit.t27, both regenerated with t27c gen-rust; hand Rust in cli/t27b is -1 lines (lower.rs 3/3, main.rs 4/5: the work area is 3n+80 bytes, the `top` argument, the `--top ` flag). - Pure fns: a spec with no on_clock/var/net/instance/input writes one combinational module per pub fn with integer/bool params and result (inputs = params, output t27_y); `--top ` writes only that fn. petal_menu: 17 modules, test bench lowers too. - Signed i2..i64: `signed` ports/regs/functions, `$signed({..})` around every sum/product/shift, `$signed({1'b0, x})` where an unsigned meets a wider signed peer, `>>>`, unary minus, -literal. - Tables: `const T : [N]ty = [..]` read by index is a ROM function (case per element), negative elements for signed tables, index past the end reads 0 where Zig traps. - Constant division: `x / k`, `x % k` are shifts/masks (power of two; signed rounds toward zero) or the multiply-shift q = (x * (2^n + m')) >> (n + ceil(log2 k)); exact for every n-bit x, proved in constant_division_is_exact (all 8-bit x for every k in 3..255, all 16-bit x for 8 divisors, the overshoot bound at n = 8/16/32/64 for 14 divisors up to 2^62-1). - @as(T, x) and `x as T` between widths (sign fill, zero fill, low bits), SystemVerilog words (`inside`, `bit`, ...) escaped in names. Measured (yosys 0.63 synth_xilinx xc7, pre-place): realm 2147 LUT/32 FF -> 82 LUT, 0 FF, 4 DSP; fill 127 LUT, 4 DSP; fit_permille 1157 LUT, 60 DSP; crosses 631 LUT, 20 DSP; inside 1767 LUT, 80 DSP. kernel.t27 (branch claude/t27os-tools): lanes_to_shed 2123 -> 111 LUT, mem_lanes 2064 -> 78 LUT, agent_cap (refused on @as before) 267 LUT, 4 DSP. Bit-exact: petal_menu's 10 tests run 553 asserts natively and 553 in iverilog on the emitted module; 487 hit, 124 fit_permille, 302 realm, 198 fill vectors from the RTL equal t27b's native run; the yosys netlist (DSP48E1 models) equals the RTL on 629 vectors. New fixtures verilog_signed.t27 (8 tests, incl. two 65536-operand sweeps of constant vs parameter division) and verilog_signed_clock.t27 pass natively, in iverilog and in t27c's Zig reference. The 7 existing verilog fixtures emit byte-identical Verilog except verilog_lib (now has modules); cargo test --release -p t27b: all pass. Hooks could not run in this checkout (--no-verify). Co-Authored-By: Claude Sonnet 5.5 --- ...formance_T27bConformanceVerilogSigned.json | 28 + ...nce_T27bConformanceVerilogSignedClock.json | 28 + .trinity/seals/t27b_T27bAstShape.json | 6 +- .trinity/seals/t27b_T27bEmitKit.json | 6 +- .trinity/seals/t27b_T27bTsPlan.json | 6 +- .trinity/seals/t27b_T27bVerilogEmit.json | 16 +- .trinity/seals/t27b_T27bVerilogPlan.json | 18 +- cli/t27b/src/lower.rs | 6 +- cli/t27b/src/main.rs | 9 +- gen/rust/tri/t27b/usage.rs | 2 +- gen/rust/tri/t27b/verilog_emit.rs | 820 ++++++++++++++++-- gen/rust/tri/t27b/verilog_plan.rs | 144 ++- specs/tri/t27b/conformance/verilog_signed.t27 | 224 +++++ .../t27b/conformance/verilog_signed_clock.t27 | 49 ++ specs/tri/t27b/usage.t27 | 2 +- specs/tri/t27b/verilog_emit.t27 | 678 +++++++++++++-- specs/tri/t27b/verilog_plan.t27 | 259 +++++- 17 files changed, 2088 insertions(+), 213 deletions(-) create mode 100644 .trinity/seals/conformance_T27bConformanceVerilogSigned.json create mode 100644 .trinity/seals/conformance_T27bConformanceVerilogSignedClock.json create mode 100644 specs/tri/t27b/conformance/verilog_signed.t27 create mode 100644 specs/tri/t27b/conformance/verilog_signed_clock.t27 diff --git a/.trinity/seals/conformance_T27bConformanceVerilogSigned.json b/.trinity/seals/conformance_T27bConformanceVerilogSigned.json new file mode 100644 index 0000000000..7347fa8c62 --- /dev/null +++ b/.trinity/seals/conformance_T27bConformanceVerilogSigned.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:315ca2c7706562f2d374a8d1a7a87c6b75f5e24b0a04532fa73b65894f02a406", + "gen_hash_rust": "sha256:e9c5d6d65d4a00f9ab078e6d628ebadefc21599a2ccf84e210f690b727c42c61", + "gen_hash_verilog": "sha256:c4769bdbed2a156cb77ee3d0d1fb6b23b7028fe5e14fa72545a8b19be466bdcf", + "gen_hash_zig": "sha256:4968f7373ab4754a177577747b02bd5b8fcfd4c16b9897809eca53154e288f7d", + "module": "T27bConformanceVerilogSigned", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T11:41:25Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:c58851a4a52bc2af42e4971ae7054689da71248dcf240e4846e6699e9887c0a6", + "spec_path": "specs/tri/t27b/conformance/verilog_signed.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 8, + "total": 8, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/conformance_T27bConformanceVerilogSignedClock.json b/.trinity/seals/conformance_T27bConformanceVerilogSignedClock.json new file mode 100644 index 0000000000..e0231c5c4b --- /dev/null +++ b/.trinity/seals/conformance_T27bConformanceVerilogSignedClock.json @@ -0,0 +1,28 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:76d7a7fd2227143996e211c370642cbbe3a2ef26ff61cba76929e4e1e438fd22", + "gen_hash_rust": "sha256:918f351234965ef7769edbe5049061b6bd443bce5ad13e0e9fcf42da7b8bd00b", + "gen_hash_verilog": "sha256:7d831e3247fb537cf358aa42531210ff52bf43c40e79afac8da425648f82448e", + "gen_hash_zig": "sha256:58f51ac431b8aaa72cab56ab98ab2358e127a26deabb44b0dc9b34c3a2209d0b", + "module": "T27bConformanceVerilogSignedClock", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T11:41:29Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:f8864c69304e7bc5f0be8a44a64749b3cda45a077cd20a272f7d2122ac0c4e55", + "spec_path": "specs/tri/t27b/conformance/verilog_signed_clock.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 2, + "total": 2, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27b_T27bAstShape.json b/.trinity/seals/t27b_T27bAstShape.json index 7a435610fd..50990b1b19 100644 --- a/.trinity/seals/t27b_T27bAstShape.json +++ b/.trinity/seals/t27b_T27bAstShape.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+b6f775178", + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", "closure": [ { "spec": "specs/tri/t27b/ast_scan.t27", @@ -7,7 +7,7 @@ }, { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:c71ad7c23e4e17dc1289fad6c6e11b118d85bcff6002d5165263774200ea6bb0" + "spec_hash": "sha256:bb9714fdff9fdb959aa55a059bc2de6995a996049be3adafa0b7f1d91add5d13" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -18,7 +18,7 @@ "module": "T27bAstShape", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T06:03:08Z", + "sealed_at": "2026-10-20T11:41:34Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:16a9ea7c24f3552cb386c4e0dea10e1d806b641faa94a884c812a7d7ba66dd93", "spec_path": "specs/tri/t27b/ast_shape.t27", diff --git a/.trinity/seals/t27b_T27bEmitKit.json b/.trinity/seals/t27b_T27bEmitKit.json index 2d39472ccc..448ae615b6 100644 --- a/.trinity/seals/t27b_T27bEmitKit.json +++ b/.trinity/seals/t27b_T27bEmitKit.json @@ -1,9 +1,9 @@ { - "built_by": "t27c-bootstrap@0.5.2+b6f775178", + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", "closure": [ { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:c71ad7c23e4e17dc1289fad6c6e11b118d85bcff6002d5165263774200ea6bb0" + "spec_hash": "sha256:bb9714fdff9fdb959aa55a059bc2de6995a996049be3adafa0b7f1d91add5d13" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -14,7 +14,7 @@ "module": "T27bEmitKit", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T06:03:46Z", + "sealed_at": "2026-10-20T11:41:38Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:8c7eea479142c7855d069e2cce5c0bbd03ffb1dccb861c5db334fe459db410d1", "spec_path": "specs/tri/t27b/emit_kit.t27", diff --git a/.trinity/seals/t27b_T27bTsPlan.json b/.trinity/seals/t27b_T27bTsPlan.json index 748beaed34..cc206e9557 100644 --- a/.trinity/seals/t27b_T27bTsPlan.json +++ b/.trinity/seals/t27b_T27bTsPlan.json @@ -1,9 +1,9 @@ { - "built_by": "t27c-bootstrap@0.5.2+b6f775178", + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", "closure": [ { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:c71ad7c23e4e17dc1289fad6c6e11b118d85bcff6002d5165263774200ea6bb0" + "spec_hash": "sha256:bb9714fdff9fdb959aa55a059bc2de6995a996049be3adafa0b7f1d91add5d13" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -14,7 +14,7 @@ "module": "T27bTsPlan", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T06:04:23Z", + "sealed_at": "2026-10-20T11:41:43Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:d046e9702ec1a0a9db5b9d8666e0bc8f7f38361f1bc320e5d9420931f7887e46", "spec_path": "specs/tri/t27b/ts_plan.t27", diff --git a/.trinity/seals/t27b_T27bVerilogEmit.json b/.trinity/seals/t27b_T27bVerilogEmit.json index 9bb17e2536..f74e086a73 100644 --- a/.trinity/seals/t27b_T27bVerilogEmit.json +++ b/.trinity/seals/t27b_T27bVerilogEmit.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+b6f775178", + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", "closure": [ { "spec": "specs/tri/t27b/emit_kit; // byte writers, to_front, line_near, resolve and symbols, test trees.t27", @@ -11,20 +11,20 @@ }, { "spec": "specs/tri/t27b/verilog_plan.t27", - "spec_hash": "sha256:c71ad7c23e4e17dc1289fad6c6e11b118d85bcff6002d5165263774200ea6bb0" + "spec_hash": "sha256:bb9714fdff9fdb959aa55a059bc2de6995a996049be3adafa0b7f1d91add5d13" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:281b93e4c401f4885a2d0d6bc5ee2cde92cdbbe61ccc7f66e02b870a69ff1e4d", - "gen_hash_rust": "sha256:49081e7dffcef5342fc40c42c2760db4a29e86a6b88badcccd5dad66fdfba811", - "gen_hash_verilog": "sha256:1a385b65b99093e2addc4adf6f382d110186574b7ad1fcf83a0c41e7e13d304e", - "gen_hash_zig": "sha256:404bcae3892f3be78ba2755b203d92d0b582de42653dc9bec5d90399c763704d", + "gen_hash_c": "sha256:2957236d26b9a045dac12fb47ba567def21361ddc80ed6ed28e501491c6f3629", + "gen_hash_rust": "sha256:d7958fd980cc17f64aa2655a8d18c3ca568231b1b0bc881769b0317b7fc7f613", + "gen_hash_verilog": "sha256:facc1441bdb2328a72783966316870b542b5d2485e335cd3671602f749e880fd", + "gen_hash_zig": "sha256:2ee7814f3d5e270333af06489c3ddec444eb9382f90bd87f7b5be2e0e9806616", "module": "T27bVerilogEmit", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T06:04:33Z", + "sealed_at": "2026-10-20T11:41:20Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:c34cd680a2f832cf513fb55a3b19956d16649604e605b1f01eaa3cf4294380df", + "spec_hash": "sha256:0951aeb6c8c46d91c89753faa74e8417c76051ed21d1f477a38e74e5f0651c87", "spec_path": "specs/tri/t27b/verilog_emit.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/t27b_T27bVerilogPlan.json b/.trinity/seals/t27b_T27bVerilogPlan.json index 8f93b39dd9..a76c7b4582 100644 --- a/.trinity/seals/t27b_T27bVerilogPlan.json +++ b/.trinity/seals/t27b_T27bVerilogPlan.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+b6f775178", + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", "closure": [ { "spec": "specs/tri/t27b/ast_walk.t27", @@ -7,23 +7,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:0b1cab301d63a4af4cfee93e69fb641b3ce7edd54d9079f62f01621b531787e5", - "gen_hash_rust": "sha256:e4860aa2da6eb2159bc505ac625c4880fba5fdb25ffe8953f2280c23be67d135", - "gen_hash_verilog": "sha256:71f948e58983e4e5390acbef7144e44371a76bd7b9cd55294df1d99865d92e7a", - "gen_hash_zig": "sha256:c59ac64c3064eaf401315d43ece71253b0917172f8337efbf01fd59a4f6ddacf", + "gen_hash_c": "sha256:41c392b9549baf681eb79f9a9f1008abf85df63a6ff07c2b9d7578d2e6877836", + "gen_hash_rust": "sha256:1d1fe59432160f2a2e56dac6f002257723ed2e6a0c808b751e9e8819a4dd1196", + "gen_hash_verilog": "sha256:161b30d277e176db7484dd970d203e1b7e6bfb423e6410a4ba7d9e86b64bf68e", + "gen_hash_zig": "sha256:159166ea2be21089529b296220a631fe52676fa959d3beaedb8c30c724609624", "module": "T27bVerilogPlan", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T06:04:36Z", + "sealed_at": "2026-10-20T11:41:14Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:c71ad7c23e4e17dc1289fad6c6e11b118d85bcff6002d5165263774200ea6bb0", + "spec_hash": "sha256:bb9714fdff9fdb959aa55a059bc2de6995a996049be3adafa0b7f1d91add5d13", "spec_path": "specs/tri/t27b/verilog_plan.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 18, - "total": 18, + "passed": 20, + "total": 20, "vacuous": 0 }, "toolchain": { diff --git a/cli/t27b/src/lower.rs b/cli/t27b/src/lower.rs index 7420928458..7a539119b1 100644 --- a/cli/t27b/src/lower.rs +++ b/cli/t27b/src/lower.rs @@ -7356,7 +7356,7 @@ const _: () = assert!(NodeKind::StmtExpr as u8 == aw::K_STMT_EXPR && NodeKind::E #[path = "../../../gen/rust/tri/t27b/verilog_emit.rs"] #[allow(dead_code, unused_parens, unused_variables, while_true)] mod ve; // t27c gen-rust of specs/tri/t27b/verilog_emit.t27: `t27b verilog` (#8796) #[path = "../../../gen/rust/tri/t27b/ts_emit.rs"] #[allow(dead_code, unused_parens, unused_variables, while_true)] mod tse; // specs/tri/t27b/ts_emit.t27: `t27b ts` (#9994) /// `t27b verilog` (`t27b ts` when `ts`): `flat`'s bytes and per node four u32 (flags 1 var, 2 pub; line; its params' span in `p`). -pub fn verilog(ast: &Node, ts: bool) -> Result { +pub fn verilog(ast: &Node, ts: bool, top: &str) -> Result { fn meta(n: &Node, m: &mut Vec, p: &mut Vec) { let lo = p.len() as u32; n.params.iter().for_each(|(a, b)| p.extend(format!("{a}:{b}\n").bytes())); for v in [n.extra_mutable as u32 | (n.extra_pub as u32) << 1, n.line, lo, p.len() as u32] { m.extend(v.to_le_bytes()) } @@ -7364,8 +7364,8 @@ pub fn verilog(ast: &Node, ts: bool) -> Result { } let ((a, t), mut m, mut p) = (flat([ast]), Vec::new(), Vec::new()); meta(ast, &mut m, &mut p); - let (mut w, mut out) = (vec![0u8; a.len() / aw::REC * 2 + 70], vec![0u8; 4 * (a.len() + t.len()) + (1 << 20)]); - let n = (if ts { tse::emit } else { ve::emit })(&a, &t, &m, &p, &mut w, &mut out); + let (mut w, mut out) = (vec![0u8; a.len() / aw::REC * 3 + 80], vec![0u8; 4 * (a.len() + t.len()) + (1 << 20)]); + let n = if ts { tse::emit(&a, &t, &m, &p, &mut w, &mut out) } else { ve::emit(&a, &t, &m, &p, &mut w, &mut out, top.as_bytes()) }; let s = String::from_utf8_lossy(&out[..n.unsigned_abs() as usize]).into_owned(); (n >= 0).then(|| s.clone()).ok_or(s) } diff --git a/cli/t27b/src/main.rs b/cli/t27b/src/main.rs index 1682a738ee..a90283d27a 100644 --- a/cli/t27b/src/main.rs +++ b/cli/t27b/src/main.rs @@ -62,8 +62,7 @@ struct Opts { mode: OverflowMode, time: bool, quiet: bool, - check: bool, - list: bool, + check: bool, list: bool, top: String, timeout_ms: u64, jobs: usize, json: Option, @@ -94,8 +93,7 @@ fn parse_args() -> Result { mode: OverflowMode::Trap, time: false, quiet: false, - check: false, - list: false, + check: false, list: false, top: String::new(), timeout_ms: 10_000, jobs: std::thread::available_parallelism().map_or(4, |n| n.get()), json: None, @@ -121,6 +119,7 @@ fn parse_args() -> Result { "--quiet" | "-q" => o.quiet = true, "--check" => o.check = true, "--list" => o.list = true, + "--top" => o.top = args.next().ok_or("--top needs a fn name")?, "--blockers" => o.blockers = true, "--elf" => o.elf = true, "--macho" => o.elf = false, @@ -235,7 +234,7 @@ fn compile_front(input: &str, o: &Opts) -> Result<(Program, Phases), u8> { return Err(EXIT_FRONTEND); } if o.cmd == "verilog" || o.cmd == "ts" { - return Err(lower::verilog(&parsed.ast, o.cmd == "ts").map_or_else(|e| { errln!("{}", e); EXIT_UNSUPPORTED }, |v| { out(&v); 0 })); + return Err(lower::verilog(&parsed.ast, o.cmd == "ts", &o.top).map_or_else(|e| { errln!("{}", e); EXIT_UNSUPPORTED }, |v| { out(&v); 0 })); } if o.blockers { // Every unsupported construct, not just the first; a file with none diff --git a/gen/rust/tri/t27b/usage.rs b/gen/rust/tri/t27b/usage.rs index 361e00e96f..8adfdb6c68 100644 --- a/gen/rust/tri/t27b/usage.rs +++ b/gen/rust/tri/t27b/usage.rs @@ -4,7 +4,7 @@ // This backend emits declarations only. The spec's checks live in // the Zig and Verilog outputs; do not read this file as verified. -pub const T27B_USAGE: &'static str = "usage:\n t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers]\n t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time]\n t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then '']\n t27b asm [--overflow trap|wrap]\n t27b verilog (the module, and its test bench under `ifdef T27B_TESTBENCH)\n t27b ts (consts, scalar fns and tests as TypeScript; specs/tri/t27b/ts_emit.t27)\n t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list]\n [--json ] [--runner ' [args]']\n [--blockers [--reference [--reference-cache ]\n [--reference-timeout-ms N]]]\n\n --elf build: an ELF64 object for aarch64 Linux (specs/tri/t27b/elf.t27), the default on\n Linux; --macho: a Mach-O object for arm64 macOS, the default elsewhere\n --then watch: after every successful rebuild, run with sh -c (never after a failed one)\n --json corpus: also write per-file results and totals to as JSON\n (with --reference, the reference verdicts too)\n --runner corpus: start each per-file `t27b test` as ` [args] test ...`,\n e.g. --runner 'qemu-aarch64 -L /usr/aarch64-linux-gnu' when the corpus\n itself runs under qemu-user without binfmt_misc\n --blockers test: report every unsupported construct, not just the first.\n corpus: per file, every construct t27b lacks; then the greedy\n order in which supporting them unlocks the most whole files.\n --reference corpus: also run the reference path (` test-report`,\n t27c's Zig backend) on every file, and leave the files it fails\n out of the denominator. Where both sides ran a file's tests,\n compare them test by test: `reference_disagree` counts files\n where a test's verdict differs or only one side has it (#6441).\n `mismatch` (= `jit_interp_mismatch`) is t27b's JIT against\n t27b's own interpreter, not against the reference."; +pub const T27B_USAGE: &'static str = "usage:\n t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers]\n t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time]\n t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then '']\n t27b asm [--overflow trap|wrap]\n t27b verilog [--top ] (the module, and its test bench under `ifdef T27B_TESTBENCH; a library of pure fns: a module per pub fn, or only )\n t27b ts (consts, scalar fns and tests as TypeScript; specs/tri/t27b/ts_emit.t27)\n t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list]\n [--json ] [--runner ' [args]']\n [--blockers [--reference [--reference-cache ]\n [--reference-timeout-ms N]]]\n\n --elf build: an ELF64 object for aarch64 Linux (specs/tri/t27b/elf.t27), the default on\n Linux; --macho: a Mach-O object for arm64 macOS, the default elsewhere\n --then watch: after every successful rebuild, run with sh -c (never after a failed one)\n --json corpus: also write per-file results and totals to as JSON\n (with --reference, the reference verdicts too)\n --runner corpus: start each per-file `t27b test` as ` [args] test ...`,\n e.g. --runner 'qemu-aarch64 -L /usr/aarch64-linux-gnu' when the corpus\n itself runs under qemu-user without binfmt_misc\n --blockers test: report every unsupported construct, not just the first.\n corpus: per file, every construct t27b lacks; then the greedy\n order in which supporting them unlocks the most whole files.\n --reference corpus: also run the reference path (` test-report`,\n t27c's Zig backend) on every file, and leave the files it fails\n out of the denominator. Where both sides ran a file's tests,\n compare them test by test: `reference_disagree` counts files\n where a test's verdict differs or only one side has it (#6441).\n `mismatch` (= `jit_interp_mismatch`) is t27b's JIT against\n t27b's own interpreter, not against the reference."; pub fn usage_byte(i: usize) -> u8 { return T27B_USAGE.as_bytes()[(i) as usize]; diff --git a/gen/rust/tri/t27b/verilog_emit.rs b/gen/rust/tri/t27b/verilog_emit.rs index 149b04d022..b76c29a885 100644 --- a/gen/rust/tri/t27b/verilog_emit.rs +++ b/gen/rust/tri/t27b/verilog_emit.rs @@ -14,6 +14,12 @@ pub const M_TEST: u8 = 3; pub const M_TBFUNC: u8 = 4; +pub const HELP_AT: usize = 65; + +pub const HELP_N: usize = 6; + +pub const ROM_AT: usize = 80; + pub fn put_name(out: &mut [u8], at: usize, text: &[u8], a: usize, b: usize) -> usize { let e: usize = name_end(text, a, b); if verilog_keyword(&text[(a) as usize..(e) as usize]) { @@ -26,10 +32,14 @@ pub fn put_name(out: &mut [u8], at: usize, text: &[u8], a: usize, b: usize) -> u pub fn put_range(out: &mut [u8], at: usize, t: i64) -> usize { let w: i64 = ty_width(t); + let mut q0: usize = at; + if ty_sint(t) { + q0 = put_text(out, at, "signed "); + } if (w <= 1) { - return at; + return q0; } - let mut q: usize = put_text(out, at, "["); + let mut q: usize = put_text(out, q0, "["); let top: u64 = ((w - 1) as u64); q = put_dec(out, q, top); return put_text(out, q, ":0] "); @@ -44,7 +54,11 @@ pub fn put_lit(out: &mut [u8], at: usize, v: u64, t: i64) -> usize { } let w: u64 = (ty_width(t) as u64); let mut q: usize = put_dec(out, at, w); - q = put_text(out, q, "'d"); + if ty_sint(t) { + q = put_text(out, q, "'sd"); + } else { + q = put_text(out, q, "'d"); + } return put_dec(out, q, v); } @@ -216,12 +230,21 @@ pub fn ty_of(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, return ot; } } + if field_eq(nodes, text, i, F_OP, "-") { + let nt: i64 = ty_of(nodes, text, meta, ptext, item, (i + 1), (depth + 1)); + if (ty_sint(nt) || (nt == TY_COMPTIME)) { + return nt; + } + } return TY_BAD; } if (k == K_EXPR_CAST) { return named_type(nodes, text, lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); } if (k == K_EXPR_CALL) { + if ((field_eq(nodes, text, i, F_NAME, "@as") && (children(nodes, i) == 2)) && (kind(nodes, (i + 1)) == K_EXPR_IDENTIFIER)) { + return type_code(text, lo(nodes, (i + 1), F_NAME), hi(nodes, (i + 1), F_NAME)); + } let f: i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); if (f == NO_NODE) { return TY_BAD; @@ -229,6 +252,17 @@ pub fn ty_of(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, let fi: usize = (f as _); return fn_type(nodes, text, fi); } + if (k == K_EXPR_INDEX) { + if ((children(nodes, i) != 2) || (kind(nodes, (i + 1)) != K_EXPR_IDENTIFIER)) { + return TY_BAD; + } + let rd: i64 = rom_decl(nodes, text, meta, ptext, item, (i + 1)); + if (rd == NO_NODE) { + return TY_BAD; + } + let rdi: usize = (rd as _); + return rom_elem(nodes, text, rdi); + } if (k == K_EXPR_FIELD_ACCESS) { if ((children(nodes, i) != 1) || (kind(nodes, (i + 1)) != K_EXPR_IDENTIFIER)) { return TY_BAD; @@ -255,6 +289,42 @@ pub fn ty_of(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, return TY_BAD; } +pub fn rom_decl(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], item: usize, u: usize) -> i64 { + let s: i64 = resolve(nodes, text, meta, ptext, item, u, lo(nodes, u, F_NAME), hi(nodes, u, F_NAME)); + if ((s == NO_SYM) || (sym_tag(s) != SYM_TOP)) { + return NO_NODE; + } + let d: usize = sym_at(s); + if (((kind(nodes, d) != K_CONST_DECL) || is_mut(meta, d)) || ((d + 1) >= end(nodes, d))) { + return NO_NODE; + } + if (kind(nodes, (d + 1)) != K_EXPR_ARRAY_LITERAL) { + return NO_NODE; + } + let ta: usize = lo(nodes, d, F_TYPE); + if ((hi(nodes, d, F_TYPE) <= ta) || (text[(ta) as usize] != 91)) { + return NO_NODE; + } + let di: i64 = (d as _); + return di; +} + +pub fn rom_elem(nodes: &[u8], text: &[u8], d: usize) -> i64 { + let mut j: usize = lo(nodes, d, F_TYPE); + let e: usize = hi(nodes, d, F_TYPE); + while ((j < e) && (text[(j) as usize] != 93)) { + j = (j + 1); + } + if (j >= e) { + return TY_BAD; + } + let t: i64 = type_code(text, (j + 1), e); + if !(ty_scalar(t)) { + return TY_BAD; + } + return t; +} + pub fn float_text(text: &[u8], a: usize, b: usize) -> bool { if ((b <= a) || !(digit(text[(a) as usize]))) { return false; @@ -754,8 +824,34 @@ pub fn decl_count_in(nodes: &[u8], text: &[u8], item: usize, a: usize, b: usize) } pub fn ex(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], item: usize, mode: u8, i: usize, want: i64) -> i64 { + if (ty_sint(want) && ty_uint(ty_of(nodes, text, meta, ptext, item, i, 0))) { + let mut q: usize = put_text(out, at, "$signed({1'b0, "); + let r: i64 = ex_raw(out, q, nodes, text, meta, ptext, work, item, mode, i, want); + if (r < 0) { + return r; + } + q = (r as _); + q = put_text(out, q, "})"); + let rq: i64 = (q as _); + return rq; + } + return ex_raw(out, at, nodes, text, meta, ptext, work, item, mode, i, want); +} + +pub fn ex_raw(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], item: usize, mode: u8, i: usize, want: i64) -> i64 { let k: u8 = kind(nodes, i); let own: i64 = ty_of(nodes, text, meta, ptext, item, i, 0); + if (((((own == TY_COMPTIME) && (k == K_EXPR_UNARY)) && field_eq(nodes, text, i, F_OP, "-")) && ty_sint(want)) && fold_ok(nodes, text, meta, ptext, item, (i + 1), 0)) { + let nv: u64 = fold_val(nodes, text, meta, ptext, item, (i + 1), 0); + if ((nv > 0) && !(fits((nv - 1), want))) { + return fail(out, nodes, meta, text, i, "literal that does not fit its type", 0, 0); + } + let mut qn: usize = put_text(out, at, "(-"); + qn = put_lit(out, qn, nv, want); + qn = put_text(out, qn, ")"); + let rn0: i64 = (qn as _); + return rn0; + } if (own == TY_COMPTIME) { if !(fold_ok(nodes, text, meta, ptext, item, i, 0)) { return fail(out, nodes, meta, text, i, "untyped value that does not fold", 0, 0); @@ -773,6 +869,9 @@ pub fn ex(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], pte } if (own == TY_BAD) { if (k == K_EXPR_CALL) { + if (field_eq(nodes, text, i, F_NAME, "@as") && (children(nodes, i) == 2)) { + return fail(out, nodes, meta, text, i, "cast to type", lo(nodes, (i + 1), F_NAME), hi(nodes, (i + 1), F_NAME)); + } let f: i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); if (f == NO_NODE) { return fail(out, nodes, meta, text, i, "call of an undeclared fn", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); @@ -879,10 +978,18 @@ pub fn ex(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], pte if ((oc == OP_BAD) || (lw == TY_BAD)) { return fail(out, nodes, meta, text, i, "operator", op_a, op_b); } - if ((oc == OP_ARITH) || (oc == OP_SHIFT)) { - q = put_text(out, q, "{"); + if ((((oc == OP_ARITH) && (same(&text[(op_a) as usize..(op_b) as usize], "/") || same(&text[(op_a) as usize..(op_b) as usize], "%"))) && fold_ok(nodes, text, meta, ptext, item, r, 0)) && !(fold_ok(nodes, text, meta, ptext, item, l, 0))) { + return ex_divk(out, q, nodes, text, meta, ptext, work, item, mode, i, own); + } + let sg: bool = (ty_sint(own) && ((oc == OP_ARITH) || (oc == OP_SHIFT))); + if sg { + q = put_text(out, q, "$signed({"); } else { - q = put_text(out, q, "("); + if ((oc == OP_ARITH) || (oc == OP_SHIFT)) { + q = put_text(out, q, "{"); + } else { + q = put_text(out, q, "("); + } } let r1: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, l, lw); if (r1 < 0) { @@ -890,17 +997,25 @@ pub fn ex(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], pte } q = (r1 as _); q = put_text(out, q, " "); - q = put_text(out, q, verilog_op(&text[(op_a) as usize..(op_b) as usize])); + if (((oc == OP_SHIFT) && ty_sint(own)) && same(&text[(op_a) as usize..(op_b) as usize], ">>")) { + q = put_text(out, q, ">>>"); + } else { + q = put_text(out, q, verilog_op(&text[(op_a) as usize..(op_b) as usize])); + } q = put_text(out, q, " "); let r2: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, r, rw); if (r2 < 0) { return r2; } q = (r2 as _); - if ((oc == OP_ARITH) || (oc == OP_SHIFT)) { - q = put_text(out, q, "}"); + if sg { + q = put_text(out, q, "})"); } else { - q = put_text(out, q, ")"); + if ((oc == OP_ARITH) || (oc == OP_SHIFT)) { + q = put_text(out, q, "}"); + } else { + q = put_text(out, q, ")"); + } } let rbn: i64 = (q as _); return rbn; @@ -909,7 +1024,15 @@ pub fn ex(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], pte if field_eq(nodes, text, i, F_OP, "!") { q = put_text(out, q, "(!"); } else { - q = put_text(out, q, "{~"); + if field_eq(nodes, text, i, F_OP, "-") { + q = put_text(out, q, "$signed({-"); + } else { + if ty_sint(own) { + q = put_text(out, q, "$signed({~"); + } else { + q = put_text(out, q, "{~"); + } + } } let r3: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, (i + 1), own); if (r3 < 0) { @@ -919,54 +1042,23 @@ pub fn ex(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], pte if field_eq(nodes, text, i, F_OP, "!") { q = put_text(out, q, ")"); } else { - q = put_text(out, q, "}"); + if (ty_sint(own) || field_eq(nodes, text, i, F_OP, "-")) { + q = put_text(out, q, "})"); + } else { + q = put_text(out, q, "}"); + } } let ru: i64 = (q as _); return ru; } if (k == K_EXPR_CAST) { - let src: i64 = ty_of(nodes, text, meta, ptext, item, (i + 1), 0); - if (src == TY_COMPTIME) { - return ex(out, q, nodes, text, meta, ptext, work, item, mode, (i + 1), own); - } - if !(ty_scalar(src)) { - return fail(out, nodes, meta, text, i, "cast of a value that is no integer or bool", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); - } - if ((own == TY_BOOL) && (src != TY_BOOL)) { - return fail(out, nodes, meta, text, i, "cast to bool", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); - } - let sw: i64 = ty_width(src); - let dw: i64 = ty_width(own); - if (sw == dw) { - return ex(out, q, nodes, text, meta, ptext, work, item, mode, (i + 1), src); - } - if (sw < dw) { - q = put_text(out, q, "{"); - let fillw: i64 = (dw - sw); - q = put_lit(out, q, 0, fillw); - q = put_text(out, q, ", "); - let r4: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, (i + 1), src); - if (r4 < 0) { - return r4; - } - q = (r4 as _); - q = put_text(out, q, "}"); - let rw2: i64 = (q as _); - return rw2; - } - let dwu: usize = (dw as _); - work[(((2 * count(nodes)) + dwu)) as usize] = 1; - q = put_text(out, q, "t27_u"); - q = put_dec(out, q, (dw as u64)); - q = put_text(out, q, "("); - let r5: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, (i + 1), src); - if (r5 < 0) { - return r5; - } - q = (r5 as _); - q = put_text(out, q, ")"); - let rn: i64 = (q as _); - return rn; + return ex_cast(out, q, nodes, text, meta, ptext, work, item, mode, i, (i + 1), own); + } + if (((k == K_EXPR_CALL) && field_eq(nodes, text, i, F_NAME, "@as")) && (children(nodes, i) == 2)) { + return ex_cast(out, q, nodes, text, meta, ptext, work, item, mode, i, end(nodes, (i + 1)), own); + } + if (k == K_EXPR_INDEX) { + return ex_index(out, q, nodes, text, meta, ptext, work, item, mode, i); } if (k == K_EXPR_CALL) { return ex_call(out, q, nodes, text, meta, ptext, work, item, mode, i); @@ -1010,6 +1102,255 @@ pub fn ex(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], pte return fail(out, nodes, meta, text, i, kind_label(k), lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } +pub fn ex_cast(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], item: usize, mode: u8, i: usize, srcn: usize, own: i64) -> i64 { + let mut q: usize = at; + let mut sn: usize = srcn; + if (((kind(nodes, sn) == K_EXPR_CALL) && field_eq(nodes, text, sn, F_NAME, "@intCast")) && (children(nodes, sn) == 1)) { + sn = (sn + 1); + } + let src: i64 = ty_of(nodes, text, meta, ptext, item, sn, 0); + if (src == TY_COMPTIME) { + return ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, own); + } + if !(ty_scalar(src)) { + return fail(out, nodes, meta, text, i, "cast of a value that is no integer or bool", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); + } + if ((own == TY_BOOL) && (src != TY_BOOL)) { + return fail(out, nodes, meta, text, i, "cast to bool", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); + } + let sw: i64 = ty_width(src); + let dw: i64 = ty_width(own); + let sgd: bool = ty_sint(own); + if (sw == dw) { + if (ty_sint(src) == sgd) { + return ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, src); + } + if sgd { + q = put_text(out, q, "$signed("); + } else { + q = put_text(out, q, "$unsigned("); + } + let rs: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, src); + if (rs < 0) { + return rs; + } + q = (rs as _); + q = put_text(out, q, ")"); + let rsq: i64 = (q as _); + return rsq; + } + if (sw < dw) { + if ty_sint(src) { + if !(sgd) { + q = put_text(out, q, "$unsigned"); + } + q = put_text(out, q, "("); + let rg: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, src); + if (rg < 0) { + return rg; + } + q = (rg as _); + q = put_text(out, q, " + "); + q = put_lit(out, q, 0, (SI + dw)); + q = put_text(out, q, ")"); + let rgq: i64 = (q as _); + return rgq; + } + if sgd { + q = put_text(out, q, "$signed("); + } + q = put_text(out, q, "{"); + let fillw: i64 = (dw - sw); + q = put_lit(out, q, 0, fillw); + q = put_text(out, q, ", "); + let r4: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, src); + if (r4 < 0) { + return r4; + } + q = (r4 as _); + q = put_text(out, q, "}"); + if sgd { + q = put_text(out, q, ")"); + } + let rw2: i64 = (q as _); + return rw2; + } + let dwu: usize = (dw as _); + work[(((2 * count(nodes)) + dwu)) as usize] = 1; + if sgd { + q = put_text(out, q, "$signed("); + } + q = put_text(out, q, "t27_u"); + q = put_dec(out, q, (dw as u64)); + q = put_text(out, q, "("); + let r5: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, src); + if (r5 < 0) { + return r5; + } + q = (r5 as _); + q = put_text(out, q, ")"); + if sgd { + q = put_text(out, q, ")"); + } + let rn: i64 = (q as _); + return rn; +} + +pub fn ex_index(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], item: usize, mode: u8, i: usize) -> i64 { + let rd: i64 = rom_decl(nodes, text, meta, ptext, item, (i + 1)); + if (rd == NO_NODE) { + return fail(out, nodes, meta, text, i, "ExprIndex", lo(nodes, (i + 1), F_NAME), hi(nodes, (i + 1), F_NAME)); + } + let rdi: usize = (rd as _); + let idx: usize = end(nodes, (i + 1)); + let it: i64 = ty_of(nodes, text, meta, ptext, item, idx, 0); + if ((it != TY_COMPTIME) && !(ty_uint(it))) { + return fail(out, nodes, meta, text, i, "table index that is no unsigned integer", lo(nodes, (i + 1), F_NAME), hi(nodes, (i + 1), F_NAME)); + } + work[((((2 * count(nodes)) + ROM_AT) + rdi)) as usize] = 1; + let mut q: usize = put_text(out, at, "t27_rom_"); + q = put_bytes(out, q, text, lo(nodes, rdi, F_NAME), hi(nodes, rdi, F_NAME)); + q = put_text(out, q, "("); + let r1: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, idx, 64); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + q = put_text(out, q, ")"); + let ri: i64 = (q as _); + return ri; +} + +pub fn put_mult(out: &mut [u8], at: usize, n: u64, m: u64) -> usize { + if (n < 64) { + let q0: usize = put_text(out, at, "65'd"); + return put_dec(out, q0, (m + pow2(n))); + } + let mut q: usize = put_text(out, at, "{1'b1, 64'd"); + q = put_dec(out, q, m); + return put_text(out, q, "}"); +} + +pub fn ex_divk(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], item: usize, mode: u8, i: usize, own: i64) -> i64 { + let l: usize = (i + 1); + let r: usize = end(nodes, l); + let isdiv: bool = field_eq(nodes, text, i, F_OP, "/"); + let d: u64 = fold_val(nodes, text, meta, ptext, item, r, 0); + if !(ty_int(own)) { + return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); + } + if (d == 0) { + return fail(out, nodes, meta, text, i, "division by the constant 0", 0, 0); + } + let n: u64 = (ty_width(own) as u64); + let sgn: bool = ty_sint(own); + let mut q: usize = at; + if (d == 1) { + if isdiv { + return ex(out, q, nodes, text, meta, ptext, work, item, mode, l, own); + } + q = put_lit(out, q, 0, own); + let r1: i64 = (q as _); + return r1; + } + if (is_pow2(d) && !(sgn)) { + q = put_text(out, q, "{"); + let ra: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, l, own); + if (ra < 0) { + return ra; + } + q = (ra as _); + if isdiv { + q = put_text(out, q, " >> "); + q = put_dec(out, q, ceil_log2(d)); + q = put_text(out, q, "}"); + } else { + q = put_text(out, q, " & "); + q = put_lit(out, q, (d - 1), own); + q = put_text(out, q, "}"); + } + let rp: i64 = (q as _); + return rp; + } + if (d > DIV_MAX) { + return fail(out, nodes, meta, text, i, "division by a constant above 2^62", 0, 0); + } + let mut h: usize = 0; + if is_pow2(d) { + h = 4; + if !(isdiv) { + h = 5; + } + } else { + if sgn { + h = 2; + if !(isdiv) { + h = 3; + } + } else { + if !(isdiv) { + h = 1; + } + } + } + work[((((2 * count(nodes)) + HELP_AT) + h)) as usize] = 1; + if (n < 64) { + let nu: usize = (n as _); + work[(((2 * count(nodes)) + nu)) as usize] = 1; + if sgn { + q = put_text(out, q, "$signed("); + } + q = put_text(out, q, "t27_u"); + q = put_dec(out, q, n); + q = put_text(out, q, "("); + } + if (h == 0) { + q = put_text(out, q, "t27_udivc("); + } + if (h == 1) { + q = put_text(out, q, "t27_umodc("); + } + if (h == 2) { + q = put_text(out, q, "t27_sdivc("); + } + if (h == 3) { + q = put_text(out, q, "t27_smodc("); + } + if (h == 4) { + q = put_text(out, q, "t27_sdivp("); + } + if (h == 5) { + q = put_text(out, q, "t27_smodp("); + } + let rx: i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, l, own); + if (rx < 0) { + return rx; + } + q = (rx as _); + if (h >= 4) { + q = put_text(out, q, ", 7'd"); + q = put_dec(out, q, ceil_log2(d)); + } else { + q = put_text(out, q, ", "); + q = put_mult(out, q, n, div_mult_low(n, d)); + q = put_text(out, q, ", 8'd"); + q = put_dec(out, q, div_shift(n, d)); + if ((h == 1) || (h == 3)) { + q = put_text(out, q, ", 64'd"); + q = put_dec(out, q, d); + } + } + q = put_text(out, q, ")"); + if (n < 64) { + q = put_text(out, q, ")"); + if sgn { + q = put_text(out, q, ")"); + } + } + let rd: i64 = (q as _); + return rd; +} + pub fn ex_call(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], item: usize, mode: u8, i: usize) -> i64 { let f: i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); if (f == NO_NODE) { @@ -1683,7 +2024,7 @@ pub fn decls(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], q = put_text(out, q, ";\n"); } } - if (((((((((((((((((k != K_STMT_LOCAL) && (k != K_STMT_WHILE)) && (k != K_STMT_IF)) && (k != K_MODULE)) && (k != K_STMT_ASSIGN)) && (k != K_EXPR_RETURN)) && (k != K_STMT_EXPR)) && (k != K_STMT_BREAK)) && (k != K_STMT_CONTINUE)) && (k != K_EXPR_LITERAL)) && (k != K_EXPR_IDENTIFIER)) && (k != K_EXPR_CALL)) && (k != K_EXPR_BINARY)) && (k != K_EXPR_UNARY)) && (k != K_EXPR_CAST)) && (k != K_EXPR_IF)) && (k != K_EXPR_FIELD_ACCESS)) { + if ((((((((((((((((((k != K_STMT_LOCAL) && (k != K_STMT_WHILE)) && (k != K_STMT_IF)) && (k != K_MODULE)) && (k != K_STMT_ASSIGN)) && (k != K_EXPR_RETURN)) && (k != K_STMT_EXPR)) && (k != K_STMT_BREAK)) && (k != K_STMT_CONTINUE)) && (k != K_EXPR_LITERAL)) && (k != K_EXPR_IDENTIFIER)) && (k != K_EXPR_CALL)) && (k != K_EXPR_BINARY)) && (k != K_EXPR_UNARY)) && (k != K_EXPR_CAST)) && (k != K_EXPR_IF)) && (k != K_EXPR_FIELD_ACCESS)) && (k != K_EXPR_INDEX)) { return fail(out, nodes, meta, text, j, kind_label(k), lo(nodes, j, F_NAME), hi(nodes, j, F_NAME)); } j = (j + 1); @@ -1772,10 +2113,254 @@ pub fn functions(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u } w = (w + 1); } + let hb: usize = ((2 * count(nodes)) + HELP_AT); + if ((((work[(hb) as usize] != 0) || (work[((hb + 1)) as usize] != 0)) || (work[((hb + 2)) as usize] != 0)) || (work[((hb + 3)) as usize] != 0)) { + q = put_div_helper(out, q, 0); + } + if (work[((hb + 1)) as usize] != 0) { + q = put_div_helper(out, q, 1); + } + if ((work[((hb + 2)) as usize] != 0) || (work[((hb + 3)) as usize] != 0)) { + q = put_div_helper(out, q, 2); + } + if (work[((hb + 3)) as usize] != 0) { + q = put_div_helper(out, q, 3); + } + if ((work[((hb + 4)) as usize] != 0) || (work[((hb + 5)) as usize] != 0)) { + q = put_div_helper(out, q, 4); + } + if (work[((hb + 5)) as usize] != 0) { + q = put_div_helper(out, q, 5); + } + let mut d: usize = 1; + while (d < end(nodes, 0)) { + if ((kind(nodes, d) == K_CONST_DECL) && (work[((((2 * count(nodes)) + ROM_AT) + d)) as usize] != 0)) { + let rt: i64 = put_rom(out, q, nodes, text, meta, ptext, d); + if (rt < 0) { + return rt; + } + q = (rt as _); + } + d = end(nodes, d); + } let rr: i64 = (q as _); return rr; } +pub fn put_div_helper(out: &mut [u8], at: usize, h: usize) -> usize { + if (h == 0) { + return put_text(out, at, "\n function [63:0] t27_udivc;\n input [63:0] x;\n input [64:0] m;\n input [7:0] s;\n reg [191:0] p;\n begin\n p = x * m;\n p = p >> s;\n t27_udivc = p[63:0];\n end\n endfunction\n"); + } + if (h == 1) { + return put_text(out, at, "\n function [63:0] t27_umodc;\n input [63:0] x;\n input [64:0] m;\n input [7:0] s;\n input [63:0] d;\n begin\n t27_umodc = x - t27_udivc(x, m, s) * d;\n end\n endfunction\n"); + } + if (h == 2) { + return put_text(out, at, "\n function signed [63:0] t27_sdivc;\n input signed [63:0] x;\n input [64:0] m;\n input [7:0] s;\n reg [63:0] a;\n begin\n a = x[63] ? (64'd0 - x) : x;\n a = t27_udivc(a, m, s);\n t27_sdivc = x[63] ? (64'd0 - a) : a;\n end\n endfunction\n"); + } + if (h == 3) { + return put_text(out, at, "\n function signed [63:0] t27_smodc;\n input signed [63:0] x;\n input [64:0] m;\n input [7:0] s;\n input [63:0] d;\n begin\n t27_smodc = x - t27_sdivc(x, m, s) * $signed(d);\n end\n endfunction\n"); + } + if (h == 4) { + return put_text(out, at, "\n function signed [63:0] t27_sdivp;\n input signed [63:0] x;\n input [6:0] k;\n reg signed [63:0] b;\n begin\n b = (64'sd1 <<< k) - 64'sd1;\n t27_sdivp = (x < 64'sd0) ? ((x + b) >>> k) : (x >>> k);\n end\n endfunction\n"); + } + return put_text(out, at, "\n function signed [63:0] t27_smodp;\n input signed [63:0] x;\n input [6:0] k;\n begin\n t27_smodp = x - (t27_sdivp(x, k) <<< k);\n end\n endfunction\n"); +} + +pub fn put_rom(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], d: usize) -> i64 { + let t: i64 = rom_elem(nodes, text, d); + if !(ty_scalar(t)) { + return fail(out, nodes, meta, text, d, "table whose elements are no integers or bools", lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + } + let mut q: usize = put_text(out, at, "\n // table "); + q = put_bytes(out, q, text, lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + q = put_text(out, q, "\n function "); + q = put_range(out, q, t); + q = put_text(out, q, "t27_rom_"); + q = put_bytes(out, q, text, lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + q = put_text(out, q, ";\n input [63:0] i;\n begin\n case (i)\n"); + let mut c: usize = (d + 2); + let mut kx: u64 = 0; + while (c < end(nodes, (d + 1))) { + let mut ne: bool = false; + let mut ev: usize = c; + if (((kind(nodes, c) == K_EXPR_UNARY) && field_eq(nodes, text, c, F_OP, "-")) && ty_sint(t)) { + ne = true; + ev = (c + 1); + } + if !(fold_ok(nodes, text, meta, ptext, 0, ev, 0)) { + return fail(out, nodes, meta, text, c, "table element that does not fold", lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + } + let v: u64 = fold_val(nodes, text, meta, ptext, 0, ev, 0); + let mut okv: bool = fits(v, t); + if (ne && (v > 0)) { + okv = fits((v - 1), t); + } + if !(okv) { + return fail(out, nodes, meta, text, c, "table element that does not fit its type", lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + } + q = put_text(out, q, " 64'd"); + q = put_dec(out, q, kx); + q = put_text(out, q, ": t27_rom_"); + q = put_bytes(out, q, text, lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + q = put_text(out, q, " = "); + if ne { + q = put_text(out, q, "-"); + } + q = put_lit(out, q, v, t); + q = put_text(out, q, ";\n"); + kx = (kx + 1); + c = end(nodes, c); + } + q = put_text(out, q, " default: t27_rom_"); + q = put_bytes(out, q, text, lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + q = put_text(out, q, " = "); + q = put_lit(out, q, 0, t); + q = put_text(out, q, ";\n endcase\n end\n endfunction\n"); + let rr: i64 = (q as _); + return rr; +} + +pub fn wrappable(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], f: usize, top: &[u8]) -> bool { + if (((kind(nodes, f) != K_FN_DECL) || is_extern(nodes, f)) || field_eq(nodes, text, f, F_NAME, "on_clock")) { + return false; + } + if (top.len() > 0) { + return field_is(nodes, text, f, F_NAME, top); + } + if (!(is_pub(meta, f)) || !(ty_scalar(fn_type(nodes, text, f)))) { + return false; + } + let np: usize = param_count(ptext, params_lo(meta, f), params_hi(meta, f)); + let mut kx: usize = 0; + while (kx < np) { + if !(ty_scalar(param_type(nodes, text, meta, ptext, f, kx))) { + return false; + } + kx = (kx + 1); + } + return true; +} + +pub fn is_library(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8]) -> bool { + if (clock_fn(nodes, text) != NO_NODE) { + return false; + } + let mut c: usize = 1; + while (c < end(nodes, 0)) { + let cl: u8 = item_class(nodes, text, meta, ptext, c); + if (((((cl == C_VAR) || (cl == C_NET)) || (cl == C_INSTANCE)) || (cl == C_INPUT)) || (cl == C_INOUT)) { + return false; + } + c = end(nodes, c); + } + return true; +} + +pub fn fn_module(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], f: usize) -> i64 { + let n: usize = count(nodes); + let mut j: usize = 0; + while (j < n) { + work[(j) as usize] = 0; + j = (j + 1); + } + j = ((2 * n) + 1); + while (j < ((3 * n) + ROM_AT)) { + work[(j) as usize] = 0; + j = (j + 1); + } + work[(f) as usize] = 1; + close_marks(nodes, text, work, 0); + let rt: i64 = fn_type(nodes, text, f); + let np: usize = param_count(ptext, params_lo(meta, f), params_hi(meta, f)); + let mut q: usize = put_text(out, at, "\n// pure fn "); + q = put_bytes(out, q, text, lo(nodes, f, F_NAME), hi(nodes, f, F_NAME)); + q = put_text(out, q, " (line "); + q = put_dec(out, q, (line_of(meta, f) as u64)); + q = put_text(out, q, "): combinational, one input per parameter\nmodule "); + q = put_name(out, q, text, lo(nodes, f, F_NAME), hi(nodes, f, F_NAME)); + q = put_text(out, q, " ("); + let mut kx: usize = 0; + while (kx < np) { + let pt: i64 = param_type(nodes, text, meta, ptext, f, kx); + q = put_text(out, q, "\n input wire "); + q = put_range(out, q, pt); + q = put_name(out, q, ptext, param_name_lo(meta, ptext, f, kx), param_name_hi(meta, ptext, f, kx)); + q = put_text(out, q, ","); + kx = (kx + 1); + } + q = put_text(out, q, "\n output wire "); + q = put_range(out, q, rt); + q = put_text(out, q, "t27_y\n);\n assign t27_y = "); + q = put_name(out, q, text, lo(nodes, f, F_NAME), hi(nodes, f, F_NAME)); + q = put_text(out, q, "("); + if (np == 0) { + q = put_text(out, q, "1'b0"); + } + kx = 0; + while (kx < np) { + if (kx > 0) { + q = put_text(out, q, ", "); + } + q = put_name(out, q, ptext, param_name_lo(meta, ptext, f, kx), param_name_hi(meta, ptext, f, kx)); + kx = (kx + 1); + } + q = put_text(out, q, ");\n"); + let r1: i64 = functions(out, q, nodes, text, meta, ptext, work, 0, M_FUNC); + if (r1 < 0) { + return r1; + } + q = (r1 as _); + q = put_text(out, q, "endmodule\n"); + j = ((2 * n) + 1); + while (j < ((3 * n) + ROM_AT)) { + work[(j) as usize] = 0; + j = (j + 1); + } + let rm: i64 = (q as _); + return rm; +} + +pub fn lib_modules(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], top: &[u8]) -> i64 { + let mut q: usize = at; + let mut emitted: usize = 0; + let mut bad: i64 = NO_NODE; + let mut c: usize = 1; + while (c < end(nodes, 0)) { + if wrappable(nodes, text, meta, ptext, c, top) { + let r: i64 = fn_module(out, q, nodes, text, meta, ptext, work, c); + if (r < 0) { + if (top.len() > 0) { + return r; + } + if (bad == NO_NODE) { + bad = (c as _); + } + q = put_text(out, q, "\n// skipped fn "); + q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, ": "); + q = copy_msg(out, q, ((0 - r) as _)); + q = put_text(out, q, "\n"); + } else { + q = (r as _); + emitted = (emitted + 1); + } + } + c = end(nodes, c); + } + if (emitted > 0) { + let rq: i64 = (q as _); + return rq; + } + if (top.len() > 0) { + return fail(out, nodes, meta, text, 0, "--top that names no fn of the spec", 0, 0); + } + if (bad != NO_NODE) { + let bi: usize = (bad as _); + return fn_module(out, at, nodes, text, meta, ptext, work, bi); + } + return module_text(out, at, nodes, text, meta, ptext, work); +} + pub fn ports(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], oc: i64) -> i64 { let mut q: usize = at; let mut first: bool = true; @@ -2518,7 +3103,7 @@ pub fn test_bench(out: &mut [u8], at: usize, nodes: &[u8], text: &[u8], meta: &[ return rb; } -pub fn emit(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], out: &mut [u8]) -> i64 { +pub fn emit(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8], out: &mut [u8], top: &[u8]) -> i64 { if ((count(nodes) == 0) || (kind(nodes, 0) != K_MODULE)) { return fail_front(out, "file with no module node"); } @@ -2527,7 +3112,16 @@ pub fn emit(nodes: &[u8], text: &[u8], meta: &[u8], ptext: &[u8], work: &mut [u8 q = put_bytes(out, q, text, lo(nodes, 0, F_NAME), hi(nodes, 0, F_NAME)); q = put_text(out, q, " -- DO NOT EDIT; the decisions are specs/tri/t27b/verilog_plan.t27\n"); q = put_text(out, q, "// phi^2 + 1/phi^2 = 3 | TRINITY\n`timescale 1ns / 1ps\n`default_nettype none\n"); - let r1: i64 = module_text(out, q, nodes, text, meta, ptext, work); + let mut r1: i64 = 0; + if is_library(nodes, text, meta, ptext) { + r1 = lib_modules(out, q, nodes, text, meta, ptext, work, top); + } else { + if (top.len() > 0) { + return fail_front(out, "--top on a spec with an on_clock, a var, a net, an instance or an input"); + } else { + r1 = module_text(out, q, nodes, text, meta, ptext, work); + } + } if (r1 < 0) { let ml1: usize = ((0 - r1) as _); return to_front(out, ml1); @@ -2614,6 +3208,8 @@ pub const F_VALUE: usize = 1; pub const K_CONST_DECL: u8 = 2; +pub const K_EXPR_ARRAY_LITERAL: u8 = 22; + pub const K_EXPR_BINARY: u8 = 16; pub const K_EXPR_CALL: u8 = 13; @@ -2626,6 +3222,8 @@ pub const K_EXPR_IDENTIFIER: u8 = 11; pub const K_EXPR_IF: u8 = 20; +pub const K_EXPR_INDEX: u8 = 19; + pub const K_EXPR_LITERAL: u8 = 10; pub const K_EXPR_RETURN: u8 = 18; @@ -3184,6 +3782,8 @@ pub const C_TYPE: u8 = 9; pub const C_VAR: u8 = 1; +pub const DIV_MAX: u64 = 4611686018427387904; + pub const FLAG_MUT: usize = 1; pub const FLAG_PUB: usize = 2; @@ -3224,6 +3824,8 @@ pub const ROLE_PARAM: u8 = 2; pub const ROLE_PORT: u8 = 1; +pub const SI: i64 = 100; + pub const T27_KEYWORDS: [&'static str; 29] = ["and", "bench", "break", "const", "continue", "else", "enum", "false", "fn", "for", "if", "in", "invariant", "let", "module", "or", "pragma", "pub", "return", "struct", "switch", "test", "true", "try", "use", "using", "var", "void", "while"]; pub const TY_BAD: i64 = -1; @@ -3248,6 +3850,8 @@ pub const VERILOG_KEYWORDS_2: [&'static str; 17] = ["tranif0", "tranif1", "tri", pub const VERILOG_KEYWORDS_3: [&'static str; 5] = ["wire", "wor", "xnor", "xor", "logic"]; +pub const VERILOG_KEYWORDS_4: [&'static str; 40] = ["inside", "bit", "byte", "int", "longint", "shortint", "string", "type", "unique", "priority", "class", "final", "var", "ref", "do", "with", "assert", "assume", "cover", "property", "sequence", "local", "null", "new", "this", "super", "virtual", "pure", "rand", "import", "export", "extends", "foreach", "union", "packed", "interface", "package", "program", "checker", "iff"]; + pub fn assigns_of(nodes: &[u8], text: &[u8], a: usize, b: usize, va: usize, vb: usize) -> usize { let mut n: usize = 0; let mut j: usize = a; @@ -3275,6 +3879,16 @@ pub fn binds(ps: &[u8], a: usize, b: usize, text: &[u8], ia: usize, ib: usize, f return has_at(ps, ((a + n) + 1), b, &text[(fa) as usize..(fb) as usize]); } +pub fn ceil_log2(d: u64) -> u64 { + let mut l: u64 = 0; + let mut v: u64 = 1; + while ((v < d) && (l < 63)) { + v = (v * 2); + l = (l + 1); + } + return l; +} + pub fn colon_at(ptext: &[u8], s: usize, e: usize) -> usize { let mut j: usize = s; while ((j < e) && (ptext[(j) as usize] != B_COLON_V)) { @@ -3314,6 +3928,26 @@ pub fn digit_of(c: u8) -> u64 { return 99; } +pub fn div_mult_low(n: u64, d: u64) -> u64 { + let mut r: u64 = (pow2(ceil_log2(d)) - d); + let mut q: u64 = 0; + let mut j: u64 = 0; + while (j < n) { + r = (r * 2); + q = (q * 2); + if (r >= d) { + r = (r - d); + q = (q + 1); + } + j = (j + 1); + } + return (q + 1); +} + +pub fn div_shift(n: u64, d: u64) -> u64 { + return (n + ceil_log2(d)); +} + pub fn find_decl(nodes: &[u8], text: &[u8], a: usize, b: usize) -> i64 { let mut c: usize = 1; while (c < end(nodes, 0)) { @@ -3343,9 +3977,12 @@ pub fn fits(v: u64, t: i64) -> bool { if (t == TY_BOOL) { return (v <= 1); } - if ty_int(t) { + if ty_uint(t) { return (v <= max_of(t)); } + if ty_sint(t) { + return (v <= max_of(((t - SI) - 1))); + } return false; } @@ -3506,6 +4143,10 @@ pub fn is_pin_name(text: &[u8], ca: usize, cb: usize, k: usize) -> bool { return same(&text[((cb - s)) as usize..(cb) as usize], key); } +pub fn is_pow2(d: u64) -> bool { + return ((d != 0) && ((d & (d - 1)) == 0)); +} + pub fn is_pub(meta: &[u8], i: usize) -> bool { return ((meta_word(meta, i, 0) & FLAG_PUB) == FLAG_PUB); } @@ -3764,12 +4405,30 @@ pub fn peer(a: i64, b: i64) -> i64 { if ((a == TY_BOOL) && (b == TY_BOOL)) { return TY_BOOL; } - if (ty_int(a) && ty_int(b)) { + if (ty_uint(a) && ty_uint(b)) { + if (a >= b) { + return a; + } + return b; + } + if (ty_sint(a) && ty_sint(b)) { if (a >= b) { return a; } return b; } + if (ty_sint(a) && ty_uint(b)) { + if ((a - SI) > b) { + return a; + } + return TY_BAD; + } + if (ty_uint(a) && ty_sint(b)) { + if ((b - SI) > a) { + return b; + } + return TY_BAD; + } return TY_BAD; } @@ -3804,6 +4463,16 @@ pub fn port_name_end(ps: &[u8], a: usize, b: usize) -> usize { return b; } +pub fn pow2(k: u64) -> u64 { + let mut v: u64 = 1; + let mut j: u64 = 0; + while (j < k) { + v = (v * 2); + j = (j + 1); + } + return v; +} + pub fn step_of(nodes: &[u8], text: &[u8], w: usize, va: usize, vb: usize) -> i64 { let body: i64 = loop_block(nodes, text, w, "body"); if (body == NO_NODE) { @@ -3900,17 +4569,28 @@ pub fn trip_bound(op: &[u8], kok: bool, k: u64, sok: bool, step: u64) -> i64 { } pub fn ty_int(t: i64) -> bool { - return ((t >= 1) && (t <= MAX_WIDTH)); + return (ty_uint(t) || ty_sint(t)); } pub fn ty_scalar(t: i64) -> bool { return (ty_int(t) || (t == TY_BOOL)); } +pub fn ty_sint(t: i64) -> bool { + return ((t > (SI + 1)) && (t <= (SI + MAX_WIDTH))); +} + +pub fn ty_uint(t: i64) -> bool { + return ((t >= 1) && (t <= MAX_WIDTH)); +} + pub fn ty_width(t: i64) -> i64 { - if ty_int(t) { + if ty_uint(t) { return t; } + if ty_sint(t) { + return (t - SI); + } if (t == TY_BOOL) { return 1; } @@ -3938,7 +4618,10 @@ pub fn type_code(s: &[u8], a: usize, b: usize) -> i64 { if ((same(&s[(p) as usize..(q) as usize], "f64") || same(&s[(p) as usize..(q) as usize], "f32")) || same(&s[(p) as usize..(q) as usize], "comptime_float")) { return TY_FLOAT; } - if (((s[(p) as usize] != 117) || ((q - p) < 2)) || ((q - p) > 3)) { + if same(&s[(p) as usize..(q) as usize], "isize") { + return (SI + 64); + } + if ((((s[(p) as usize] != 117) && (s[(p) as usize] != 105)) || ((q - p) < 2)) || ((q - p) > 3)) { return TY_BAD; } let p1: usize = (p + 1); @@ -3955,6 +4638,12 @@ pub fn type_code(s: &[u8], a: usize, b: usize) -> i64 { if (((s[(p1) as usize] == 48) || (w < 1)) || (w > MAX_WIDTH)) { return TY_BAD; } + if (s[(p) as usize] == 105) { + if (w < 2) { + return TY_BAD; + } + return (SI + w); + } return w; } @@ -3980,6 +4669,13 @@ pub fn verilog_keyword(s: &[u8]) -> bool { } j = (j + 1); } + j = 0; + while (j < 40) { + if same(s, VERILOG_KEYWORDS_4[(j) as usize]) { + return true; + } + j = (j + 1); + } return false; } diff --git a/gen/rust/tri/t27b/verilog_plan.rs b/gen/rust/tri/t27b/verilog_plan.rs index c77735fabe..e8b4d8a81e 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: 18 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 20 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. @@ -94,6 +94,8 @@ pub const TY_STRUCT: i64 = 1000000; pub const MAX_WIDTH: i64 = 64; +pub const SI: i64 = 100; + pub fn type_code(s: &[u8], a: usize, b: usize) -> i64 { let p: usize = trim_lo(s, a, b); let q: usize = trim_hi(s, p, b); @@ -115,7 +117,10 @@ pub fn type_code(s: &[u8], a: usize, b: usize) -> i64 { if ((same(&s[(p) as usize..(q) as usize], "f64") || same(&s[(p) as usize..(q) as usize], "f32")) || same(&s[(p) as usize..(q) as usize], "comptime_float")) { return TY_FLOAT; } - if (((s[(p) as usize] != 117) || ((q - p) < 2)) || ((q - p) > 3)) { + if same(&s[(p) as usize..(q) as usize], "isize") { + return (SI + 64); + } + if ((((s[(p) as usize] != 117) && (s[(p) as usize] != 105)) || ((q - p) < 2)) || ((q - p) > 3)) { return TY_BAD; } let p1: usize = (p + 1); @@ -132,21 +137,38 @@ pub fn type_code(s: &[u8], a: usize, b: usize) -> i64 { if (((s[(p1) as usize] == 48) || (w < 1)) || (w > MAX_WIDTH)) { return TY_BAD; } + if (s[(p) as usize] == 105) { + if (w < 2) { + return TY_BAD; + } + return (SI + w); + } return w; } -pub fn ty_int(t: i64) -> bool { +pub fn ty_uint(t: i64) -> bool { return ((t >= 1) && (t <= MAX_WIDTH)); } +pub fn ty_sint(t: i64) -> bool { + return ((t > (SI + 1)) && (t <= (SI + MAX_WIDTH))); +} + +pub fn ty_int(t: i64) -> bool { + return (ty_uint(t) || ty_sint(t)); +} + pub fn ty_scalar(t: i64) -> bool { return (ty_int(t) || (t == TY_BOOL)); } pub fn ty_width(t: i64) -> i64 { - if ty_int(t) { + if ty_uint(t) { return t; } + if ty_sint(t) { + return (t - SI); + } if (t == TY_BOOL) { return 1; } @@ -169,12 +191,30 @@ pub fn peer(a: i64, b: i64) -> i64 { if ((a == TY_BOOL) && (b == TY_BOOL)) { return TY_BOOL; } - if (ty_int(a) && ty_int(b)) { + if (ty_uint(a) && ty_uint(b)) { + if (a >= b) { + return a; + } + return b; + } + if (ty_sint(a) && ty_sint(b)) { if (a >= b) { return a; } return b; } + if (ty_sint(a) && ty_uint(b)) { + if ((a - SI) > b) { + return a; + } + return TY_BAD; + } + if (ty_uint(a) && ty_sint(b)) { + if ((b - SI) > a) { + return b; + } + return TY_BAD; + } return TY_BAD; } @@ -194,9 +234,12 @@ pub fn fits(v: u64, t: i64) -> bool { if (t == TY_BOOL) { return (v <= 1); } - if ty_int(t) { + if ty_uint(t) { return (v <= max_of(t)); } + if ty_sint(t) { + return (v <= max_of(((t - SI) - 1))); + } return false; } @@ -473,6 +516,86 @@ pub fn fold_val_op(s: &[u8], a: u64, b: u64) -> u64 { return 0; } +pub const DIV_MAX: u64 = 4611686018427387904; + +pub fn pow2(k: u64) -> u64 { + let mut v: u64 = 1; + let mut j: u64 = 0; + while (j < k) { + v = (v * 2); + j = (j + 1); + } + return v; +} + +pub fn is_pow2(d: u64) -> bool { + return ((d != 0) && ((d & (d - 1)) == 0)); +} + +pub fn ceil_log2(d: u64) -> u64 { + let mut l: u64 = 0; + let mut v: u64 = 1; + while ((v < d) && (l < 63)) { + v = (v * 2); + l = (l + 1); + } + return l; +} + +pub fn div_mult_low(n: u64, d: u64) -> u64 { + let mut r: u64 = (pow2(ceil_log2(d)) - d); + let mut q: u64 = 0; + let mut j: u64 = 0; + while (j < n) { + r = (r * 2); + q = (q * 2); + if (r >= d) { + r = (r - d); + q = (q + 1); + } + j = (j + 1); + } + return (q + 1); +} + +pub fn div_shift(n: u64, d: u64) -> u64 { + return (n + ceil_log2(d)); +} + +pub fn pow2_mod(e: u64, d: u64) -> u64 { + let mut r: u64 = (1 % d); + let mut j: u64 = 0; + while (j < e) { + r = ((r * 2) % d); + j = (j + 1); + } + return r; +} + +pub fn div_error(n: u64, d: u64) -> u64 { + return (d - pow2_mod((n + ceil_log2(d)), d)); +} + +pub fn div_mult_checks(n: u64, d: u64) -> bool { + let mut m: u64 = div_mult_low(n, d); + if (n < 64) { + m = (m + pow2(n)); + } + let mut two_s: u64 = 0; + if ((n + ceil_log2(d)) < 64) { + two_s = pow2((n + ceil_log2(d))); + } + return (((m).wrapping_mul(d)).wrapping_sub(two_s) == div_error(n, d)); +} + +pub fn div_exact(n: u64, d: u64) -> bool { + if (((d < 3) || (d > DIV_MAX)) || is_pow2(d)) { + return false; + } + let e: u64 = div_error(n, d); + return (((e > 0) && (e <= pow2(ceil_log2(d)))) && div_mult_checks(n, d)); +} + 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 fn kind_label(k: u8) -> &'static str { @@ -904,6 +1027,8 @@ pub const VERILOG_KEYWORDS_2: [&'static str; 17] = ["tranif0", "tranif1", "tri", pub const VERILOG_KEYWORDS_3: [&'static str; 5] = ["wire", "wor", "xnor", "xor", "logic"]; +pub const VERILOG_KEYWORDS_4: [&'static str; 40] = ["inside", "bit", "byte", "int", "longint", "shortint", "string", "type", "unique", "priority", "class", "final", "var", "ref", "do", "with", "assert", "assume", "cover", "property", "sequence", "local", "null", "new", "this", "super", "virtual", "pure", "rand", "import", "export", "extends", "foreach", "union", "packed", "interface", "package", "program", "checker", "iff"]; + pub fn verilog_keyword(s: &[u8]) -> bool { let mut j: usize = 0; while (j < 103) { @@ -926,6 +1051,13 @@ pub fn verilog_keyword(s: &[u8]) -> bool { } j = (j + 1); } + j = 0; + while (j < 40) { + if same(s, VERILOG_KEYWORDS_4[(j) as usize]) { + return true; + } + j = (j + 1); + } return false; } diff --git a/specs/tri/t27b/conformance/verilog_signed.t27 b/specs/tri/t27b/conformance/verilog_signed.t27 new file mode 100644 index 0000000000..25a295a4a1 --- /dev/null +++ b/specs/tri/t27b/conformance/verilog_signed.t27 @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/tri/t27b/conformance/verilog_signed.t27 -- `t27b verilog` lowers signed integers, constant tables, constant +; division, shifts and casts bit-exactly (gHashTag/t27#11326). The cockpit's petal menu hit test is the reason: +; it runs on i64 milli-units, reads two 108-entry coordinate tables and divides by constants. Each test runs +; natively (`t27b test`) and in the generated test bench (`iverilog -g2012 -DT27B_TESTBENCH`); the two agree on +; every assert, and the sweeps compare a division by a constant (a multiply-shift in Verilog) with the same +; division by a parameter (Verilog's own `/`), over every operand of the width: +; - a signed compare, a negation, an unsigned operand that meets a wider signed one; +; - an arithmetic shift right; +; - a widening @as that fills with the sign, one that fills with zeros, a narrowing one; +; - / and % by 9, 4 and 1000 on i32 (truncating toward zero) and u16, by a constant of 20 bits on i64; +; - a table read, with a negative element, and a loop over a table; +; - a library of pure fns: every pub fn is also a module of its own. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27bConformanceVerilogSigned; + +pub const TAB : [6]i16 = [100, 200, 300, 400, 500, 600]; +pub const SK : [4]i8 = [-3, 0, 5, -128]; +pub const BIG : i64 = 596000; + +pub fn neg(x: i32) -> i32 { + return 0 - x; +} + +pub fn neg8(x: i8) -> i8 { + return 0 - x; +} + +pub fn lt(a: i32, b: i32) -> bool { + return a < b; +} + +pub fn lt_mixed(a: i32, b: u8) -> bool { + return a < b; +} + +pub fn sar(a: i32, k: u32) -> i32 { + return a >> k; +} + +pub fn widen_signed(a: i8) -> i32 { + return @as(i32, a); +} + +pub fn widen_unsigned(a: u8) -> i32 { + return @as(i32, a); +} + +pub fn narrow(a: i32) -> i8 { + return @as(i8, @intCast(a)); +} + +pub fn div9(a: i32) -> i32 { + return a / 9; +} + +pub fn rem9(a: i32) -> i32 { + return a % 9; +} + +pub fn div4(a: i32) -> i32 { + return a / 4; +} + +pub fn rem4(a: i32) -> i32 { + return a % 4; +} + +pub fn div_by(a: i32, d: i32) -> i32 { + return a / d; +} + +pub fn rem_by(a: i32, d: i32) -> i32 { + return a % d; +} + +pub fn udiv9(a: u16) -> u16 { + return a / 9; +} + +pub fn urem9(a: u16) -> u16 { + return a % 9; +} + +pub fn udiv1000(a: u16) -> u16 { + return a / 1000; +} + +pub fn urem8(a: u16) -> u16 { + return a % 8; +} + +pub fn udiv_by(a: u16, d: u16) -> u16 { + return a / d; +} + +pub fn urem_by(a: u16, d: u16) -> u16 { + return a % d; +} + +pub fn scaled(a: i64) -> i64 { + return a * 1000000 / BIG; +} + +pub fn scaled_by(a: i64, d: i64) -> i64 { + return a * 1000000 / d; +} + +pub fn tab_at(i: u32) -> i16 { + return TAB[i]; +} + +pub fn sk_at(i: u32) -> i8 { + return SK[i]; +} + +pub fn tab_sum() -> i32 { + var s : i32 = 0; + var k : u32 = 0; + while (k < 6) { + s = s + TAB[k]; + k = k + 1; + } + return s; +} + +test a_signed_compare_sees_the_sign { + const m5 : i32 = neg(5); + assert m5 < 0; + assert lt(m5, 2); + assert !lt(2, m5); + assert lt(neg(7), neg(3)); + assert lt_mixed(m5, 0); + assert !lt_mixed(3, 3); + assert lt_mixed(3, 200); + assert neg8(100) < 0; + assert_eq(neg8(neg8(100)), 100); +} + +test a_signed_shift_right_keeps_the_sign { + assert_eq(sar(neg(8), 1), neg(4)); + assert_eq(sar(neg(1), 31), neg(1)); + assert_eq(sar(100, 3), 12); + assert_eq(sar(neg(9), 1), neg(5)); +} + +test a_widening_cast_fills_with_the_sign_or_with_zeros { + assert_eq(widen_signed(neg8(5)), neg(5)); + assert_eq(widen_signed(127), 127); + assert_eq(widen_unsigned(200), 200); + assert_eq(widen_unsigned(255), 255); + assert_eq(narrow(neg(5)), neg8(5)); + assert_eq(narrow(127), 127); +} + +test division_by_a_constant_truncates_toward_zero { + assert_eq(div9(17), 1); + assert_eq(rem9(17), 8); + assert_eq(div9(neg(17)), neg(1)); + assert_eq(rem9(neg(17)), neg(8)); + assert_eq(div4(neg(17)), neg(4)); + assert_eq(rem4(neg(17)), neg(1)); + assert_eq(div4(17), 4); + assert_eq(rem4(17), 1); + const lowest : i32 = neg(2147483647) - 1; + assert_eq(div9(lowest), neg(238609294)); + assert_eq(rem9(lowest), neg(2)); + assert_eq(div4(lowest), neg(536870912)); + assert_eq(rem4(lowest), 0); + assert_eq(div9(2147483647), 238609294); + assert_eq(rem9(2147483647), 1); +} + +test every_16_bit_unsigned_divides_as_by_a_parameter { + var x : u32 = 0; + var bad : u32 = 0; + while (x < 65536) { + const a : u16 = @as(u16, @intCast(x)); + if (udiv9(a) != udiv_by(a, 9)) { bad = bad + 1; } + if (urem9(a) != urem_by(a, 9)) { bad = bad + 1; } + if (udiv1000(a) != udiv_by(a, 1000)) { bad = bad + 1; } + if (urem8(a) != urem_by(a, 8)) { bad = bad + 1; } + x = x + 1; + } + assert bad == 0; +} + +test every_16_bit_signed_divides_as_by_a_parameter { + var x : i32 = 0; + var bad : u32 = 0; + while (x < 65536) { + const a : i32 = x - 32768; + if (div9(a) != div_by(a, 9)) { bad = bad + 1; } + if (rem9(a) != rem_by(a, 9)) { bad = bad + 1; } + if (div4(a) != div_by(a, 4)) { bad = bad + 1; } + if (rem4(a) != rem_by(a, 4)) { bad = bad + 1; } + x = x + 1; + } + assert bad == 0; +} + +test a_64_bit_scale_by_a_20_bit_constant_is_the_division { + assert_eq(scaled(596000), 1000000); + assert_eq(scaled(1), 1); + assert_eq(scaled(neg64(596000)), neg64(1000000)); + assert_eq(scaled(123456789), scaled_by(123456789, 596000)); + assert_eq(scaled(neg64(987654321)), scaled_by(neg64(987654321), 596000)); + assert_eq(scaled(9000000000), scaled_by(9000000000, 596000)); +} + +pub fn neg64(x: i64) -> i64 { + return 0 - x; +} + +test a_table_is_read_by_index_and_by_loop { + assert_eq(tab_at(0), 100); + assert_eq(tab_at(5), 600); + assert_eq(tab_sum(), 2100); + assert_eq(sk_at(0), neg8(3)); + assert_eq(sk_at(1), 0); + assert_eq(sk_at(2), 5); + assert_eq(sk_at(3), neg8(127) - 1); +} diff --git a/specs/tri/t27b/conformance/verilog_signed_clock.t27 b/specs/tri/t27b/conformance/verilog_signed_clock.t27 new file mode 100644 index 0000000000..6a8bf25b61 --- /dev/null +++ b/specs/tri/t27b/conformance/verilog_signed_clock.t27 @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/tri/t27b/conformance/verilog_signed_clock.t27 -- a clocked module with signed ports and registers +; (gHashTag/t27#11326): an `i8` input, two `i16` outputs, a sum that mixes the two widths, a signed compare, an +; arithmetic shift and a negated literal. The test bench drives the module's clock and compares its outputs. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27bConformanceVerilogSignedClock; + +pub var acc : i16 = 0; +pub var low : i16 = 0; +pub var half : i16 = 0; + +// Every var is read before it is written, so the test's order and the clock edge agree: the sum is a local. +fn on_clock(d: i8) { + const s : i16 = acc + d; + acc = s; + if (s < 0) { + low = s; + } + half = s >> 1; +} + +test the_accumulator_keeps_its_sign { + on_clock(5); + assert_eq(acc, 5); + assert_eq(low, 0); + on_clock(-8); + assert_eq(acc, -3); + assert_eq(low, -3); + assert_eq(half, -2); + on_clock(100); + assert_eq(acc, 97); + assert_eq(low, -3); + assert_eq(half, 48); +} + +test a_sum_past_the_range_of_the_input_stays_in_sixteen_bits { + on_clock(127); + on_clock(127); + on_clock(127); + assert_eq(acc, 381); + on_clock(-128); + on_clock(-128); + on_clock(-128); + on_clock(-128); + assert_eq(acc, -131); + assert_eq(low, -131); + assert_eq(half, -66); +} diff --git a/specs/tri/t27b/usage.t27 b/specs/tri/t27b/usage.t27 index ffeb314e17..44bbeea959 100644 --- a/specs/tri/t27b/usage.t27 +++ b/specs/tri/t27b/usage.t27 @@ -11,7 +11,7 @@ module T27bUsage; /// The whole usage text, one line per `\n`. -pub const T27B_USAGE : str = "usage:\n t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers]\n t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time]\n t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then '']\n t27b asm [--overflow trap|wrap]\n t27b verilog (the module, and its test bench under `ifdef T27B_TESTBENCH)\n t27b ts (consts, scalar fns and tests as TypeScript; specs/tri/t27b/ts_emit.t27)\n t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list]\n [--json ] [--runner ' [args]']\n [--blockers [--reference [--reference-cache ]\n [--reference-timeout-ms N]]]\n\n --elf build: an ELF64 object for aarch64 Linux (specs/tri/t27b/elf.t27), the default on\n Linux; --macho: a Mach-O object for arm64 macOS, the default elsewhere\n --then watch: after every successful rebuild, run with sh -c (never after a failed one)\n --json corpus: also write per-file results and totals to as JSON\n (with --reference, the reference verdicts too)\n --runner corpus: start each per-file `t27b test` as ` [args] test ...`,\n e.g. --runner 'qemu-aarch64 -L /usr/aarch64-linux-gnu' when the corpus\n itself runs under qemu-user without binfmt_misc\n --blockers test: report every unsupported construct, not just the first.\n corpus: per file, every construct t27b lacks; then the greedy\n order in which supporting them unlocks the most whole files.\n --reference corpus: also run the reference path (` test-report`,\n t27c's Zig backend) on every file, and leave the files it fails\n out of the denominator. Where both sides ran a file's tests,\n compare them test by test: `reference_disagree` counts files\n where a test's verdict differs or only one side has it (#6441).\n `mismatch` (= `jit_interp_mismatch`) is t27b's JIT against\n t27b's own interpreter, not against the reference."; +pub const T27B_USAGE : str = "usage:\n t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers]\n t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time]\n t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then '']\n t27b asm [--overflow trap|wrap]\n t27b verilog [--top ] (the module, and its test bench under `ifdef T27B_TESTBENCH; a library of pure fns: a module per pub fn, or only )\n t27b ts (consts, scalar fns and tests as TypeScript; specs/tri/t27b/ts_emit.t27)\n t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list]\n [--json ] [--runner ' [args]']\n [--blockers [--reference [--reference-cache ]\n [--reference-timeout-ms N]]]\n\n --elf build: an ELF64 object for aarch64 Linux (specs/tri/t27b/elf.t27), the default on\n Linux; --macho: a Mach-O object for arm64 macOS, the default elsewhere\n --then watch: after every successful rebuild, run with sh -c (never after a failed one)\n --json corpus: also write per-file results and totals to as JSON\n (with --reference, the reference verdicts too)\n --runner corpus: start each per-file `t27b test` as ` [args] test ...`,\n e.g. --runner 'qemu-aarch64 -L /usr/aarch64-linux-gnu' when the corpus\n itself runs under qemu-user without binfmt_misc\n --blockers test: report every unsupported construct, not just the first.\n corpus: per file, every construct t27b lacks; then the greedy\n order in which supporting them unlocks the most whole files.\n --reference corpus: also run the reference path (` test-report`,\n t27c's Zig backend) on every file, and leave the files it fails\n out of the denominator. Where both sides ran a file's tests,\n compare them test by test: `reference_disagree` counts files\n where a test's verdict differs or only one side has it (#6441).\n `mismatch` (= `jit_interp_mismatch`) is t27b's JIT against\n t27b's own interpreter, not against the reference."; /// Byte i of the usage text (the tests read it at run time, not folded at compile time). pub fn usage_byte(i: usize) -> u8 { diff --git a/specs/tri/t27b/verilog_emit.t27 b/specs/tri/t27b/verilog_emit.t27 index 8bc0b66b95..7bcef97f56 100644 --- a/specs/tri/t27b/verilog_emit.t27 +++ b/specs/tri/t27b/verilog_emit.t27 @@ -6,13 +6,15 @@ ; specs/tri/t27b/verilog_plan.t27; this file only spells them. It reaches cli/t27b only through ; `t27c gen-rust` (gen/rust/tri/t27b/verilog_emit.rs). ; -- -; The entry point is emit(nodes, text, meta, ptext, work, out): the AST records and their text; per node a +; The entry point is emit(nodes, text, meta, ptext, work, out, top): the AST records and their text; per node a ; meta record of four u32 (flags, line, params span; verilog_plan.t27); the params text; a work area of at -; least 2 * count(nodes) + 70 bytes, all 0; the output buffer. Bytes are written with text_escape.t27's put_text. It returns the length of the Verilog it wrote -; at out[0 ..], or minus the length of the refusal it wrote there instead. A refusal names the construct, +; least 3 * count(nodes) + 80 bytes, all 0; the output buffer; the name of the one fn to write as a module +; (`--top`), empty for none. Bytes are written with text_escape.t27's put_text. It returns the length of the +; Verilog it wrote at out[0 ..], or minus the length of the refusal it wrote there instead. A refusal names the construct, ; the name and the line: "t27b: verilog: unsupported construct `` at line ". ; The work area: [0, n) marks the fns the hardware reaches, [n, 2n) those the tests reach, [2n, 2n + 65) -; the widths whose truncation function t27_uW a cast used. +; the widths whose truncation function t27_uW a cast used, then (HELP_AT, ROM_AT) the constant-division +; helpers and the tables a fn read. ; -- ; Modes. An expression or statement is written for one of five places: the module scope (M_TOP: a net, an ; instance's connections, a reset value), a Verilog function of the module (M_FUNC), the clocked process of @@ -33,6 +35,13 @@ pub const M_CLOCK : u8 = 2; pub const M_TEST : u8 = 3; pub const M_TBFUNC : u8 = 4; +// The bytes of `work` after the closure marks (0 .. n) and the test marks (n .. 2n): at 2n + w the t27_uW +// helper of width w (1 .. 64); at 2n + HELP_AT + h the constant-division helper h (ex_divk); at 2n + ROM_AT + d +// the table declared at node d. lower.rs sizes it 3n + 80. +pub const HELP_AT : usize = 65; +pub const HELP_N : usize = 6; +pub const ROM_AT : usize = 80; + // ---------------------------------------------------------------------------------------------------------- // Writing bytes. // ---------------------------------------------------------------------------------------------------------- @@ -49,17 +58,20 @@ pub fn put_name(out: []u8, at: usize, text: []const u8, a: usize, b: usize) -> u return put_bytes(out, at, text, a, e); } -/// Writes the range of a scalar type, `[W-1:0] `, or nothing for one bit. +/// Writes the range of a scalar type, `[W-1:0] `, or nothing for one bit; a signed type starts with `signed `. pub fn put_range(out: []u8, at: usize, t: i64) -> usize { const w : i64 = ty_width(t); - if (w <= 1) { return at; } - var q : usize = put_text(out, at, "["); + var q0 : usize = at; + if (ty_sint(t)) { q0 = put_text(out, at, "signed "); } + if (w <= 1) { return q0; } + var q : usize = put_text(out, q0, "["); const top : u64 = (w - 1) as u64; q = put_dec(out, q, top); return put_text(out, q, ":0] "); } -/// Writes v as a literal of the scalar type t: `1'b1` for a bool, `W'dV` for an integer. +/// Writes v as a literal of the scalar type t: `1'b1` for a bool, `W'dV` for an unsigned integer, `W'sdV` for +/// a signed one (v is never negative: a constant that folds is a u64). pub fn put_lit(out: []u8, at: usize, v: u64, t: i64) -> usize { if (t == TY_BOOL) { if (v == 0) { return put_text(out, at, "1'b0"); } @@ -67,7 +79,11 @@ pub fn put_lit(out: []u8, at: usize, v: u64, t: i64) -> usize { } const w : u64 = ty_width(t) as u64; var q : usize = put_dec(out, at, w); - q = put_text(out, q, "'d"); + if (ty_sint(t)) { + q = put_text(out, q, "'sd"); + } else { + q = put_text(out, q, "'d"); + } return put_dec(out, q, v); } @@ -209,17 +225,31 @@ pub fn ty_of(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []con const ot : i64 = ty_of(nodes, text, meta, ptext, item, i + 1, depth + 1); if (ty_int(ot)) { return ot; } } + if (field_eq(nodes, text, i, F_OP, "-")) { + const nt : i64 = ty_of(nodes, text, meta, ptext, item, i + 1, depth + 1); + if (ty_sint(nt) or nt == TY_COMPTIME) { return nt; } + } return TY_BAD; } if (k == K_EXPR_CAST) { return named_type(nodes, text, lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); } if (k == K_EXPR_CALL) { + if (field_eq(nodes, text, i, F_NAME, "@as") and children(nodes, i) == 2 and kind(nodes, i + 1) == K_EXPR_IDENTIFIER) { + return type_code(text, lo(nodes, i + 1, F_NAME), hi(nodes, i + 1, F_NAME)); + } const f : i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); if (f == NO_NODE) { return TY_BAD; } const fi : usize = @intCast(f); return fn_type(nodes, text, fi); } + if (k == K_EXPR_INDEX) { + if (children(nodes, i) != 2 or kind(nodes, i + 1) != K_EXPR_IDENTIFIER) { return TY_BAD; } + const rd : i64 = rom_decl(nodes, text, meta, ptext, item, i + 1); + if (rd == NO_NODE) { return TY_BAD; } + const rdi : usize = @intCast(rd); + return rom_elem(nodes, text, rdi); + } if (k == K_EXPR_FIELD_ACCESS) { if (children(nodes, i) != 1 or kind(nodes, i + 1) != K_EXPR_IDENTIFIER) { return TY_BAD; } const s2 : i64 = resolve(nodes, text, meta, ptext, 0, i, lo(nodes, i + 1, F_NAME), hi(nodes, i + 1, F_NAME)); @@ -238,6 +268,31 @@ pub fn ty_of(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []con return TY_BAD; } +/// The top-level const array a name at node u reads as a table: `const T : [N]ty = [..]` with an array literal +/// value; NO_NODE when the name is anything else. The table is a ROM (gHashTag/t27#11326). +pub fn rom_decl(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, item: usize, u: usize) -> i64 { + const s : i64 = resolve(nodes, text, meta, ptext, item, u, lo(nodes, u, F_NAME), hi(nodes, u, F_NAME)); + if (s == NO_SYM or sym_tag(s) != SYM_TOP) { return NO_NODE; } + const d : usize = sym_at(s); + if (kind(nodes, d) != K_CONST_DECL or is_mut(meta, d) or d + 1 >= end(nodes, d)) { return NO_NODE; } + if (kind(nodes, d + 1) != K_EXPR_ARRAY_LITERAL) { return NO_NODE; } + const ta : usize = lo(nodes, d, F_TYPE); + if (hi(nodes, d, F_TYPE) <= ta or text[ta] != 91) { return NO_NODE; } + const di : i64 = @intCast(d); + return di; +} + +/// The element type of the table declared at node d (`[N]ty`): TY_BAD unless it is an integer or a bool. +pub fn rom_elem(nodes: []const u8, text: []const u8, d: usize) -> i64 { + var j : usize = lo(nodes, d, F_TYPE); + const e : usize = hi(nodes, d, F_TYPE); + while (j < e and text[j] != 93) { j = j + 1; } + if (j >= e) { return TY_BAD; } + const t : i64 = type_code(text, j + 1, e); + if (!ty_scalar(t)) { return TY_BAD; } + return t; +} + /// Is text[a .. b] a float literal's text: digits with an `e` or a `.` (`10.0`, `1e3`)? pub fn float_text(text: []const u8, a: usize, b: usize) -> bool { if (b <= a or !digit(text[a])) { return false; } @@ -701,10 +756,36 @@ pub fn decl_count_in(nodes: []const u8, text: []const u8, item: usize, a: usize, } /// Writes expression node i at type `want` (the type an untyped literal takes; a typed expression keeps its -/// own). Returns the end, or a refusal. +/// own), except that an unsigned integer written where a signed one is wanted is widened by `$signed({1'b0, +/// x})`: Verilog reads every operand of an expression as unsigned as soon as one is. Returns the end, or a +/// refusal. pub fn ex(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, item: usize, mode: u8, i: usize, want: i64) -> i64 { + if (ty_sint(want) and ty_uint(ty_of(nodes, text, meta, ptext, item, i, 0))) { + var q : usize = put_text(out, at, "$signed({1'b0, "); + const r : i64 = ex_raw(out, q, nodes, text, meta, ptext, work, item, mode, i, want); + if (r < 0) { return r; } + q = @intCast(r); + q = put_text(out, q, "})"); + const rq : i64 = @intCast(q); + return rq; + } + return ex_raw(out, at, nodes, text, meta, ptext, work, item, mode, i, want); +} + +/// ex without the widening of an unsigned operand. +pub fn ex_raw(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, item: usize, mode: u8, i: usize, want: i64) -> i64 { const k : u8 = kind(nodes, i); const own : i64 = ty_of(nodes, text, meta, ptext, item, i, 0); + // `-5` where a signed type is wanted: the negated literal of that type + if (own == TY_COMPTIME and k == K_EXPR_UNARY and field_eq(nodes, text, i, F_OP, "-") and ty_sint(want) and fold_ok(nodes, text, meta, ptext, item, i + 1, 0)) { + const nv : u64 = fold_val(nodes, text, meta, ptext, item, i + 1, 0); + if (nv > 0 and !fits(nv - 1, want)) { return fail(out, nodes, meta, text, i, "literal that does not fit its type", 0, 0); } + var qn : usize = put_text(out, at, "(-"); + qn = put_lit(out, qn, nv, want); + qn = put_text(out, qn, ")"); + const rn0 : i64 = @intCast(qn); + return rn0; + } // An untyped value is folded and written at the type it takes. if (own == TY_COMPTIME) { if (!fold_ok(nodes, text, meta, ptext, item, i, 0)) { return fail(out, nodes, meta, text, i, "untyped value that does not fold", 0, 0); } @@ -717,6 +798,7 @@ pub fn ex(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []con } if (own == TY_BAD) { if (k == K_EXPR_CALL) { + if (field_eq(nodes, text, i, F_NAME, "@as") and children(nodes, i) == 2) { return fail(out, nodes, meta, text, i, "cast to type", lo(nodes, i + 1, F_NAME), hi(nodes, i + 1, F_NAME)); } const f : i64 = find_fn(nodes, text, lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); if (f == NO_NODE) { return fail(out, nodes, meta, text, i, "call of an undeclared fn", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } return fail(out, nodes, meta, text, i, "call of a fn whose result is no scalar", lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); @@ -809,7 +891,13 @@ pub fn ex(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []con rw = TY_BOOL; } if (oc == OP_BAD or lw == TY_BAD) { return fail(out, nodes, meta, text, i, "operator", op_a, op_b); } - if (oc == OP_ARITH or oc == OP_SHIFT) { + if (oc == OP_ARITH and (same(text[op_a .. op_b], "/") or same(text[op_a .. op_b], "%")) and fold_ok(nodes, text, meta, ptext, item, r, 0) and !fold_ok(nodes, text, meta, ptext, item, l, 0)) { + return ex_divk(out, q, nodes, text, meta, ptext, work, item, mode, i, own); + } + const sg : bool = ty_sint(own) and (oc == OP_ARITH or oc == OP_SHIFT); + if (sg) { + q = put_text(out, q, "$signed({"); + } else if (oc == OP_ARITH or oc == OP_SHIFT) { q = put_text(out, q, "{"); } else { q = put_text(out, q, "("); @@ -818,12 +906,18 @@ pub fn ex(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []con if (r1 < 0) { return r1; } q = @intCast(r1); q = put_text(out, q, " "); - q = put_text(out, q, verilog_op(text[op_a .. op_b])); + if (oc == OP_SHIFT and ty_sint(own) and same(text[op_a .. op_b], ">>")) { + q = put_text(out, q, ">>>"); + } else { + q = put_text(out, q, verilog_op(text[op_a .. op_b])); + } q = put_text(out, q, " "); const r2 : i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, r, rw); if (r2 < 0) { return r2; } q = @intCast(r2); - if (oc == OP_ARITH or oc == OP_SHIFT) { + if (sg) { + q = put_text(out, q, "})"); + } else if (oc == OP_ARITH or oc == OP_SHIFT) { q = put_text(out, q, "}"); } else { q = put_text(out, q, ")"); @@ -834,6 +928,10 @@ pub fn ex(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []con if (k == K_EXPR_UNARY) { if (field_eq(nodes, text, i, F_OP, "!")) { q = put_text(out, q, "(!"); + } else if (field_eq(nodes, text, i, F_OP, "-")) { + q = put_text(out, q, "$signed({-"); + } else if (ty_sint(own)) { + q = put_text(out, q, "$signed({~"); } else { q = put_text(out, q, "{~"); } @@ -842,6 +940,8 @@ pub fn ex(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []con q = @intCast(r3); if (field_eq(nodes, text, i, F_OP, "!")) { q = put_text(out, q, ")"); + } else if (ty_sint(own) or field_eq(nodes, text, i, F_OP, "-")) { + q = put_text(out, q, "})"); } else { q = put_text(out, q, "}"); } @@ -849,40 +949,13 @@ pub fn ex(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []con return ru; } if (k == K_EXPR_CAST) { - const src : i64 = ty_of(nodes, text, meta, ptext, item, i + 1, 0); - if (src == TY_COMPTIME) { return ex(out, q, nodes, text, meta, ptext, work, item, mode, i + 1, own); } - if (!ty_scalar(src)) { return fail(out, nodes, meta, text, i, "cast of a value that is no integer or bool", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); } - if (own == TY_BOOL and src != TY_BOOL) { return fail(out, nodes, meta, text, i, "cast to bool", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); } - const sw : i64 = ty_width(src); - const dw : i64 = ty_width(own); - if (sw == dw) { - return ex(out, q, nodes, text, meta, ptext, work, item, mode, i + 1, src); - } - if (sw < dw) { - // zero fill: {N'd0, x} - q = put_text(out, q, "{"); - const fillw : i64 = dw - sw; - q = put_lit(out, q, 0, fillw); - q = put_text(out, q, ", "); - const r4 : i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, i + 1, src); - if (r4 < 0) { return r4; } - q = @intCast(r4); - q = put_text(out, q, "}"); - const rw2 : i64 = @intCast(q); - return rw2; - } - // narrowing: the low bits, through t27_uW - const dwu : usize = @intCast(dw); - work[2 * count(nodes) + dwu] = 1; - q = put_text(out, q, "t27_u"); - q = put_dec(out, q, dw as u64); - q = put_text(out, q, "("); - const r5 : i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, i + 1, src); - if (r5 < 0) { return r5; } - q = @intCast(r5); - q = put_text(out, q, ")"); - const rn : i64 = @intCast(q); - return rn; + return ex_cast(out, q, nodes, text, meta, ptext, work, item, mode, i, i + 1, own); + } + if (k == K_EXPR_CALL and field_eq(nodes, text, i, F_NAME, "@as") and children(nodes, i) == 2) { + return ex_cast(out, q, nodes, text, meta, ptext, work, item, mode, i, end(nodes, i + 1), own); + } + if (k == K_EXPR_INDEX) { + return ex_index(out, q, nodes, text, meta, ptext, work, item, mode, i); } if (k == K_EXPR_CALL) { return ex_call(out, q, nodes, text, meta, ptext, work, item, mode, i); @@ -918,6 +991,201 @@ pub fn ex(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []con return fail(out, nodes, meta, text, i, kind_label(k), lo(nodes, i, F_NAME), hi(nodes, i, F_NAME)); } +/// Writes the cast of expression node srcn to type `own`, the value of node i (`x as T`, or `@as(T, x)`, where +/// `@as(T, @intCast(y))` converts y). Equal widths keep the bits (`$signed`, `$unsigned`); a wider type fills +/// with zeros, or with the sign of a signed source; a narrower one keeps the low bits through t27_uW. +pub fn ex_cast(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, item: usize, mode: u8, i: usize, srcn: usize, own: i64) -> i64 { + var q : usize = at; + var sn : usize = srcn; + if (kind(nodes, sn) == K_EXPR_CALL and field_eq(nodes, text, sn, F_NAME, "@intCast") and children(nodes, sn) == 1) { sn = sn + 1; } + const src : i64 = ty_of(nodes, text, meta, ptext, item, sn, 0); + if (src == TY_COMPTIME) { return ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, own); } + if (!ty_scalar(src)) { return fail(out, nodes, meta, text, i, "cast of a value that is no integer or bool", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); } + if (own == TY_BOOL and src != TY_BOOL) { return fail(out, nodes, meta, text, i, "cast to bool", lo(nodes, i, F_TYPE), hi(nodes, i, F_TYPE)); } + const sw : i64 = ty_width(src); + const dw : i64 = ty_width(own); + const sgd : bool = ty_sint(own); + if (sw == dw) { + if (ty_sint(src) == sgd) { return ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, src); } + if (sgd) { + q = put_text(out, q, "$signed("); + } else { + q = put_text(out, q, "$unsigned("); + } + const rs : i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, src); + if (rs < 0) { return rs; } + q = @intCast(rs); + q = put_text(out, q, ")"); + const rsq : i64 = @intCast(q); + return rsq; + } + if (sw < dw) { + if (ty_sint(src)) { + // sign fill: a signed source plus a signed zero of the wider width is sign-extended + if (!sgd) { q = put_text(out, q, "$unsigned"); } + q = put_text(out, q, "("); + const rg : i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, src); + if (rg < 0) { return rg; } + q = @intCast(rg); + q = put_text(out, q, " + "); + q = put_lit(out, q, 0, SI + dw); + q = put_text(out, q, ")"); + const rgq : i64 = @intCast(q); + return rgq; + } + // zero fill: {N'd0, x} + if (sgd) { q = put_text(out, q, "$signed("); } + q = put_text(out, q, "{"); + const fillw : i64 = dw - sw; + q = put_lit(out, q, 0, fillw); + q = put_text(out, q, ", "); + const r4 : i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, src); + if (r4 < 0) { return r4; } + q = @intCast(r4); + q = put_text(out, q, "}"); + if (sgd) { q = put_text(out, q, ")"); } + const rw2 : i64 = @intCast(q); + return rw2; + } + // narrowing: the low bits, through t27_uW + const dwu : usize = @intCast(dw); + work[2 * count(nodes) + dwu] = 1; + if (sgd) { q = put_text(out, q, "$signed("); } + q = put_text(out, q, "t27_u"); + q = put_dec(out, q, dw as u64); + q = put_text(out, q, "("); + const r5 : i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, sn, src); + if (r5 < 0) { return r5; } + q = @intCast(r5); + q = put_text(out, q, ")"); + if (sgd) { q = put_text(out, q, ")"); } + const rn : i64 = @intCast(q); + return rn; +} + +/// Writes a read of a table (a ROM, gHashTag/t27#11326): `t27_rom_T(index)`, the index at its own unsigned +/// type, or 64 bits when it is a constant. functions() writes the table once, after the fns, because the work +/// byte of its declaration is set. +pub fn ex_index(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, item: usize, mode: u8, i: usize) -> i64 { + const rd : i64 = rom_decl(nodes, text, meta, ptext, item, i + 1); + if (rd == NO_NODE) { return fail(out, nodes, meta, text, i, "ExprIndex", lo(nodes, i + 1, F_NAME), hi(nodes, i + 1, F_NAME)); } + const rdi : usize = @intCast(rd); + const idx : usize = end(nodes, i + 1); + const it : i64 = ty_of(nodes, text, meta, ptext, item, idx, 0); + if (it != TY_COMPTIME and !ty_uint(it)) { return fail(out, nodes, meta, text, i, "table index that is no unsigned integer", lo(nodes, i + 1, F_NAME), hi(nodes, i + 1, F_NAME)); } + work[2 * count(nodes) + ROM_AT + rdi] = 1; + var q : usize = put_text(out, at, "t27_rom_"); + q = put_bytes(out, q, text, lo(nodes, rdi, F_NAME), hi(nodes, rdi, F_NAME)); + q = put_text(out, q, "("); + const r1 : i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, idx, 64); + if (r1 < 0) { return r1; } + q = @intCast(r1); + q = put_text(out, q, ")"); + const ri : i64 = @intCast(q); + return ri; +} + +/// Writes the multiplier 2^n + m of a constant division as a 65-bit literal. +pub fn put_mult(out: []u8, at: usize, n: u64, m: u64) -> usize { + if (n < 64) { + const q0 : usize = put_text(out, at, "65'd"); + return put_dec(out, q0, m + pow2(n)); + } + var q : usize = put_text(out, at, "{1'b1, 64'd"); + q = put_dec(out, q, m); + return put_text(out, q, "}"); +} + +/// Writes `x / k` or `x % k` for a constant k (gHashTag/t27#11326), x the left operand of node i, at the +/// binary node's type `own`. Never a general divider: k = 1 is x or 0; a power of two is a shift or a mask +/// (a signed one, t27_sdivp and t27_smodp, rounds toward zero); any other k is the multiply-shift of +/// verilog_plan.t27 (t27_udivc, t27_umodc, t27_sdivc, t27_smodc), exact for every operand of the type's +/// width (constant_division_is_exact). The helpers work on 64 bits; the result is cut back to the type. +pub fn ex_divk(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, item: usize, mode: u8, i: usize, own: i64) -> i64 { + const l : usize = i + 1; + const r : usize = end(nodes, l); + const isdiv : bool = field_eq(nodes, text, i, F_OP, "/"); + const d : u64 = fold_val(nodes, text, meta, ptext, item, r, 0); + if (!ty_int(own)) { return fail(out, nodes, meta, text, i, "operator", lo(nodes, i, F_OP), hi(nodes, i, F_OP)); } + if (d == 0) { return fail(out, nodes, meta, text, i, "division by the constant 0", 0, 0); } + const n : u64 = ty_width(own) as u64; + const sgn : bool = ty_sint(own); + var q : usize = at; + if (d == 1) { + if (isdiv) { return ex(out, q, nodes, text, meta, ptext, work, item, mode, l, own); } + q = put_lit(out, q, 0, own); + const r1 : i64 = @intCast(q); + return r1; + } + if (is_pow2(d) and !sgn) { + q = put_text(out, q, "{"); + const ra : i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, l, own); + if (ra < 0) { return ra; } + q = @intCast(ra); + if (isdiv) { + q = put_text(out, q, " >> "); + q = put_dec(out, q, ceil_log2(d)); + q = put_text(out, q, "}"); + } else { + q = put_text(out, q, " & "); + q = put_lit(out, q, d - 1, own); + q = put_text(out, q, "}"); + } + const rp : i64 = @intCast(q); + return rp; + } + if (d > DIV_MAX) { return fail(out, nodes, meta, text, i, "division by a constant above 2^62", 0, 0); } + // a helper of 64 bits, then the low n bits + var h : usize = 0; + if (is_pow2(d)) { + h = 4; + if (!isdiv) { h = 5; } + } else if (sgn) { + h = 2; + if (!isdiv) { h = 3; } + } else if (!isdiv) { + h = 1; + } + work[2 * count(nodes) + HELP_AT + h] = 1; + if (n < 64) { + const nu : usize = @intCast(n); + work[2 * count(nodes) + nu] = 1; + if (sgn) { q = put_text(out, q, "$signed("); } + q = put_text(out, q, "t27_u"); + q = put_dec(out, q, n); + q = put_text(out, q, "("); + } + if (h == 0) { q = put_text(out, q, "t27_udivc("); } + if (h == 1) { q = put_text(out, q, "t27_umodc("); } + if (h == 2) { q = put_text(out, q, "t27_sdivc("); } + if (h == 3) { q = put_text(out, q, "t27_smodc("); } + if (h == 4) { q = put_text(out, q, "t27_sdivp("); } + if (h == 5) { q = put_text(out, q, "t27_smodp("); } + const rx : i64 = ex(out, q, nodes, text, meta, ptext, work, item, mode, l, own); + if (rx < 0) { return rx; } + q = @intCast(rx); + if (h >= 4) { + q = put_text(out, q, ", 7'd"); + q = put_dec(out, q, ceil_log2(d)); + } else { + q = put_text(out, q, ", "); + q = put_mult(out, q, n, div_mult_low(n, d)); + q = put_text(out, q, ", 8'd"); + q = put_dec(out, q, div_shift(n, d)); + if (h == 1 or h == 3) { + q = put_text(out, q, ", 64'd"); + q = put_dec(out, q, d); + } + } + q = put_text(out, q, ")"); + if (n < 64) { + q = put_text(out, q, ")"); + if (sgn) { q = put_text(out, q, ")"); } + } + const rd : i64 = @intCast(q); + return rd; +} + /// Writes a call of a fn with a body: `f(a, b)` with each argument at its parameter's type; a fn with no /// parameter gets the dummy input t27_none, given 1'b0. pub fn ex_call(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, item: usize, mode: u8, i: usize) -> i64 { @@ -1496,7 +1764,7 @@ pub fn decls(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: [] q = put_text(out, q, ";\n"); } } - if (k != K_STMT_LOCAL and k != K_STMT_WHILE and k != K_STMT_IF and k != K_MODULE and k != K_STMT_ASSIGN and k != K_EXPR_RETURN and k != K_STMT_EXPR and k != K_STMT_BREAK and k != K_STMT_CONTINUE and k != K_EXPR_LITERAL and k != K_EXPR_IDENTIFIER and k != K_EXPR_CALL and k != K_EXPR_BINARY and k != K_EXPR_UNARY and k != K_EXPR_CAST and k != K_EXPR_IF and k != K_EXPR_FIELD_ACCESS) { + if (k != K_STMT_LOCAL and k != K_STMT_WHILE and k != K_STMT_IF and k != K_MODULE and k != K_STMT_ASSIGN and k != K_EXPR_RETURN and k != K_STMT_EXPR and k != K_STMT_BREAK and k != K_STMT_CONTINUE and k != K_EXPR_LITERAL and k != K_EXPR_IDENTIFIER and k != K_EXPR_CALL and k != K_EXPR_BINARY and k != K_EXPR_UNARY and k != K_EXPR_CAST and k != K_EXPR_IF and k != K_EXPR_FIELD_ACCESS and k != K_EXPR_INDEX) { return fail(out, nodes, meta, text, j, kind_label(k), lo(nodes, j, F_NAME), hi(nodes, j, F_NAME)); } j = j + 1; @@ -1575,10 +1843,223 @@ pub fn functions(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta } w = w + 1; } + // the constant-division helpers: the multiply-shift is shared by the others + const hb : usize = 2 * count(nodes) + HELP_AT; + if (work[hb] != 0 or work[hb + 1] != 0 or work[hb + 2] != 0 or work[hb + 3] != 0) { q = put_div_helper(out, q, 0); } + if (work[hb + 1] != 0) { q = put_div_helper(out, q, 1); } + if (work[hb + 2] != 0 or work[hb + 3] != 0) { q = put_div_helper(out, q, 2); } + if (work[hb + 3] != 0) { q = put_div_helper(out, q, 3); } + if (work[hb + 4] != 0 or work[hb + 5] != 0) { q = put_div_helper(out, q, 4); } + if (work[hb + 5] != 0) { q = put_div_helper(out, q, 5); } + // the tables the fns read + var d : usize = 1; + while (d < end(nodes, 0)) { + if (kind(nodes, d) == K_CONST_DECL and work[2 * count(nodes) + ROM_AT + d] != 0) { + const rt : i64 = put_rom(out, q, nodes, text, meta, ptext, d); + if (rt < 0) { return rt; } + q = @intCast(rt); + } + d = end(nodes, d); + } const rr : i64 = @intCast(q); return rr; } +/// Writes constant-division helper h (ex_divk's numbering) at the module's indent. They take 64 bits and +/// give 64 bits; the multiplier m is 2^n + m' of the operand's width n and s is n + ceil(log2 k). +pub fn put_div_helper(out: []u8, at: usize, h: usize) -> usize { + if (h == 0) { + return put_text(out, at, "\n function [63:0] t27_udivc;\n input [63:0] x;\n input [64:0] m;\n input [7:0] s;\n reg [191:0] p;\n begin\n p = x * m;\n p = p >> s;\n t27_udivc = p[63:0];\n end\n endfunction\n"); + } + if (h == 1) { + return put_text(out, at, "\n function [63:0] t27_umodc;\n input [63:0] x;\n input [64:0] m;\n input [7:0] s;\n input [63:0] d;\n begin\n t27_umodc = x - t27_udivc(x, m, s) * d;\n end\n endfunction\n"); + } + if (h == 2) { + return put_text(out, at, "\n function signed [63:0] t27_sdivc;\n input signed [63:0] x;\n input [64:0] m;\n input [7:0] s;\n reg [63:0] a;\n begin\n a = x[63] ? (64'd0 - x) : x;\n a = t27_udivc(a, m, s);\n t27_sdivc = x[63] ? (64'd0 - a) : a;\n end\n endfunction\n"); + } + if (h == 3) { + return put_text(out, at, "\n function signed [63:0] t27_smodc;\n input signed [63:0] x;\n input [64:0] m;\n input [7:0] s;\n input [63:0] d;\n begin\n t27_smodc = x - t27_sdivc(x, m, s) * $signed(d);\n end\n endfunction\n"); + } + if (h == 4) { + return put_text(out, at, "\n function signed [63:0] t27_sdivp;\n input signed [63:0] x;\n input [6:0] k;\n reg signed [63:0] b;\n begin\n b = (64'sd1 <<< k) - 64'sd1;\n t27_sdivp = (x < 64'sd0) ? ((x + b) >>> k) : (x >>> k);\n end\n endfunction\n"); + } + return put_text(out, at, "\n function signed [63:0] t27_smodp;\n input signed [63:0] x;\n input [6:0] k;\n begin\n t27_smodp = x - (t27_sdivp(x, k) <<< k);\n end\n endfunction\n"); +} + +/// Writes the table declared at node d as a ROM: a function with one `case` arm per element; an index past +/// the end reads 0. +pub fn put_rom(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, d: usize) -> i64 { + const t : i64 = rom_elem(nodes, text, d); + if (!ty_scalar(t)) { return fail(out, nodes, meta, text, d, "table whose elements are no integers or bools", lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); } + var q : usize = put_text(out, at, "\n // table "); + q = put_bytes(out, q, text, lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + q = put_text(out, q, "\n function "); + q = put_range(out, q, t); + q = put_text(out, q, "t27_rom_"); + q = put_bytes(out, q, text, lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + q = put_text(out, q, ";\n input [63:0] i;\n begin\n case (i)\n"); + var c : usize = d + 2; + var kx : u64 = 0; + while (c < end(nodes, d + 1)) { + // an element is a constant, or `-` and a constant for a signed table + var ne : bool = false; + var ev : usize = c; + if (kind(nodes, c) == K_EXPR_UNARY and field_eq(nodes, text, c, F_OP, "-") and ty_sint(t)) { + ne = true; + ev = c + 1; + } + if (!fold_ok(nodes, text, meta, ptext, 0, ev, 0)) { return fail(out, nodes, meta, text, c, "table element that does not fold", lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); } + const v : u64 = fold_val(nodes, text, meta, ptext, 0, ev, 0); + var okv : bool = fits(v, t); + if (ne and v > 0) { okv = fits(v - 1, t); } + if (!okv) { return fail(out, nodes, meta, text, c, "table element that does not fit its type", lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); } + q = put_text(out, q, " 64'd"); + q = put_dec(out, q, kx); + q = put_text(out, q, ": t27_rom_"); + q = put_bytes(out, q, text, lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + q = put_text(out, q, " = "); + if (ne) { q = put_text(out, q, "-"); } + q = put_lit(out, q, v, t); + q = put_text(out, q, ";\n"); + kx = kx + 1; + c = end(nodes, c); + } + q = put_text(out, q, " default: t27_rom_"); + q = put_bytes(out, q, text, lo(nodes, d, F_NAME), hi(nodes, d, F_NAME)); + q = put_text(out, q, " = "); + q = put_lit(out, q, 0, t); + q = put_text(out, q, ";\n endcase\n end\n endfunction\n"); + const rr : i64 = @intCast(q); + return rr; +} + +/// Is fn f one that gets a module of its own (a library spec, gHashTag/t27#11326): a body, not on_clock, an +/// integer or bool for every parameter and for the result, and `pub` unless `top` names it? +pub fn wrappable(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, f: usize, top: []const u8) -> bool { + if (kind(nodes, f) != K_FN_DECL or is_extern(nodes, f) or field_eq(nodes, text, f, F_NAME, "on_clock")) { return false; } + if (top.len > 0) { return field_is(nodes, text, f, F_NAME, top); } + if (!is_pub(meta, f) or !ty_scalar(fn_type(nodes, text, f))) { return false; } + const np : usize = param_count(ptext, params_lo(meta, f), params_hi(meta, f)); + var kx : usize = 0; + while (kx < np) { + if (!ty_scalar(param_type(nodes, text, meta, ptext, f, kx))) { return false; } + kx = kx + 1; + } + return true; +} + +/// Is the spec a library of decisions: no on_clock, and no var, net, instance or input? +pub fn is_library(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8) -> bool { + if (clock_fn(nodes, text) != NO_NODE) { return false; } + var c : usize = 1; + while (c < end(nodes, 0)) { + const cl : u8 = item_class(nodes, text, meta, ptext, c); + if (cl == C_VAR or cl == C_NET or cl == C_INSTANCE or cl == C_INPUT or cl == C_INOUT) { return false; } + c = end(nodes, c); + } + return true; +} + +/// Writes the module of the pure fn f: `module f (input p0, ..., output t27_y); assign t27_y = f(p0, ...);` +/// and the fns f reaches. The marks and the helper flags are cleared before and after, so each module carries +/// exactly its own. +pub fn fn_module(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, f: usize) -> i64 { + const n : usize = count(nodes); + var j : usize = 0; + while (j < n) { + work[j] = 0; + j = j + 1; + } + j = 2 * n + 1; + while (j < 3 * n + ROM_AT) { + work[j] = 0; + j = j + 1; + } + work[f] = 1; + close_marks(nodes, text, work, 0); + const rt : i64 = fn_type(nodes, text, f); + const np : usize = param_count(ptext, params_lo(meta, f), params_hi(meta, f)); + var q : usize = put_text(out, at, "\n// pure fn "); + q = put_bytes(out, q, text, lo(nodes, f, F_NAME), hi(nodes, f, F_NAME)); + q = put_text(out, q, " (line "); + q = put_dec(out, q, line_of(meta, f) as u64); + q = put_text(out, q, "): combinational, one input per parameter\nmodule "); + q = put_name(out, q, text, lo(nodes, f, F_NAME), hi(nodes, f, F_NAME)); + q = put_text(out, q, " ("); + var kx : usize = 0; + while (kx < np) { + const pt : i64 = param_type(nodes, text, meta, ptext, f, kx); + q = put_text(out, q, "\n input wire "); + q = put_range(out, q, pt); + q = put_name(out, q, ptext, param_name_lo(meta, ptext, f, kx), param_name_hi(meta, ptext, f, kx)); + q = put_text(out, q, ","); + kx = kx + 1; + } + q = put_text(out, q, "\n output wire "); + q = put_range(out, q, rt); + q = put_text(out, q, "t27_y\n);\n assign t27_y = "); + q = put_name(out, q, text, lo(nodes, f, F_NAME), hi(nodes, f, F_NAME)); + q = put_text(out, q, "("); + if (np == 0) { q = put_text(out, q, "1'b0"); } + kx = 0; + while (kx < np) { + if (kx > 0) { q = put_text(out, q, ", "); } + q = put_name(out, q, ptext, param_name_lo(meta, ptext, f, kx), param_name_hi(meta, ptext, f, kx)); + kx = kx + 1; + } + q = put_text(out, q, ");\n"); + const r1 : i64 = functions(out, q, nodes, text, meta, ptext, work, 0, M_FUNC); + if (r1 < 0) { return r1; } + q = @intCast(r1); + q = put_text(out, q, "endmodule\n"); + j = 2 * n + 1; + while (j < 3 * n + ROM_AT) { + work[j] = 0; + j = j + 1; + } + const rm : i64 = @intCast(q); + return rm; +} + +/// Writes the modules of a library spec: one per wrappable fn (only `top` when one is named), a fn that does +/// not lower noted as a comment and skipped unless it is the one named. When none lowers, the first refusal +/// is the answer; when none is wrappable, the empty module of the spec as before. +pub fn lib_modules(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, top: []const u8) -> i64 { + var q : usize = at; + var emitted : usize = 0; + var bad : i64 = NO_NODE; + var c : usize = 1; + while (c < end(nodes, 0)) { + if (wrappable(nodes, text, meta, ptext, c, top)) { + const r : i64 = fn_module(out, q, nodes, text, meta, ptext, work, c); + if (r < 0) { + if (top.len > 0) { return r; } + if (bad == NO_NODE) { bad = @intCast(c); } + q = put_text(out, q, "\n// skipped fn "); + q = put_bytes(out, q, text, lo(nodes, c, F_NAME), hi(nodes, c, F_NAME)); + q = put_text(out, q, ": "); + q = copy_msg(out, q, @intCast(0 - r)); + q = put_text(out, q, "\n"); + } else { + q = @intCast(r); + emitted = emitted + 1; + } + } + c = end(nodes, c); + } + if (emitted > 0) { + const rq : i64 = @intCast(q); + return rq; + } + if (top.len > 0) { return fail(out, nodes, meta, text, 0, "--top that names no fn of the spec", 0, 0); } + if (bad != NO_NODE) { + const bi : usize = @intCast(bad); + return fn_module(out, at, nodes, text, meta, ptext, work, bi); + } + return module_text(out, at, nodes, text, meta, ptext, work); +} + + /// Writes the port list of the module: the implicit inputs, on_clock's unbound parameters, the outputs. pub fn ports(out: []u8, at: usize, nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, oc: i64) -> i64 { var q : usize = at; @@ -2240,14 +2721,21 @@ pub fn test_bench(out: []u8, at: usize, nodes: []const u8, text: []const u8, met /// The entry point (see the header): the module, then its test bench; a test bench that does not lower is a /// comment naming why, and the module stands. -pub fn emit(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, out: []u8) -> i64 { +pub fn emit(nodes: []const u8, text: []const u8, meta: []const u8, ptext: []const u8, work: []u8, out: []u8, top: []const u8) -> i64 { if (count(nodes) == 0 or kind(nodes, 0) != K_MODULE) { return fail_front(out, "file with no module node"); } closures(nodes, text, meta, ptext, work); var q : usize = put_text(out, 0, "// Generated by t27b verilog from module "); q = put_bytes(out, q, text, lo(nodes, 0, F_NAME), hi(nodes, 0, F_NAME)); q = put_text(out, q, " -- DO NOT EDIT; the decisions are specs/tri/t27b/verilog_plan.t27\n"); q = put_text(out, q, "// phi^2 + 1/phi^2 = 3 | TRINITY\n`timescale 1ns / 1ps\n`default_nettype none\n"); - const r1 : i64 = module_text(out, q, nodes, text, meta, ptext, work); + var r1 : i64 = 0; + if (is_library(nodes, text, meta, ptext)) { + r1 = lib_modules(out, q, nodes, text, meta, ptext, work, top); + } else if (top.len > 0) { + return fail_front(out, "--top on a spec with an on_clock, a var, a net, an instance or an input"); + } else { + r1 = module_text(out, q, nodes, text, meta, ptext, work); + } if (r1 < 0) { const ml1 : usize = @intCast(0 - r1); return to_front(out, ml1); @@ -2327,14 +2815,14 @@ test an_input_spelled_in_underscore_is_the_port_in { i = i + 1; } tmeta(mb[0 .. 80], 3, FLAG_PUB, 4); - var wk : [80]u8 = undefined; + var wk : [140]u8 = undefined; i = 0; - while (i < 80) { + while (i < 140) { wk[i] = 0; i = i + 1; } var out : [4096]u8 = undefined; - const r : i64 = emit(nb[0 .. 360], tx, mb[0 .. 80], "", wk[0 .. 80], out[0 .. 4096]); + const r : i64 = emit(nb[0 .. 360], tx, mb[0 .. 80], "", wk[0 .. 140], out[0 .. 4096], ""); assert r > 0; const n : usize = @intCast(r); assert holds(out[0 .. 4096], n, "input wire [7:0] in,") or holds(out[0 .. 4096], n, "input wire [7:0] in\n"); @@ -2344,11 +2832,11 @@ test an_input_spelled_in_underscore_is_the_port_in { vset(nb[0 .. 360], 1, F_NAME, 18, 23); vset(nb[0 .. 360], 4, F_NAME, 18, 23); i = 0; - while (i < 80) { + while (i < 140) { wk[i] = 0; i = i + 1; } - const r2 : i64 = emit(nb[0 .. 360], tx, mb[0 .. 80], "", wk[0 .. 80], out[0 .. 4096]); + const r2 : i64 = emit(nb[0 .. 360], tx, mb[0 .. 80], "", wk[0 .. 140], out[0 .. 4096], ""); assert r2 > 0; const n2 : usize = @intCast(r2); assert holds(out[0 .. 4096], n2, "input wire [7:0] data_"); @@ -2357,11 +2845,11 @@ test an_input_spelled_in_underscore_is_the_port_in { vset(nb[0 .. 360], 1, F_NAME, 23, 27); vset(nb[0 .. 360], 4, F_NAME, 23, 27); i = 0; - while (i < 80) { + while (i < 140) { wk[i] = 0; i = i + 1; } - const r3 : i64 = emit(nb[0 .. 360], tx, mb[0 .. 80], "", wk[0 .. 80], out[0 .. 4096]); + const r3 : i64 = emit(nb[0 .. 360], tx, mb[0 .. 80], "", wk[0 .. 140], out[0 .. 4096], ""); assert r3 > 0; const n3 : usize = @intCast(r3); assert holds(out[0 .. 4096], n3, "input wire [7:0] \\for "); @@ -2396,14 +2884,14 @@ test a_clocked_counter_lowers_to_a_register_and_an_always_block { tmeta(mb[0 .. 144], 1, FLAG_MUT, 2); tmeta(mb[0 .. 144], 3, 0, 3); tmeta(mb[0 .. 144], 4, 0, 3); - var wk : [88]u8 = undefined; + var wk : [140]u8 = undefined; i = 0; - while (i < 88) { + while (i < 140) { wk[i] = 0; i = i + 1; } var out : [4096]u8 = undefined; - const r : i64 = emit(nb[0 .. 648], tx, mb[0 .. 144], "", wk[0 .. 88], out[0 .. 4096]); + const r : i64 = emit(nb[0 .. 648], tx, mb[0 .. 144], "", wk[0 .. 140], out[0 .. 4096], ""); assert r > 0; const n : usize = @intCast(r); assert holds(out[0 .. 4096], n, "module M ("); @@ -2418,11 +2906,11 @@ test a_clocked_counter_lowers_to_a_register_and_an_always_block { // a pub var makes x an internal register: the module declares its ports tmeta(mb[0 .. 144], 1, FLAG_MUT | FLAG_PUB, 2); i = 0; - while (i < 88) { + while (i < 140) { wk[i] = 0; i = i + 1; } - const r2 : i64 = emit(nb[0 .. 648], tx, mb[0 .. 144], "", wk[0 .. 88], out[0 .. 4096]); + const r2 : i64 = emit(nb[0 .. 648], tx, mb[0 .. 144], "", wk[0 .. 140], out[0 .. 4096], ""); assert r2 > 0; const n2 : usize = @intCast(r2); assert holds(out[0 .. 4096], n2, "output reg [7:0] x"); @@ -2479,14 +2967,14 @@ test a_constant_rst_n_and_en_are_folded_out_of_the_always_block { 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 wk : [140]u8 = undefined; var i : usize = 0; - while (i < 96) { + while (i < 140) { 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]); + const r : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "", wk[0 .. 140], out[0 .. 4096], ""); assert r > 0; const n : usize = @intCast(r); assert holds(out[0 .. 4096], n, "input wire clk"); @@ -2503,11 +2991,11 @@ test a_constant_rst_n_and_en_are_folded_out_of_the_always_block { // 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) { + while (i < 140) { wk[i] = 0; i = i + 1; } - const r2 : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "", wk[0 .. 96], out[0 .. 4096]); + const r2 : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "", wk[0 .. 140], out[0 .. 4096], ""); assert r2 > 0; const n2 : usize = @intCast(r2); assert holds(out[0 .. 4096], n2, "always @(posedge clk) begin"); @@ -2523,14 +3011,14 @@ test an_rst_n_port_keeps_its_async_reset_and_a_false_en_keeps_its_wire { 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 wk : [140]u8 = undefined; var i : usize = 0; - while (i < 96) { + while (i < 140) { 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]); + const r : i64 = emit(nb[0 .. 936], tx, mb[0 .. 208], "", wk[0 .. 140], out[0 .. 4096], ""); assert r > 0; const n : usize = @intCast(r); assert holds(out[0 .. 4096], n, "input wire rst_n"); @@ -2566,14 +3054,14 @@ test a_call_of_an_undeclared_fn_is_refused_by_name { } tmeta(mb[0 .. 128], 1, FLAG_MUT, 3); tmeta(mb[0 .. 128], 6, 0, 7); - var wk : [86]u8 = undefined; + var wk : [140]u8 = undefined; i = 0; - while (i < 86) { + while (i < 140) { wk[i] = 0; i = i + 1; } var out : [4096]u8 = undefined; - const r : i64 = emit(nb[0 .. 576], tx, mb[0 .. 128], "", wk[0 .. 86], out[0 .. 4096]); + const r : i64 = emit(nb[0 .. 576], tx, mb[0 .. 128], "", wk[0 .. 140], out[0 .. 4096], ""); assert r < 0; const n : usize = @intCast(0 - r); assert holds(out[0 .. 4096], n, "unsupported construct call of an undeclared fn `STARTUPE2` at line 7"); @@ -2630,9 +3118,9 @@ test a_declared_clock_period_rides_on_the_wire_of_the_output_it_names { tmeta(mb[0 .. 160], i, 0, i + 1); i = i + 1; } - var wk : [90]u8 = undefined; + var wk : [140]u8 = undefined; i = 0; - while (i < 90) { + while (i < 140) { wk[i] = 0; i = i + 1; } @@ -2643,7 +3131,7 @@ test a_declared_clock_period_rides_on_the_wire_of_the_output_it_names { const r0 : i64 = put_period(out[0 .. 4096], 0, nb[0 .. 720], tx, mb[0 .. 160], "", 6); assert r0 == 33; assert holds(out[0 .. 4096], 33, " (* t27_period_ps = 113040 *)\n"); - const r : i64 = emit(nb[0 .. 720], tx, mb[0 .. 160], "", wk[0 .. 90], out[0 .. 4096]); + const r : i64 = emit(nb[0 .. 720], tx, mb[0 .. 160], "", wk[0 .. 140], out[0 .. 4096], ""); assert r > 0; const n : usize = @intCast(r); assert holds(out[0 .. 4096], n, " (* t27_period_ps = 113040 *)\n wire g__O;\n"); @@ -2651,11 +3139,11 @@ test a_declared_clock_period_rides_on_the_wire_of_the_output_it_names { vset(nb[0 .. 720], 8, F_NAME, 26, 39); assert !period_named(nb[0 .. 720], tx, mb[0 .. 160], "", 8); i = 0; - while (i < 90) { + while (i < 140) { wk[i] = 0; i = i + 1; } - const r2 : i64 = emit(nb[0 .. 720], tx, mb[0 .. 160], "", wk[0 .. 90], out[0 .. 4096]); + const r2 : i64 = emit(nb[0 .. 720], tx, mb[0 .. 160], "", wk[0 .. 140], out[0 .. 4096], ""); assert r2 < 0; const n2 : usize = @intCast(0 - r2); assert holds(out[0 .. 4096], n2, "unsupported construct clock period declared for no instance output `x_O_PERIOD_PS` at line 9"); @@ -2663,11 +3151,11 @@ test a_declared_clock_period_rides_on_the_wire_of_the_output_it_names { vset(nb[0 .. 720], 8, F_NAME, 26, 27); vset(nb[0 .. 720], 7, F_VALUE, 44, 45); i = 0; - while (i < 90) { + while (i < 140) { wk[i] = 0; i = i + 1; } - const r3 : i64 = emit(nb[0 .. 720], tx, mb[0 .. 160], "", wk[0 .. 90], out[0 .. 4096]); + const r3 : i64 = emit(nb[0 .. 720], tx, mb[0 .. 160], "", wk[0 .. 140], out[0 .. 4096], ""); assert r3 < 0; 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"); @@ -2707,14 +3195,14 @@ test a_lane_set_is_one_port_and_an_inout_is_handed_through { 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; + var wk : [140]u8 = undefined; i = 0; - while (i < 100) { + while (i < 140) { 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]); + const r : i64 = emit(nb[0 .. 864], tx, mb[0 .. 192], "d__0:u8\nd__1:u8\nq:u8\n", wk[0 .. 140], 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 @@ -2723,36 +3211,36 @@ test a_lane_set_is_one_port_and_an_inout_is_handed_through { 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); + 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 .. 140], 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) { + while (i < 140) { 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]); + const r2 : i64 = emit(nb[0 .. 864], tx, mb[0 .. 192], "d__0:u8\nd__2:u8\nq:u8\n", wk[0 .. 140], 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) { + while (i < 140) { 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]); + const r3 : i64 = emit(nb[0 .. 864], tx, mb[0 .. 192], "d__0:u8\nd__1:u16\nq:u8\n", wk[0 .. 140], 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) { + while (i < 140) { 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]); + const r4 : i64 = emit(nb[0 .. 864], tx, mb[0 .. 192], "d__0:u8\nd__1:u8\nq:u16\n", wk[0 .. 140], 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"); @@ -2794,9 +3282,9 @@ test a_pin_attribute_rides_on_the_port_it_names { i = i + 1; } tmeta(mb[0 .. 176], 7, FLAG_PUB, 8); - var wk : [100]u8 = undefined; + var wk : [140]u8 = undefined; i = 0; - while (i < 100) { + while (i < 140) { wk[i] = 0; i = i + 1; } @@ -2812,7 +3300,7 @@ test a_pin_attribute_rides_on_the_port_it_names { vset(nb[0 .. 792], 4, F_KIND, 25, 31); assert pin_decl(nb[0 .. 792], tx, mb[0 .. 176], 1, "x") == 5 and pin_decl(nb[0 .. 792], tx, mb[0 .. 176], 1, "y") == NO_NODE; var out : [4096]u8 = undefined; - const r : i64 = emit(nb[0 .. 792], tx, mb[0 .. 176], "", wk[0 .. 100], out[0 .. 4096]); + const r : i64 = emit(nb[0 .. 792], tx, mb[0 .. 176], "", wk[0 .. 140], out[0 .. 4096], ""); assert r > 0; const n : usize = @intCast(r); // each attribute in PIN_KEYS order, before the port it names, and nowhere else @@ -2821,11 +3309,11 @@ test a_pin_attribute_rides_on_the_port_it_names { // a pin attribute that names no port is refused by name vset(nb[0 .. 792], 9, F_NAME, 59, 64); i = 0; - while (i < 100) { + while (i < 140) { wk[i] = 0; i = i + 1; } - const r2 : i64 = emit(nb[0 .. 792], tx, mb[0 .. 176], "", wk[0 .. 100], out[0 .. 4096]); + const r2 : i64 = emit(nb[0 .. 792], tx, mb[0 .. 176], "", wk[0 .. 140], out[0 .. 4096], ""); assert r2 < 0; const n2 : usize = @intCast(0 - r2); assert holds(out[0 .. 4096], n2, "unsupported construct pin attribute for no port of the module `z_LOC` at line 10"); diff --git a/specs/tri/t27b/verilog_plan.t27 b/specs/tri/t27b/verilog_plan.t27 index 158bb2d07a..36274f6cb9 100644 --- a/specs/tri/t27b/verilog_plan.t27 +++ b/specs/tri/t27b/verilog_plan.t27 @@ -84,8 +84,29 @@ ; Widths. Every value is exact at its t27 type: unsigned widths 1 to 64 and bool. A sum, a product, a ; shift or a complement is written inside a concatenation, `{a + b}`, so Verilog evaluates it at its own ; width and not at the width of the context (the defect of #8341); a literal is always sized to the type it -; takes; a narrowing cast of an expression is a call of t27_uW, a widening one a zero fill. Signed types -; are refused. +; takes; a narrowing cast of an expression is a call of t27_uW, a widening one a zero fill (a sign fill for a +; signed source). +; Signed integers (gHashTag/t27#11326). `i2` to `i64` lower as `signed` wires, regs and function results. +; The value of a signed expression is always written so that Verilog reads it as signed: a sum, a product, a +; shift or a complement is `$signed({a + b})`, and an unsigned operand that meets a signed peer is widened by +; `$signed({1'b0, x})`, because one unsigned operand makes Verilog compare and shift every operand of its +; expression as unsigned. A signed `>>` is `>>>`. A `u` and an `i` operand meet at the `i` only when the `i` is +; wider (Zig's coercion); otherwise the operator is refused by name. +; Constant tables (gHashTag/t27#11326). A module-level `const T : [N]ty = [..]` of integers or bools read by +; an index is a ROM: `t27_rom_T(i)`, a Verilog function with one `case` arm per element, written after the +; fns that read it. An index past the end reads 0 where Zig would trap. +; Constant division (gHashTag/t27#11326). `x / k` and `x % k` with `k` a compile-time constant are never a +; general divider. A power of two is a shift or a mask (a signed one rounds toward zero, t27_sdivp). Any other +; `k` is the multiply-shift of div_mult_low and div_shift: q = (x * (2^n + m)) >> (n + l), where n is the +; operand's width and l is ceil(log2 k); it is exact for every n-bit x, which this file proves in +; constant_division_is_exact. A signed `x` divides |x| and negates, so `/` truncates toward zero and `%` +; takes the dividend's sign, as Zig's @divTrunc and @rem do. A divisor that is not a constant is Verilog's +; `/` and `%` (the signed forms agree with Zig's), a general divider. +; Pure fns (gHashTag/t27#11326). A spec with no `fn on_clock`, no var, net, instance or input is a library of +; decisions. Each `pub fn` of it whose parameters and result are integers or bools is also a combinational +; module of the fn's own name: one input port per parameter, one output port `t27_y`. `t27b verilog +; --top ` writes only that fn's module (any fn, pub or not); a library with no such fn is an empty module +; as before. ; Loops. Hardware has no loop whose trip count depends on data, so a `while` in the module is written as a ; `for` over a static bound with the condition as a guard; the bound is read from the source, never ; guessed (trip_bound). In the test bench a loop without such a bound is a plain `while`. @@ -175,7 +196,8 @@ pub fn colon_at(ptext: []const u8, s: usize, e: usize) -> usize { } // ---------------------------------------------------------------------------------------------------------- -// Types. A type is one i64 code: 1 to 64 an unsigned integer of that width; TY_BOOL; TY_COMPTIME an untyped +// Types. A type is one i64 code: 1 to 64 an unsigned integer of that width; SI + 2 to SI + 64 a signed one; +// TY_BOOL; TY_COMPTIME an untyped // integer literal or constant; TY_VOID no value; TY_STR and TY_FLOAT only as parameters of an extern; // TY_STRUCT + i the struct declared at node i (an extern's result); TY_BAD anything else, refused where it is // used. @@ -189,6 +211,8 @@ pub const TY_STR : i64 = 67; pub const TY_FLOAT : i64 = 68; pub const TY_STRUCT : i64 = 1000000; pub const MAX_WIDTH : i64 = 64; +/// A signed integer of width w (2 to 64) is the code SI + w. +pub const SI : i64 = 100; /// The code of the type text s[a .. b] (trimmed). Struct names are looked up by the writer, not here. pub fn type_code(s: []const u8, a: usize, b: usize) -> i64 { @@ -202,7 +226,8 @@ pub fn type_code(s: []const u8, a: usize, b: usize) -> i64 { if (same(s[p .. q], "comptime_int")) { return TY_COMPTIME; } if (same(s[p .. q], "str") or same(s[p .. q], "string") or same(s[p .. q], "[]const u8")) { return TY_STR; } if (same(s[p .. q], "f64") or same(s[p .. q], "f32") or same(s[p .. q], "comptime_float")) { return TY_FLOAT; } - if (s[p] != 117 or q - p < 2 or q - p > 3) { return TY_BAD; } + if (same(s[p .. q], "isize")) { return SI + 64; } + if ((s[p] != 117 and s[p] != 105) or q - p < 2 or q - p > 3) { return TY_BAD; } // p1 is bound once: t27c's Zig backend hoists a repeated `p + 1` above the declaration of p (#2629). const p1 : usize = p + 1; var w : i64 = 0; @@ -214,14 +239,28 @@ pub fn type_code(s: []const u8, a: usize, b: usize) -> i64 { j = j + 1; } if (s[p1] == 48 or w < 1 or w > MAX_WIDTH) { return TY_BAD; } + if (s[p] == 105) { + if (w < 2) { return TY_BAD; } + return SI + w; + } return w; } /// Is the type an unsigned integer this path holds? -pub fn ty_int(t: i64) -> bool { +pub fn ty_uint(t: i64) -> bool { return t >= 1 and t <= MAX_WIDTH; } +/// Is the type a signed integer this path holds? +pub fn ty_sint(t: i64) -> bool { + return t > SI + 1 and t <= SI + MAX_WIDTH; +} + +/// Is the type an integer, signed or not? +pub fn ty_int(t: i64) -> bool { + return ty_uint(t) or ty_sint(t); +} + /// Is the type a value a register or a wire holds: an integer or a bool? pub fn ty_scalar(t: i64) -> bool { return ty_int(t) or t == TY_BOOL; @@ -229,14 +268,16 @@ pub fn ty_scalar(t: i64) -> bool { /// The width in bits of a scalar type (a bool is one bit); 0 for anything else. pub fn ty_width(t: i64) -> i64 { - if (ty_int(t)) { return t; } + if (ty_uint(t)) { return t; } + if (ty_sint(t)) { return t - SI; } if (t == TY_BOOL) { return 1; } return 0; } -/// The type both operands of an arithmetic or a comparison take (Zig's peer type for unsigned integers): -/// an untyped operand takes the other's type, two integers the wider one, two bools bool. TY_BAD when they -/// do not meet (a bool and an integer, or anything not scalar). +/// The type both operands of an arithmetic or a comparison take (Zig's peer type): an untyped operand takes +/// the other's type, two integers of one signedness the wider one, an unsigned and a strictly wider signed +/// the signed, two bools bool. TY_BAD when they do not meet (a bool and an integer, an unsigned and a +/// signed that is not wider, or anything not scalar). pub fn peer(a: i64, b: i64) -> i64 { if (a == TY_COMPTIME) { if (b == TY_COMPTIME or ty_scalar(b)) { return b; } @@ -247,10 +288,23 @@ pub fn peer(a: i64, b: i64) -> i64 { return TY_BAD; } if (a == TY_BOOL and b == TY_BOOL) { return TY_BOOL; } - if (ty_int(a) and ty_int(b)) { + if (ty_uint(a) and ty_uint(b)) { if (a >= b) { return a; } return b; } + if (ty_sint(a) and ty_sint(b)) { + if (a >= b) { return a; } + return b; + } + // an unsigned meets a signed at the signed one only when every value of it fits: the signed is wider + if (ty_sint(a) and ty_uint(b)) { + if (a - SI > b) { return a; } + return TY_BAD; + } + if (ty_uint(a) and ty_sint(b)) { + if (b - SI > a) { return b; } + return TY_BAD; + } return TY_BAD; } @@ -271,7 +325,8 @@ pub fn max_of(w: i64) -> u64 { /// Does v fit the scalar type t? A bool holds 0 and 1. pub fn fits(v: u64, t: i64) -> bool { if (t == TY_BOOL) { return v <= 1; } - if (ty_int(t)) { return v <= max_of(t); } + if (ty_uint(t)) { return v <= max_of(t); } + if (ty_sint(t)) { return v <= max_of(t - SI - 1); } return false; } @@ -434,6 +489,100 @@ pub fn fold_val_op(s: []const u8, a: u64, b: u64) -> u64 { return 0; } +// ---------------------------------------------------------------------------------------------------------- +// Constant division (gHashTag/t27#11326). x / k and x % k, k a constant, are a multiply and a shift. +// ---------------------------------------------------------------------------------------------------------- + +/// The largest constant divisor that lowers; above it the operator is refused by name. +pub const DIV_MAX : u64 = 4611686018427387904; + +/// 2^k, for k up to 63. +pub fn pow2(k: u64) -> u64 { + var v : u64 = 1; + var j : u64 = 0; + while (j < k) { + v = v * 2; + j = j + 1; + } + return v; +} + +/// Is d a power of two (1 is)? +pub fn is_pow2(d: u64) -> bool { + return d != 0 and (d & (d - 1)) == 0; +} + +/// ceil(log2(d)) for d >= 1: the l with 2^(l-1) < d <= 2^l, 0 for d = 1. +pub fn ceil_log2(d: u64) -> u64 { + var l : u64 = 0; + var v : u64 = 1; + while (v < d and l < 63) { + v = v * 2; + l = l + 1; + } + return l; +} + +/// The low n bits of the multiplier m = 2^n + m' that divides an n-bit x by d (not a power of two): m' = +/// floor(2^n * (2^l - d) / d) + 1, l = ceil_log2(d), by long division so that nothing is wider than 64 +/// bits. 2^l - d < d, and d <= DIV_MAX, so the remainder doubles without overflow. +pub fn div_mult_low(n: u64, d: u64) -> u64 { + var r : u64 = pow2(ceil_log2(d)) - d; + var q : u64 = 0; + var j : u64 = 0; + while (j < n) { + r = r * 2; + q = q * 2; + if (r >= d) { + r = r - d; + q = q + 1; + } + j = j + 1; + } + return q + 1; +} + +/// The shift of the multiply-shift: n + ceil_log2(d). +pub fn div_shift(n: u64, d: u64) -> u64 { + return n + ceil_log2(d); +} + +/// 2^e mod d, by doubling. +pub fn pow2_mod(e: u64, d: u64) -> u64 { + var r : u64 = 1 % d; + var j : u64 = 0; + while (j < e) { + r = r * 2 % d; + j = j + 1; + } + return r; +} + +/// By how much m * d overshoots 2^(n + l): e = d - (2^(n + l) mod d). The multiply-shift is exact for every +/// n-bit x when 0 < e <= 2^l: x * m / 2^s = x / d + x * e / (d * 2^s), the second term is below +/// 2^n * 2^l / (d * 2^(n + l)) = 1 / d, and the fractional part of x / d is at most (d - 1) / d. +pub fn div_error(n: u64, d: u64) -> u64 { + return d - pow2_mod(n + ceil_log2(d), d); +} + +/// Is m' (div_mult_low) the multiplier whose overshoot is div_error: (2^n + m') * d - 2^(n + l) == e, taken +/// modulo 2^64 (both sides are below 2^64 once the multiplier is right)? +pub fn div_mult_checks(n: u64, d: u64) -> bool { + var m : u64 = div_mult_low(n, d); + if (n < 64) { m = m + pow2(n); } + var two_s : u64 = 0; + if (n + ceil_log2(d) < 64) { two_s = pow2(n + ceil_log2(d)); } + return m *% d -% two_s == div_error(n, d); +} + +/// Does the multiply-shift divide every n-bit x exactly by d: a non-power of two d in 3 ..= DIV_MAX with +/// 0 < e <= 2^l and the multiplier of div_mult_low the one that has that overshoot? +pub fn div_exact(n: u64, d: u64) -> bool { + if (d < 3 or d > DIV_MAX or is_pow2(d)) { return false; } + const e : u64 = div_error(n, d); + return e > 0 and e <= pow2(ceil_log2(d)) and div_mult_checks(n, d); +} + /// The names of the node kinds, in NodeKind's order, for refusals. pub const KIND_NAMES : [35]str = ["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"]; @@ -840,8 +989,11 @@ pub fn trip_bound(op: []const u8, kok: bool, k: u64, sok: bool, step: u64) -> i6 pub const VERILOG_KEYWORDS : [103]str = ["always", "and", "assign", "automatic", "begin", "buf", "bufif0", "bufif1", "case", "casex", "casez", "cell", "cmos", "config", "deassign", "default", "defparam", "design", "disable", "edge", "else", "end", "endcase", "endconfig", "endfunction", "endgenerate", "endmodule", "endprimitive", "endspecify", "endtable", "endtask", "event", "for", "force", "forever", "fork", "function", "generate", "genvar", "highz0", "highz1", "if", "ifnone", "incdir", "include", "initial", "inout", "input", "instance", "integer", "join", "large", "liblist", "library", "localparam", "macromodule", "medium", "module", "nand", "negedge", "nmos", "nor", "noshowcancelled", "not", "notif0", "notif1", "or", "output", "parameter", "pmos", "posedge", "primitive", "pull0", "pull1", "pulldown", "pullup", "pulsestyle_onevent", "pulsestyle_ondetect", "rcmos", "real", "realtime", "reg", "release", "repeat", "rnmos", "rpmos", "rtran", "rtranif0", "rtranif1", "scalared", "showcancelled", "signed", "small", "specify", "specparam", "strong0", "strong1", "supply0", "supply1", "table", "task", "time", "tran"]; pub const VERILOG_KEYWORDS_2 : [17]str = ["tranif0", "tranif1", "tri", "tri0", "tri1", "triand", "trior", "trireg", "unsigned", "use", "uwire", "vectored", "wait", "wand", "weak0", "weak1", "while"]; pub const VERILOG_KEYWORDS_3 : [5]str = ["wire", "wor", "xnor", "xor", "logic"]; +/// The SystemVerilog words a t27 name can meet and a simulator reading `-g2012` then rejects (`inside` is +/// the hit test of the cockpit's petal menu, gHashTag/t27#11326). +pub const VERILOG_KEYWORDS_4 : [40]str = ["inside", "bit", "byte", "int", "longint", "shortint", "string", "type", "unique", "priority", "class", "final", "var", "ref", "do", "with", "assert", "assume", "cover", "property", "sequence", "local", "null", "new", "this", "super", "virtual", "pure", "rand", "import", "export", "extends", "foreach", "union", "packed", "interface", "package", "program", "checker", "iff"]; -/// Is s a reserved word of Verilog-2005 (or `logic`)? +/// Is s a reserved word of Verilog-2005 or of the SystemVerilog words of VERILOG_KEYWORDS_4 (and `logic`)? pub fn verilog_keyword(s: []const u8) -> bool { var j : usize = 0; while (j < 103) { @@ -858,6 +1010,11 @@ pub fn verilog_keyword(s: []const u8) -> bool { if (same(s, VERILOG_KEYWORDS_3[j])) { return true; } j = j + 1; } + j = 0; + while (j < 40) { + if (same(s, VERILOG_KEYWORDS_4[j])) { return true; } + j = j + 1; + } return false; } @@ -928,7 +1085,9 @@ test type_texts_read_as_codes { assert type_code("u65", 0, 3) == TY_BAD; assert type_code("u0", 0, 2) == TY_BAD; assert type_code("u08", 0, 3) == TY_BAD; - assert type_code("i32", 0, 3) == TY_BAD; + assert type_code("i32", 0, 3) == SI + 32; + assert type_code("i64", 0, 3) == SI + 64 and type_code("i2", 0, 2) == SI + 2 and type_code("isize", 0, 5) == SI + 64; + assert type_code("i1", 0, 2) == TY_BAD and type_code("i65", 0, 3) == TY_BAD and type_code("i08", 0, 3) == TY_BAD; assert type_code("bool", 0, 4) == TY_BOOL; assert type_code("", 0, 0) == TY_VOID; assert type_code("void", 0, 4) == TY_VOID; @@ -952,6 +1111,77 @@ test widths_peers_and_ranges { assert fits(255, 8) and !fits(256, 8) and fits(1, TY_BOOL) and !fits(2, TY_BOOL) and fits(U64_MAX, 64); } +test signed_types_have_widths_peers_and_ranges { + assert ty_sint(SI + 8) and !ty_sint(8) and !ty_sint(SI + 1) and !ty_sint(SI + 65) and !ty_sint(TY_BOOL); + assert ty_int(SI + 64) and ty_int(64) and ty_scalar(SI + 8) and !ty_uint(SI + 8); + assert ty_width(SI + 8) == 8 and ty_width(SI + 64) == 64; + assert peer(SI + 8, SI + 16) == SI + 16 and peer(SI + 32, SI + 8) == SI + 32; + assert peer(TY_COMPTIME, SI + 16) == SI + 16 and peer(SI + 16, TY_COMPTIME) == SI + 16; + assert peer(32, SI + 64) == SI + 64 and peer(SI + 64, 32) == SI + 64 and peer(8, SI + 16) == SI + 16; + assert peer(32, SI + 32) == TY_BAD and peer(SI + 8, 16) == TY_BAD and peer(64, SI + 64) == TY_BAD; + assert peer(TY_BOOL, SI + 8) == TY_BAD and peer(SI + 8, TY_BOOL) == TY_BAD; + assert fits(127, SI + 8) and !fits(128, SI + 8) and fits(9223372036854775807, SI + 64) and !fits(9223372036854775808, SI + 64); + assert fits(1, SI + 2) and !fits(2, SI + 2); +} + +test constant_division_is_exact { + assert pow2(0) == 1 and pow2(10) == 1024 and pow2(63) == 9223372036854775808; + assert is_pow2(1) and is_pow2(4096) and !is_pow2(0) and !is_pow2(12); + assert ceil_log2(1) == 0 and ceil_log2(2) == 1 and ceil_log2(3) == 2 and ceil_log2(9) == 4 and ceil_log2(16) == 4 and ceil_log2(17) == 5; + assert ceil_log2(596000) == 20 and ceil_log2(DIV_MAX) == 62; + // the worked case: x / 9 for 8-bit x is (x * (256 + 200)) >> 12, since floor(256 * 7 / 9) + 1 = 200 + assert div_mult_low(8, 9) == 200 and div_shift(8, 9) == 12; + assert (200 * (pow2(8) + div_mult_low(8, 9))) / pow2(div_shift(8, 9)) == 22; + // every 8-bit x, every non-power-of-two divisor of 3 ..= 255: the multiply-shift is x / d + var bad : u64 = 0; + var d : u64 = 3; + while (d < 256) { + if (!is_pow2(d)) { + const m : u64 = pow2(8) + div_mult_low(8, d); + const sh : u64 = pow2(div_shift(8, d)); + var x : u64 = 0; + while (x < 256) { + if ((x * m) / sh != x / d) { bad = bad + 1; } + x = x + 1; + } + } + d = d + 1; + } + assert bad == 0; + // every 16-bit x, for divisors that stress it: just above and below a power of two, odd, and large + var bad16 : u64 = 0; + var di : usize = 0; + const divisors16 : [8]u64 = [3, 7, 9, 10, 1000, 4097, 65521, 65535]; + while (di < 8) { + const d16 : u64 = divisors16[di]; + const m16 : u64 = pow2(16) + div_mult_low(16, d16); + const sh16 : u64 = pow2(div_shift(16, d16)); + var x16 : u64 = 0; + while (x16 < 65536) { + if ((x16 * m16) / sh16 != x16 / d16) { bad16 = bad16 + 1; } + x16 = x16 + 1; + } + di = di + 1; + } + assert bad16 == 0; + // at 8, 16, 32 and 64 bits the overshoot proof holds for the divisors the cockpit specs use and the extremes + const widths : [4]u64 = [8, 16, 32, 64]; + const divs : [14]u64 = [3, 5, 6, 7, 9, 10, 12, 25, 100, 1000, 1000000, 596000, 526000, 4611686018427387903]; + var wi : usize = 0; + var ok : u64 = 0; + while (wi < 4) { + var dj : usize = 0; + while (dj < 14) { + if (div_exact(widths[wi], divs[dj])) { ok = ok + 1; } + dj = dj + 1; + } + wi = wi + 1; + } + assert ok == 56; + assert !div_exact(64, 4) and !div_exact(64, 2) and !div_exact(64, 4611686018427387905); + assert div_exact(64, 4611686018427387904) == false; +} + test operators_have_a_class_and_a_spelling { assert op_class("+") == OP_ARITH and op_class("+%") == OP_ARITH and op_class("^") == OP_ARITH; assert op_class("<<") == OP_SHIFT and op_class(">>") == OP_SHIFT; @@ -1158,7 +1388,8 @@ test a_continue_that_could_skip_the_step_unbinds_the_loop { test verilog_keywords_are_escaped_by_name { assert verilog_keyword("wait") and verilog_keyword("reg") and verilog_keyword("module"); assert verilog_keyword("while") and verilog_keyword("xor") and verilog_keyword("logic"); - assert !verilog_keyword("seed") and !verilog_keyword("waits") and !verilog_keyword(""); + assert verilog_keyword("inside") and verilog_keyword("bit") and verilog_keyword("iff"); + assert !verilog_keyword("seed") and !verilog_keyword("waits") and !verilog_keyword("") and !verilog_keyword("insides"); } test a_t27_keyword_with_one_trailing_underscore_names_the_keyword { From d90626d2c9f71b08ed7fdaba95dc879000326040 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 18:42:55 +0700 Subject: [PATCH 2/2] t27b verilog README: libraries, --top, signed, tables, constant division; hit does not fit as one combinational module (Closes #11375) Documents what rung 1 lowers and what still refuses (f64/string params, structs, a while with no static bound, a table read by a signed index, a divisor above 2^62, @intCast outside @as, a non-constant x / y which stays Verilog's divider). The hit test, measured (yosys 0.63 synth_xilinx xc7, pre-place): `t27b verilog specs/t27f/petal_menu.t27 --top hit` emits a non-empty module. Because the loop variable p is a register behind a done flag, yosys cannot make it constant per petal, so each unrolled petal is a full `inside` (80 DSP48E1, ~1.4k LUT): hit with the loop cut to 2 petals is 160 DSP, 2232 LUT (40 s), to 4 petals 320 DSP, 5096 LUT (75 s); 27 petals is therefore ~2160 DSP and ~38k LUT against 740 DSP on the A200T, and yosys had not finished the full hit/on_tap in 17 minutes. The next step is an induction-variable form of the counted loop (p = loop counter, so the vertex tables fold) and serialising the 64-bit multiplies, not more lowering. Co-Authored-By: Claude Sonnet 5.5 --- cli/t27b/README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cli/t27b/README.md b/cli/t27b/README.md index 2a2b8d8cfc..e68b9f5854 100644 --- a/cli/t27b/README.md +++ b/cli/t27b/README.md @@ -338,8 +338,18 @@ refused by name, exit 2. Every decision is in `specs/tri/t27b/verilog_plan.t27`: * how a spec declares an extern module, an instance and a net; * the port derivation; * the use closure; -* exact widths; -* the static loop bound. +* exact widths, unsigned and signed; +* the static loop bound; +* constant tables (ROMs) and division by a constant (a multiply-shift, never a divider). + +A spec with no `fn on_clock`, var, net, instance or input is a library of decisions, and +`t27b verilog [--top ]` writes one combinational module per `pub fn` whose parameters and +result are integers or bools (inputs = the parameters, output `t27_y`), or only the module of ``. +`i2`..`i64` lower as `signed`; `@as(T, x)`, `x as T`, `>>` (`>>>` when signed), `-x`, `T[i]` on a +module-level `const T : [N]ty = [..]` and `x / k`, `x % k` for a constant `k` lower as well +(`specs/tri/t27b/conformance/verilog_signed*.t27`; gHashTag/t27#11326). What still refuses: an +`f64`/string parameter, a struct, a `while` with no static bound, a table read by a signed index, a +divisor above 2^62, `@intCast` outside `@as`, and a general `x / y` is Verilog's divider. The writer is `specs/tri/t27b/verilog_emit.t27`. The fixtures are `specs/tri/t27b/conformance/verilog_*.t27` (#8796).