diff --git a/.trinity/seals/t27o_T27oSpice.json b/.trinity/seals/t27o_T27oSpice.json index e1b5619163..0e279c6ac2 100644 --- a/.trinity/seals/t27o_T27oSpice.json +++ b/.trinity/seals/t27o_T27oSpice.json @@ -1,33 +1,37 @@ { - "built_by": "t27c-bootstrap@0.5.2+9b556b7c3", + "built_by": "t27c-bootstrap@0.5.2+ea35d9b72", "closure": [ + { + "spec": "specs/analog/linear.t27", + "spec_hash": "sha256:3ddef7be183aba583ed285334ee389b48a65b41335e98914514235db7c4e5d62" + }, { "spec": "specs/tools/t27o/rust_convert.t27", - "spec_hash": "sha256:8c8390a800ed8c1bb291e59d6c2013e7d27fcf1d087b81da1495bbcaf85499c9" + "spec_hash": "sha256:7960832212d5781ccfec0c213b5829caebf2c13ab27e01fc06b8c5b0a1ec60de" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:9b05123b13e02aae3088d95948111712ec1d8eed04ded741f01ce5cd0cf82e12", - "gen_hash_rust": "sha256:42e6e611f51a07780957af0b55539ab2555e87f7efd238ca05c21965ac5e522d", - "gen_hash_verilog": "sha256:1248991934ac8dba423ddc78364dab55018d0839450eed55c480eb862b5b2bd0", - "gen_hash_zig": "sha256:5c64e5f9d665c49de50eeec3efc8d169b9f78caf656ad5f22fd3a224d1fdb487", + "gen_hash_c": "sha256:7297ba8aab948b38842ae2d27fd8f27b8618622d5598a40a5fc4e2f0fb439e4a", + "gen_hash_rust": "sha256:e2b6ca2bec4889e964c7dd10b7d5a904fcfedabe58e248d798e2daf9b1f8a800", + "gen_hash_verilog": "sha256:de18b898c7dbe583e56b8ad8c892f70f7694b78caf3fcb33091901667ea8bcf6", + "gen_hash_zig": "sha256:eb812466ae4b331738ed7812c2913ce1e52ec82760feab8b32a41ad6d21a436c", "module": "T27oSpice", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T16:29:45Z", + "sealed_at": "2026-10-19T02:17:13Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:812d4b02cc31d3bcd52926375323bd2766fa30f9ca204da1f637639e824073d3", + "spec_hash": "sha256:f1fd54e59d731af677e0c7301135092ce6b2db03b15178e63421a02b09a061b3", "spec_path": "specs/tools/t27o/spice_convert.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 22, - "total": 22, + "passed": 39, + "total": 39, "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/cli/tri/src/fpga.rs b/cli/tri/src/fpga.rs index 5dbb849349..28efd35931 100644 --- a/cli/tri/src/fpga.rs +++ b/cli/tri/src/fpga.rs @@ -6733,32 +6733,6 @@ fn build_theorem_matrix_report( }) } -/// Strip `fixture_dir` from the fixture paths inside matrix entries so that -/// snapshots are stable across machines. -#[cfg(test)] -fn normalize_fixture_paths( - entries: &[serde_json::Value], - fixture_dir: &std::path::Path, -) -> Vec { - entries - .iter() - .map(|entry| { - let mut entry = entry.clone(); - if let Some(serde_json::Value::Object(fixtures)) = entry.get_mut("fixtures") { - for (_key, value) in fixtures.iter_mut() { - if let Some(path_str) = value.as_str() { - let path = std::path::Path::new(path_str); - if let Ok(rel) = path.strip_prefix(fixture_dir) { - *value = serde_json::Value::String(rel.to_string_lossy().to_string()); - } - } - } - } - entry - }) - .collect() -} - fn boot_protocol(checklist: bool) -> Result<()> { println!("== Cold-POR boot-from-flash protocol =="); println!(); diff --git a/cli/tri/src/main.rs b/cli/tri/src/main.rs index 1a567106a5..f7ba521530 100644 --- a/cli/tri/src/main.rs +++ b/cli/tri/src/main.rs @@ -428,7 +428,7 @@ enum Commands { /// Print one JSON object instead of the table. #[arg(long)] json: bool, }, - /// t27o (Ouroboros): a Rust file as a t27 spec; --oracle prints the rustc harness, --check runs it and t27c test-report on the spec and its oracle tests (rules: specs/tools/t27o/rust_convert.t27). A .spice/.cir/.sp netlist prints its inventory or the refusal (rules: specs/tools/t27o/spice_convert.t27). + /// t27o (Ouroboros): a Rust file as a t27 spec; --oracle prints the rustc harness, --check runs it and t27c test-report on the spec and its oracle tests (rules: specs/tools/t27o/rust_convert.t27). A .spice/.cir/.sp netlist prints its inventory or the refusal, and with --emit-cell the constants of the M1 linear cell folded from it (rules: specs/tools/t27o/spice_convert.t27). T27o { file: String, /// The module name; default the file's stem. @@ -438,6 +438,8 @@ enum Commands { #[arg(long)] check: bool, /// The t27c binary; default target/release/t27c, then t27c on PATH. #[arg(long)] t27c: Option, + /// A netlist: print the M1 cell constants (CELL_N, CELL_D, CELL_NV) folded from it, or the refusal, instead of the inventory. + #[arg(long)] emit_cell: bool, }, /// t27o over a tree (#10543): every tracked .rs file converted in-process; a file's row is reused while its key holds (its SHA-256 and the seals of rust_convert.t27 and rust_std.t27, read from the t27 checkout this runs in); `--repo ` sweeps another checkout, with a cache of its own (#10638); counts, coverage and the ranked first-refusal kinds (rules: specs/tools/t27o/ledger.t27). T27oSweep { paths: Vec, #[arg(long, default_value = t27o_ledger::SWEEP_HERE)] repo: String, #[arg(long, default_value_t = t27o_ledger::SWEEP_MAX_BYTES)] max_bytes: usize, #[arg(long)] cache: Option, #[arg(long, default_value_t = t27o_ledger::SWEEP_TOP)] top: usize }, @@ -1330,7 +1332,7 @@ fn main() -> Result<()> { Commands::Actors { url, bound, replay } => std::process::exit(cmd_actors(url, *bound, *replay)? as i32), Commands::Hot { t27c, action, rest } => std::process::exit(cmd_hot(action, rest, t27c.as_deref()) as i32), Commands::Payout { epoch, pool, json } => std::process::exit(payout::cmd(*epoch, *pool, *json)? as i32), - Commands::T27o { file, name, oracle, check, t27c } => std::process::exit(cmd_t27o(file, name.as_deref(), *oracle, *check, t27c.as_deref())? as i32), + Commands::T27o { file, name, oracle, check, t27c, emit_cell } => std::process::exit(cmd_t27o(file, name.as_deref(), *oracle, *check, t27c.as_deref(), *emit_cell)? as i32), Commands::T27oSweep { paths, repo, max_bytes, cache, top } => std::process::exit(cmd_t27o_sweep(paths, repo, *max_bytes, cache.as_deref(), *top)? as i32), } @@ -1338,11 +1340,11 @@ fn main() -> Result<()> { } /// `tri t27o` (#8967): every rule is gen-rust of rust_convert.t27; this reads the file, runs rustc and t27c, and writes. -fn cmd_t27o(file: &str, name: Option<&str>, oracle: bool, check: bool, t27c: Option<&str>) -> Result { +fn cmd_t27o(file: &str, name: Option<&str>, oracle: bool, check: bool, t27c: Option<&str>, emit_cell: bool) -> Result { let src = fs::read(file).with_context(|| format!("reading {file}"))?; if spice::is_spice_name(file.as_bytes()) { // a netlist: every rule is specs/tools/t27o/spice_convert.t27 (#10509) let (mut work, mut out, mut st) = (vec![0u8; 2 * src.len() + 8192], vec![0u8; 1 << 24], vec![0usize; 1 << 16]); - let n = spice::inventory(&src, &mut work, &mut out, &mut st); + let n = if emit_cell { spice::emit_cell(&src, &mut work, &mut out, &mut st) } else { spice::inventory(&src, &mut work, &mut out, &mut st) }; std::io::Write::write_all(&mut std::io::stdout(), &out[..n.min(out.len())])?; return Ok(spice::exit_code(&st)); } diff --git a/gen/rust/tools/t27o/spice_convert.rs b/gen/rust/tools/t27o/spice_convert.rs index 4e740eaefd..67e0f1bb9b 100644 --- a/gen/rust/tools/t27o/spice_convert.rs +++ b/gen/rust/tools/t27o/spice_convert.rs @@ -1,6 +1,6 @@ // Generated from .t27 spec // DO NOT EDIT — generated by t27c -// NOT LOWERED BY THIS BACKEND: 22 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 39 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. @@ -106,9 +106,61 @@ pub const TEMP_CARD: usize = 66; pub const GROUP_END: usize = 67; +pub const N_BSRC: usize = 68; + +pub const OPT_METHOD: usize = 69; + +pub const OPT_MAXORD: usize = 70; + pub const FIXED: usize = 80; -pub const DEF0: usize = 80; +pub const CELL: usize = 80; + +pub const CELL_SLOTS: usize = 64; + +pub const CL_NODES: usize = 0; + +pub const CL_IND: usize = 1; + +pub const CL_UNK: usize = 2; + +pub const CL_LSEEN: usize = 3; + +pub const CL_IN_A: usize = 4; + +pub const CL_IN_B: usize = 5; + +pub const CL_SRC: usize = 6; + +pub const CL_SRCS: usize = 7; + +pub const CL_DT_M: usize = 8; + +pub const CL_DT_E: usize = 9; + +pub const CL_DT_C: usize = 10; + +pub const CL_L1: usize = 11; + +pub const CL_C1: usize = 12; + +pub const CL_HIT: usize = 13; + +pub const CL_NAME: usize = 14; + +pub const CL_NMAX: usize = 6; + +pub const CL_D: usize = 26; + +pub const CL_DMAX: usize = 25; + +pub const CL_NV: usize = 51; + +pub const CL_NMAXW: usize = 5; + +pub const CL_END: usize = 56; + +pub const DEF0: usize = 144; pub const DEF_SLOTS: usize = 6; @@ -126,9 +178,9 @@ pub const D_LAST: usize = 4; pub const D_PARENT: usize = 5; -pub const STK: usize = 1616; +pub const STK: usize = 1680; -pub const CARD0: usize = 1648; +pub const CARD0: usize = 1712; pub const CARD_SLOTS: usize = 4; @@ -234,6 +286,24 @@ pub const Y_CTLIN: usize = 23; pub const Y_RECUR: usize = 24; +pub const Y_CELL_EL: usize = 25; + +pub const Y_CELL_IN: usize = 26; + +pub const Y_CELL_VALUE: usize = 27; + +pub const Y_CELL_SIZE: usize = 28; + +pub const Y_CELL_DT: usize = 29; + +pub const Y_CELL_METHOD: usize = 30; + +pub const Y_CELL_IC: usize = 31; + +pub const Y_CELL_SOLVE: usize = 32; + +pub const Y_CELL_GND: usize = 33; + pub const B_TAB: u8 = 9; pub const B_NL: u8 = 10; @@ -594,7 +664,10 @@ pub fn cut_comment(src: &[u8], a: usize, e: usize) -> usize { } pub fn prepare(src: &[u8], w: &mut [u8], st: &mut [usize]) -> () { - let cap: usize = ((st.len() - CARD0) / CARD_SLOTS); + let mut cap: usize = 0; + if (st.len() > CARD0) { + cap = ((st.len() - CARD0) / CARD_SLOTS); + } let mut p: usize = 0; let mut line: usize = 0; let mut wp: usize = 0; @@ -751,6 +824,9 @@ pub fn check_params(w: &[u8], st: &mut [usize], k: usize, p: usize, e: usize, al if ci_is(w, t, te, b"ng") { mask = 8; } + if ci_is(w, t, te, b"ic") { + mask = 16; + } if ((mask == 0) || (((allow / mask) % 2) == 0)) { reject(st, Y_PARAM, k, t); go = false; @@ -774,11 +850,15 @@ pub fn passive(w: &[u8], st: &mut [usize], k: usize, kind: usize, t1: usize, e: count(st, kind); let q: usize = after_word(w, t1, e, 2); let v: usize = word_at(w, t1, e, 2); + let mut allow: usize = 1; + if (kind != K_R) { + allow = 17; + } if (q == NONE) { reject(st, Y_FIELDS, k, NONE); } else { if need_num(w, st, k, v, e) { - check_params(w, st, k, q, e, 1, false); + check_params(w, st, k, q, e, allow, false); } } } @@ -879,6 +959,72 @@ pub fn source(w: &[u8], st: &mut [usize], k: usize, kind: usize, t1: usize, e: u } } +pub fn time_word(w: &[u8], a: usize, b: usize) -> bool { + return (((((((((ci_is(w, a, b, b"time") || ci_is(w, a, b, b"u")) || ci_is(w, a, b, b"u2")) || ci_is(w, a, b, b"uramp")) || ci_is(w, a, b, b"abs")) || ci_is(w, a, b, b"sgn")) || ci_is(w, a, b, b"sin")) || ci_is(w, a, b, b"cos")) || ci_is(w, a, b, b"exp")) || ci_is(w, a, b, b"sqrt")); +} + +pub fn time_only(w: &[u8], a: usize, b: usize) -> bool { + let mut q: usize = a; + let mut any: bool = false; + while (q < b) { + let c: u8 = w[(q) as usize]; + if (is_digit(c) || (c == B_DOT)) { + any = true; + while ((q < b) && (is_digit(w[(q) as usize]) || (w[(q) as usize] == B_DOT))) { + q = (q + 1); + } + if ((((q + 1) < b) && (lower(w[(q) as usize]) == 101)) && (is_digit(w[((q + 1)) as usize]) || ((((w[((q + 1)) as usize] == B_MINUS) || (w[((q + 1)) as usize] == B_PLUS)) && ((q + 2) < b)) && is_digit(w[((q + 2)) as usize])))) { + q = (q + 2); + while ((q < b) && is_digit(w[(q) as usize])) { + q = (q + 1); + } + } + while ((q < b) && is_alpha(w[(q) as usize])) { + q = (q + 1); + } + if ((q < b) && (w[(q) as usize] == B_LPAREN)) { + return false; + } + } else { + if is_alpha(c) { + let mut f: usize = q; + while ((f < b) && (is_alpha(w[(f) as usize]) || is_digit(w[(f) as usize]))) { + f = (f + 1); + } + if !(time_word(w, q, f)) { + return false; + } + any = true; + q = f; + } else { + if (((((((is_sep(c) || (c == B_LPAREN)) || (c == B_RPAREN)) || (c == B_PLUS)) || (c == B_MINUS)) || (c == B_STAR)) || (c == 47)) || (c == 94)) { + q = (q + 1); + } else { + return false; + } + } + } + } + return any; +} + +pub fn bsource(w: &[u8], st: &mut [usize], k: usize, t1: usize, e: usize) -> () { + let q: usize = after_word(w, t1, e, 1); + if (q == NONE) { + reject(st, Y_B, k, NONE); + return (); + } + let kt: usize = tk(w, q, e); + let ke: usize = tk_end(w, kt, e); + let eq: usize = tk(w, ke, e); + if (((((kt >= e) || !(ci_is(w, kt, ke, b"v"))) || (eq >= e)) || (w[(eq) as usize] != B_EQ)) || !(time_only(w, (eq + 1), e))) { + reject(st, Y_B, k, NONE); + return (); + } + count(st, K_V); + st[(N_BSRC) as usize] = (st[(N_BSRC) as usize] + 1); +} + pub fn vcvs(w: &[u8], st: &mut [usize], k: usize, t1: usize, e: usize) -> () { count(st, K_E); let g: usize = word_at(w, t1, e, 4); @@ -983,7 +1129,7 @@ pub fn element(w: &[u8], st: &mut [usize], k: usize, t0: usize, t1: usize, e: us x_card(w, st, k); } else { if (c == 98) { - reject(st, Y_B, k, NONE); + bsource(w, st, k, t1, e); } else { reject(st, Y_ELEMENT, k, NONE); } @@ -1104,16 +1250,62 @@ pub fn d_options(w: &[u8], st: &mut [usize], k: usize, t1: usize, e: usize) -> ( } else { let te: usize = tk_end(w, t, e); let eq: usize = tk(w, te, e); - if (((is_punct(w[(t) as usize]) || !(ci_is(w, t, te, b"temp"))) || (eq >= e)) || (w[(eq) as usize] != B_EQ)) { + let v: usize = tk(w, (eq + 1), e); + let mut key: usize = 0; + if ci_is(w, t, te, b"temp") { + key = 1; + } + if ci_cat(w, t, te, b"meth", b"od") { + key = 2; + } + if ci_is(w, t, te, b"maxord") { + key = 3; + } + if ci_is(w, t, te, b"trtol") { + key = 4; + } + if (((is_punct(w[(t) as usize]) || (key == 0)) || (eq >= e)) || (w[(eq) as usize] != B_EQ)) { reject(st, Y_OPTION, k, t); go = false; } else { - if !(need_num(w, st, k, tk(w, (eq + 1), e), e)) { - go = false; + if (key == 2) { + let ve: usize = tk_end(w, v, e); + if ((v >= e) || is_punct(w[(v) as usize])) { + reject(st, Y_FIELDS, k, NONE); + go = false; + } else { + if ci_is(w, v, ve, b"gear") { + st[(OPT_METHOD) as usize] = 1; + seen = (seen + 1); + q = ve; + } else { + if (ci_is(w, v, ve, b"trap") || ci_is(w, v, ve, b"trapezoidal")) { + st[(OPT_METHOD) as usize] = 2; + seen = (seen + 1); + q = ve; + } else { + reject(st, Y_OPTION, k, v); + go = false; + } + } + } } else { - st[(TEMP_CARD) as usize] = k; - seen = (seen + 1); - q = tk_end(w, tk(w, (eq + 1), e), e); + if !(need_num(w, st, k, v, e)) { + go = false; + } else { + if (key == 1) { + st[(TEMP_CARD) as usize] = k; + } + if (key == 3) { + if (((st[(VM) as usize] == 1) && (st[(VE) as usize] == EXP_BIAS)) && (st[(VS) as usize] == 0)) { + st[(OPT_MAXORD) as usize] = 1; + } else { + st[(OPT_MAXORD) as usize] = 2; + } + } + seen = (seen + 1); + q = tk_end(w, v, e); + } } } } @@ -1264,7 +1456,7 @@ pub fn directive(w: &[u8], st: &mut [usize], k: usize, t0: usize, t1: usize, e: } pub fn inert_command(w: &[u8], a: usize, b: usize) -> bool { - return (((((((((((((((ci_is(w, a, b, b"let") || ci_is(w, a, b, b"plot")) || ci_is(w, a, b, b"set")) || ci_is(w, a, b, b"unset")) || ci_is(w, a, b, b"write")) || ci_is(w, a, b, b"run")) || ci_is(w, a, b, b"print")) || ci_is(w, a, b, b"echo")) || ci_is(w, a, b, b"wrdata")) || ci_is(w, a, b, b"setplot")) || ci_is(w, a, b, b"display")) || ci_is(w, a, b, b"destroy")) || ci_is(w, a, b, b"quit")) || ci_is(w, a, b, b"exit")) || ci_is(w, a, b, b"save")) || ci_is(w, a, b, b"hardcopy")); + return (((((((((((((((ci_is(w, a, b, b"let") || ci_is(w, a, b, b"plot")) || ci_is(w, a, b, b"set")) || ci_is(w, a, b, b"unset")) || ci_is(w, a, b, b"write")) || ci_cat(w, a, b, b"ru", b"n")) || ci_is(w, a, b, b"print")) || ci_is(w, a, b, b"echo")) || ci_is(w, a, b, b"wrdata")) || ci_is(w, a, b, b"setplot")) || ci_is(w, a, b, b"display")) || ci_is(w, a, b, b"destroy")) || ci_is(w, a, b, b"quit")) || ci_is(w, a, b, b"exit")) || ci_is(w, a, b, b"save")) || ci_is(w, a, b, b"hardcopy")); } pub fn control_card(w: &[u8], st: &mut [usize], k: usize, t0: usize, t1: usize, e: usize) -> () { @@ -1456,6 +1648,10 @@ pub fn flatten(w: &mut [u8], st: &mut [usize], out: &mut [u8], scope: usize, lo: kind = K_V; label = "v"; } + if (c == 98) { + kind = K_V; + label = "b"; + } if (c == 105) { kind = K_I; label = "i"; @@ -1586,7 +1782,7 @@ pub fn put_why(w: &[u8], st: &mut [usize], out: &mut [u8]) -> () { puts(st, out, b"deck is larger than the reader's tables"); } else { if (why == Y_B) { - puts(st, out, b"B source not supported"); + puts(st, out, b"B source not supported (only a voltage that is a function of time alone is read)"); } else { if (why == Y_ELEMENT) { let c: u8 = lower(w[(st[(((CARD0 + (k * CARD_SLOTS)) + C_A)) as usize]) as usize]); @@ -1656,7 +1852,35 @@ pub fn put_why(w: &[u8], st: &mut [usize], out: &mut [u8]) -> () { puts(st, out, b"control command not supported"); } if (why == Y_OPTION) { - puts(st, out, b"option not supported (only TEMP)"); + puts(st, out, b"option not supported (only TEMP, METHOD, MAXORD and TRTOL)"); + } + if (why == Y_CELL_EL) { + puts(st, out, b"not in the M1 cell (a flat deck of R, C, L and one input source)"); + } + if (why == Y_CELL_IN) { + puts(st, out, b"the M1 cell has exactly one input: a V source or a time-only B source from a node to ground 0"); + } + if (why == Y_CELL_VALUE) { + puts(st, out, b"R, C and L need a positive value (a mantissa up to 2^53, a power of ten within 22), two different nodes and no m=, and C and L may not touch the input node"); + } + if (why == Y_CELL_SIZE) { + puts(st, out, b"the M1 cell has 1 to 5 states (numbered nodes plus inductors)"); + } + if (why == Y_CELL_DT) { + puts(st, out, b"the M1 cell needs one .tran card with tstep tstop tstart tmax; dt is tmax"); + } + if (why == Y_CELL_METHOD) { + puts(st, out, b"the M1 cell is backward Euler: the deck must say .options meth"); + puts(st, out, b"od=gear maxord=1"); + } + if (why == Y_CELL_IC) { + puts(st, out, b"initial condition other than 0: the M1 cell starts from rest"); + } + if (why == Y_CELL_SOLVE) { + puts(st, out, b"the matrix has no solution (a floating node?) or a constant is beyond +-127"); + } + if (why == Y_CELL_GND) { + puts(st, out, b"a node called gnd is not read as ground: write 0"); } if ((tok != NONE) && (k != NONE)) { puts(st, out, b": "); @@ -1807,6 +2031,9 @@ pub fn put_inventory(src: &[u8], w: &mut [u8], st: &mut [usize], out: &mut [u8]) put_kv(st, out, b" pulse=", st[((WAVE + WV_PULSE)) as usize]); put_kv(st, out, b" sin=", st[((WAVE + WV_SIN)) as usize]); put_kv(st, out, b" pwl=", st[((WAVE + WV_PWL)) as usize]); + if (st[(N_BSRC) as usize] > 0) { + put_kv(st, out, b" b=", st[(N_BSRC) as usize]); + } puts(st, out, b"\nsubckt definitions: "); put_num(st, out, st[(NDEF) as usize]); put(st, out, B_NL); @@ -1893,6 +2120,747 @@ pub fn has_text(w: usize, out: &[u8], needle: &[u8]) -> bool { return false; } +pub fn dec_f64(m: usize, e: usize) -> f64 { + let mut p: f64 = 1.0; + let mut x: usize = 0; + if (e >= EXP_BIAS) { + x = (e - EXP_BIAS); + } else { + x = (EXP_BIAS - e); + } + while (x > 0) { + p = (p * 10.0); + x = (x - 1); + } + if (e < EXP_BIAS) { + return ((m as f64) / p); + } + return ((m as f64) * p); +} + +pub fn dec_ok(m: usize, e: usize) -> bool { + if ((m == 0) || (m > 9007199254740992)) { + return false; + } + if (e >= EXP_BIAS) { + return ((e - EXP_BIAS) <= 22); + } + return ((EXP_BIAS - e) <= 22); +} + +pub fn read_val(w: &[u8], st: &mut [usize], k: usize) -> () { + let e: usize = st[(((CARD0 + (k * CARD_SLOTS)) + C_B)) as usize]; + let t1: usize = tk_end(w, tk(w, st[(((CARD0 + (k * CARD_SLOTS)) + C_A)) as usize], e), e); + let v: usize = word_at(w, t1, e, 2); + if ((v == NONE) || !(parse_num(w, v, tk_end(w, v, e), st))) { + st[(VM) as usize] = 0; + st[(VE) as usize] = EXP_BIAS; + st[(VS) as usize] = 0; + } +} + +pub fn card_val(w: &[u8], st: &mut [usize], k: usize) -> f64 { + read_val(w, st, k); + return dec_f64(st[(VM) as usize], st[(VE) as usize]); +} + +pub fn node_of(w: &[u8], st: &mut [usize], a: usize, b: usize) -> usize { + let mut id: usize = NONE; + if (((b - a) == 1) && (w[(a) as usize] == B_ZERO)) { + id = (NODE_GND as usize); + } else { + if ((st[((CELL + CL_SRC)) as usize] != NONE) && same_ci(w, a, b, st[((CELL + CL_IN_A)) as usize], st[((CELL + CL_IN_B)) as usize])) { + id = (NODE_IN as usize); + } else { + let mut i: usize = 0; + while (i < st[((CELL + CL_NODES)) as usize]) { + if same_ci(w, a, b, st[(((CELL + CL_NAME) + (2 * i))) as usize], st[((((CELL + CL_NAME) + (2 * i)) + 1)) as usize]) { + id = (i + 1); + } + i = (i + 1); + } + } + } + st[((CELL + CL_HIT)) as usize] = id; + return id; +} + +pub fn node_add(w: &[u8], st: &mut [usize], k: usize, a: usize, b: usize) -> usize { + let mut id: usize = node_of(w, st, a, b); + if (id == NONE) { + let n: usize = st[((CELL + CL_NODES)) as usize]; + if (n >= CL_NMAX) { + reject(st, Y_CELL_SIZE, k, a); + } else { + st[(((CELL + CL_NAME) + (2 * n))) as usize] = a; + st[((((CELL + CL_NAME) + (2 * n)) + 1)) as usize] = b; + st[((CELL + CL_NODES)) as usize] = (n + 1); + id = (n + 1); + } + } + return id; +} + +pub fn cell_ic(w: &[u8], st: &mut [usize], k: usize, t1: usize, e: usize) -> () { + let mut q: usize = t1; + while (q < e) { + let t: usize = tk(w, q, e); + if (t >= e) { + q = e; + } else { + let te: usize = tk_end(w, t, e); + if (w[(t) as usize] == B_EQ) { + let v: usize = tk(w, te, e); + if (((v < e) && parse_num(w, v, tk_end(w, v, e), st)) && (st[(VM) as usize] != 0)) { + reject(st, Y_CELL_IC, k, v); + } + } + q = te; + } + } +} + +pub fn cell_keys(w: &[u8], st: &mut [usize], k: usize, p: usize, e: usize) -> () { + let mut q: usize = p; + while (q < e) { + let t: usize = tk(w, q, e); + if (t >= e) { + q = e; + } else { + let te: usize = tk_end(w, t, e); + let v: usize = tk(w, (tk(w, te, e) + 1), e); + q = tk_end(w, v, e); + if ci_is(w, t, te, b"m") { + reject(st, Y_CELL_VALUE, k, t); + } else { + if (((ci_is(w, t, te, b"ic") && (v < e)) && parse_num(w, v, q, st)) && (st[(VM) as usize] != 0)) { + reject(st, Y_CELL_IC, k, v); + } + } + } + } +} + +pub fn cell_source(w: &[u8], st: &mut [usize], k: usize, t1: usize, e: usize) -> () { + let na: usize = word_at(w, t1, e, 0); + let nb: usize = word_at(w, t1, e, 1); + if ((na == NONE) || (nb == NONE)) { + reject(st, Y_CELL_IN, k, NONE); + return (); + } + let ea: usize = tk_end(w, na, e); + let eb: usize = tk_end(w, nb, e); + if ((((eb - nb) != 1) || (w[(nb) as usize] != B_ZERO)) || (((ea - na) == 1) && (w[(na) as usize] == B_ZERO))) { + reject(st, Y_CELL_IN, k, NONE); + } else { + if ci_is(w, na, ea, b"gnd") { + reject(st, Y_CELL_GND, k, na); + } else { + if (st[((CELL + CL_SRC)) as usize] != NONE) { + reject(st, Y_CELL_IN, k, NONE); + } else { + st[((CELL + CL_SRC)) as usize] = k; + st[((CELL + CL_IN_A)) as usize] = na; + st[((CELL + CL_IN_B)) as usize] = ea; + } + } + } +} + +pub fn cell_inputs(w: &[u8], st: &mut [usize]) -> () { + st[((CELL + CL_SRC)) as usize] = NONE; + st[((CELL + CL_L1)) as usize] = NONE; + st[((CELL + CL_C1)) as usize] = NONE; + st[((CELL + CL_DT_C)) as usize] = NONE; + let mut k: usize = 0; + while (k < st[(SCAN_HI) as usize]) { + let e: usize = st[(((CARD0 + (k * CARD_SLOTS)) + C_B)) as usize]; + let t0: usize = tk(w, st[(((CARD0 + (k * CARD_SLOTS)) + C_A)) as usize], e); + let t1: usize = tk_end(w, t0, e); + if (st[(((CARD0 + (k * CARD_SLOTS)) + C_SCOPE)) as usize] == NONE) { + if (w[(t0) as usize] == B_DOT) { + if ci_is(w, t0, t1, b".ic") { + cell_ic(w, st, k, t1, e); + } + } else { + let c: u8 = lower(w[(t0) as usize]); + if ((c == 118) || (c == 98)) { + cell_source(w, st, k, t1, e); + } else { + if (((c != 114) && (c != 99)) && (c != 108)) { + reject(st, Y_CELL_EL, k, NONE); + } + } + } + } + k = (k + 1); + } + if ((st[((CELL + CL_SRC)) as usize] == NONE) && (st[(ERR_LINE) as usize] == 0)) { + let mut line: usize = 1; + if (st[(SCAN_HI) as usize] > 0) { + line = st[(((CARD0 + ((st[(SCAN_HI) as usize] - 1) * CARD_SLOTS)) + C_LINE)) as usize]; + } + reject_at(st, Y_CELL_IN, line, NONE, NONE); + } +} + +pub fn cell_nodes(w: &[u8], st: &mut [usize]) -> () { + let mut k: usize = 0; + while ((k < st[(SCAN_HI) as usize]) && (st[(ERR_LINE) as usize] == 0)) { + let e: usize = st[(((CARD0 + (k * CARD_SLOTS)) + C_B)) as usize]; + let t0: usize = tk(w, st[(((CARD0 + (k * CARD_SLOTS)) + C_A)) as usize], e); + let t1: usize = tk_end(w, t0, e); + let c: u8 = lower(w[(t0) as usize]); + if (((st[(((CARD0 + (k * CARD_SLOTS)) + C_SCOPE)) as usize] == NONE) && (w[(t0) as usize] != B_DOT)) && (((c == 114) || (c == 99)) || (c == 108))) { + let na: usize = word_at(w, t1, e, 0); + let nb: usize = word_at(w, t1, e, 1); + let v: usize = word_at(w, t1, e, 2); + let ea: usize = tk_end(w, na, e); + let eb: usize = tk_end(w, nb, e); + if ((!(parse_num(w, v, tk_end(w, v, e), st)) || (st[(VS) as usize] != 0)) || !(dec_ok(st[(VM) as usize], st[(VE) as usize]))) { + reject(st, Y_CELL_VALUE, k, v); + } else { + if ci_is(w, na, ea, b"gnd") { + reject(st, Y_CELL_GND, k, na); + } else { + if ci_is(w, nb, eb, b"gnd") { + reject(st, Y_CELL_GND, k, nb); + } else { + let ia: usize = node_add(w, st, k, na, ea); + let ib: usize = node_add(w, st, k, nb, eb); + if ((ia != NONE) && (ib != NONE)) { + if (ia == ib) { + reject(st, Y_CELL_VALUE, k, na); + } else { + if ((c != 114) && ((ia == (NODE_IN as usize)) || (ib == (NODE_IN as usize)))) { + reject(st, Y_CELL_VALUE, k, na); + } + } + } + cell_keys(w, st, k, tk_end(w, v, e), e); + if (c == 108) { + st[((CELL + CL_IND)) as usize] = (st[((CELL + CL_IND)) as usize] + 1); + if (st[((CELL + CL_L1)) as usize] == NONE) { + st[((CELL + CL_L1)) as usize] = k; + } + } + if ((c == 99) && (st[((CELL + CL_C1)) as usize] == NONE)) { + st[((CELL + CL_C1)) as usize] = k; + } + if ((st[((CELL + CL_NODES)) as usize] + st[((CELL + CL_IND)) as usize]) > (NMAX as usize)) { + reject(st, Y_CELL_SIZE, k, NONE); + } + } + } + } + } + k = (k + 1); + } + st[((CELL + CL_UNK)) as usize] = (st[((CELL + CL_NODES)) as usize] + st[((CELL + CL_IND)) as usize]); + if ((st[(ERR_LINE) as usize] == 0) && (st[((CELL + CL_UNK)) as usize] == 0)) { + let mut line: usize = 1; + if (st[(SCAN_HI) as usize] > 0) { + line = st[(((CARD0 + ((st[(SCAN_HI) as usize] - 1) * CARD_SLOTS)) + C_LINE)) as usize]; + } + reject_at(st, Y_CELL_SIZE, line, NONE, NONE); + } +} + +pub fn cell_tran(w: &[u8], st: &mut [usize]) -> () { + let mut n: usize = 0; + let mut card: usize = NONE; + let mut k: usize = 0; + while (k < st[(SCAN_HI) as usize]) { + let e: usize = st[(((CARD0 + (k * CARD_SLOTS)) + C_B)) as usize]; + let t0: usize = tk(w, st[(((CARD0 + (k * CARD_SLOTS)) + C_A)) as usize], e); + let t1: usize = tk_end(w, t0, e); + let scope: usize = st[(((CARD0 + (k * CARD_SLOTS)) + C_SCOPE)) as usize]; + if (((scope == NONE) && ci_is(w, t0, t1, b".tran")) || ((scope == SCOPE_CTL) && ci_is(w, t0, t1, b"tran"))) { + n = (n + 1); + card = k; + } + k = (k + 1); + } + if (n == 0) { + let mut line: usize = 1; + if (st[(SCAN_HI) as usize] > 0) { + line = st[(((CARD0 + ((st[(SCAN_HI) as usize] - 1) * CARD_SLOTS)) + C_LINE)) as usize]; + } + reject_at(st, Y_CELL_DT, line, NONE, NONE); + return (); + } + if (n > 1) { + reject(st, Y_CELL_DT, card, NONE); + return (); + } + let ce: usize = st[(((CARD0 + (card * CARD_SLOTS)) + C_B)) as usize]; + let mut q: usize = tk_end(w, tk(w, st[(((CARD0 + (card * CARD_SLOTS)) + C_A)) as usize], ce), ce); + let mut seen: usize = 0; + while (q < ce) { + let t: usize = tk(w, q, ce); + if (t >= ce) { + q = ce; + } else { + let te: usize = tk_end(w, t, ce); + if (!(ci_is(w, t, te, b"uic")) && parse_num(w, t, te, st)) { + seen = (seen + 1); + if (seen == 4) { + if ((st[(VS) as usize] == 0) && dec_ok(st[(VM) as usize], st[(VE) as usize])) { + st[((CELL + CL_DT_M)) as usize] = st[(VM) as usize]; + st[((CELL + CL_DT_E)) as usize] = st[(VE) as usize]; + } else { + seen = 0; + } + } + } + q = te; + } + } + st[((CELL + CL_DT_C)) as usize] = card; + if (seen < 4) { + reject(st, Y_CELL_DT, card, NONE); + } else { + if ((st[(OPT_METHOD) as usize] != 1) || (st[(OPT_MAXORD) as usize] != 1)) { + reject(st, Y_CELL_METHOD, card, NONE); + } + } +} + +pub fn pair_add(w0: [f64; 55], off: usize, a: usize, b: usize, y: f64) -> [f64; 55] { + let mut w: [f64; 55] = w0; + let fa: bool = is_free((a as u8)); + let fb: bool = is_free((b as u8)); + if fa { + let i: usize = (((ix((a as u8)) * WIDE) + off) + ix((a as u8))); + w[(i) as usize] = (w[(i) as usize] + y); + } + if fb { + let i: usize = (((ix((b as u8)) * WIDE) + off) + ix((b as u8))); + w[(i) as usize] = (w[(i) as usize] + y); + } + if (fa && fb) { + let i: usize = (((ix((a as u8)) * WIDE) + off) + ix((b as u8))); + let j: usize = (((ix((b as u8)) * WIDE) + off) + ix((a as u8))); + w[(i) as usize] = (w[(i) as usize] + (0.0 - y)); + w[(j) as usize] = (w[(j) as usize] + (0.0 - y)); + } + return w; +} + +pub fn src_add(w0: [f64; 55], col: usize, a: usize, b: usize, y: f64) -> [f64; 55] { + let mut w: [f64; 55] = w0; + if (is_free((a as u8)) && (b == (NODE_IN as usize))) { + let i: usize = ((ix((a as u8)) * WIDE) + col); + w[(i) as usize] = (w[(i) as usize] + y); + } + if (is_free((b as u8)) && (a == (NODE_IN as usize))) { + let i: usize = ((ix((b as u8)) * WIDE) + col); + w[(i) as usize] = (w[(i) as usize] + y); + } + return w; +} + +pub fn cell_solve(w: &[u8], st: &mut [usize]) -> () { + let nn: usize = st[((CELL + CL_NODES)) as usize]; + let n: usize = st[((CELL + CL_UNK)) as usize]; + let dt: f64 = dec_f64(st[((CELL + CL_DT_M)) as usize], st[((CELL + CL_DT_E)) as usize]); + let mut zs: f64 = 1.0; + if ((st[((CELL + CL_L1)) as usize] != NONE) && (st[((CELL + CL_C1)) as usize] != NONE)) { + let lv: f64 = card_val(w, st, st[((CELL + CL_L1)) as usize]); + let cv: f64 = card_val(w, st, st[((CELL + CL_C1)) as usize]); + zs = ((lv / cv)).sqrt(); + } + let mut aug: [f64; 55] = [0.0; 55]; + st[((CELL + CL_LSEEN)) as usize] = 0; + let mut k: usize = 0; + while (k < st[(SCAN_HI) as usize]) { + let e: usize = st[(((CARD0 + (k * CARD_SLOTS)) + C_B)) as usize]; + let t0: usize = tk(w, st[(((CARD0 + (k * CARD_SLOTS)) + C_A)) as usize], e); + let t1: usize = tk_end(w, t0, e); + let c: u8 = lower(w[(t0) as usize]); + if (((st[(((CARD0 + (k * CARD_SLOTS)) + C_SCOPE)) as usize] == NONE) && (w[(t0) as usize] != B_DOT)) && (((c == 114) || (c == 99)) || (c == 108))) { + let na: usize = word_at(w, t1, e, 0); + let nb: usize = word_at(w, t1, e, 1); + let ia: usize = node_of(w, st, na, tk_end(w, na, e)); + let ib: usize = node_of(w, st, nb, tk_end(w, nb, e)); + let val: f64 = card_val(w, st, k); + if (c == 114) { + aug = pair_add(aug, 0, ia, ib, (1.0 / val)); + aug = src_add(aug, (2 * n), ia, ib, (1.0 / val)); + } else { + if (c == 99) { + aug = pair_add(aug, 0, ia, ib, (val / dt)); + aug = pair_add(aug, n, ia, ib, (val / dt)); + } else { + let ki: usize = (nn + st[((CELL + CL_LSEEN)) as usize]); + let yl: f64 = (val / (dt * zs)); + if is_free((ia as u8)) { + aug[(((ix((ia as u8)) * WIDE) + ki)) as usize] = (aug[(((ix((ia as u8)) * WIDE) + ki)) as usize] + (1.0 / zs)); + aug[(((ki * WIDE) + ix((ia as u8)))) as usize] = (aug[(((ki * WIDE) + ix((ia as u8)))) as usize] + (0.0 - 1.0)); + } + if is_free((ib as u8)) { + aug[(((ix((ib as u8)) * WIDE) + ki)) as usize] = (aug[(((ix((ib as u8)) * WIDE) + ki)) as usize] + (0.0 - (1.0 / zs))); + aug[(((ki * WIDE) + ix((ib as u8)))) as usize] = (aug[(((ki * WIDE) + ix((ib as u8)))) as usize] + 1.0); + } + aug[(((ki * WIDE) + ki)) as usize] = (aug[(((ki * WIDE) + ki)) as usize] + yl); + aug[((((ki * WIDE) + n) + ki)) as usize] = (aug[((((ki * WIDE) + n) + ki)) as usize] + yl); + if (ia == (NODE_IN as usize)) { + aug[(((ki * WIDE) + (2 * n))) as usize] = (aug[(((ki * WIDE) + (2 * n))) as usize] + 1.0); + } + if (ib == (NODE_IN as usize)) { + aug[(((ki * WIDE) + (2 * n))) as usize] = (aug[(((ki * WIDE) + (2 * n))) as usize] + (0.0 - 1.0)); + } + st[((CELL + CL_LSEEN)) as usize] = (st[((CELL + CL_LSEEN)) as usize] + 1); + } + } + } + k = (k + 1); + } + let sol: [f64; 55] = eliminate(aug, n); + let mn: [f64; 30] = mn_of(sol, n); + let mut ok: bool = true; + let mut i: usize = 0; + while (i < n) { + let mut j: usize = 0; + while (j < n) { + let mut m: f64 = mn[(((i * n) + j)) as usize]; + if (i == j) { + m = (m - 1.0); + } + if (fabs64(m) < 127.0) { + st[((((CELL + CL_D) + (i * n)) + j)) as usize] = (q_sm(m) as usize); + } else { + ok = false; + } + j = (j + 1); + } + if (fabs64(mn[(((n * n) + i)) as usize]) < 127.0) { + st[(((CELL + CL_NV) + i)) as usize] = (q_sm(mn[(((n * n) + i)) as usize]) as usize); + } else { + ok = false; + } + i = (i + 1); + } + if !(ok) { + reject(st, Y_CELL_SOLVE, st[((CELL + CL_SRC)) as usize], NONE); + } +} + +pub fn cell_build(src: &[u8], w: &mut [u8], st: &mut [usize]) -> () { + st[(END_LAST) as usize] = NONE; + st[(CUR) as usize] = NONE; + st[(CTL_CARD) as usize] = NONE; + st[(TEMP_CARD) as usize] = NONE; + st[(DEF_HIT) as usize] = NONE; + prepare(src, w, st); + mark_end(w, st); + scan(w, st); + if (st[(ERR_LINE) as usize] == 0) { + cell_inputs(w, st); + } + if (st[(ERR_LINE) as usize] == 0) { + cell_nodes(w, st); + } + if (st[(ERR_LINE) as usize] == 0) { + cell_tran(w, st); + } + if (st[(ERR_LINE) as usize] == 0) { + cell_solve(w, st); + } +} + +pub fn put_word(st: &mut [usize], out: &mut [u8], wd: usize) -> () { + if (wd >= 2147483648) { + puts(st, out, b"NEG | "); + put_num(st, out, (wd - 2147483648)); + } else { + put_num(st, out, wd); + } +} + +pub fn put_states(w: &[u8], st: &mut [usize], out: &mut [u8]) -> () { + let mut i: usize = 0; + while (i < st[((CELL + CL_NODES)) as usize]) { + puts(st, out, b"// x"); + put_num(st, out, i); + puts(st, out, b" = v("); + put_text(w, st, out, st[(((CELL + CL_NAME) + (2 * i))) as usize], st[((((CELL + CL_NAME) + (2 * i)) + 1)) as usize], SHOW); + puts(st, out, b")\n"); + i = (i + 1); + } + let mut k: usize = 0; + let mut l: usize = 0; + while (k < st[(SCAN_HI) as usize]) { + let e: usize = st[(((CARD0 + (k * CARD_SLOTS)) + C_B)) as usize]; + let t0: usize = tk(w, st[(((CARD0 + (k * CARD_SLOTS)) + C_A)) as usize], e); + if (((st[(((CARD0 + (k * CARD_SLOTS)) + C_SCOPE)) as usize] == NONE) && (w[(t0) as usize] != B_DOT)) && (lower(w[(t0) as usize]) == 108)) { + puts(st, out, b"// x"); + put_num(st, out, (st[((CELL + CL_NODES)) as usize] + l)); + puts(st, out, b" = i("); + put_text(w, st, out, t0, tk_end(w, t0, e), SHOW); + puts(st, out, b") * zs\n"); + l = (l + 1); + } + k = (k + 1); + } +} + +pub fn put_words(st: &mut [usize], out: &mut [u8], name: &[u8], base: usize, len: usize, per: usize) -> () { + puts(st, out, b"pub const CELL_"); + puts(st, out, name); + puts(st, out, b" : ["); + put_num(st, out, len); + puts(st, out, b"]u32 = ["); + let mut i: usize = 0; + while (i < len) { + if ((per < len) && ((i % per) == 0)) { + if (i > 0) { + puts(st, out, b","); + } + puts(st, out, b"\n "); + } else { + if (i > 0) { + puts(st, out, b", "); + } + } + put_word(st, out, st[(((CELL + base) + i)) as usize]); + i = (i + 1); + } + if (per < len) { + puts(st, out, b"\n"); + } + puts(st, out, b"]"); + if ((len == 1) && (st[((CELL + base)) as usize] < 2147483648)) { + puts(st, out, b" ** 1"); + } + puts(st, out, b";\n"); +} + +pub fn put_cell(src: &[u8], w: &[u8], st: &mut [usize], out: &mut [u8]) -> () { + let n: usize = st[((CELL + CL_UNK)) as usize]; + puts(st, out, b"// t27o --emit-cell: "); + put_text(src, st, out, st[(TITLE_A) as usize], st[(TITLE_B) as usize], SHOW); + puts(st, out, b"\n// the input u is the source on node "); + put_text(w, st, out, st[((CELL + CL_IN_A)) as usize], st[((CELL + CL_IN_B)) as usize], SHOW); + puts(st, out, b" (line "); + put_num(st, out, st[(((CARD0 + (st[((CELL + CL_SRC)) as usize] * CARD_SLOTS)) + C_LINE)) as usize]); + puts(st, out, b"); backward Euler, dt = "); + st[(VM) as usize] = st[((CELL + CL_DT_M)) as usize]; + st[(VE) as usize] = st[((CELL + CL_DT_E)) as usize]; + st[(VS) as usize] = 0; + put_val(st, out); + puts(st, out, b" s (the tmax of line "); + put_num(st, out, st[(((CARD0 + (st[((CELL + CL_DT_C)) as usize] * CARD_SLOTS)) + C_LINE)) as usize]); + puts(st, out, b"); zs = "); + if ((st[((CELL + CL_L1)) as usize] != NONE) && (st[((CELL + CL_C1)) as usize] != NONE)) { + puts(st, out, b"sqrt("); + read_val(w, st, st[((CELL + CL_L1)) as usize]); + put_val(st, out); + puts(st, out, b" / "); + read_val(w, st, st[((CELL + CL_C1)) as usize]); + put_val(st, out); + puts(st, out, b")\n"); + } else { + puts(st, out, b"1\n"); + } + puts(st, out, b"// the states, in the order of the words:\n"); + put_states(w, st, out); + puts(st, out, b"pub const CELL_N : usize = "); + put_num(st, out, n); + puts(st, out, b";\n"); + put_words(st, out, b"D", CL_D, (n * n), n); + put_words(st, out, b"NV", CL_NV, n, n); +} + +pub fn emit_cell(src: &[u8], w: &mut [u8], out: &mut [u8], st: &mut [usize]) -> usize { + cell_build(src, w, st); + if (st[(ERR_LINE) as usize] != 0) { + put_reject(w, st, out); + } else { + put_cell(src, w, st, out); + } + return st[(W) as usize]; +} + +pub const DECK_RC: &'static str = "M0 RC low-pass, backward Euler on a fixed 50 ns grid\nB1 in 0 V = u(time-425n) - u(time-5425n)\nR1 in 1 1k\nC1 1 0 1n IC=0\n.options meth~d=gear maxord=1 trtol=1e7\n.tran 50n 10475n 0 50n uic\n.end\n"; + +pub const DECK_RLC: &'static str = "M1 series RLC, backward Euler on a fixed 5 ns grid\nB1 in 0 V = u(time-42.5n) - u(time-542.5n)\nR1 in 1 100\nL1 1 2 10u IC=0\nC1 2 0 1n IC=0\n.options meth~d=gear maxord=1 trtol=1e7\n.tran 5n 1047.5n 0 5n uic\n.end\n"; + +pub const DECK_LAD3: &'static str = "M1 3-node RC ladder, backward Euler on a fixed 50 ns grid\nB1 in 0 V = u(time-425n) - u(time-5425n)\nR1 in 1 1k\nR2 1 2 1k\nR3 2 3 1k\nC1 1 0 1n IC=0\nC2 2 0 1n IC=0\nC3 3 0 1n IC=0\n.options meth~d=gear maxord=1 trtol=1e7\n.tran 50n 10475n 0 50n uic\n.end\n"; + +pub const DECK_LAD5: &'static str = "M1 5-node RC ladder, backward Euler on a fixed 50 ns grid\nB1 in 0 V = u(time-425n) - u(time-5425n)\nR1 in 1 1k\nR2 1 2 1k\nR3 2 3 1k\nR4 3 4 1k\nR5 4 5 1k\nC1 1 0 1n IC=0\nC2 2 0 1n IC=0\nC3 3 0 1n IC=0\nC4 4 0 1n IC=0\nC5 5 0 1n IC=0\n.options meth~d=gear maxord=1 trtol=1e7\n.tran 50n 10475n 0 50n uic\n.end\n"; + +pub fn deck_text(dst: &mut [u8], lit: &[u8]) -> usize { + let mut i: usize = 0; + while ((i < lit.len()) && (i < dst.len())) { + if (lit[(i) as usize] == 126) { + dst[(i) as usize] = 111; + } else { + dst[(i) as usize] = lit[(i) as usize]; + } + i = (i + 1); + } + return i; +} + +pub fn num_of(s: &[u8], st: &mut [usize]) -> bool { + return parse_num(s, 0, s.len(), st); +} + +pub fn time_expr(s: &[u8]) -> bool { + return time_only(s, 0, s.len()); +} + +pub fn cell_diff(st: impl AsRef<[usize]>, n: usize, d: impl AsRef<[u32]>, nv: impl AsRef<[u32]>) -> usize { + let st: &[usize] = st.as_ref(); + let d: &[u32] = d.as_ref(); + let nv: &[u32] = nv.as_ref(); + let mut miss: usize = 0; + let mut i: usize = 0; + while (i < (n * n)) { + if ((st[(((CELL + CL_D) + i)) as usize] as u32) != d[(i) as usize]) { + miss = (miss + 1); + } + i = (i + 1); + } + i = 0; + while (i < n) { + if ((st[(((CELL + CL_NV) + i)) as usize] as u32) != nv[(i) as usize]) { + miss = (miss + 1); + } + i = (i + 1); + } + return miss; +} + +pub const LAD3_D: [u32; 9] = [(NEG | 1493555), 696219, 33153, 696219, (NEG | 1460402), 729372, 33153, 729372, (NEG | 764183)]; + +pub const LAD3_N: usize = 3; + +pub const LAD3_NV: [u32; 3] = [764183, 34811, 1658]; + +pub const LAD5_D: [u32; 25] = [(NEG | 1493558), 696153, 31709, 1444, 69, 696153, (NEG | 1461849), 697597, 31778, 1513, 31709, 697597, (NEG | 1461780), 697666, 33222, 1444, 31778, 697666, (NEG | 1460336), 729375, 69, 1513, 33222, 729375, (NEG | 764183)]; + +pub const LAD5_N: usize = 5; + +pub const LAD5_NV: [u32; 5] = [764183, 34808, 1585, 72, 3]; + +pub const NEG: u32 = 0x80000000; + +pub const NMAX: usize = 5; + +pub const NODE_GND: u8 = 0; + +pub const NODE_IN: u8 = 15; + +pub const RC_D: [u32; 1] = [(NEG | 798915)]; + +pub const RC_N: usize = 1; + +pub const RC_NV: [u32; 1] = [798915; 1]; + +pub const RLC_D: [u32; 9] = [(NEG | 16777216), 797017, (NEG | 15940348), 0, (NEG | 39851), 797017, 0, (NEG | 797017), (NEG | 836868)]; + +pub const RLC_N: usize = 3; + +pub const RLC_NV: [u32; 3] = [15980199, 39851, 797017]; + +pub const SCALE: f64 = 16777216.0; + +pub const WIDE: usize = 11; + +pub fn eliminate(aug: [f64; 55], n: usize) -> [f64; 55] { + let mut w: [f64; 55] = aug; + let mut c: usize = 0; + let mut r: usize = 0; + let mut p: usize = 0; + let mut k: usize = 0; + while (c < n) { + p = c; + r = (c + 1); + while (r < n) { + if (fabs64(w[(((r * WIDE) + c)) as usize]) > fabs64(w[(((p * WIDE) + c)) as usize])) { + p = r; + } + r = (r + 1); + } + if (p != c) { + k = 0; + while (k < WIDE) { + let t: f64 = w[(((c * WIDE) + k)) as usize]; + w[(((c * WIDE) + k)) as usize] = w[(((p * WIDE) + k)) as usize]; + w[(((p * WIDE) + k)) as usize] = t; + k = (k + 1); + } + } + let piv: f64 = w[(((c * WIDE) + c)) as usize]; + k = 0; + while (k < WIDE) { + w[(((c * WIDE) + k)) as usize] = (w[(((c * WIDE) + k)) as usize] / piv); + k = (k + 1); + } + r = 0; + while (r < n) { + if (r != c) { + let f: f64 = w[(((r * WIDE) + c)) as usize]; + k = 0; + while (k < WIDE) { + w[(((r * WIDE) + k)) as usize] = (w[(((r * WIDE) + k)) as usize] - (f * w[(((c * WIDE) + k)) as usize])); + k = (k + 1); + } + } + r = (r + 1); + } + c = (c + 1); + } + return w; +} + +pub fn fabs64(x: f64) -> f64 { + if (x < 0.0) { + return (0.0 - x); + } + return x; +} + +pub fn is_free(node: u8) -> bool { + return ((node != NODE_GND) && (node != NODE_IN)); +} + +pub fn ix(node: u8) -> usize { + return ((node as usize) - 1); +} + +pub fn mn_of(w: [f64; 55], n: usize) -> [f64; 30] { + let mut o: [f64; 30] = [0.0; 30]; + let mut i: usize = 0; + let mut j: usize = 0; + while (i < n) { + j = 0; + while (j < n) { + o[(((i * n) + j)) as usize] = w[((((i * WIDE) + n) + j)) as usize]; + j = (j + 1); + } + o[(((n * n) + i)) as usize] = w[(((i * WIDE) + (2 * n))) as usize]; + i = (i + 1); + } + return o; +} + +pub fn pack(m: u32, neg: bool) -> u32 { + if (m == 0) { + return 0; + } + if neg { + return (m | NEG); + } + return m; +} + +pub fn q_sm(v: f64) -> u32 { + return pack((((fabs64(v) * SCALE) + 0.5) as u32), (v < 0.0)); +} + pub const B_DEL: u8 = 127; pub const B_LOWER_A: u8 = 97; diff --git a/specs/tools/t27o/spice_convert.t27 b/specs/tools/t27o/spice_convert.t27 index f9b6882f50..e081bc0a76 100644 --- a/specs/tools/t27o/spice_convert.t27 +++ b/specs/tools/t27o/spice_convert.t27 @@ -3,7 +3,8 @@ // analog-emulation epic #10486): reads an ngspice netlist, flattens its subcircuits and prints an // inventory, or refuses with the card, its line and the reason. It sits beside rust_convert.t27 as the // spec said it would (specs/tools/t27o/_convert.t27) and is reached by file extension: -// `tri t27o deck.spice | deck.cir | deck.sp`. Emitting the M1 linear-cell spec from the inventory is M2b. +// `tri t27o deck.spice | deck.cir | deck.sp`. M2b (#10766) adds the other direction of the same reader: the +// constants of the M1 linear cell (specs/analog/linear.t27) folded from a deck (see "The cell", below). // -- // The dialect is ngspice's (the IHP open PDK targets ngspice). What this reader does with a deck: // - The first physical line is the TITLE and is never a card (ngspice does the same). It is printed @@ -14,11 +15,14 @@ // any other trailing letters are a unit, as ngspice reads them (`10pF`, `5V`; also `1farad` = 1e-15, // the well-known ngspice trap). `mil` is refused, not guessed. A number is kept exact as a decimal // mantissa and a power of ten (0.13u is 13e-8): no float is involved. At most 18 significant digits. -// - Cards read: R C L (two nodes, a value, `m=`), V I (DC, PULSE, SIN, PWL), E (a VCVS: two node pairs +// - Cards read: R C L (two nodes, a value, `m=`; a C and an L also `ic=`), V I (DC, PULSE, SIN, PWL), +// B (a voltage of time alone: `B1 in 0 V = u(time-425n)`, see "The B source"), E (a VCVS: two node pairs // and a gain), X (an instance of a subcircuit or of a PDK device), `.subckt` / `.ends` (nested // definitions are scoped to the definition that holds them), `.lib
` and `.include -// ` (recorded, never followed: no PDK file is read), `.options TEMP=`, `.PARAM name=literal`, -// `.tran`, `.ic` / `.nodeset` (`v(node)=literal`), `.save` and `.global` (recorded), `.end`. +// ` (recorded, never followed: no PDK file is read), `.options TEMP=` and the integration choice +// `method=gear|trap|trapezoidal`, `maxord=`, `trtol=` (recorded: OPT_METHOD, OPT_MAXORD), +// `.PARAM name=literal`, `.tran`, `.ic` / `.nodeset` (`v(node)=literal`), `.save` and `.global` +// (recorded), `.end`. // - PDK devices are recognised by the model name of an X card and counted, not evaluated (evaluation is // M3): sg13_lv_nmos and sg13_lv_pmos (4 nodes, w l ng m) and cap_cmim (2 nodes, w l m). A `.subckt` // of the same name in the deck wins over the table, as it does in ngspice. cap_cmim is beyond what @@ -26,10 +30,11 @@ // - `.control` ... `.endc`: `tran` is read like a `.tran` card; let plot set unset write run print echo // wrdata setplot display destroy quit exit save hardcopy are post-processing, counted as ignored; // any other command is refused (an `ac` or `op` would be an analysis nobody here evaluates). -// - Refused with `line N: ` and the card text, never approximated: a B source, any other +// - Refused with `line N: ` and the card text, never approximated: a B source that is not a voltage +// of time alone, any other // element letter (D Q J K M G F H S W T U O P Z Y), `.measure`, `.func`, `.model` and every other // directive not listed above, a `{expression}` or 'quoted expression', a value that is not a literal -// number, an instance parameter other than m (w l ng for PDK devices), subcircuit parameters, a node +// number, an instance parameter other than m (w l ng for PDK devices; ic for C and L), subcircuit parameters, a node // count that differs from the subcircuit's ports, an undefined subcircuit, a subcircuit nested deeper // than MAX_DEPTH, a subcircuit that instantiates itself, a fractional `m` on an instance, a `.subckt` // without `.ends`. Every card is read; the refusal reported is the one on the EARLIEST line, so a @@ -50,16 +55,61 @@ // first spelling, `m.x3.x1.xm1`, is not reproduced.) Only leaf devices are listed; the X cards that open a // level are counted. An `m=` on an X card multiplies everything below it; the inventory reports MOS and // MIM devices weighted by the product of the m of the instance and of its ancestors. Node names are not -// resolved through the port maps: M2a counts and names, M2b will wire. +// resolved through the port maps: the inventory counts and names, and the cell below reads flat decks and +// refuses an instance (wiring a hierarchy is M4d, #10754). +// -- +// The B source: `B1 n+ n- V = expression`. An expression built from numbers, `time`, u u2 uramp abs sgn sin cos +// exp sqrt, + - * / ^ ( ) and commas is a function of time alone, so the source is an independent voltage like a +// V source: it counts as a voltage source (`v=` of the inventory, `b=` of the sources line, leaf label `b`). +// ngspice's own decks for the fixed-step runs (specs/analog/decks) drive their circuit with one, because a B +// source declares no breakpoint and the step stays on the grid. Anything else in the expression (v( i( a node or +// branch name, @, a comparison, a ?:) or a B current source is refused: that source reads the circuit. // -- // The caller passes the deck, a work buffer (at least 2 * deck.len + 8192 bytes: the cleaned cards, then -// the path scratch), an output buffer and a zeroed state vector: FIXED counters, the subcircuit table -// (MAXDEF rows), the stack of definitions the flattener is inside (MAX_DEPTH), then CARD_SLOTS slots a -// card, as many cards as fit; a deck that does not fit is refused, never cut. The result is the number of -// bytes the output needs; the buffer was too small when that is above out.len. `exit_code` is 0 for an -// accepted deck and 1 for a refused one. +// the path scratch), an output buffer and a zeroed state vector: FIXED counters, CELL_SLOTS of cell workspace, +// the subcircuit table (MAXDEF rows), the stack of definitions the flattener is inside (MAX_DEPTH), then +// CARD_SLOTS slots a card, as many cards as fit; a deck that does not fit is refused, never cut. The result +// is the number of bytes the output needs; the buffer was too small when that is above out.len. `exit_code` +// is 0 for an accepted deck and 1 for a refused one. +// -- +// The cell (M2b, #10766). `emit_cell` reads a deck as the linear cell of specs/analog/linear.t27 and prints +// its constants CELL_N, CELL_D and CELL_NV as t27, from the netlist alone: no per-circuit text. The cell is +// x' = x + rnd(D x + N u), D = M - I and N the backward Euler fold of (G + C/dt) x' = (C/dt) x + B u, every +// word a Q.24 sign-magnitude u32 (bit 31 is the sign). What the deck must be, and what the converter does: +// - FLAT R, C, L and ONE input: a V source, or a B source of time alone, from a node to ground (`0`); its +// plus node is the cell's input node and the source is the column of B (its waveform does not matter: the +// run supplies u). An I or E source and an X card (a subcircuit instance or a PDK device) are refused as +// outside the cell; so is a second input, a floating source, `m=`, an initial condition other than 0 (the +// cell starts at rest), a node called gnd (ngspice's reading of that name is not checked here; write 0), a +// C or L on the input node, and a deck with no state or with more than NMAX = 5. +// - THE STEP. Exactly one `.tran` (or the control `tran`) with all four numbers `tstep tstop tstart tmax`; +// dt is the fourth, tmax. The deck must say `.options method=gear maxord=1`, which ngspice runs as backward +// Euler: that is the method the cell folds. Without it ngspice integrates by the trapezoidal rule, and the +// words would describe another run: refused. +// - THE STATES, the ordering rule. The unknowns are the free nodes numbered 1, 2, ... in the order their names +// first appear in the R, C and L cards of the deck (names compared without case, the input node and `0` +// left out), then one per inductor, in card order, its current stored times ZS. The tables of linear.t27 +// number their nodes 1, 2, ... by hand, and the four decks of specs/analog/decks name theirs 1, 2, ... in the +// order they first appear, so the words come out in the order linear.t27 pins. A deck that names its nodes in another +// order gets its states in that order (the tests show the ladder listed from its far end), and the block +// lists which node or inductor each state is, so the order is stated, never implicit. +// - ZS, the characteristic impedance that keeps the inductor rows near the voltages': sqrt(L1 / C1) with L1 the +// first inductor and C1 the first capacitor of the deck (100 Ohm for linear.t27's RLC), and 1 when the deck +// lacks either. A value of the deck is the f64 nearest to its decimal text (dec_f64: one multiplication or +// one division of exact operands), which is the literal linear.t27 writes (1.0e-9 for 1n). +// - THE FOLD. cell_solve stamps the cards into the work matrix [A | C/dt | B] rule for rule as linear.t27's +// stamp does (R: G, and G u on the right-hand side when an end is the input; C: C/dt into A and into the +// middle block; L: a branch row with KCL +-i/ZS at its nodes), solves it by linear.t27's own `eliminate` +// and `mn_of`, and rounds D = M - I and N with linear.t27's own `q_sm`, by `use`: a word can not differ from +// the cell's by a rounding rule. The stamp is the one part written again here: linear.t27's `stamp` hands a +// local array to a function that writes it, which t27c gen-rust lowers to `&mut &(w)`, and the Rust of this +// spec is what `tri t27o` runs. pair_add and src_add take and return the matrix instead. The tests hold the +// copy to the original: the four circuits' words are linear.t27's pinned constants, bit for bit. +// - REFUSED: a constant beyond +-127 or a matrix with no solution (a floating node) is a refusal, not a word. +// - NOT DONE: a hierarchy (M4d), a MOS or any nonlinear device (M3), a current source, more than one input, a +// dt that is not tmax, an integration other than backward Euler. // -- -// Writing this spec, three t27c behaviours shaped it (each is a compiler limit, not a choice): +// Writing this spec, five t27c behaviours shaped it (each is a compiler limit, not a choice): // - `use` splices by NAME: every word of the spec outside a `//` comment, string literals and `;` lines // included, that is also a declaration of an imported module is pulled in with everything that // declaration calls. A local called `bit`, a message saying "at line" or ".param" pulled all of @@ -70,6 +120,14 @@ // then moves its `&mut [usize]` into it: the card and definition tables are therefore indexed in place, // not through accessor fns, and find_def writes a slot so the state vector stays `&mut`. // - A string literal has \n and \t escapes but no \r; the tests build their CRLF deck at run time. +// - Since this spec imports analog::linear, the same splice also pulls linear.t27's `run` (whose `step` is +// declared by rust_convert too) from the string "run" of the control commands, and rust_convert's `method` +// from the option word, `at`, `bad` and `unknowns` from a message or a local. Those words are spelled in +// two pieces or avoided (ci_cat, the ~ of deck_text, other locals); the test decks write the option word +// with a ~ for the o. After adding a word, regenerate the Rust: a function nobody here calls, in +// gen/rust/tools/t27o/spice_convert.rs, is a word that pulled it. +// - gen-rust lowers a local array passed to a function that writes it as `&mut &(w)`, which rustc refuses: +// cell_solve keeps its matrix local and moves it through pair_add and src_add by value. // ASCII only (L3) // phi^2 + 1/phi^2 = 3 | TRINITY @@ -83,6 +141,7 @@ use tools::t27o::rust_convert::is_space; use tools::t27o::rust_convert::is_digit; use tools::t27o::rust_convert::is_alpha; use tools::t27o::rust_convert::out_is; +use analog::linear::{eliminate, mn_of, q_sm, is_free, ix, fabs64, WIDE, NMAX, NODE_IN, NODE_GND, NEG, RC_N, RC_D, RC_NV, RLC_N, RLC_D, RLC_NV, LAD3_N, LAD3_D, LAD3_NV, LAD5_N, LAD5_D, LAD5_NV}; // ---------------------------------------------------------------- state @@ -154,10 +213,49 @@ pub const DEF_HIT : usize = 65; pub const TEMP_CARD : usize = 66; /// Where group_count left the position just after the ) it read. pub const GROUP_END : usize = 67; +/// B sources of time alone, and what .options said about the integration: OPT_METHOD is 1 for +/// method=gear and 2 for the other methods; OPT_MAXORD is 1 for maxord=1 and 2 for any other order. +pub const N_BSRC : usize = 68; +pub const OPT_METHOD : usize = 69; +pub const OPT_MAXORD : usize = 70; pub const FIXED : usize = 80; +/// The cell workspace (see "The cell"): CELL_SLOTS slots. +pub const CELL : usize = 80; +pub const CELL_SLOTS : usize = 64; +/// Offsets inside it. CL_NODES is how many free nodes were numbered, CL_IND the inductors, CL_UNK their sum +/// (the unknowns); CL_LSEEN counts the inductors stamped so far. CL_IN_A..CL_IN_B is the input node's name and +/// CL_SRC the card of the input source (NONE before one is found), CL_SRCS how many input sources the deck has. +/// CL_DT_M..CL_DT_E is dt as the decimal the .tran card wrote (mantissa, biased exponent), CL_DT_C that card. +/// CL_L1 and CL_C1 are the first inductor's and the first capacitor's cards (NONE when there is none). +pub const CL_NODES : usize = 0; +pub const CL_IND : usize = 1; +pub const CL_UNK : usize = 2; +pub const CL_LSEEN : usize = 3; +pub const CL_IN_A : usize = 4; +pub const CL_IN_B : usize = 5; +pub const CL_SRC : usize = 6; +pub const CL_SRCS : usize = 7; +pub const CL_DT_M : usize = 8; +pub const CL_DT_E : usize = 9; +pub const CL_DT_C : usize = 10; +pub const CL_L1 : usize = 11; +pub const CL_C1 : usize = 12; +/// The answer of the last node_of (the write keeps the state vector a &mut in the generated Rust). +pub const CL_HIT : usize = 13; +/// The names of the numbered free nodes, two slots each (CL_NMAX of them), in the order of first appearance. +pub const CL_NAME : usize = 14; +pub const CL_NMAX : usize = 6; +/// The folded words: D row-major (CL_DMAX slots), then NV (CL_NMAXW slots), as sign-magnitude u32 words. +pub const CL_D : usize = 26; +pub const CL_DMAX : usize = 25; +pub const CL_NV : usize = 51; +pub const CL_NMAXW : usize = 5; +/// The first word after NV: the cell workspace ends here (CL_NV + CL_NMAXW = 56 of CELL_SLOTS). +pub const CL_END : usize = 56; + /// The subcircuit table: DEF_SLOTS slots a definition, MAXDEF of them. -pub const DEF0 : usize = 80; +pub const DEF0 : usize = 144; pub const DEF_SLOTS : usize = 6; pub const MAXDEF : usize = 256; pub const D_NAME_A : usize = 0; @@ -168,10 +266,10 @@ pub const D_LAST : usize = 4; pub const D_PARENT : usize = 5; /// The definitions the flattener is inside, outermost first: MAX_DEPTH slots. -pub const STK : usize = 1616; +pub const STK : usize = 1680; /// The card table: CARD_SLOTS slots a card, as many as the state vector has room for. -pub const CARD0 : usize = 1648; +pub const CARD0 : usize = 1712; pub const CARD_SLOTS : usize = 4; pub const C_A : usize = 0; pub const C_B : usize = 1; @@ -236,6 +334,17 @@ pub const Y_SUBPARAM : usize = 21; pub const Y_MULT : usize = 22; pub const Y_CTLIN : usize = 23; pub const Y_RECUR : usize = 24; +/// Why a deck is not a cell (M2b): an element the cell has no row for, the input source, a value or a +/// terminal, the size, the step, the integration method, an initial condition, a matrix with no solution. +pub const Y_CELL_EL : usize = 25; +pub const Y_CELL_IN : usize = 26; +pub const Y_CELL_VALUE : usize = 27; +pub const Y_CELL_SIZE : usize = 28; +pub const Y_CELL_DT : usize = 29; +pub const Y_CELL_METHOD : usize = 30; +pub const Y_CELL_IC : usize = 31; +pub const Y_CELL_SOLVE : usize = 32; +pub const Y_CELL_GND : usize = 33; // The bytes the reader looks at, as numbers (a char literal is a Rust char in gen-rust). pub const B_TAB : u8 = 9; @@ -526,7 +635,8 @@ pub fn cut_comment(src: []const u8, a: usize, e: usize) -> usize { /// dropped, inline comments cut, every `+` line joined to the card before it. The card table records /// where each card is and the physical line it started on. pub fn prepare(src: []const u8, w: []u8, st: []usize) -> void { - const cap : usize = (st.len - CARD0) / CARD_SLOTS; + var cap : usize = 0; + if (st.len > CARD0) { cap = (st.len - CARD0) / CARD_SLOTS; } var p : usize = 0; var line : usize = 0; var wp : usize = 0; @@ -646,7 +756,7 @@ pub fn need_num(w: []const u8, st: []usize, k: usize, t: usize, e: usize) -> boo } /// The key=value pairs from p to the end of the card k. `allow` is a mask set of the keys that may -/// appear: 1 m, 2 w, 4 l, 8 ng. Every value is a number; with `whole` the m must be a whole number. +/// appear: 1 m, 2 w, 4 l, 8 ng, 16 ic. Every value is a number; with `whole` the m must be a whole number. pub fn check_params(w: []const u8, st: []usize, k: usize, p: usize, e: usize, allow: usize, whole: bool) -> void { var q : usize = p; var go : bool = true; @@ -667,6 +777,7 @@ pub fn check_params(w: []const u8, st: []usize, k: usize, p: usize, e: usize, al if (ci_is(w, t, te, "w")) { mask = 2; } if (ci_is(w, t, te, "l")) { mask = 4; } if (ci_is(w, t, te, "ng")) { mask = 8; } + if (ci_is(w, t, te, "ic")) { mask = 16; } if (mask == 0 or (allow / mask) % 2 == 0) { reject(st, Y_PARAM, k, t); go = false; @@ -684,15 +795,17 @@ pub fn check_params(w: []const u8, st: []usize, k: usize, p: usize, e: usize, al } } -/// R, C and L: name, two nodes, a value, then m=. +/// R, C and L: name, two nodes, a value, then m=; a capacitor and an inductor also take ic=. pub fn passive(w: []const u8, st: []usize, k: usize, kind: usize, t1: usize, e: usize) -> void { count(st, kind); const q : usize = after_word(w, t1, e, 2); const v : usize = word_at(w, t1, e, 2); + var allow : usize = 1; + if (kind != K_R) { allow = 17; } if (q == NONE) { reject(st, Y_FIELDS, k, NONE); } else if (need_num(w, st, k, v, e)) { - check_params(w, st, k, q, e, 1, false); + check_params(w, st, k, q, e, allow, false); } } @@ -774,6 +887,65 @@ pub fn source(w: []const u8, st: []usize, k: usize, kind: usize, t1: usize, e: u } } +/// Is the identifier w[a..b] one that a B expression may hold and still be a function of time alone? `time`, the +/// step and ramp functions u, u2 and uramp, and the plain functions of one number. v, i, a node name, a branch +/// name and every other word are not (a B source that reads a node is a nonlinear or controlled source). +pub fn time_word(w: []const u8, a: usize, b: usize) -> bool { + return ci_is(w, a, b, "time") or ci_is(w, a, b, "u") or ci_is(w, a, b, "u2") or ci_is(w, a, b, "uramp") or ci_is(w, a, b, "abs") or ci_is(w, a, b, "sgn") or ci_is(w, a, b, "sin") or ci_is(w, a, b, "cos") or ci_is(w, a, b, "exp") or ci_is(w, a, b, "sqrt"); +} + +/// Is the expression w[a..b] built from numbers, `time`, those functions and + - * / ^ ( ) , and blanks, and from +/// nothing else? A number is digits and dots, an exponent, then a scale factor and units; it may not be followed by +/// a (. True means a function of time alone; any other character or word means it is not read. +pub fn time_only(w: []const u8, a: usize, b: usize) -> bool { + var q : usize = a; + var any : bool = false; + while (q < b) { + const c : u8 = w[q]; + if (is_digit(c) or c == B_DOT) { + any = true; + while (q < b and (is_digit(w[q]) or w[q] == B_DOT)) { q = q + 1; } + if (q + 1 < b and lower(w[q]) == 101 and (is_digit(w[q + 1]) or ((w[q + 1] == B_MINUS or w[q + 1] == B_PLUS) and q + 2 < b and is_digit(w[q + 2])))) { + q = q + 2; + while (q < b and is_digit(w[q])) { q = q + 1; } + } + while (q < b and is_alpha(w[q])) { q = q + 1; } + if (q < b and w[q] == B_LPAREN) { return false; } + } else if (is_alpha(c)) { + var f : usize = q; + while (f < b and (is_alpha(w[f]) or is_digit(w[f]))) { f = f + 1; } + if (!time_word(w, q, f)) { return false; } + any = true; + q = f; + } else if (is_sep(c) or c == B_LPAREN or c == B_RPAREN or c == B_PLUS or c == B_MINUS or c == B_STAR or c == 47 or c == 94) { + q = q + 1; + } else { + return false; + } + } + return any; +} + +/// B: name n+ n- V = expression. A voltage that is a function of time alone is an input, like a V source (ngspice +/// calls it behavioural; the decks of specs/analog/decks drive their circuit with one so the step is exact). A B +/// current source, an expression that reads a node or a branch, or any other form is refused. +pub fn bsource(w: []const u8, st: []usize, k: usize, t1: usize, e: usize) -> void { + const q : usize = after_word(w, t1, e, 1); + if (q == NONE) { + reject(st, Y_B, k, NONE); + return; + } + const kt : usize = tk(w, q, e); + const ke : usize = tk_end(w, kt, e); + const eq : usize = tk(w, ke, e); + if (kt >= e or !ci_is(w, kt, ke, "v") or eq >= e or w[eq] != B_EQ or !time_only(w, eq + 1, e)) { + reject(st, Y_B, k, NONE); + return; + } + count(st, K_V); + st[N_BSRC] = st[N_BSRC] + 1; +} + /// E: a voltage-controlled voltage source, name n+ n- nc+ nc- gain, and nothing else. pub fn vcvs(w: []const u8, st: []usize, k: usize, t1: usize, e: usize) -> void { count(st, K_E); @@ -858,7 +1030,7 @@ pub fn element(w: []const u8, st: []usize, k: usize, t0: usize, t1: usize, e: us else if (c == 105) { source(w, st, k, K_I, t1, e); } else if (c == 101) { vcvs(w, st, k, t1, e); } else if (c == 120) { x_card(w, st, k); } - else if (c == 98) { reject(st, Y_B, k, NONE); } + else if (c == 98) { bsource(w, st, k, t1, e); } else { reject(st, Y_ELEMENT, k, NONE); } } @@ -964,7 +1136,8 @@ pub fn only_words(w: []const u8, t1: usize, e: usize, n: usize) -> bool { return tk(w, q, e) >= e; } -/// .options key=value ... : only TEMP. +/// .options key=value ... : TEMP, and the integration the cell is judged by: method=gear (or trap, trapezoidal), +/// maxord= and trtol=. Any other key is refused. pub fn d_options(w: []const u8, st: []usize, k: usize, t1: usize, e: usize) -> void { st[N_OPT] = st[N_OPT] + 1; var q : usize = t1; @@ -977,15 +1150,41 @@ pub fn d_options(w: []const u8, st: []usize, k: usize, t1: usize, e: usize) -> v } else { const te : usize = tk_end(w, t, e); const eq : usize = tk(w, te, e); - if (is_punct(w[t]) or !ci_is(w, t, te, "temp") or eq >= e or w[eq] != B_EQ) { + const v : usize = tk(w, eq + 1, e); + var key : usize = 0; + if (ci_is(w, t, te, "temp")) { key = 1; } + if (ci_cat(w, t, te, "meth", "od")) { key = 2; } + if (ci_is(w, t, te, "maxord")) { key = 3; } + if (ci_is(w, t, te, "trtol")) { key = 4; } + if (is_punct(w[t]) or key == 0 or eq >= e or w[eq] != B_EQ) { reject(st, Y_OPTION, k, t); go = false; - } else if (!need_num(w, st, k, tk(w, eq + 1, e), e)) { + } else if (key == 2) { + const ve : usize = tk_end(w, v, e); + if (v >= e or is_punct(w[v])) { + reject(st, Y_FIELDS, k, NONE); + go = false; + } else if (ci_is(w, v, ve, "gear")) { + st[OPT_METHOD] = 1; + seen = seen + 1; + q = ve; + } else if (ci_is(w, v, ve, "trap") or ci_is(w, v, ve, "trapezoidal")) { + st[OPT_METHOD] = 2; + seen = seen + 1; + q = ve; + } else { + reject(st, Y_OPTION, k, v); + go = false; + } + } else if (!need_num(w, st, k, v, e)) { go = false; } else { - st[TEMP_CARD] = k; + if (key == 1) { st[TEMP_CARD] = k; } + if (key == 3) { + if (st[VM] == 1 and st[VE] == EXP_BIAS and st[VS] == 0) { st[OPT_MAXORD] = 1; } else { st[OPT_MAXORD] = 2; } + } seen = seen + 1; - q = tk_end(w, tk(w, eq + 1, e), e); + q = tk_end(w, v, e); } } } @@ -1094,7 +1293,7 @@ pub fn directive(w: []const u8, st: []usize, k: usize, t0: usize, t1: usize, e: /// Is the control command w[a..b] one that only plots, prints, saves or sets a variable? pub fn inert_command(w: []const u8, a: usize, b: usize) -> bool { - return ci_is(w, a, b, "let") or ci_is(w, a, b, "plot") or ci_is(w, a, b, "set") or ci_is(w, a, b, "unset") or ci_is(w, a, b, "write") or ci_is(w, a, b, "run") or ci_is(w, a, b, "print") or ci_is(w, a, b, "echo") or ci_is(w, a, b, "wrdata") or ci_is(w, a, b, "setplot") or ci_is(w, a, b, "display") or ci_is(w, a, b, "destroy") or ci_is(w, a, b, "quit") or ci_is(w, a, b, "exit") or ci_is(w, a, b, "save") or ci_is(w, a, b, "hardcopy"); + return ci_is(w, a, b, "let") or ci_is(w, a, b, "plot") or ci_is(w, a, b, "set") or ci_is(w, a, b, "unset") or ci_is(w, a, b, "write") or ci_cat(w, a, b, "ru", "n") or ci_is(w, a, b, "print") or ci_is(w, a, b, "echo") or ci_is(w, a, b, "wrdata") or ci_is(w, a, b, "setplot") or ci_is(w, a, b, "display") or ci_is(w, a, b, "destroy") or ci_is(w, a, b, "quit") or ci_is(w, a, b, "exit") or ci_is(w, a, b, "save") or ci_is(w, a, b, "hardcopy"); } /// A card inside .control ... .endc. @@ -1254,6 +1453,7 @@ pub fn flatten(w: []u8, st: []usize, out: []u8, scope: usize, lo: usize, hi: usi if (c == 99) { kind = K_C; label = "c"; } if (c == 108) { kind = K_L; label = "l"; } if (c == 118) { kind = K_V; label = "v"; } + if (c == 98) { kind = K_V; label = "b"; } if (c == 105) { kind = K_I; label = "i"; } if (c == 101) { kind = K_E; label = "e"; } if (kind < K_COUNT) { @@ -1354,7 +1554,7 @@ pub fn put_why(w: []const u8, st: []usize, out: []u8) -> void { const tok : usize = st[ERR_TOK]; if (why == Y_CONT) { puts(st, out, "continuation line with no card before it"); } else if (why == Y_SIZE) { puts(st, out, "deck is larger than the reader's tables"); } - else if (why == Y_B) { puts(st, out, "B source not supported"); } + else if (why == Y_B) { puts(st, out, "B source not supported (only a voltage that is a function of time alone is read)"); } else if (why == Y_ELEMENT) { const c : u8 = lower(w[st[CARD0 + k * CARD_SLOTS + C_A]]); if (c >= 97 and c <= 122) { put(st, out, c - 32); } else { put(st, out, c); } @@ -1383,7 +1583,19 @@ pub fn put_why(w: []const u8, st: []usize, out: []u8) -> void { if (why == Y_OPEN) { puts(st, out, ".subckt without .ends"); } if (why == Y_DUP) { puts(st, out, "subcircuit defined twice"); } if (why == Y_CTL) { puts(st, out, "control command not supported"); } - if (why == Y_OPTION) { puts(st, out, "option not supported (only TEMP)"); } + if (why == Y_OPTION) { puts(st, out, "option not supported (only TEMP, METHOD, MAXORD and TRTOL)"); } + if (why == Y_CELL_EL) { puts(st, out, "not in the M1 cell (a flat deck of R, C, L and one input source)"); } + if (why == Y_CELL_IN) { puts(st, out, "the M1 cell has exactly one input: a V source or a time-only B source from a node to ground 0"); } + if (why == Y_CELL_VALUE) { puts(st, out, "R, C and L need a positive value (a mantissa up to 2^53, a power of ten within 22), two different nodes and no m=, and C and L may not touch the input node"); } + if (why == Y_CELL_SIZE) { puts(st, out, "the M1 cell has 1 to 5 states (numbered nodes plus inductors)"); } + if (why == Y_CELL_DT) { puts(st, out, "the M1 cell needs one .tran card with tstep tstop tstart tmax; dt is tmax"); } + if (why == Y_CELL_METHOD) { + puts(st, out, "the M1 cell is backward Euler: the deck must say .options meth"); + puts(st, out, "od=gear maxord=1"); + } + if (why == Y_CELL_IC) { puts(st, out, "initial condition other than 0: the M1 cell starts from rest"); } + if (why == Y_CELL_SOLVE) { puts(st, out, "the matrix has no solution (a floating node?) or a constant is beyond +-127"); } + if (why == Y_CELL_GND) { puts(st, out, "a node called gnd is not read as ground: write 0"); } if (tok != NONE and k != NONE) { puts(st, out, ": "); put_tok(w, st, out, tok, st[CARD0 + k * CARD_SLOTS + C_B]); @@ -1509,6 +1721,7 @@ pub fn put_inventory(src: []const u8, w: []u8, st: []usize, out: []u8) -> void { put_kv(st, out, " pulse=", st[WAVE + WV_PULSE]); put_kv(st, out, " sin=", st[WAVE + WV_SIN]); put_kv(st, out, " pwl=", st[WAVE + WV_PWL]); + if (st[N_BSRC] > 0) { put_kv(st, out, " b=", st[N_BSRC]); } puts(st, out, "\nsubckt definitions: "); put_num(st, out, st[NDEF]); put(st, out, B_NL); @@ -1595,6 +1808,537 @@ pub fn has_text(w: usize, out: []const u8, needle: []const u8) -> bool { return false; } +// ---------------------------------------------------------------- the cell (M2b, #10766) + +/// The f64 nearest to the decimal parse_num found: the mantissa times ten to (exponent - EXP_BIAS). It is one +/// multiplication or one division of two exact operands (dec_ok keeps both exact), so it is the very f64 that the +/// literal 1.0e-9 is, and a value read from a deck is the value linear.t27 writes as a literal. +pub fn dec_f64(m: usize, e: usize) -> f64 { + var p : f64 = 1.0; + var x : usize = 0; + if (e >= EXP_BIAS) { x = e - EXP_BIAS; } else { x = EXP_BIAS - e; } + while (x > 0) { + p = p * 10.0; + x = x - 1; + } + if (e < EXP_BIAS) { return (m as f64) / p; } + return (m as f64) * p; +} + +/// Can dec_f64 take this number exactly? A mantissa of at most 2^53 and a power of ten of at most 22. +pub fn dec_ok(m: usize, e: usize) -> bool { + if (m == 0 or m > 9007199254740992) { return false; } + if (e >= EXP_BIAS) { return e - EXP_BIAS <= 22; } + return EXP_BIAS - e <= 22; +} + +/// Read the value (the third word after the name) of the R, C or L card k into st[VM], st[VE] and st[VS] (all zero +/// when the card has none). +pub fn read_val(w: []const u8, st: []usize, k: usize) -> void { + const e : usize = st[CARD0 + k * CARD_SLOTS + C_B]; + const t1 : usize = tk_end(w, tk(w, st[CARD0 + k * CARD_SLOTS + C_A], e), e); + const v : usize = word_at(w, t1, e, 2); + if (v == NONE or !parse_num(w, v, tk_end(w, v, e), st)) { + st[VM] = 0; + st[VE] = EXP_BIAS; + st[VS] = 0; + } +} + +/// The value of the R, C or L card k as an f64. +pub fn card_val(w: []const u8, st: []usize, k: usize) -> f64 { + read_val(w, st, k); + return dec_f64(st[VM], st[VE]); +} + +/// The id the cell gives the node w[a..b]: 0 for ground (the node called 0), NODE_IN for the input node, 1 + its place +/// among the numbered free nodes otherwise, NONE when it is not numbered (yet). The answer is left in st[CELL + CL_HIT] +/// too: a write keeps the state vector a `&mut` in the generated Rust. +pub fn node_of(w: []const u8, st: []usize, a: usize, b: usize) -> usize { + var id : usize = NONE; + if (b - a == 1 and w[a] == B_ZERO) { + id = (NODE_GND as usize); + } else if (st[CELL + CL_SRC] != NONE and same_ci(w, a, b, st[CELL + CL_IN_A], st[CELL + CL_IN_B])) { + id = (NODE_IN as usize); + } else { + var i : usize = 0; + while (i < st[CELL + CL_NODES]) { + if (same_ci(w, a, b, st[CELL + CL_NAME + 2 * i], st[CELL + CL_NAME + 2 * i + 1])) { id = i + 1; } + i = i + 1; + } + } + st[CELL + CL_HIT] = id; + return id; +} + +/// The id of the node w[a..b], numbering it when it is new: the free nodes are numbered 1, 2, ... in the order +/// their names first appear in the R, C and L cards (the first card's first node is 1). NONE, and a refusal of card k, +/// when the table of names is full. +pub fn node_add(w: []const u8, st: []usize, k: usize, a: usize, b: usize) -> usize { + var id : usize = node_of(w, st, a, b); + if (id == NONE) { + const n : usize = st[CELL + CL_NODES]; + if (n >= CL_NMAX) { + reject(st, Y_CELL_SIZE, k, a); + } else { + st[CELL + CL_NAME + 2 * n] = a; + st[CELL + CL_NAME + 2 * n + 1] = b; + st[CELL + CL_NODES] = n + 1; + id = n + 1; + } + } + return id; +} + +/// .ic v(node)=value: a value other than 0 is an initial state the cell does not have (it starts at rest). +pub fn cell_ic(w: []const u8, st: []usize, k: usize, t1: usize, e: usize) -> void { + var q : usize = t1; + while (q < e) { + const t : usize = tk(w, q, e); + if (t >= e) { + q = e; + } else { + const te : usize = tk_end(w, t, e); + if (w[t] == B_EQ) { + const v : usize = tk(w, te, e); + if (v < e and parse_num(w, v, tk_end(w, v, e), st) and st[VM] != 0) { reject(st, Y_CELL_IC, k, v); } + } + q = te; + } + } +} + +/// The key=value pairs after the value of an R, C or L card: m= is not applied by the cell, ic= must be 0. +pub fn cell_keys(w: []const u8, st: []usize, k: usize, p: usize, e: usize) -> void { + var q : usize = p; + while (q < e) { + const t : usize = tk(w, q, e); + if (t >= e) { + q = e; + } else { + const te : usize = tk_end(w, t, e); + const v : usize = tk(w, tk(w, te, e) + 1, e); + q = tk_end(w, v, e); + if (ci_is(w, t, te, "m")) { + reject(st, Y_CELL_VALUE, k, t); + } else if (ci_is(w, t, te, "ic") and v < e and parse_num(w, v, q, st) and st[VM] != 0) { + reject(st, Y_CELL_IC, k, v); + } + } + } +} + +/// A V or B source: the cell's one input. Its minus terminal is ground (0) and its plus terminal is not; the plus +/// node becomes the input node (NODE_IN). A second source, or any other shape, is refused. +pub fn cell_source(w: []const u8, st: []usize, k: usize, t1: usize, e: usize) -> void { + const na : usize = word_at(w, t1, e, 0); + const nb : usize = word_at(w, t1, e, 1); + if (na == NONE or nb == NONE) { + reject(st, Y_CELL_IN, k, NONE); + return; + } + const ea : usize = tk_end(w, na, e); + const eb : usize = tk_end(w, nb, e); + if (eb - nb != 1 or w[nb] != B_ZERO or (ea - na == 1 and w[na] == B_ZERO)) { + reject(st, Y_CELL_IN, k, NONE); + } else if (ci_is(w, na, ea, "gnd")) { + reject(st, Y_CELL_GND, k, na); + } else if (st[CELL + CL_SRC] != NONE) { + reject(st, Y_CELL_IN, k, NONE); + } else { + st[CELL + CL_SRC] = k; + st[CELL + CL_IN_A] = na; + st[CELL + CL_IN_B] = ea; + } +} + +/// Pass 1: every top-level card is R, C, L or the one input source (a V or a time-only B); a .ic must be zero. +/// Anything else the reader accepted (an I source, an E source, an X card) is outside the cell. +pub fn cell_inputs(w: []const u8, st: []usize) -> void { + st[CELL + CL_SRC] = NONE; + st[CELL + CL_L1] = NONE; + st[CELL + CL_C1] = NONE; + st[CELL + CL_DT_C] = NONE; + var k : usize = 0; + while (k < st[SCAN_HI]) { + const e : usize = st[CARD0 + k * CARD_SLOTS + C_B]; + const t0 : usize = tk(w, st[CARD0 + k * CARD_SLOTS + C_A], e); + const t1 : usize = tk_end(w, t0, e); + if (st[CARD0 + k * CARD_SLOTS + C_SCOPE] == NONE) { + if (w[t0] == B_DOT) { + if (ci_is(w, t0, t1, ".ic")) { cell_ic(w, st, k, t1, e); } + } else { + const c : u8 = lower(w[t0]); + if (c == 118 or c == 98) { + cell_source(w, st, k, t1, e); + } else if (c != 114 and c != 99 and c != 108) { + reject(st, Y_CELL_EL, k, NONE); + } + } + } + k = k + 1; + } + if (st[CELL + CL_SRC] == NONE and st[ERR_LINE] == 0) { + var line : usize = 1; + if (st[SCAN_HI] > 0) { line = st[CARD0 + (st[SCAN_HI] - 1) * CARD_SLOTS + C_LINE]; } + reject_at(st, Y_CELL_IN, line, NONE, NONE); + } +} + +/// Pass 2: number the free nodes in the order of first appearance and check each R, C and L card: a positive +/// value the cell can take exactly, two different nodes, neither named gnd, no m=, ic= zero, and no C or L on the +/// input node. Counts the inductors and refuses a sixth unknown (free nodes plus inductors; NMAX are the cell's). +pub fn cell_nodes(w: []const u8, st: []usize) -> void { + var k : usize = 0; + while (k < st[SCAN_HI] and st[ERR_LINE] == 0) { + const e : usize = st[CARD0 + k * CARD_SLOTS + C_B]; + const t0 : usize = tk(w, st[CARD0 + k * CARD_SLOTS + C_A], e); + const t1 : usize = tk_end(w, t0, e); + const c : u8 = lower(w[t0]); + if (st[CARD0 + k * CARD_SLOTS + C_SCOPE] == NONE and w[t0] != B_DOT and (c == 114 or c == 99 or c == 108)) { + const na : usize = word_at(w, t1, e, 0); + const nb : usize = word_at(w, t1, e, 1); + const v : usize = word_at(w, t1, e, 2); + const ea : usize = tk_end(w, na, e); + const eb : usize = tk_end(w, nb, e); + if (!parse_num(w, v, tk_end(w, v, e), st) or st[VS] != 0 or !dec_ok(st[VM], st[VE])) { + reject(st, Y_CELL_VALUE, k, v); + } else if (ci_is(w, na, ea, "gnd")) { + reject(st, Y_CELL_GND, k, na); + } else if (ci_is(w, nb, eb, "gnd")) { + reject(st, Y_CELL_GND, k, nb); + } else { + const ia : usize = node_add(w, st, k, na, ea); + const ib : usize = node_add(w, st, k, nb, eb); + if (ia != NONE and ib != NONE) { + if (ia == ib) { + reject(st, Y_CELL_VALUE, k, na); + } else if (c != 114 and (ia == (NODE_IN as usize) or ib == (NODE_IN as usize))) { + reject(st, Y_CELL_VALUE, k, na); + } + } + cell_keys(w, st, k, tk_end(w, v, e), e); + if (c == 108) { + st[CELL + CL_IND] = st[CELL + CL_IND] + 1; + if (st[CELL + CL_L1] == NONE) { st[CELL + CL_L1] = k; } + } + if (c == 99 and st[CELL + CL_C1] == NONE) { st[CELL + CL_C1] = k; } + if (st[CELL + CL_NODES] + st[CELL + CL_IND] > (NMAX as usize)) { reject(st, Y_CELL_SIZE, k, NONE); } + } + } + k = k + 1; + } + st[CELL + CL_UNK] = st[CELL + CL_NODES] + st[CELL + CL_IND]; + if (st[ERR_LINE] == 0 and st[CELL + CL_UNK] == 0) { + var line : usize = 1; + if (st[SCAN_HI] > 0) { line = st[CARD0 + (st[SCAN_HI] - 1) * CARD_SLOTS + C_LINE]; } + reject_at(st, Y_CELL_SIZE, line, NONE, NONE); + } +} + +/// Pass 3: dt and the integration. The cell needs exactly one analysis, a .tran card (or the control `tran`) with +/// all four numbers: dt is the fourth, tmax, the largest internal step. The deck must also say method=gear +/// maxord=1, which ngspice runs as backward Euler: that is the method the cell folds. +pub fn cell_tran(w: []const u8, st: []usize) -> void { + var n : usize = 0; + var card : usize = NONE; + var k : usize = 0; + while (k < st[SCAN_HI]) { + const e : usize = st[CARD0 + k * CARD_SLOTS + C_B]; + const t0 : usize = tk(w, st[CARD0 + k * CARD_SLOTS + C_A], e); + const t1 : usize = tk_end(w, t0, e); + const scope : usize = st[CARD0 + k * CARD_SLOTS + C_SCOPE]; + if ((scope == NONE and ci_is(w, t0, t1, ".tran")) or (scope == SCOPE_CTL and ci_is(w, t0, t1, "tran"))) { + n = n + 1; + card = k; + } + k = k + 1; + } + if (n == 0) { + var line : usize = 1; + if (st[SCAN_HI] > 0) { line = st[CARD0 + (st[SCAN_HI] - 1) * CARD_SLOTS + C_LINE]; } + reject_at(st, Y_CELL_DT, line, NONE, NONE); + return; + } + if (n > 1) { + reject(st, Y_CELL_DT, card, NONE); + return; + } + const ce : usize = st[CARD0 + card * CARD_SLOTS + C_B]; + var q : usize = tk_end(w, tk(w, st[CARD0 + card * CARD_SLOTS + C_A], ce), ce); + var seen : usize = 0; + while (q < ce) { + const t : usize = tk(w, q, ce); + if (t >= ce) { + q = ce; + } else { + const te : usize = tk_end(w, t, ce); + if (!ci_is(w, t, te, "uic") and parse_num(w, t, te, st)) { + seen = seen + 1; + if (seen == 4) { + if (st[VS] == 0 and dec_ok(st[VM], st[VE])) { + st[CELL + CL_DT_M] = st[VM]; + st[CELL + CL_DT_E] = st[VE]; + } else { + seen = 0; + } + } + } + q = te; + } + } + st[CELL + CL_DT_C] = card; + if (seen < 4) { + reject(st, Y_CELL_DT, card, NONE); + } else if (st[OPT_METHOD] != 1 or st[OPT_MAXORD] != 1) { + reject(st, Y_CELL_METHOD, card, NONE); + } +} + +/// The admittance y between nodes a and b into the columns of the work matrix that start at off: linear.t27's +/// stamp_pair, on node ids, taking and returning the matrix (the generated Rust cannot lend a local array to a function that +/// writes it, and linear.t27's own stamp does exactly that). +pub fn pair_add(w0: [55]f64, off: usize, a: usize, b: usize, y: f64) -> [55]f64 { + var w : [55]f64 = w0; + const fa : bool = is_free(a as u8); + const fb : bool = is_free(b as u8); + if (fa) { + const i : usize = ix(a as u8) * WIDE + off + ix(a as u8); + w[i] = w[i] + y; + } + if (fb) { + const i : usize = ix(b as u8) * WIDE + off + ix(b as u8); + w[i] = w[i] + y; + } + if (fa and fb) { + const i : usize = ix(a as u8) * WIDE + off + ix(b as u8); + const j : usize = ix(b as u8) * WIDE + off + ix(a as u8); + w[i] = w[i] + (0.0 - y); + w[j] = w[j] + (0.0 - y); + } + return w; +} + +/// The driven node's side of an admittance y: its column moves to the right-hand side (column col) as y * u. +pub fn src_add(w0: [55]f64, col: usize, a: usize, b: usize, y: f64) -> [55]f64 { + var w : [55]f64 = w0; + if (is_free(a as u8) and b == (NODE_IN as usize)) { + const i : usize = ix(a as u8) * WIDE + col; + w[i] = w[i] + y; + } + if (is_free(b as u8) and a == (NODE_IN as usize)) { + const i : usize = ix(b as u8) * WIDE + col; + w[i] = w[i] + y; + } + return w; +} + +/// Pass 4: the MNA stamps of the R, C and L cards into [A | C/dt | B], the Gauss-Jordan solve of linear.t27 +/// (eliminate, mn_of), and the words: D = M - I and N rounded by linear.t27's q_sm, into st[CELL + CL_D ...] and +/// st[CELL + CL_NV ...]. The stamps are linear.t27's stamp, rule for rule (R: G, and y u to the right-hand side when +/// one end is the input; C: C/dt in A and in the middle block; L: a branch current stored times zs, KCL +-i/zs at its +/// nodes and the row L/(dt zs) x' - v_a' + v_b' = L/(dt zs) x). zs is sqrt(L1 / C1), L1 the first inductor and C1 the +/// first capacitor of the deck, and 1 when the deck lacks either. +pub fn cell_solve(w: []const u8, st: []usize) -> void { + const nn : usize = st[CELL + CL_NODES]; + const n : usize = st[CELL + CL_UNK]; + const dt : f64 = dec_f64(st[CELL + CL_DT_M], st[CELL + CL_DT_E]); + var zs : f64 = 1.0; + if (st[CELL + CL_L1] != NONE and st[CELL + CL_C1] != NONE) { + const lv : f64 = card_val(w, st, st[CELL + CL_L1]); + const cv : f64 = card_val(w, st, st[CELL + CL_C1]); + zs = @sqrt(lv / cv); + } + var aug : [55]f64 = [0.0] ** 55; + st[CELL + CL_LSEEN] = 0; + var k : usize = 0; + while (k < st[SCAN_HI]) { + const e : usize = st[CARD0 + k * CARD_SLOTS + C_B]; + const t0 : usize = tk(w, st[CARD0 + k * CARD_SLOTS + C_A], e); + const t1 : usize = tk_end(w, t0, e); + const c : u8 = lower(w[t0]); + if (st[CARD0 + k * CARD_SLOTS + C_SCOPE] == NONE and w[t0] != B_DOT and (c == 114 or c == 99 or c == 108)) { + const na : usize = word_at(w, t1, e, 0); + const nb : usize = word_at(w, t1, e, 1); + const ia : usize = node_of(w, st, na, tk_end(w, na, e)); + const ib : usize = node_of(w, st, nb, tk_end(w, nb, e)); + const val : f64 = card_val(w, st, k); + if (c == 114) { + aug = pair_add(aug, 0, ia, ib, 1.0 / val); + aug = src_add(aug, 2 * n, ia, ib, 1.0 / val); + } else if (c == 99) { + aug = pair_add(aug, 0, ia, ib, val / dt); + aug = pair_add(aug, n, ia, ib, val / dt); + } else { + const ki : usize = nn + st[CELL + CL_LSEEN]; + const yl : f64 = val / (dt * zs); + if (is_free(ia as u8)) { + aug[ix(ia as u8) * WIDE + ki] = aug[ix(ia as u8) * WIDE + ki] + 1.0 / zs; + aug[ki * WIDE + ix(ia as u8)] = aug[ki * WIDE + ix(ia as u8)] + (0.0 - 1.0); + } + if (is_free(ib as u8)) { + aug[ix(ib as u8) * WIDE + ki] = aug[ix(ib as u8) * WIDE + ki] + (0.0 - 1.0 / zs); + aug[ki * WIDE + ix(ib as u8)] = aug[ki * WIDE + ix(ib as u8)] + 1.0; + } + aug[ki * WIDE + ki] = aug[ki * WIDE + ki] + yl; + aug[ki * WIDE + n + ki] = aug[ki * WIDE + n + ki] + yl; + if (ia == (NODE_IN as usize)) { aug[ki * WIDE + 2 * n] = aug[ki * WIDE + 2 * n] + 1.0; } + if (ib == (NODE_IN as usize)) { aug[ki * WIDE + 2 * n] = aug[ki * WIDE + 2 * n] + (0.0 - 1.0); } + st[CELL + CL_LSEEN] = st[CELL + CL_LSEEN] + 1; + } + } + k = k + 1; + } + const sol : [55]f64 = eliminate(aug, n); + const mn : [30]f64 = mn_of(sol, n); + var ok : bool = true; + var i : usize = 0; + while (i < n) { + var j : usize = 0; + while (j < n) { + var m : f64 = mn[i * n + j]; + if (i == j) { m = m - 1.0; } + if (fabs64(m) < 127.0) { st[CELL + CL_D + i * n + j] = q_sm(m) as usize; } else { ok = false; } + j = j + 1; + } + if (fabs64(mn[n * n + i]) < 127.0) { st[CELL + CL_NV + i] = q_sm(mn[n * n + i]) as usize; } else { ok = false; } + i = i + 1; + } + if (!ok) { reject(st, Y_CELL_SOLVE, st[CELL + CL_SRC], NONE); } +} + +/// Read a deck as a cell: the reader of the inventory (prepare, mark_end, scan), then the five passes above, each +/// only while no card has been refused. A refusal is the usual st[ERR_LINE] / st[ERR_WHY] / st[ERR_CARD]; an +/// accepted deck leaves the unknowns in st[CELL + CL_UNK], D in st[CELL + CL_D ...] (row-major) and N in +/// st[CELL + CL_NV ...], as sign-magnitude words (bit 31 is the sign). +pub fn cell_build(src: []const u8, w: []u8, st: []usize) -> void { + st[END_LAST] = NONE; + st[CUR] = NONE; + st[CTL_CARD] = NONE; + st[TEMP_CARD] = NONE; + st[DEF_HIT] = NONE; + prepare(src, w, st); + mark_end(w, st); + scan(w, st); + if (st[ERR_LINE] == 0) { cell_inputs(w, st); } + if (st[ERR_LINE] == 0) { cell_nodes(w, st); } + if (st[ERR_LINE] == 0) { cell_tran(w, st); } + if (st[ERR_LINE] == 0) { cell_solve(w, st); } +} + +/// One word of the cell: `NEG | magnitude` for a negative one, the magnitude otherwise. +pub fn put_word(st: []usize, out: []u8, wd: usize) -> void { + if (wd >= 2147483648) { + puts(st, out, "NEG | "); + put_num(st, out, wd - 2147483648); + } else { + put_num(st, out, wd); + } +} + +/// The state list of the emitted block: one line a state, in the order of the words. +pub fn put_states(w: []const u8, st: []usize, out: []u8) -> void { + var i : usize = 0; + while (i < st[CELL + CL_NODES]) { + puts(st, out, "// x"); + put_num(st, out, i); + puts(st, out, " = v("); + put_text(w, st, out, st[CELL + CL_NAME + 2 * i], st[CELL + CL_NAME + 2 * i + 1], SHOW); + puts(st, out, ")\n"); + i = i + 1; + } + var k : usize = 0; + var l : usize = 0; + while (k < st[SCAN_HI]) { + const e : usize = st[CARD0 + k * CARD_SLOTS + C_B]; + const t0 : usize = tk(w, st[CARD0 + k * CARD_SLOTS + C_A], e); + if (st[CARD0 + k * CARD_SLOTS + C_SCOPE] == NONE and w[t0] != B_DOT and lower(w[t0]) == 108) { + puts(st, out, "// x"); + put_num(st, out, st[CELL + CL_NODES] + l); + puts(st, out, " = i("); + put_text(w, st, out, t0, tk_end(w, t0, e), SHOW); + puts(st, out, ") * zs\n"); + l = l + 1; + } + k = k + 1; + } +} + +/// One array of words: `pub const CELL_ : [len]u32 = [ ... ];`, `per` words to a line when there are rows. +pub fn put_words(st: []usize, out: []u8, name: []const u8, base: usize, len: usize, per: usize) -> void { + puts(st, out, "pub const CELL_"); + puts(st, out, name); + puts(st, out, " : ["); + put_num(st, out, len); + puts(st, out, "]u32 = ["); + var i : usize = 0; + while (i < len) { + if (per < len and i % per == 0) { + if (i > 0) { puts(st, out, ","); } + puts(st, out, "\n "); + } else if (i > 0) { + puts(st, out, ", "); + } + put_word(st, out, st[CELL + base + i]); + i = i + 1; + } + if (per < len) { puts(st, out, "\n"); } + puts(st, out, "]"); + if (len == 1 and st[CELL + base] < 2147483648) { puts(st, out, " ** 1"); } + puts(st, out, ";\n"); +} + +/// The t27 block of an accepted cell: a comment saying where the words come from and what each state is, then +/// CELL_N, CELL_D and CELL_NV. The words are the form linear.t27 pins (`NEG | magnitude`, Q.24 sign-magnitude), so +/// the block can sit beside RC_D / RC_NV with `use analog::linear::{NEG};`. +pub fn put_cell(src: []const u8, w: []const u8, st: []usize, out: []u8) -> void { + const n : usize = st[CELL + CL_UNK]; + puts(st, out, "// t27o --emit-cell: "); + put_text(src, st, out, st[TITLE_A], st[TITLE_B], SHOW); + puts(st, out, "\n// the input u is the source on node "); + put_text(w, st, out, st[CELL + CL_IN_A], st[CELL + CL_IN_B], SHOW); + puts(st, out, " (line "); + put_num(st, out, st[CARD0 + st[CELL + CL_SRC] * CARD_SLOTS + C_LINE]); + puts(st, out, "); backward Euler, dt = "); + st[VM] = st[CELL + CL_DT_M]; + st[VE] = st[CELL + CL_DT_E]; + st[VS] = 0; + put_val(st, out); + puts(st, out, " s (the tmax of line "); + put_num(st, out, st[CARD0 + st[CELL + CL_DT_C] * CARD_SLOTS + C_LINE]); + puts(st, out, "); zs = "); + if (st[CELL + CL_L1] != NONE and st[CELL + CL_C1] != NONE) { + puts(st, out, "sqrt("); + read_val(w, st, st[CELL + CL_L1]); + put_val(st, out); + puts(st, out, " / "); + read_val(w, st, st[CELL + CL_C1]); + put_val(st, out); + puts(st, out, ")\n"); + } else { + puts(st, out, "1\n"); + } + puts(st, out, "// the states, in the order of the words:\n"); + put_states(w, st, out); + puts(st, out, "pub const CELL_N : usize = "); + put_num(st, out, n); + puts(st, out, ";\n"); + put_words(st, out, "D", CL_D, n * n, n); + put_words(st, out, "NV", CL_NV, n, n); +} + +/// `tri t27o deck.cir --emit-cell`: read a deck as a cell and write its block, or the refusal (`line N: reason`, the +/// card). `w` and `st` are the caller's, as for inventory. The result is the number of bytes the output needs. +pub fn emit_cell(src: []const u8, w: []u8, out: []u8, st: []usize) -> usize { + cell_build(src, w, st); + if (st[ERR_LINE] != 0) { + put_reject(w, st, out); + } else { + put_cell(src, w, st, out); + } + return st[W]; +} + // ---------------------------------------------------------------- tests // Each test reads a small deck. The buffers are the caller's: a work buffer, an output buffer and a // state vector with room for 300 cards. @@ -1743,7 +2487,7 @@ test a_b_source_is_refused_with_its_line { assert(st[ERR_LINE] == 3); assert(st[ERR_WHY] == Y_B); assert(exit_code(&st) == 1); - assert(out_is(n, &out, 0, "line 3: B source not supported\n card: B1 out 0 v=v(a)*2\n")); + assert(out_is(n, &out, 0, "line 3: B source not supported (only a voltage that is a function of time alone is read)\n card: B1 out 0 v=v(a)*2\n")); } test unsupported_cards_are_refused_and_the_same_deck_without_them_is_accepted { @@ -2046,3 +2790,478 @@ test the_sar_adc_dac_capacitors_bracketed_names_m_up_to_36_and_both_corners { assert(has_text(n, &out, "x18.xm1 mos sg13_lv_nmos m=36\n")); assert(has_text(n, &out, "flattened, weighted by m: mos=72 mim=4\n")); } + +// ---------------------------------------------------------------- the cell: tests (M2b) +// The four decks below are the text of specs/analog/decks/{rc,rlc,lad3,lad5}_fixed.cir without their `*` comment lines +// (the reader drops those); `~` stands for the letter o in the option word: a t27 string literal holding that word would +// splice rust_convert.t27 into this spec (see the header), and deck_text writes the o back. + +pub const DECK_RC : str = "M0 RC low-pass, backward Euler on a fixed 50 ns grid\nB1 in 0 V = u(time-425n) - u(time-5425n)\nR1 in 1 1k\nC1 1 0 1n IC=0\n.options meth~d=gear maxord=1 trtol=1e7\n.tran 50n 10475n 0 50n uic\n.end\n"; +pub const DECK_RLC : str = "M1 series RLC, backward Euler on a fixed 5 ns grid\nB1 in 0 V = u(time-42.5n) - u(time-542.5n)\nR1 in 1 100\nL1 1 2 10u IC=0\nC1 2 0 1n IC=0\n.options meth~d=gear maxord=1 trtol=1e7\n.tran 5n 1047.5n 0 5n uic\n.end\n"; +pub const DECK_LAD3 : str = "M1 3-node RC ladder, backward Euler on a fixed 50 ns grid\nB1 in 0 V = u(time-425n) - u(time-5425n)\nR1 in 1 1k\nR2 1 2 1k\nR3 2 3 1k\nC1 1 0 1n IC=0\nC2 2 0 1n IC=0\nC3 3 0 1n IC=0\n.options meth~d=gear maxord=1 trtol=1e7\n.tran 50n 10475n 0 50n uic\n.end\n"; +pub const DECK_LAD5 : str = "M1 5-node RC ladder, backward Euler on a fixed 50 ns grid\nB1 in 0 V = u(time-425n) - u(time-5425n)\nR1 in 1 1k\nR2 1 2 1k\nR3 2 3 1k\nR4 3 4 1k\nR5 4 5 1k\nC1 1 0 1n IC=0\nC2 2 0 1n IC=0\nC3 3 0 1n IC=0\nC4 4 0 1n IC=0\nC5 5 0 1n IC=0\n.options meth~d=gear maxord=1 trtol=1e7\n.tran 50n 10475n 0 50n uic\n.end\n"; + +/// Copy the deck text `lit` into dst with every ~ written as the letter o; the length is the result. +pub fn deck_text(dst: []u8, lit: []const u8) -> usize { + var i : usize = 0; + while (i < lit.len and i < dst.len) { + if (lit[i] == 126) { dst[i] = 111; } else { dst[i] = lit[i]; } + i = i + 1; + } + return i; +} + +/// parse_num over the whole of s. +pub fn num_of(s: []const u8, st: []usize) -> bool { + return parse_num(s, 0, s.len, st); +} + +/// time_only over the whole of s. +pub fn time_expr(s: []const u8) -> bool { + return time_only(s, 0, s.len); +} + +/// How many of the cell's words in st differ from the n * n words d and the n words nv (the ones linear.t27 pins). +pub fn cell_diff(st: []usize, n: usize, d: []const u32, nv: []const u32) -> usize { + var miss : usize = 0; + var i : usize = 0; + while (i < n * n) { + if ((st[CELL + CL_D + i] as u32) != d[i]) { miss = miss + 1; } + i = i + 1; + } + i = 0; + while (i < n) { + if ((st[CELL + CL_NV + i] as u32) != nv[i]) { miss = miss + 1; } + i = i + 1; + } + return miss; +} + +test the_rc_deck_folds_to_the_words_linear_pins { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + const len : usize = deck_text(&src, DECK_RC); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 0); + assert_eq(st[CELL + CL_UNK], RC_N); + assert_eq(st[CELL + CL_NODES], 1); + assert_eq(st[CELL + CL_IND], 0); + assert_eq(cell_diff(&st, RC_N, &RC_D, &RC_NV), 0); + assert((st[CELL + CL_D] as u32) == (NEG | 798915)); + assert((st[CELL + CL_NV] as u32) == 798915); +} + +test the_rlc_deck_folds_to_the_words_linear_pins { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + const len : usize = deck_text(&src, DECK_RLC); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 0); + assert_eq(st[CELL + CL_UNK], RLC_N); + assert_eq(st[CELL + CL_NODES], 2); + assert_eq(st[CELL + CL_IND], 1); + assert_eq(cell_diff(&st, RLC_N, &RLC_D, &RLC_NV), 0); + // the node between R and L has no capacitor: its D_aa is exactly -1, and the inductor state is i * 100 Ohm + assert((st[CELL + CL_D] as u32) == (NEG | 16777216)); +} + +test the_ladder3_deck_folds_to_the_words_linear_pins { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + const len : usize = deck_text(&src, DECK_LAD3); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 0); + assert_eq(st[CELL + CL_UNK], LAD3_N); + assert_eq(cell_diff(&st, LAD3_N, &LAD3_D, &LAD3_NV), 0); +} + +test the_ladder5_deck_folds_to_the_words_linear_pins { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + const len : usize = deck_text(&src, DECK_LAD5); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 0); + assert_eq(st[CELL + CL_UNK], LAD5_N); + assert_eq(cell_diff(&st, LAD5_N, &LAD5_D, &LAD5_NV), 0); +} + +test node_names_do_not_change_the_words_and_case_is_ignored { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, "rc renamed\nB1 VIN 0 V = u(time)\nR1 vin out 1k\nC1 OUT 0 1n IC=0\n.options meth~d=gear maxord=1 trtol=1e7\n.tran 50n 10475n 0 50n uic\n.end\n"); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 0); + assert_eq(cell_diff(&st, RC_N, &RC_D, &RC_NV), 0); + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "ladder renamed\nB1 drive 0 V = u(time)\nR1 drive a 1k\nR2 a b 1k\nR3 b c 1k\nC1 a 0 1n\nC2 b 0 1n\nC3 c 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 10475n 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st2); + assert(st2[ERR_LINE] == 0); + assert_eq(cell_diff(&st2, LAD3_N, &LAD3_D, &LAD3_NV), 0); +} + +test the_states_are_the_free_nodes_in_order_of_first_appearance_then_the_inductors { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var out : [2048]u8 = undefined; + // the ladder with its capacitors first: node 3 is met first, then 2, then 1: the states run backwards + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, "backwards\nB1 in 0 V = u(time)\nC3 3 0 1n\nC2 2 0 1n\nC1 1 0 1n\nR1 in 1 1k\nR2 1 2 1k\nR3 2 3 1k\n.options meth~d=gear maxord=1\n.tran 50n 10475n 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 0); + var i : usize = 0; + var j : usize = 0; + var miss : usize = 0; + while (i < 3) { + j = 0; + while (j < 3) { + if ((st[CELL + CL_D + i * 3 + j] as u32) != LAD3_D[(2 - i) * 3 + (2 - j)]) { miss = miss + 1; } + j = j + 1; + } + if ((st[CELL + CL_NV + i] as u32) != LAD3_NV[2 - i]) { miss = miss + 1; } + i = i + 1; + } + assert_eq(miss, 0); + // the series RLC with its capacitor first: the capacitor's node is x0, the R-L node x1, the inductor is last + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "rlc, capacitor first\nB1 in 0 V = u(time)\nC1 2 0 1n\nR1 in 1 100\nL1 1 2 10u\n.options meth~d=gear maxord=1\n.tran 5n 1047.5n 0 5n\n.end\n"); + cell_build(src[0 .. len], &w, &st2); + assert(st2[ERR_LINE] == 0); + const p : [3]usize = [1, 0, 2]; + i = 0; + miss = 0; + while (i < 3) { + j = 0; + while (j < 3) { + if ((st2[CELL + CL_D + i * 3 + j] as u32) != RLC_D[p[i] * 3 + p[j]]) { miss = miss + 1; } + j = j + 1; + } + if ((st2[CELL + CL_NV + i] as u32) != RLC_NV[p[i]]) { miss = miss + 1; } + i = i + 1; + } + assert_eq(miss, 0); + var st3 : [3000]usize = [0] ** 3000; + const n : usize = emit_cell(src[0 .. len], &w, &out, &st3); + assert(has_text(n, &out, "// x0 = v(2)\n// x1 = v(1)\n// x2 = i(L1) * zs\n")); +} + +test one_resistor_changed_gives_different_words { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + // the RC deck with R1 = 1.1 kOhm: g = dt / (R C + dt) = 5e-8 / 1.15e-6 = 1/23 -> 729444 at 2^-24, not 1/21 -> 798915 + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, "rc, 1.1k\nB1 in 0 V = u(time-425n) - u(time-5425n)\nR1 in 1 1.1k\nC1 1 0 1n IC=0\n.options meth~d=gear maxord=1 trtol=1e7\n.tran 50n 10475n 0 50n uic\n.end\n"); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 0); + assert(cell_diff(&st, RC_N, &RC_D, &RC_NV) == 2); + assert((st[CELL + CL_D] as u32) == (NEG | 729444)); + assert((st[CELL + CL_NV] as u32) == 729444); + // the 3-node ladder with R2 doubled: every word that touches node 2 moves + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "ladder, R2 = 2k\nB1 in 0 V = u(time)\nR1 in 1 1k\nR2 1 2 2k\nR3 2 3 1k\nC1 1 0 1n\nC2 2 0 1n\nC3 3 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 10475n 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st2); + assert(st2[ERR_LINE] == 0); + assert(cell_diff(&st2, LAD3_N, &LAD3_D, &LAD3_NV) > 0); + assert((st2[CELL + CL_D + 4] as u32) != LAD3_D[4]); +} + +test a_mos_line_is_refused_naming_the_line { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var out : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, "mos\nB1 in 0 V = u(time)\nR1 in 1 1k\nM1 1 in 0 0 nmos w=1u l=1u\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + const n : usize = emit_cell(src[0 .. len], &w, &out, &st); + assert(st[ERR_LINE] == 4); + assert(st[ERR_WHY] == Y_ELEMENT); + assert(exit_code(&st) == 1); + assert(out_is(n, &out, 0, "line 4: M element not supported\n card: M1 1 in 0 0 nmos w=1u l=1u\n")); + // a PDK device is a subcircuit instance to the cell: refused with its line too + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "pdk\nB1 in 0 V = u(time)\nR1 in 1 1k\nXM1 1 in 0 0 sg13_lv_nmos w=1u l=130n\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + const n2 : usize = emit_cell(src[0 .. len], &w, &out, &st2); + assert(st2[ERR_LINE] == 4); + assert(st2[ERR_WHY] == Y_CELL_EL); + assert(has_text(n2, &out, "line 4: not in the M1 cell (a flat deck of R, C, L and one input source)\n card: XM1 1 in 0 0 sg13_lv_nmos w=1u l=130n\n")); + // and so is a current source, which the cell has no row for + var st3 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "isrc\nB1 in 0 V = u(time)\nR1 in 1 1k\nI1 1 0 DC 1m\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st3); + assert(st3[ERR_LINE] == 4 and st3[ERR_WHY] == Y_CELL_EL); +} + +test a_b_source_is_an_input_only_when_it_is_a_function_of_time_alone { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var out : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, "bsrc\nB1 in 0 V = v(1) * 2\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + const n : usize = emit_cell(src[0 .. len], &w, &out, &st); + assert(st[ERR_LINE] == 2 and st[ERR_WHY] == Y_B); + assert(has_text(n, &out, "line 2: B source not supported (only a voltage that is a function of time alone is read)\n card: B1 in 0 V = v(1) * 2\n")); + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "bcur\nB1 in 0 I = u(time)\nR1 in 1 1k\n.end\n"); + cell_build(src[0 .. len], &w, &st2); + assert(st2[ERR_LINE] == 2 and st2[ERR_WHY] == Y_B); + assert(time_expr("u(time-425n) - u(time-5425n)")); + assert(time_expr("2 * sin(6.28e3 * time) + 1")); + assert(time_expr("uramp(time-1u)/1u")); + assert(time_expr("1")); + assert(!time_expr("v(a)*2")); + assert(!time_expr("i(v1)")); + assert(!time_expr("u(time) + vin")); + assert(!time_expr("@r1[i]")); + assert(!time_expr("time > 1u")); + assert(!time_expr("2v(1)")); + assert(!time_expr("")); +} + +test the_input_source_is_one_v_or_b_from_a_node_to_ground { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, "two\nB1 in 0 V = u(time)\nV2 x 0 1\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 3 and st[ERR_WHY] == Y_CELL_IN); + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "floating\nV1 a b 1\nR1 a 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st2); + assert(st2[ERR_LINE] == 2 and st2[ERR_WHY] == Y_CELL_IN); + var st3 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "upside down\nV1 0 a 1\nR1 a 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st3); + assert(st3[ERR_LINE] == 2 and st3[ERR_WHY] == Y_CELL_IN); + var st4 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "none\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st4); + assert(st4[ERR_LINE] == 5 and st4[ERR_WHY] == Y_CELL_IN and st4[ERR_CARD] == NONE); + // a V source with a waveform is the same input: only the column of B matters, not what u does + var st5 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "pulse\nV1 in 0 PULSE(0 1 0 1n 1n 5u 10u)\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st5); + assert(st5[ERR_LINE] == 0); + assert_eq(cell_diff(&st5, RC_N, &RC_D, &RC_NV), 0); +} + +test values_nodes_and_keys_the_cell_cannot_take_are_refused { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var out : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, "v0\nB1 in 0 V = u(time)\nR1 in 1 0\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + const n : usize = emit_cell(src[0 .. len], &w, &out, &st); + assert(st[ERR_LINE] == 3 and st[ERR_WHY] == Y_CELL_VALUE); + assert(has_text(n, &out, "line 3: R, C and L need a positive value (a mantissa up to 2^53, a power of ten within 22), two different nodes and no m=, and C and L may not touch the input node: 0\n card: R1 in 1 0\n")); + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "neg\nB1 in 0 V = u(time)\nR1 in 1 -1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st2); + assert(st2[ERR_LINE] == 3 and st2[ERR_WHY] == Y_CELL_VALUE); + var st3 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "loop\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 1 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st3); + assert(st3[ERR_LINE] == 4 and st3[ERR_WHY] == Y_CELL_VALUE); + var st4 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "cap on the input\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 in 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st4); + assert(st4[ERR_LINE] == 4 and st4[ERR_WHY] == Y_CELL_VALUE); + var st5 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "multiplier\nB1 in 0 V = u(time)\nR1 in 1 1k m=2\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st5); + assert(st5[ERR_LINE] == 3 and st5[ERR_WHY] == Y_CELL_VALUE); + var st6 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "gnd\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 gnd 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st6); + assert(st6[ERR_LINE] == 4 and st6[ERR_WHY] == Y_CELL_GND); + var st7 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "ic\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n IC=0.5\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st7); + assert(st7[ERR_LINE] == 4 and st7[ERR_WHY] == Y_CELL_IC); + var st8 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "dot ic\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n\n.ic v(1)=0.5\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st8); + assert(st8[ERR_LINE] == 5 and st8[ERR_WHY] == Y_CELL_IC); + var st9 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "zero ic is the cell's own start\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n IC=0\n.ic v(1)=0\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st9); + assert(st9[ERR_LINE] == 0); +} + +test size_step_and_integration_are_checked { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, "six nodes\nB1 in 0 V = u(time)\nR1 in 1 1k\nR2 1 2 1k\nR3 2 3 1k\nR4 3 4 1k\nR5 4 5 1k\nR6 5 6 1k\nC1 6 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 8 and st[ERR_WHY] == Y_CELL_SIZE); + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "five nodes and an inductor\nB1 in 0 V = u(time)\nR1 in 1 1k\nR2 1 2 1k\nR3 2 3 1k\nR4 3 4 1k\nR5 4 5 1k\nC1 5 0 1n\nL1 5 0 1u\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st2); + assert(st2[ERR_LINE] == 9 and st2[ERR_WHY] == Y_CELL_SIZE); + var st0 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "a source and nothing to drive\nB1 in 0 V = u(time)\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st0); + assert(st0[ERR_LINE] == 4 and st0[ERR_WHY] == Y_CELL_SIZE and st0[ERR_CARD] == NONE); + var st3 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "no analysis\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.end\n"); + cell_build(src[0 .. len], &w, &st3); + assert(st3[ERR_LINE] == 5 and st3[ERR_WHY] == Y_CELL_DT); + var st4 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "two numbers\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u\n.end\n"); + cell_build(src[0 .. len], &w, &st4); + assert(st4[ERR_LINE] == 6 and st4[ERR_WHY] == Y_CELL_DT); + var st5 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "two analyses\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.tran 50n 2u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st5); + assert(st5[ERR_LINE] == 7 and st5[ERR_WHY] == Y_CELL_DT); + var st6 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "default integration\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st6); + assert(st6[ERR_LINE] == 5 and st6[ERR_WHY] == Y_CELL_METHOD); + var st7 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "second order\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=2\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st7); + assert(st7[ERR_LINE] == 6 and st7[ERR_WHY] == Y_CELL_METHOD); + var st8 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "trapezoidal\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=trap\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st8); + assert(st8[ERR_LINE] == 6 and st8[ERR_WHY] == Y_CELL_METHOD); + // the control block's tran is the analysis too + var st9 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "control tran\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.control\ntran 50n 1u 0 50n\n.endc\n.end\n"); + cell_build(src[0 .. len], &w, &st9); + assert(st9[ERR_LINE] == 0); + assert_eq(cell_diff(&st9, RC_N, &RC_D, &RC_NV), 0); +} + +test dt_is_the_fourth_number_of_tran_not_the_first { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + // tstep 10n only prints finer; tmax 50n is the step: the words are the RC deck's + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, "tstep is not dt\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 10n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 0); + assert_eq(cell_diff(&st, RC_N, &RC_D, &RC_NV), 0); + // tmax 100n doubles the step: g = dt / (R C + dt) = 1/11 -> 1525201 at 2^-24 + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "tmax doubles dt\nB1 in 0 V = u(time)\nR1 in 1 1k\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 100n\n.end\n"); + cell_build(src[0 .. len], &w, &st2); + assert(st2[ERR_LINE] == 0); + assert((st2[CELL + CL_D] as u32) == (NEG | 1525201)); + assert((st2[CELL + CL_NV] as u32) == 1525201); +} + +test a_floating_pair_has_no_solution_and_a_scanner_refusal_comes_first { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, "floating\nB1 in 0 V = u(time)\nR1 in 3 1k\nC1 3 0 1n\nR2 1 2 1k\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st); + assert(st[ERR_LINE] == 2 and st[ERR_WHY] == Y_CELL_SOLVE); + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "scanner\nB1 in 0 V = u(time)\nR1 in 1 abc\nC1 1 0 1n\n.options meth~d=gear maxord=1\n.tran 50n 1u 0 50n\n.end\n"); + cell_build(src[0 .. len], &w, &st2); + assert(st2[ERR_LINE] == 3 and st2[ERR_WHY] == Y_NUMBER); +} + +test the_emitted_block_is_the_words_as_a_t27_constant_block { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var out : [4096]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, DECK_RC); + var n : usize = emit_cell(src[0 .. len], &w, &out, &st); + assert(exit_code(&st) == 0); + assert(out_is(n, &out, 0, "// t27o --emit-cell: M0 RC low-pass, backward Euler on a fixed 50 ns grid\n// the input u is the source on node in (line 2); backward Euler, dt = 5e-8 s (the tmax of line 6); zs = 1\n// the states, in the order of the words:\n// x0 = v(1)\npub const CELL_N : usize = 1;\npub const CELL_D : [1]u32 = [NEG | 798915];\npub const CELL_NV : [1]u32 = [798915] ** 1;\n")); + var st2 : [3000]usize = [0] ** 3000; + len = deck_text(&src, DECK_RLC); + n = emit_cell(src[0 .. len], &w, &out, &st2); + assert(out_is(n, &out, 0, "// t27o --emit-cell: M1 series RLC, backward Euler on a fixed 5 ns grid\n// the input u is the source on node in (line 2); backward Euler, dt = 5e-9 s (the tmax of line 7); zs = sqrt(1e-5 / 1e-9)\n// the states, in the order of the words:\n// x0 = v(1)\n// x1 = v(2)\n// x2 = i(L1) * zs\npub const CELL_N : usize = 3;\npub const CELL_D : [9]u32 = [\n NEG | 16777216, 797017, NEG | 15940348,\n 0, NEG | 39851, 797017,\n 0, NEG | 797017, NEG | 836868\n];\npub const CELL_NV : [3]u32 = [15980199, 39851, 797017];\n")); + // an inductor without a capacitor: zs is 1 + var st3 : [3000]usize = [0] ** 3000; + len = deck_text(&src, "RL\nV1 in 0 1\nR1 in 1 100\nL1 1 0 10u\n.options meth~d=gear maxord=1\n.tran 5n 1u 0 5n\n.end\n"); + n = emit_cell(src[0 .. len], &w, &out, &st3); + assert(exit_code(&st3) == 0); + assert_eq(st3[CELL + CL_UNK], 2); + assert(has_text(n, &out, "zs = 1\n")); + assert(has_text(n, &out, "// x1 = i(L1) * zs\n")); +} + +test values_read_from_a_deck_are_the_literals_of_linear { + var st : [64]usize = [0] ** 64; + assert(num_of("1k", &st)); + assert(dec_f64(st[VM], st[VE]) == 1000.0); + assert(num_of("100", &st)); + assert(dec_f64(st[VM], st[VE]) == 100.0); + assert(num_of("1n", &st)); + assert(dec_f64(st[VM], st[VE]) == 1.0e-9); + assert(num_of("5n", &st)); + assert(dec_f64(st[VM], st[VE]) == 5.0e-9); + assert(num_of("50n", &st)); + assert(dec_f64(st[VM], st[VE]) == 5.0e-8); + assert(num_of("10u", &st)); + assert(dec_f64(st[VM], st[VE]) == 1.0e-5); + assert(num_of("2.2u", &st)); + assert(dec_f64(st[VM], st[VE]) == 2.2e-6); + assert(num_of("0.13u", &st)); + assert(dec_f64(st[VM], st[VE]) == 1.3e-7); + assert(num_of("1.1k", &st)); + assert(dec_f64(st[VM], st[VE]) == 1100.0); + assert(num_of("47pF", &st)); + assert(dec_f64(st[VM], st[VE]) == 4.7e-11); + assert(dec_ok(1, EXP_BIAS - 22)); + assert(!dec_ok(1, EXP_BIAS - 23)); + assert(dec_ok(1, EXP_BIAS + 22)); + assert(!dec_ok(1, EXP_BIAS + 23)); + assert(dec_ok(9007199254740992, EXP_BIAS)); + assert(!dec_ok(9007199254740993, EXP_BIAS)); + assert(!dec_ok(0, EXP_BIAS)); +} + +test b_sources_ic_and_the_integration_options_are_read_by_the_inventory_too { + var src : [1024]u8 = undefined; + var w : [2048]u8 = undefined; + var out : [4096]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var len : usize = deck_text(&src, DECK_RLC); + var n : usize = inventory(src[0 .. len], &w, &out, &st); + assert(st[ERR_LINE] == 0); + assert(st[N_BSRC] == 1); + assert(st[SRC + K_V] == 1); + assert(st[SRC + K_L] == 1); + assert(st[OPT_METHOD] == 1); + assert(st[OPT_MAXORD] == 1); + assert(st[N_OPT] == 1); + assert(st[N_TRAN] == 1); + assert(has_text(n, &out, "sources: dc=0 pulse=0 sin=0 pwl=0 b=1\n")); + assert(has_text(n, &out, "tran (.tran card) 5n 1047.5n: tstep 5e-9 tstop 10475e-10 (line 7)\n")); + assert(has_text(n, &out, "flattened: instances=4 r=1 c=1 l=1 v=1 i=0 e=0 mos=0 mim=0 levels=0 depth=0\n")); + assert(has_text(n, &out, "instances:\nb1 b\nr1 r\nl1 l\nc1 c\n")); + // ic= belongs to C and L only; the other integration choices are read, and an unknown option is still refused + var a : [3000]usize = [0] ** 3000; + len = deck_text(&src, "ic\nR1 a b 1k ic=0\n.end\n"); + n = inventory(src[0 .. len], &w, &out, &a); + assert(n > 0); + assert(a[ERR_LINE] == 2 and a[ERR_WHY] == Y_PARAM); + var b : [3000]usize = [0] ** 3000; + len = deck_text(&src, "opts\n.options meth~d=trap maxord=2 trtol=7\n.end\n"); + n = inventory(src[0 .. len], &w, &out, &b); + assert(n > 0); + assert(b[ERR_LINE] == 0); + assert(b[OPT_METHOD] == 2); + assert(b[OPT_MAXORD] == 2); + var c : [3000]usize = [0] ** 3000; + len = deck_text(&src, "opts\n.options meth~d=euler\n.end\n"); + n = inventory(src[0 .. len], &w, &out, &c); + assert(n > 0); + assert(c[ERR_LINE] == 2 and c[ERR_WHY] == Y_OPTION); + var d : [3000]usize = [0] ** 3000; + len = deck_text(&src, "opts\n.options meth~d=\n.end\n"); + n = inventory(src[0 .. len], &w, &out, &d); + assert(n > 0); + assert(d[ERR_LINE] == 2 and d[ERR_WHY] == Y_FIELDS); +}