From 379843c06ac9a2df9eb6e41b5985a06b16d745cc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 11:38:05 +0700 Subject: [PATCH] analog M4d: the SAR ADC logic and asynchronous clock -- gate level from the netlist, against ngspice PSP103 edges (Closes #10754) specs/tools/t27o/spice_convert.t27 (M2a/M2b's one SPICE front end, extended): emit_gates reads the hierarchy of a named subcircuit down to its gates (inverter, nand, nor, nor3, tgate, delay_cell) and lists each with the nets on its pins, resolved through the port maps to the root's net names (ports first, internal nets as ., 0 is ground). 5 tests, incl. refusals with their line. gen/rust regenerated, compiles with rustc. specs/analog/sar_logic.t27: a gate / switch-level event model of SAR_logic + clock_gen built from that table (113 gates, 100 nets): inertial delays d0 + k * fanout per arc, a tgate as a switch with a stored node and drive strengths, the delay cell tabulated per trim code. 12 tests: CK11 .. CK1 rise once each in ngspice's order, within 17 % / 92 ps of its Valid-to-CK delays; the 8 trim codes within 4.3 % of the clks-to-clkc delays; the negative control (one dff with Set and Reset swapped) fails the order check and shows CK7 and CK6 as ngspice does. Reference decks (specs/analog/decks/sar_gate_arcs.cir, sar_delay_cell.cir, sar_logic_ref.cir, sar_logic_swap.cir): IHP PSP103 tt at 50 C (the golden deck's temperature) instead of LEVEL=1, the golden subcircuits read at run time through IHP_PDK, IHP_OSDI and SAR_GOLDEN_SUBCKTS. Gate level for the logic; the transistors are only in the decks. The repeated clkc falling edges are 83 to 127 % late in the model (the delay cell is history dependent) and say so in the header. No speed claim. tri t27o --emit-gates is a follow-up (cmd_t27o is being edited by #10765). Co-Authored-By: Claude Sonnet 5.5 --- .trinity/seals/analog_AnalogSarLogic.json | 33 + .trinity/seals/t27o_T27oSpice.json | 18 +- gen/rust/tools/t27o/spice_convert.rs | 537 ++++++++- specs/analog/decks/sar_delay_cell.cir | 121 ++ specs/analog/decks/sar_gate_arcs.cir | 189 ++++ specs/analog/decks/sar_logic_ref.cir | 96 ++ specs/analog/decks/sar_logic_swap.cir | 56 + specs/analog/sar_logic.t27 | 1229 +++++++++++++++++++++ specs/tools/t27o/spice_convert.t27 | 612 +++++++++- 9 files changed, 2879 insertions(+), 12 deletions(-) create mode 100644 .trinity/seals/analog_AnalogSarLogic.json create mode 100644 specs/analog/decks/sar_delay_cell.cir create mode 100644 specs/analog/decks/sar_gate_arcs.cir create mode 100644 specs/analog/decks/sar_logic_ref.cir create mode 100644 specs/analog/decks/sar_logic_swap.cir create mode 100644 specs/analog/sar_logic.t27 diff --git a/.trinity/seals/analog_AnalogSarLogic.json b/.trinity/seals/analog_AnalogSarLogic.json new file mode 100644 index 0000000000..a51ccd3f5c --- /dev/null +++ b/.trinity/seals/analog_AnalogSarLogic.json @@ -0,0 +1,33 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+ea35d9b72", + "closure": [ + { + "spec": "specs/tools/t27o/spice_convert.t27", + "spec_hash": "sha256:1a11784a07eb726b483971ecf12046b3f97cc51b365d7b694e2557bd806a90f9" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:4c6e5eb8b9dcad8cd7f0baa5fa69623cf901104be289d236af89bf01338d85c4", + "gen_hash_rust": "sha256:3ccf030691186395e647b86349eec96e00220eda2c501145b6fc4b5cbab319b2", + "gen_hash_verilog": "sha256:eb217c9f971878de543a4e8e92bf159c3e5e00550854092b12ad0d1fe57339a7", + "gen_hash_zig": "sha256:489a6b9840a98f636d622dc562c25ec27bd73b9a1cbe0d163cb8f49340b89966", + "module": "AnalogSarLogic", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-19T04:37:36Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:ee58c543c573cc66fbafa5a3d28bd647c71aea6136a75f787b88bc7e8ddbade5", + "spec_path": "specs/analog/sar_logic.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 12, + "total": 12, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27o_T27oSpice.json b/.trinity/seals/t27o_T27oSpice.json index 0e279c6ac2..bbd2742d44 100644 --- a/.trinity/seals/t27o_T27oSpice.json +++ b/.trinity/seals/t27o_T27oSpice.json @@ -7,27 +7,27 @@ }, { "spec": "specs/tools/t27o/rust_convert.t27", - "spec_hash": "sha256:7960832212d5781ccfec0c213b5829caebf2c13ab27e01fc06b8c5b0a1ec60de" + "spec_hash": "sha256:11f3aefa0a152d9ca963773018e35b1e39730ac53b25604be67252aaf85522a3" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:7297ba8aab948b38842ae2d27fd8f27b8618622d5598a40a5fc4e2f0fb439e4a", - "gen_hash_rust": "sha256:e2b6ca2bec4889e964c7dd10b7d5a904fcfedabe58e248d798e2daf9b1f8a800", - "gen_hash_verilog": "sha256:de18b898c7dbe583e56b8ad8c892f70f7694b78caf3fcb33091901667ea8bcf6", - "gen_hash_zig": "sha256:eb812466ae4b331738ed7812c2913ce1e52ec82760feab8b32a41ad6d21a436c", + "gen_hash_c": "sha256:6dbacbeb561d807690817ee5ceebbc438e2ab8f254c77b540d8d3cb040dc1fda", + "gen_hash_rust": "sha256:f34adf3604f39109b8871f528cea5da4c822269f84b13bf3cdd0cc8f5b62b67b", + "gen_hash_verilog": "sha256:4d99c657ec249fd55ffc6e896b96ecc971b43b8cefb8e6ad4311967097234523", + "gen_hash_zig": "sha256:222e1b962dd1cf3ffeaa9e47515801e0ccd47002e257dd9d2198c1c305e04ab9", "module": "T27oSpice", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T02:17:13Z", + "sealed_at": "2026-10-19T04:37:32Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:f1fd54e59d731af677e0c7301135092ce6b2db03b15178e63421a02b09a061b3", + "spec_hash": "sha256:1a11784a07eb726b483971ecf12046b3f97cc51b365d7b694e2557bd806a90f9", "spec_path": "specs/tools/t27o/spice_convert.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 39, - "total": 39, + "passed": 44, + "total": 44, "vacuous": 0 }, "toolchain": { diff --git a/gen/rust/tools/t27o/spice_convert.rs b/gen/rust/tools/t27o/spice_convert.rs index 67e0f1bb9b..c315677331 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: 39 test(s), 0 invariant(s), 0 function(s) with no body. +// NOT LOWERED BY THIS BACKEND: 44 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. @@ -112,6 +112,8 @@ pub const OPT_METHOD: usize = 69; pub const OPT_MAXORD: usize = 70; +pub const GATE_HIT: usize = 71; + pub const FIXED: usize = 80; pub const CELL: usize = 80; @@ -304,6 +306,14 @@ pub const Y_CELL_SOLVE: usize = 32; pub const Y_CELL_GND: usize = 33; +pub const Y_GATE_TOP: usize = 34; + +pub const Y_GATE_LEAF: usize = 35; + +pub const Y_GATE_PINS: usize = 36; + +pub const Y_GATE_M: usize = 37; + pub const B_TAB: u8 = 9; pub const B_NL: u8 = 10; @@ -1882,6 +1892,18 @@ pub fn put_why(w: &[u8], st: &mut [usize], out: &mut [u8]) -> () { if (why == Y_CELL_GND) { puts(st, out, b"a node called gnd is not read as ground: write 0"); } + if (why == Y_GATE_TOP) { + puts(st, out, b"the subcircuit to read as gates is not defined in the deck"); + } + if (why == Y_GATE_LEAF) { + puts(st, out, b"not gate level: an element that is not an instance of a gate or of a subcircuit"); + } + if (why == Y_GATE_PINS) { + puts(st, out, b"a gate subcircuit lacks a pin its kind needs"); + } + if (why == Y_GATE_M) { + puts(st, out, b"m on a gate or on a level is not read in a gate level netlist (write 1 or none)"); + } if ((tok != NONE) && (k != NONE)) { puts(st, out, b": "); put_tok(w, st, out, tok, st[(((CARD0 + (k * CARD_SLOTS)) + C_B)) as usize]); @@ -2682,6 +2704,508 @@ pub fn emit_cell(src: &[u8], w: &mut [u8], out: &mut [u8], st: &mut [usize]) -> return st[(W) as usize]; } +pub const G_INV: usize = 0; + +pub const G_NAND: usize = 1; + +pub const G_NOR: usize = 2; + +pub const G_NOR3: usize = 3; + +pub const G_TGATE: usize = 4; + +pub const G_DELAY: usize = 5; + +pub const G_KINDS: usize = 6; + +pub const G_PINS: usize = 7; + +pub const GATE_SLOTS: usize = 10; + +pub const GT_SLOTS: usize = 3200; + +pub const GT_NGATE: usize = 0; + +pub const GT_NNET: usize = 1; + +pub const GT_POOL: usize = 2; + +pub const GT_LAST: usize = 3; + +pub const GT_PER_KIND: usize = 4; + +pub const GT_STK: usize = 16; + +pub const GT_NET0: usize = 128; + +pub const GT_MAXNET: usize = 256; + +pub const GT_GATE0: usize = 640; + +pub const GT_MAXGATE: usize = 256; + +pub const GT_PATH: usize = 480; + +pub const GT_NAME: usize = 640; + +pub fn pin_count(kind: usize) -> usize { + if (kind == G_INV) { + return 4; + } + if (kind == G_NAND) { + return 5; + } + if (kind == G_NOR) { + return 5; + } + if (kind == G_NOR3) { + return 6; + } + if (kind == G_TGATE) { + return 6; + } + return 7; +} + +pub fn pin_word(w: &[u8], a: usize, b: usize, names: &[u8], j: usize) -> bool { + let mut s: usize = 0; + let mut n: usize = 0; + while ((n < j) && (s < names.len())) { + while ((s < names.len()) && (names[(s) as usize] != B_SPACE)) { + s = (s + 1); + } + s = (s + 1); + n = (n + 1); + } + let mut e: usize = s; + while ((e < names.len()) && (names[(e) as usize] != B_SPACE)) { + e = (e + 1); + } + if (((s >= names.len()) || (b < a)) || ((e - s) != (b - a))) { + return false; + } + let mut i: usize = 0; + while (i < (e - s)) { + if (lower(w[((a + i)) as usize]) != names[((s + i)) as usize]) { + return false; + } + i = (i + 1); + } + return true; +} + +pub fn pin_matches(kind: usize, j: usize, w: &[u8], a: usize, b: usize) -> bool { + if (kind == G_INV) { + return pin_word(w, a, b, b"vin vout vdd vss", j); + } + if (kind == G_NAND) { + return pin_word(w, a, b, b"a b vout vdd vss", j); + } + if (kind == G_NOR) { + return pin_word(w, a, b, b"a b vout vdd vss", j); + } + if (kind == G_NOR3) { + return pin_word(w, a, b, b"a b c vout vdd vss", j); + } + if (kind == G_TGATE) { + return pin_word(w, a, b, b"clk nclk in out vdd vss", j); + } + return pin_word(w, a, b, b"in a b c out vdd vss", j); +} + +pub fn gate_kind(w: &[u8], st: &mut [usize], d: usize) -> usize { + let a: usize = st[(((DEF0 + (d * DEF_SLOTS)) + D_NAME_A)) as usize]; + let b: usize = st[(((DEF0 + (d * DEF_SLOTS)) + D_NAME_B)) as usize]; + let mut kind: usize = NONE; + if ci_is(w, a, b, b"inverter") { + kind = G_INV; + } + if ci_is(w, a, b, b"nand") { + kind = G_NAND; + } + if ci_is(w, a, b, b"nor3") { + kind = G_NOR3; + } + if ci_is(w, a, b, b"nor") { + kind = G_NOR; + } + if ci_is(w, a, b, b"tgate") { + kind = G_TGATE; + } + if ci_is(w, a, b, b"delay_cell") { + kind = G_DELAY; + } + st[(GATE_HIT) as usize] = kind; + return kind; +} + +pub fn top_is(w: &[u8], a: usize, b: usize, top: &[u8]) -> bool { + if ((b < a) || ((b - a) != top.len())) { + return false; + } + let mut k: usize = 0; + while (k < top.len()) { + if (lower(w[((a + k)) as usize]) != lower(top[(k) as usize])) { + return false; + } + k = (k + 1); + } + return true; +} + +pub fn port_at(w: &[u8], st: &mut [usize], def: usize, i: usize) -> usize { + let k0: usize = st[(((DEF0 + (def * DEF_SLOTS)) + D_FIRST)) as usize]; + let e: usize = st[(((CARD0 + (k0 * CARD_SLOTS)) + C_B)) as usize]; + let t0: usize = tk(w, st[(((CARD0 + (k0 * CARD_SLOTS)) + C_A)) as usize], e); + let nm: usize = tk(w, tk_end(w, t0, e), e); + let r: usize = word_at(w, tk_end(w, nm, e), e, i); + st[(GATE_HIT) as usize] = r; + return r; +} + +pub fn def_port(w: &[u8], st: &mut [usize], def: usize, a: usize, b: usize) -> usize { + let e: usize = st[(((CARD0 + (st[(((DEF0 + (def * DEF_SLOTS)) + D_FIRST)) as usize] * CARD_SLOTS)) + C_B)) as usize]; + let mut hit: usize = NONE; + let mut i: usize = 0; + while ((hit == NONE) && (i < st[(((DEF0 + (def * DEF_SLOTS)) + D_NPORTS)) as usize])) { + let p: usize = port_at(w, st, def, i); + if ((p != NONE) && same_ci(w, a, b, p, tk_end(w, p, e))) { + hit = i; + } + i = (i + 1); + } + st[(GATE_HIT) as usize] = hit; + return hit; +} + +pub fn pin_port(w: &[u8], st: &mut [usize], def: usize, kind: usize, j: usize) -> usize { + let e: usize = st[(((CARD0 + (st[(((DEF0 + (def * DEF_SLOTS)) + D_FIRST)) as usize] * CARD_SLOTS)) + C_B)) as usize]; + let mut hit: usize = NONE; + let mut i: usize = 0; + while ((hit == NONE) && (i < st[(((DEF0 + (def * DEF_SLOTS)) + D_NPORTS)) as usize])) { + let p: usize = port_at(w, st, def, i); + if ((p != NONE) && pin_matches(kind, j, w, p, tk_end(w, p, e))) { + hit = i; + } + i = (i + 1); + } + st[(GATE_HIT) as usize] = hit; + return hit; +} + +pub fn net_intern(w: &mut [u8], st: &mut [usize], gt: &mut [usize], k: usize, c0: usize, n: usize) -> usize { + let mut i: usize = 0; + while (i < gt[(GT_NNET) as usize]) { + if same_ci(w, gt[((GT_NET0 + (i * 2))) as usize], gt[(((GT_NET0 + (i * 2)) + 1)) as usize], c0, (c0 + n)) { + return i; + } + i = (i + 1); + } + let pos: usize = gt[(GT_POOL) as usize]; + let stop: usize = (pos + n); + if ((gt[(GT_NNET) as usize] >= GT_MAXNET) || (stop > w.len())) { + reject(st, Y_SIZE, k, NONE); + return 0; + } + let mut j: usize = 0; + while (j < n) { + w[((pos + j)) as usize] = w[((c0 + j)) as usize]; + j = (j + 1); + } + let id: usize = gt[(GT_NNET) as usize]; + gt[((GT_NET0 + (id * 2))) as usize] = pos; + gt[(((GT_NET0 + (id * 2)) + 1)) as usize] = stop; + gt[(GT_POOL) as usize] = stop; + gt[(GT_NNET) as usize] = (id + 1); + return id; +} + +pub fn net_id(w: &mut [u8], st: &mut [usize], gt: &mut [usize], k: usize, a0: usize, b0: usize, depth: usize) -> usize { + let mut a: usize = a0; + let mut b: usize = b0; + let mut d: usize = depth; + let mut mode: usize = 0; + while (mode == 0) { + if (a >= b) { + mode = 1; + } else { + if ((b == (a + 1)) && (w[(a) as usize] == B_ZERO)) { + mode = 1; + } else { + let p: usize = def_port(w, st, gt[((GT_STK + (d * 3))) as usize], a, b); + if ((p == NONE) || (d == 0)) { + mode = 2; + } else { + let card: usize = gt[(((GT_STK + (d * 3)) + 1)) as usize]; + let e2: usize = st[(((CARD0 + (card * CARD_SLOTS)) + C_B)) as usize]; + let na: usize = word_at(w, tk_end(w, st[(((CARD0 + (card * CARD_SLOTS)) + C_A)) as usize], e2), e2, p); + if (na == NONE) { + mode = 1; + } else { + a = na; + b = tk_end(w, na, e2); + d = (d - 1); + } + } + } + } + } + let c0: usize = (st[(WEND) as usize] + GT_PATH); + let mut n: usize = 0; + if (mode == 1) { + w[(c0) as usize] = B_ZERO; + n = 1; + } else { + let pl: usize = gt[(((GT_STK + (d * 3)) + 2)) as usize]; + if ((pl + (b - a)) > GT_NAME) { + reject(st, Y_SIZE, k, NONE); + return 0; + } + let mut i: usize = 0; + while (i < pl) { + w[((c0 + i)) as usize] = w[((st[(WEND) as usize] + i)) as usize]; + i = (i + 1); + } + i = 0; + while ((a + i) < b) { + w[(((c0 + pl) + i)) as usize] = lower(w[((a + i)) as usize]); + i = (i + 1); + } + n = (pl + (b - a)); + } + return net_intern(w, st, gt, k, c0, n); +} + +pub fn gate_add(w: &mut [u8], st: &mut [usize], gt: &mut [usize], k: usize, d: usize, kind: usize, plen: usize, depth: usize) -> () { + let g: usize = gt[(GT_NGATE) as usize]; + if (g >= GT_MAXGATE) { + reject(st, Y_SIZE, k, NONE); + return (); + } + let e: usize = st[(((CARD0 + (k * CARD_SLOTS)) + C_B)) as usize]; + let row: usize = (GT_GATE0 + (g * GATE_SLOTS)); + gt[(row) as usize] = kind; + let mut j: usize = 0; + while (j < G_PINS) { + gt[(((row + 1) + j)) as usize] = NONE; + j = (j + 1); + } + j = 0; + while (j < pin_count(kind)) { + let p: usize = pin_port(w, st, d, kind, j); + if (p == NONE) { + reject(st, Y_GATE_PINS, k, NONE); + } else { + let t: usize = word_at(w, tk_end(w, st[(((CARD0 + (k * CARD_SLOTS)) + C_A)) as usize], e), e, p); + if (t != NONE) { + gt[(((row + 1) + j)) as usize] = net_id(w, st, gt, k, t, tk_end(w, t, e), depth); + } + } + j = (j + 1); + } + let nm: usize = tk(w, st[(((CARD0 + (k * CARD_SLOTS)) + C_A)) as usize], e); + let nl: usize = (tk_end(w, nm, e) - nm); + let pos: usize = gt[(GT_POOL) as usize]; + let stop: usize = ((pos + plen) + nl); + if (stop > w.len()) { + reject(st, Y_SIZE, k, NONE); + return (); + } + let mut i: usize = 0; + while (i < plen) { + w[((pos + i)) as usize] = w[((st[(WEND) as usize] + i)) as usize]; + i = (i + 1); + } + i = 0; + while (i < nl) { + w[(((pos + plen) + i)) as usize] = lower(w[((nm + i)) as usize]); + i = (i + 1); + } + gt[((row + 8)) as usize] = pos; + gt[((row + 9)) as usize] = stop; + gt[(GT_POOL) as usize] = stop; + gt[((GT_PER_KIND + kind)) as usize] = (gt[((GT_PER_KIND + kind)) as usize] + 1); + gt[(GT_NGATE) as usize] = (g + 1); +} + +pub fn gate_walk(w: &mut [u8], st: &mut [usize], gt: &mut [usize], scope: usize, lo: usize, hi: usize, plen: usize, depth: usize) -> () { + gt[((GT_STK + (depth * 3))) as usize] = scope; + gt[(((GT_STK + (depth * 3)) + 2)) as usize] = plen; + let mut k: usize = lo; + while (k < hi) { + let a: usize = st[(((CARD0 + (k * CARD_SLOTS)) + C_A)) as usize]; + let e: usize = st[(((CARD0 + (k * CARD_SLOTS)) + C_B)) as usize]; + let t0: usize = tk(w, a, e); + if ((st[(((CARD0 + (k * CARD_SLOTS)) + C_SCOPE)) as usize] == scope) && (w[(t0) as usize] != B_DOT)) { + let mut cls: usize = X_NONE; + if (lower(w[(t0) as usize]) == 120) { + cls = x_class(w, st, k); + } + if (cls != X_SUB) { + reject(st, Y_GATE_LEAF, k, NONE); + } else { + let d: usize = st[(DEF_HIT) as usize]; + let kind: usize = gate_kind(w, st, d); + if (x_mult(w, st, k) != 1) { + reject(st, Y_GATE_M, k, NONE); + } else { + if (kind != NONE) { + gate_add(w, st, gt, k, d, kind, plen, depth); + } else { + let nl: usize = (tk_end(w, t0, e) - t0); + let mut open: bool = false; + let mut j: usize = 0; + while (j <= depth) { + if (gt[((GT_STK + (j * 3))) as usize] == d) { + open = true; + } + j = (j + 1); + } + if open { + reject(st, Y_RECUR, k, st[(XA) as usize]); + } else { + if ((depth + 1) >= MAX_DEPTH) { + reject(st, Y_DEPTH, k, st[(XA) as usize]); + } else { + if (((plen + nl) + 1) > GT_PATH) { + reject(st, Y_SIZE, k, NONE); + } else { + let mut i: usize = 0; + while (i < nl) { + w[(((st[(WEND) as usize] + plen) + i)) as usize] = lower(w[((t0 + i)) as usize]); + i = (i + 1); + } + w[(((st[(WEND) as usize] + plen) + nl)) as usize] = B_DOT; + gt[(((GT_STK + ((depth + 1) * 3)) + 1)) as usize] = k; + gate_walk(w, st, gt, d, (st[(((DEF0 + (d * DEF_SLOTS)) + D_FIRST)) as usize] + 1), st[(((DEF0 + (d * DEF_SLOTS)) + D_LAST)) as usize], ((plen + nl) + 1), (depth + 1)); + } + } + } + } + } + } + } + k = (k + 1); + } +} + +pub fn put_kind(st: &mut [usize], out: &mut [u8], kind: usize) -> () { + if (kind == G_INV) { + puts(st, out, b"inverter"); + } else { + if (kind == G_NAND) { + puts(st, out, b"nand"); + } else { + if (kind == G_NOR) { + puts(st, out, b"nor"); + } else { + if (kind == G_NOR3) { + puts(st, out, b"nor3"); + } else { + if (kind == G_TGATE) { + puts(st, out, b"tgate"); + } else { + puts(st, out, b"delay_cell"); + } + } + } + } + } +} + +pub fn put_gates(w: &[u8], st: &mut [usize], gt: impl AsRef<[usize]>, out: &mut [u8], root: usize) -> () { + let gt: &[usize] = gt.as_ref(); + puts(st, out, b"gates of "); + put_lower(w, st, out, st[(((DEF0 + (root * DEF_SLOTS)) + D_NAME_A)) as usize], st[(((DEF0 + (root * DEF_SLOTS)) + D_NAME_B)) as usize]); + puts(st, out, b": "); + put_num(st, out, gt[(GT_NGATE) as usize]); + puts(st, out, b" instances, "); + put_num(st, out, gt[(GT_NNET) as usize]); + puts(st, out, b" nets\nkinds:"); + let mut q: usize = 0; + while (q < G_KINDS) { + puts(st, out, b" "); + put_kind(st, out, q); + puts(st, out, b"="); + put_num(st, out, gt[((GT_PER_KIND + q)) as usize]); + q = (q + 1); + } + put(st, out, B_NL); + let mut n: usize = 0; + while (n < gt[(GT_NNET) as usize]) { + puts(st, out, b"net "); + put_num(st, out, n); + puts(st, out, b" "); + put_lower(w, st, out, gt[((GT_NET0 + (n * 2))) as usize], gt[(((GT_NET0 + (n * 2)) + 1)) as usize]); + put(st, out, B_NL); + n = (n + 1); + } + let mut g: usize = 0; + while (g < gt[(GT_NGATE) as usize]) { + let row: usize = (GT_GATE0 + (g * GATE_SLOTS)); + puts(st, out, b"gate "); + put_num(st, out, g); + puts(st, out, b" "); + put_lower(w, st, out, gt[((row + 8)) as usize], gt[((row + 9)) as usize]); + puts(st, out, b" "); + put_kind(st, out, gt[(row) as usize]); + let mut j: usize = 0; + while (j < pin_count(gt[(row) as usize])) { + puts(st, out, b" "); + put_num(st, out, gt[(((row + 1) + j)) as usize]); + j = (j + 1); + } + put(st, out, B_NL); + g = (g + 1); + } +} + +pub fn emit_gates(src: &[u8], top: &[u8], w: &mut [u8], out: &mut [u8], st: &mut [usize], gt: &mut [usize]) -> 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); + resolve(w, st); + let mut root: usize = NONE; + let mut d: usize = 0; + while (d < st[(NDEF) as usize]) { + if (((root == NONE) && (st[(((DEF0 + (d * DEF_SLOTS)) + D_PARENT)) as usize] == NONE)) && top_is(w, st[(((DEF0 + (d * DEF_SLOTS)) + D_NAME_A)) as usize], st[(((DEF0 + (d * DEF_SLOTS)) + D_NAME_B)) as usize], top)) { + root = d; + } + d = (d + 1); + } + if ((st[(ERR_LINE) as usize] == 0) && (root == NONE)) { + reject_at(st, Y_GATE_TOP, 1, NONE, NONE); + } + if ((st[(ERR_LINE) as usize] == 0) && ((((st[(WEND) as usize] + GT_PATH) + GT_NAME) + 64) > w.len())) { + reject_at(st, Y_SIZE, 1, NONE, NONE); + } + if (st[(ERR_LINE) as usize] == 0) { + let k0: usize = st[(((DEF0 + (root * DEF_SLOTS)) + D_FIRST)) as usize]; + gt[(GT_POOL) as usize] = ((st[(WEND) as usize] + GT_PATH) + GT_NAME); + gt[(GT_STK) as usize] = root; + gt[((GT_STK + 1)) as usize] = NONE; + gt[((GT_STK + 2)) as usize] = 0; + let mut i: usize = 0; + while (i < st[(((DEF0 + (root * DEF_SLOTS)) + D_NPORTS)) as usize]) { + let p: usize = port_at(w, st, root, i); + gt[(GT_LAST) as usize] = net_id(w, st, gt, k0, p, tk_end(w, p, st[(((CARD0 + (k0 * CARD_SLOTS)) + C_B)) as usize]), 0); + i = (i + 1); + } + gate_walk(w, st, gt, root, (k0 + 1), st[(((DEF0 + (root * DEF_SLOTS)) + D_LAST)) as usize], 0, 0); + } + if (st[(ERR_LINE) as usize] != 0) { + put_reject(w, st, out); + } else { + put_gates(w, st, gt, out, root); + } + 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"; @@ -2733,6 +3257,17 @@ pub fn cell_diff(st: impl AsRef<[usize]>, n: usize, d: impl AsRef<[u32]>, nv: im return miss; } +pub const DECK_GATES: &'static str = "gate level mini deck\n.subckt inverter Vin VDD VSS Vout\nXM1 Vout Vin VDD VDD sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1\nXM2 Vout Vin VSS VSS sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1\n.ends\n.subckt nor A B Vout VDD VSS\nXM1 Vout A VSS VSS sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1\nXM2 Vout B VSS VSS sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1\nXM3 Vout B net1 VDD sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1\nXM4 net1 A VDD VDD sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1\n.ends\n.subckt tgate CLK nCLK In Out VDD VSS\nXM1 Out CLK In VDD sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1\nXM2 In nCLK Out VSS sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1\n.ends\n.subckt latch VDD VSS Q CLK nQ D Reset\nx1 CLK nCLK D net1 VDD VSS tgate\nx2 Reset net1 net2 VDD VSS nor\nx3 nCLK CLK net2 Q VDD VSS tgate\nx6 Q VDD VSS nQ inverter\nx10 CLK VDD VSS nCLK inverter\n.ends\n.subckt chain CK2 CK1 VDD VSS D Valid Reset\nx1 VDD VSS CK1 Valid net1 D Reset latch\nx2 VDD VSS CK2 Valid net2 CK1 Reset latch\n.ends\n.end\n"; + +pub fn gate_is(gt: impl AsRef<[usize]>, g: usize, kind: usize, p0: usize, p1: usize, p2: usize, p3: usize, p4: usize, p5: usize) -> bool { + let gt: &[usize] = gt.as_ref(); + let row: usize = (GT_GATE0 + (g * GATE_SLOTS)); + if (gt[(row) as usize] != kind) { + return false; + } + return ((((((gt[((row + 1)) as usize] == p0) && (gt[((row + 2)) as usize] == p1)) && (gt[((row + 3)) as usize] == p2)) && (gt[((row + 4)) as usize] == p3)) && (gt[((row + 5)) as usize] == p4)) && (gt[((row + 6)) as usize] == p5)); +} + pub const LAD3_D: [u32; 9] = [(NEG | 1493555), 696219, 33153, 696219, (NEG | 1460402), 729372, 33153, 729372, (NEG | 764183)]; pub const LAD3_N: usize = 3; diff --git a/specs/analog/decks/sar_delay_cell.cir b/specs/analog/decks/sar_delay_cell.cir new file mode 100644 index 0000000000..2c3804aca6 --- /dev/null +++ b/specs/analog/decks/sar_delay_cell.cir @@ -0,0 +1,121 @@ +SAR clock_gen delay cell: nor3, delay_cell and inverter of the golden SAR deck for the 8 trim codes, PSP103 tt at 50 C +* specs/analog/decks/sar_delay_cell.cir -- reference data for specs/analog/sar_logic.t27 (M4d, #10754, epic #10486). Eight copies of the +* chain of clock_gen (nor3 -> delay_cell -> inverter) differ only in the trim code A B C of the delay cell (code = 4 A + 2 B + C; the golden +* top ties A = B = C = 0, which is code 0). clks falls once and rises once; Valid and CK1 of the nor3 are held low. The inverter's output +* carries the 8 fF the golden top puts on clkc. Measured from the 50 % point of each net: the nor3 output (n1), the delay cell output (n2), clkc. +* Paths are NOT in this file: ngspice reads IHP_PDK, IHP_OSDI and SAR_GOLDEN_SUBCKTS (see sar_gate_arcs.cir) from the environment. +* Run: IHP_PDK= IHP_OSDI= SAR_GOLDEN_SUBCKTS= ngspice -b sar_delay_cell.cir (ngspice-47) +* It prints one line per measure: = . Names: n1__ = clks edge -> nor3 output edge, dc__ = nor3 output edge -> +* delay cell output edge, iv__ = delay cell output edge -> clkc edge, tot__ = clks edge -> clkc edge (r|f the edge of clkc). +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +.include $SAR_GOLDEN_SUBCKTS +VDD vdd 0 1.5 +Vlo lo 0 0 +Vhi hi 0 1.5 +Vclks clks 0 PULSE(1.5 0 10n 0.2n 0.2n 80n 200n) +xn0 vdd 0 n1_0 clks lo lo nor3 +xd0 n2_0 vdd 0 n1_0 lo lo lo delay_cell +xi0 n2_0 vdd 0 ck_0 inverter +C0 ck_0 0 8f +xn1 vdd 0 n1_1 clks lo lo nor3 +xd1 n2_1 vdd 0 n1_1 lo lo hi delay_cell +xi1 n2_1 vdd 0 ck_1 inverter +C1 ck_1 0 8f +xn2 vdd 0 n1_2 clks lo lo nor3 +xd2 n2_2 vdd 0 n1_2 lo hi lo delay_cell +xi2 n2_2 vdd 0 ck_2 inverter +C2 ck_2 0 8f +xn3 vdd 0 n1_3 clks lo lo nor3 +xd3 n2_3 vdd 0 n1_3 lo hi hi delay_cell +xi3 n2_3 vdd 0 ck_3 inverter +C3 ck_3 0 8f +xn4 vdd 0 n1_4 clks lo lo nor3 +xd4 n2_4 vdd 0 n1_4 hi lo lo delay_cell +xi4 n2_4 vdd 0 ck_4 inverter +C4 ck_4 0 8f +xn5 vdd 0 n1_5 clks lo lo nor3 +xd5 n2_5 vdd 0 n1_5 hi lo hi delay_cell +xi5 n2_5 vdd 0 ck_5 inverter +C5 ck_5 0 8f +xn6 vdd 0 n1_6 clks lo lo nor3 +xd6 n2_6 vdd 0 n1_6 hi hi lo delay_cell +xi6 n2_6 vdd 0 ck_6 inverter +C6 ck_6 0 8f +xn7 vdd 0 n1_7 clks lo lo nor3 +xd7 n2_7 vdd 0 n1_7 hi hi hi delay_cell +xi7 n2_7 vdd 0 ck_7 inverter +C7 ck_7 0 8f +.temp 50 +.options reltol=1e-4 +.tran 5p 190n 0 50p +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=6 +run +meas tran n1_0_r trig v(clks) val=0.75 fall=1 targ v(n1_0) val=0.75 rise=1 +meas tran n1_0_f trig v(clks) val=0.75 rise=1 targ v(n1_0) val=0.75 fall=1 +meas tran dc_0_f trig v(n1_0) val=0.75 rise=1 targ v(n2_0) val=0.75 fall=1 +meas tran dc_0_r trig v(n1_0) val=0.75 fall=1 targ v(n2_0) val=0.75 rise=1 +meas tran iv_0_r trig v(n2_0) val=0.75 fall=1 targ v(ck_0) val=0.75 rise=1 +meas tran iv_0_f trig v(n2_0) val=0.75 rise=1 targ v(ck_0) val=0.75 fall=1 +meas tran tot_0_r trig v(clks) val=0.75 fall=1 targ v(ck_0) val=0.75 rise=1 +meas tran tot_0_f trig v(clks) val=0.75 rise=1 targ v(ck_0) val=0.75 fall=1 +meas tran n1_1_r trig v(clks) val=0.75 fall=1 targ v(n1_1) val=0.75 rise=1 +meas tran n1_1_f trig v(clks) val=0.75 rise=1 targ v(n1_1) val=0.75 fall=1 +meas tran dc_1_f trig v(n1_1) val=0.75 rise=1 targ v(n2_1) val=0.75 fall=1 +meas tran dc_1_r trig v(n1_1) val=0.75 fall=1 targ v(n2_1) val=0.75 rise=1 +meas tran iv_1_r trig v(n2_1) val=0.75 fall=1 targ v(ck_1) val=0.75 rise=1 +meas tran iv_1_f trig v(n2_1) val=0.75 rise=1 targ v(ck_1) val=0.75 fall=1 +meas tran tot_1_r trig v(clks) val=0.75 fall=1 targ v(ck_1) val=0.75 rise=1 +meas tran tot_1_f trig v(clks) val=0.75 rise=1 targ v(ck_1) val=0.75 fall=1 +meas tran n1_2_r trig v(clks) val=0.75 fall=1 targ v(n1_2) val=0.75 rise=1 +meas tran n1_2_f trig v(clks) val=0.75 rise=1 targ v(n1_2) val=0.75 fall=1 +meas tran dc_2_f trig v(n1_2) val=0.75 rise=1 targ v(n2_2) val=0.75 fall=1 +meas tran dc_2_r trig v(n1_2) val=0.75 fall=1 targ v(n2_2) val=0.75 rise=1 +meas tran iv_2_r trig v(n2_2) val=0.75 fall=1 targ v(ck_2) val=0.75 rise=1 +meas tran iv_2_f trig v(n2_2) val=0.75 rise=1 targ v(ck_2) val=0.75 fall=1 +meas tran tot_2_r trig v(clks) val=0.75 fall=1 targ v(ck_2) val=0.75 rise=1 +meas tran tot_2_f trig v(clks) val=0.75 rise=1 targ v(ck_2) val=0.75 fall=1 +meas tran n1_3_r trig v(clks) val=0.75 fall=1 targ v(n1_3) val=0.75 rise=1 +meas tran n1_3_f trig v(clks) val=0.75 rise=1 targ v(n1_3) val=0.75 fall=1 +meas tran dc_3_f trig v(n1_3) val=0.75 rise=1 targ v(n2_3) val=0.75 fall=1 +meas tran dc_3_r trig v(n1_3) val=0.75 fall=1 targ v(n2_3) val=0.75 rise=1 +meas tran iv_3_r trig v(n2_3) val=0.75 fall=1 targ v(ck_3) val=0.75 rise=1 +meas tran iv_3_f trig v(n2_3) val=0.75 rise=1 targ v(ck_3) val=0.75 fall=1 +meas tran tot_3_r trig v(clks) val=0.75 fall=1 targ v(ck_3) val=0.75 rise=1 +meas tran tot_3_f trig v(clks) val=0.75 rise=1 targ v(ck_3) val=0.75 fall=1 +meas tran n1_4_r trig v(clks) val=0.75 fall=1 targ v(n1_4) val=0.75 rise=1 +meas tran n1_4_f trig v(clks) val=0.75 rise=1 targ v(n1_4) val=0.75 fall=1 +meas tran dc_4_f trig v(n1_4) val=0.75 rise=1 targ v(n2_4) val=0.75 fall=1 +meas tran dc_4_r trig v(n1_4) val=0.75 fall=1 targ v(n2_4) val=0.75 rise=1 +meas tran iv_4_r trig v(n2_4) val=0.75 fall=1 targ v(ck_4) val=0.75 rise=1 +meas tran iv_4_f trig v(n2_4) val=0.75 rise=1 targ v(ck_4) val=0.75 fall=1 +meas tran tot_4_r trig v(clks) val=0.75 fall=1 targ v(ck_4) val=0.75 rise=1 +meas tran tot_4_f trig v(clks) val=0.75 rise=1 targ v(ck_4) val=0.75 fall=1 +meas tran n1_5_r trig v(clks) val=0.75 fall=1 targ v(n1_5) val=0.75 rise=1 +meas tran n1_5_f trig v(clks) val=0.75 rise=1 targ v(n1_5) val=0.75 fall=1 +meas tran dc_5_f trig v(n1_5) val=0.75 rise=1 targ v(n2_5) val=0.75 fall=1 +meas tran dc_5_r trig v(n1_5) val=0.75 fall=1 targ v(n2_5) val=0.75 rise=1 +meas tran iv_5_r trig v(n2_5) val=0.75 fall=1 targ v(ck_5) val=0.75 rise=1 +meas tran iv_5_f trig v(n2_5) val=0.75 rise=1 targ v(ck_5) val=0.75 fall=1 +meas tran tot_5_r trig v(clks) val=0.75 fall=1 targ v(ck_5) val=0.75 rise=1 +meas tran tot_5_f trig v(clks) val=0.75 rise=1 targ v(ck_5) val=0.75 fall=1 +meas tran n1_6_r trig v(clks) val=0.75 fall=1 targ v(n1_6) val=0.75 rise=1 +meas tran n1_6_f trig v(clks) val=0.75 rise=1 targ v(n1_6) val=0.75 fall=1 +meas tran dc_6_f trig v(n1_6) val=0.75 rise=1 targ v(n2_6) val=0.75 fall=1 +meas tran dc_6_r trig v(n1_6) val=0.75 fall=1 targ v(n2_6) val=0.75 rise=1 +meas tran iv_6_r trig v(n2_6) val=0.75 fall=1 targ v(ck_6) val=0.75 rise=1 +meas tran iv_6_f trig v(n2_6) val=0.75 rise=1 targ v(ck_6) val=0.75 fall=1 +meas tran tot_6_r trig v(clks) val=0.75 fall=1 targ v(ck_6) val=0.75 rise=1 +meas tran tot_6_f trig v(clks) val=0.75 rise=1 targ v(ck_6) val=0.75 fall=1 +meas tran n1_7_r trig v(clks) val=0.75 fall=1 targ v(n1_7) val=0.75 rise=1 +meas tran n1_7_f trig v(clks) val=0.75 rise=1 targ v(n1_7) val=0.75 fall=1 +meas tran dc_7_f trig v(n1_7) val=0.75 rise=1 targ v(n2_7) val=0.75 fall=1 +meas tran dc_7_r trig v(n1_7) val=0.75 fall=1 targ v(n2_7) val=0.75 rise=1 +meas tran iv_7_r trig v(n2_7) val=0.75 fall=1 targ v(ck_7) val=0.75 rise=1 +meas tran iv_7_f trig v(n2_7) val=0.75 rise=1 targ v(ck_7) val=0.75 fall=1 +meas tran tot_7_r trig v(clks) val=0.75 fall=1 targ v(ck_7) val=0.75 rise=1 +meas tran tot_7_f trig v(clks) val=0.75 rise=1 targ v(ck_7) val=0.75 fall=1 +.endc +.end diff --git a/specs/analog/decks/sar_gate_arcs.cir b/specs/analog/decks/sar_gate_arcs.cir new file mode 100644 index 0000000000..4602e1f4ec --- /dev/null +++ b/specs/analog/decks/sar_gate_arcs.cir @@ -0,0 +1,189 @@ +SAR gate arcs: inverter, nor, nor3, nand and tgate delays of the golden SAR deck, PSP103 tt at 50 C, fanout banks 1 4 16 +* specs/analog/decks/sar_gate_arcs.cir -- reference data for specs/analog/sar_logic.t27 (M4d, #10754, epic #10486). Each gate of the golden SAR +* deck (uberWAVE wp2-parser/golden/top.spice, GPL-2.0: fetched at run time, never vendored) drives a FANOUT BANK of its own inverter +* (m = 1, 4 or 16 inverter inputs, 1.8 um^2 of gate each), and the delay from the input's 50 % point to the output's 50 % point is measured +* for every input pin and both edges. From the three loads the spec fits delay = d0 + k * FO (fanout). +* Paths are NOT in this file: ngspice reads IHP_PDK (the IHP-Open-PDK checkout), IHP_OSDI (the directory with the compiled psp103.osdi +* and psp103_nqs.osdi) and SAR_GOLDEN_SUBCKTS (the subcircuit section of the golden deck, made at run time: +* sed -n '/^.subckt tracking_switch/,$p' top.spice | grep -v -i -E '^.(end|global)' > $SAR_GOLDEN_SUBCKTS ) from the environment. +* Run: IHP_PDK= IHP_OSDI= SAR_GOLDEN_SUBCKTS= ngspice -b sar_gate_arcs.cir (ngspice-47) +* It prints one line per arc: = . Names: d____ with r|f the OUTPUT edge; tgate: d_tgp (pass), d_tge (enable). +* Model, corner and temperature: sg13_lv devices, mos_tt, 50 C (the golden deck says .options TEMP = 50). Edges: 0.2 ns, 10 ns apart from 40 ns. +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +.include $SAR_GOLDEN_SUBCKTS +VDD vdd 0 1.5 +Vlo lo 0 0 +Vhi hi 0 1.5 +Vin in 0 PULSE(0 1.5 10n 0.2n 0.2n 40n 200n) +Vck ck 0 PULSE(0 1.5 10n 0.2n 0.2n 40n 200n) +Vckn ckn 0 PULSE(1.5 0 10n 0.2n 0.2n 40n 200n) +xd_inv_1 in vdd 0 y_inv_1 inverter +xl_inv_1 y_inv_1 vdd 0 z_inv_1 inverter m=1 +xd_nora_1 in lo y_nora_1 vdd 0 nor +xl_nora_1 y_nora_1 vdd 0 z_nora_1 inverter m=1 +xd_norb_1 lo in y_norb_1 vdd 0 nor +xl_norb_1 y_norb_1 vdd 0 z_norb_1 inverter m=1 +xd_nor3a_1 vdd 0 y_nor3a_1 in lo lo nor3 +xl_nor3a_1 y_nor3a_1 vdd 0 z_nor3a_1 inverter m=1 +xd_nor3b_1 vdd 0 y_nor3b_1 lo in lo nor3 +xl_nor3b_1 y_nor3b_1 vdd 0 z_nor3b_1 inverter m=1 +xd_nor3c_1 vdd 0 y_nor3c_1 lo lo in nor3 +xl_nor3c_1 y_nor3c_1 vdd 0 z_nor3c_1 inverter m=1 +xd_nanda_1 in hi vdd y_nanda_1 0 nand +xl_nanda_1 y_nanda_1 vdd 0 z_nanda_1 inverter m=1 +xd_nandb_1 hi in vdd y_nandb_1 0 nand +xl_nandb_1 y_nandb_1 vdd 0 z_nandb_1 inverter m=1 +xd_tgp_1 lo hi in y_tgp_1 vdd 0 tgate +xl_tgp_1 y_tgp_1 vdd 0 z_tgp_1 inverter m=1 +xi_tgef_1 ck vdd 0 nck_tgef_1 inverter +xj_tgef_1 nck_tgef_1 vdd 0 zj_tgef_1 inverter m=2 +xd_tgef_1 nck_tgef_1 ck lo y_tgef_1 vdd 0 tgate +xl_tgef_1 y_tgef_1 vdd 0 z_tgef_1 inverter m=1 +.ic v(y_tgef_1)=1.5 +xi_tger_1 ckn vdd 0 nck_tger_1 inverter +xj_tger_1 nck_tger_1 vdd 0 zj_tger_1 inverter m=2 +xd_tger_1 ckn nck_tger_1 hi y_tger_1 vdd 0 tgate +xl_tger_1 y_tger_1 vdd 0 z_tger_1 inverter m=1 +.ic v(y_tger_1)=0 +xd_inv_4 in vdd 0 y_inv_4 inverter +xl_inv_4 y_inv_4 vdd 0 z_inv_4 inverter m=4 +xd_nora_4 in lo y_nora_4 vdd 0 nor +xl_nora_4 y_nora_4 vdd 0 z_nora_4 inverter m=4 +xd_norb_4 lo in y_norb_4 vdd 0 nor +xl_norb_4 y_norb_4 vdd 0 z_norb_4 inverter m=4 +xd_nor3a_4 vdd 0 y_nor3a_4 in lo lo nor3 +xl_nor3a_4 y_nor3a_4 vdd 0 z_nor3a_4 inverter m=4 +xd_nor3b_4 vdd 0 y_nor3b_4 lo in lo nor3 +xl_nor3b_4 y_nor3b_4 vdd 0 z_nor3b_4 inverter m=4 +xd_nor3c_4 vdd 0 y_nor3c_4 lo lo in nor3 +xl_nor3c_4 y_nor3c_4 vdd 0 z_nor3c_4 inverter m=4 +xd_nanda_4 in hi vdd y_nanda_4 0 nand +xl_nanda_4 y_nanda_4 vdd 0 z_nanda_4 inverter m=4 +xd_nandb_4 hi in vdd y_nandb_4 0 nand +xl_nandb_4 y_nandb_4 vdd 0 z_nandb_4 inverter m=4 +xd_tgp_4 lo hi in y_tgp_4 vdd 0 tgate +xl_tgp_4 y_tgp_4 vdd 0 z_tgp_4 inverter m=4 +xi_tgef_4 ck vdd 0 nck_tgef_4 inverter +xj_tgef_4 nck_tgef_4 vdd 0 zj_tgef_4 inverter m=2 +xd_tgef_4 nck_tgef_4 ck lo y_tgef_4 vdd 0 tgate +xl_tgef_4 y_tgef_4 vdd 0 z_tgef_4 inverter m=4 +.ic v(y_tgef_4)=1.5 +xi_tger_4 ckn vdd 0 nck_tger_4 inverter +xj_tger_4 nck_tger_4 vdd 0 zj_tger_4 inverter m=2 +xd_tger_4 ckn nck_tger_4 hi y_tger_4 vdd 0 tgate +xl_tger_4 y_tger_4 vdd 0 z_tger_4 inverter m=4 +.ic v(y_tger_4)=0 +xd_inv_16 in vdd 0 y_inv_16 inverter +xl_inv_16 y_inv_16 vdd 0 z_inv_16 inverter m=16 +xd_nora_16 in lo y_nora_16 vdd 0 nor +xl_nora_16 y_nora_16 vdd 0 z_nora_16 inverter m=16 +xd_norb_16 lo in y_norb_16 vdd 0 nor +xl_norb_16 y_norb_16 vdd 0 z_norb_16 inverter m=16 +xd_nor3a_16 vdd 0 y_nor3a_16 in lo lo nor3 +xl_nor3a_16 y_nor3a_16 vdd 0 z_nor3a_16 inverter m=16 +xd_nor3b_16 vdd 0 y_nor3b_16 lo in lo nor3 +xl_nor3b_16 y_nor3b_16 vdd 0 z_nor3b_16 inverter m=16 +xd_nor3c_16 vdd 0 y_nor3c_16 lo lo in nor3 +xl_nor3c_16 y_nor3c_16 vdd 0 z_nor3c_16 inverter m=16 +xd_nanda_16 in hi vdd y_nanda_16 0 nand +xl_nanda_16 y_nanda_16 vdd 0 z_nanda_16 inverter m=16 +xd_nandb_16 hi in vdd y_nandb_16 0 nand +xl_nandb_16 y_nandb_16 vdd 0 z_nandb_16 inverter m=16 +xd_tgp_16 lo hi in y_tgp_16 vdd 0 tgate +xl_tgp_16 y_tgp_16 vdd 0 z_tgp_16 inverter m=16 +xi_tgef_16 ck vdd 0 nck_tgef_16 inverter +xj_tgef_16 nck_tgef_16 vdd 0 zj_tgef_16 inverter m=2 +xd_tgef_16 nck_tgef_16 ck lo y_tgef_16 vdd 0 tgate +xl_tgef_16 y_tgef_16 vdd 0 z_tgef_16 inverter m=16 +.ic v(y_tgef_16)=1.5 +xi_tger_16 ckn vdd 0 nck_tger_16 inverter +xj_tger_16 nck_tger_16 vdd 0 zj_tger_16 inverter m=2 +xd_tger_16 ckn nck_tger_16 hi y_tger_16 vdd 0 tgate +xl_tger_16 y_tger_16 vdd 0 z_tger_16 inverter m=16 +.ic v(y_tger_16)=0 +xd_invc_4 in vdd 0 y_invc_4 inverter +C_invc_4 y_invc_4 0 4f +xd_invc_8 in vdd 0 y_invc_8 inverter +C_invc_8 y_invc_8 0 8f +xd_invc_16 in vdd 0 y_invc_16 inverter +C_invc_16 y_invc_16 0 16f +xd_invc_32 in vdd 0 y_invc_32 inverter +C_invc_32 y_invc_32 0 32f +.temp 50 +.options reltol=1e-4 +.tran 2p 100n 0 20p +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=6 +run +meas tran d_inv_a_1_r trig v(in) val=0.75 fall=1 targ v(y_inv_1) val=0.75 rise=1 +meas tran d_inv_a_1_f trig v(in) val=0.75 rise=1 targ v(y_inv_1) val=0.75 fall=1 +meas tran d_nor_a_1_r trig v(in) val=0.75 fall=1 targ v(y_nora_1) val=0.75 rise=1 +meas tran d_nor_a_1_f trig v(in) val=0.75 rise=1 targ v(y_nora_1) val=0.75 fall=1 +meas tran d_nor_b_1_r trig v(in) val=0.75 fall=1 targ v(y_norb_1) val=0.75 rise=1 +meas tran d_nor_b_1_f trig v(in) val=0.75 rise=1 targ v(y_norb_1) val=0.75 fall=1 +meas tran d_nor3_a_1_r trig v(in) val=0.75 fall=1 targ v(y_nor3a_1) val=0.75 rise=1 +meas tran d_nor3_a_1_f trig v(in) val=0.75 rise=1 targ v(y_nor3a_1) val=0.75 fall=1 +meas tran d_nor3_b_1_r trig v(in) val=0.75 fall=1 targ v(y_nor3b_1) val=0.75 rise=1 +meas tran d_nor3_b_1_f trig v(in) val=0.75 rise=1 targ v(y_nor3b_1) val=0.75 fall=1 +meas tran d_nor3_c_1_r trig v(in) val=0.75 fall=1 targ v(y_nor3c_1) val=0.75 rise=1 +meas tran d_nor3_c_1_f trig v(in) val=0.75 rise=1 targ v(y_nor3c_1) val=0.75 fall=1 +meas tran d_nand_a_1_r trig v(in) val=0.75 fall=1 targ v(y_nanda_1) val=0.75 rise=1 +meas tran d_nand_a_1_f trig v(in) val=0.75 rise=1 targ v(y_nanda_1) val=0.75 fall=1 +meas tran d_nand_b_1_r trig v(in) val=0.75 fall=1 targ v(y_nandb_1) val=0.75 rise=1 +meas tran d_nand_b_1_f trig v(in) val=0.75 rise=1 targ v(y_nandb_1) val=0.75 fall=1 +meas tran d_tgp_1_r trig v(in) val=0.75 rise=1 targ v(y_tgp_1) val=0.75 rise=1 +meas tran d_tgp_1_f trig v(in) val=0.75 fall=1 targ v(y_tgp_1) val=0.75 fall=1 +meas tran d_tge_1_f trig v(ck) val=0.75 rise=1 targ v(y_tgef_1) val=0.75 fall=1 +meas tran d_tge_1_r trig v(ckn) val=0.75 fall=1 targ v(y_tger_1) val=0.75 rise=1 +meas tran d_inv_a_4_r trig v(in) val=0.75 fall=1 targ v(y_inv_4) val=0.75 rise=1 +meas tran d_inv_a_4_f trig v(in) val=0.75 rise=1 targ v(y_inv_4) val=0.75 fall=1 +meas tran d_nor_a_4_r trig v(in) val=0.75 fall=1 targ v(y_nora_4) val=0.75 rise=1 +meas tran d_nor_a_4_f trig v(in) val=0.75 rise=1 targ v(y_nora_4) val=0.75 fall=1 +meas tran d_nor_b_4_r trig v(in) val=0.75 fall=1 targ v(y_norb_4) val=0.75 rise=1 +meas tran d_nor_b_4_f trig v(in) val=0.75 rise=1 targ v(y_norb_4) val=0.75 fall=1 +meas tran d_nor3_a_4_r trig v(in) val=0.75 fall=1 targ v(y_nor3a_4) val=0.75 rise=1 +meas tran d_nor3_a_4_f trig v(in) val=0.75 rise=1 targ v(y_nor3a_4) val=0.75 fall=1 +meas tran d_nor3_b_4_r trig v(in) val=0.75 fall=1 targ v(y_nor3b_4) val=0.75 rise=1 +meas tran d_nor3_b_4_f trig v(in) val=0.75 rise=1 targ v(y_nor3b_4) val=0.75 fall=1 +meas tran d_nor3_c_4_r trig v(in) val=0.75 fall=1 targ v(y_nor3c_4) val=0.75 rise=1 +meas tran d_nor3_c_4_f trig v(in) val=0.75 rise=1 targ v(y_nor3c_4) val=0.75 fall=1 +meas tran d_nand_a_4_r trig v(in) val=0.75 fall=1 targ v(y_nanda_4) val=0.75 rise=1 +meas tran d_nand_a_4_f trig v(in) val=0.75 rise=1 targ v(y_nanda_4) val=0.75 fall=1 +meas tran d_nand_b_4_r trig v(in) val=0.75 fall=1 targ v(y_nandb_4) val=0.75 rise=1 +meas tran d_nand_b_4_f trig v(in) val=0.75 rise=1 targ v(y_nandb_4) val=0.75 fall=1 +meas tran d_tgp_4_r trig v(in) val=0.75 rise=1 targ v(y_tgp_4) val=0.75 rise=1 +meas tran d_tgp_4_f trig v(in) val=0.75 fall=1 targ v(y_tgp_4) val=0.75 fall=1 +meas tran d_tge_4_f trig v(ck) val=0.75 rise=1 targ v(y_tgef_4) val=0.75 fall=1 +meas tran d_tge_4_r trig v(ckn) val=0.75 fall=1 targ v(y_tger_4) val=0.75 rise=1 +meas tran d_inv_a_16_r trig v(in) val=0.75 fall=1 targ v(y_inv_16) val=0.75 rise=1 +meas tran d_inv_a_16_f trig v(in) val=0.75 rise=1 targ v(y_inv_16) val=0.75 fall=1 +meas tran d_nor_a_16_r trig v(in) val=0.75 fall=1 targ v(y_nora_16) val=0.75 rise=1 +meas tran d_nor_a_16_f trig v(in) val=0.75 rise=1 targ v(y_nora_16) val=0.75 fall=1 +meas tran d_nor_b_16_r trig v(in) val=0.75 fall=1 targ v(y_norb_16) val=0.75 rise=1 +meas tran d_nor_b_16_f trig v(in) val=0.75 rise=1 targ v(y_norb_16) val=0.75 fall=1 +meas tran d_nor3_a_16_r trig v(in) val=0.75 fall=1 targ v(y_nor3a_16) val=0.75 rise=1 +meas tran d_nor3_a_16_f trig v(in) val=0.75 rise=1 targ v(y_nor3a_16) val=0.75 fall=1 +meas tran d_nor3_b_16_r trig v(in) val=0.75 fall=1 targ v(y_nor3b_16) val=0.75 rise=1 +meas tran d_nor3_b_16_f trig v(in) val=0.75 rise=1 targ v(y_nor3b_16) val=0.75 fall=1 +meas tran d_nor3_c_16_r trig v(in) val=0.75 fall=1 targ v(y_nor3c_16) val=0.75 rise=1 +meas tran d_nor3_c_16_f trig v(in) val=0.75 rise=1 targ v(y_nor3c_16) val=0.75 fall=1 +meas tran d_nand_a_16_r trig v(in) val=0.75 fall=1 targ v(y_nanda_16) val=0.75 rise=1 +meas tran d_nand_a_16_f trig v(in) val=0.75 rise=1 targ v(y_nanda_16) val=0.75 fall=1 +meas tran d_nand_b_16_r trig v(in) val=0.75 fall=1 targ v(y_nandb_16) val=0.75 rise=1 +meas tran d_nand_b_16_f trig v(in) val=0.75 rise=1 targ v(y_nandb_16) val=0.75 fall=1 +meas tran d_tgp_16_r trig v(in) val=0.75 rise=1 targ v(y_tgp_16) val=0.75 rise=1 +meas tran d_tgp_16_f trig v(in) val=0.75 fall=1 targ v(y_tgp_16) val=0.75 fall=1 +meas tran d_tge_16_f trig v(ck) val=0.75 rise=1 targ v(y_tgef_16) val=0.75 fall=1 +meas tran d_tge_16_r trig v(ckn) val=0.75 fall=1 targ v(y_tger_16) val=0.75 rise=1 +meas tran d_invc_4_r trig v(in) val=0.75 fall=1 targ v(y_invc_4) val=0.75 rise=1 +meas tran d_invc_4_f trig v(in) val=0.75 rise=1 targ v(y_invc_4) val=0.75 fall=1 +meas tran d_invc_8_r trig v(in) val=0.75 fall=1 targ v(y_invc_8) val=0.75 rise=1 +meas tran d_invc_8_f trig v(in) val=0.75 rise=1 targ v(y_invc_8) val=0.75 fall=1 +meas tran d_invc_16_r trig v(in) val=0.75 fall=1 targ v(y_invc_16) val=0.75 rise=1 +meas tran d_invc_16_f trig v(in) val=0.75 rise=1 targ v(y_invc_16) val=0.75 fall=1 +meas tran d_invc_32_r trig v(in) val=0.75 fall=1 targ v(y_invc_32) val=0.75 rise=1 +meas tran d_invc_32_f trig v(in) val=0.75 rise=1 targ v(y_invc_32) val=0.75 fall=1 +.endc +.end diff --git a/specs/analog/decks/sar_logic_ref.cir b/specs/analog/decks/sar_logic_ref.cir new file mode 100644 index 0000000000..a31a9ba792 --- /dev/null +++ b/specs/analog/decks/sar_logic_ref.cir @@ -0,0 +1,96 @@ +SAR_logic and clock_gen of the golden SAR deck at transistor level, PSP103 tt at 50 C, ideal clks and Valid pulse trains +* specs/analog/decks/sar_logic_ref.cir -- reference data for specs/analog/sar_logic.t27 (M4d, #10754, epic #10486). The digital part of the golden SAR +* ADC deck (uberWAVE wp2-parser/golden/top.spice, GPL-2.0: fetched at run time, never vendored), at TRANSISTOR level with the IHP SG13G2 PSP103 model: +* x7 (SAR_logic: eleven dff, each four tgate + four nor + two inverter) and x4 (clock_gen: nor3, delay_cell, inverter), wired as the golden top wires +* them (D = VDD, Set = 0, the trim code A B C = 0 0 0, and the golden top's 4 fF on CK1, 8 fF on clkc, 12 fF on Valid). The comparator and the DAC +* are NOT here: Valid is an ideal source, and no CK net carries the DAC's load (the golden top hangs DAC_control_top and the bottom-plate logic on +* them), so the CK edges below are the sequencer's own. Everything but the two ideal sources is the deck's own subcircuits. +* Paths are NOT in this file: ngspice reads IHP_PDK (the IHP-Open-PDK checkout), IHP_OSDI (the directory with the compiled psp103.osdi and +* psp103_nqs.osdi) and SAR_GOLDEN_SUBCKTS (the subcircuit section of the golden deck, made at run time: +* sed -n '/^.subckt tracking_switch/,$p' top.spice | grep -v -i -E '^.(end|global)' > $SAR_GOLDEN_SUBCKTS ) from the environment. +* Run: IHP_PDK= IHP_OSDI= SAR_GOLDEN_SUBCKTS= ngspice -b sar_logic_ref.cir (ngspice-47) +* Stimulus: clks is high from 0.5 ns to 31.5 ns (the reset, 50 % points) and low after; Valid rises (50 %) at 80.1 ns and every 40 ns after, +* eleven pulses, 20 ns wide, falling (50 %) at 100.3 ns and every 40 ns after, edges 0.2 ns. Eleven pulses are one SAR conversion: CK11 .. CK1 rise one +* per pulse. It prints one line per measure, = : tclksf (clks falls), tv / tvf (Valid rises / falls, i = 1 .. 11), +* tck (CK rises, k = 11 .. 1), tcr / tcf (clkc rises / falls, i = 1 .. 11). All at 0.75 V, half of the supply. +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +.include $SAR_GOLDEN_SUBCKTS +VDD vdd 0 1.5 +Vclks clks 0 PULSE(0 1.5 0n 1n 1n 30n 1u) +Vvalid valid 0 PULSE(0 1.5 80n 0.2n 0.2n 20n 40n) +Vd d 0 1.5 +Vset set 0 0 +Va a 0 0 +Vb b 0 0 +Vc c 0 0 +x7 ck3 ck11 ck7 ck2 ck6 ck10 ck1 ck9 ck5 ck8 ck4 vdd 0 d valid set clks SAR_logic +x4 vdd clkc 0 clks valid ck1 a b c clock_gen +Cvalid valid 0 12f +Cck1 ck1 0 4f +Cclkc clkc 0 8f +.temp 50 +.options reltol=1e-3 +.tran 5p 520n 0 50p +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=7 +run +meas tran tclksf when v(clks)=0.75 fall=1 +meas tran tv1 when v(valid)=0.75 rise=1 +meas tran tv2 when v(valid)=0.75 rise=2 +meas tran tv3 when v(valid)=0.75 rise=3 +meas tran tv4 when v(valid)=0.75 rise=4 +meas tran tv5 when v(valid)=0.75 rise=5 +meas tran tv6 when v(valid)=0.75 rise=6 +meas tran tv7 when v(valid)=0.75 rise=7 +meas tran tv8 when v(valid)=0.75 rise=8 +meas tran tv9 when v(valid)=0.75 rise=9 +meas tran tv10 when v(valid)=0.75 rise=10 +meas tran tv11 when v(valid)=0.75 rise=11 +meas tran tvf1 when v(valid)=0.75 fall=1 +meas tran tvf2 when v(valid)=0.75 fall=2 +meas tran tvf3 when v(valid)=0.75 fall=3 +meas tran tvf4 when v(valid)=0.75 fall=4 +meas tran tvf5 when v(valid)=0.75 fall=5 +meas tran tvf6 when v(valid)=0.75 fall=6 +meas tran tvf7 when v(valid)=0.75 fall=7 +meas tran tvf8 when v(valid)=0.75 fall=8 +meas tran tvf9 when v(valid)=0.75 fall=9 +meas tran tvf10 when v(valid)=0.75 fall=10 +meas tran tvf11 when v(valid)=0.75 fall=11 +meas tran tck11 when v(ck11)=0.75 rise=LAST +meas tran tck10 when v(ck10)=0.75 rise=LAST +meas tran tck9 when v(ck9)=0.75 rise=LAST +meas tran tck8 when v(ck8)=0.75 rise=LAST +meas tran tck7 when v(ck7)=0.75 rise=LAST +meas tran tck6 when v(ck6)=0.75 rise=LAST +meas tran tck5 when v(ck5)=0.75 rise=LAST +meas tran tck4 when v(ck4)=0.75 rise=LAST +meas tran tck3 when v(ck3)=0.75 rise=LAST +meas tran tck2 when v(ck2)=0.75 rise=LAST +meas tran tck1 when v(ck1)=0.75 rise=LAST +meas tran tcr1 when v(clkc)=0.75 rise=1 +meas tran tcr2 when v(clkc)=0.75 rise=2 +meas tran tcr3 when v(clkc)=0.75 rise=3 +meas tran tcr4 when v(clkc)=0.75 rise=4 +meas tran tcr5 when v(clkc)=0.75 rise=5 +meas tran tcr6 when v(clkc)=0.75 rise=6 +meas tran tcr7 when v(clkc)=0.75 rise=7 +meas tran tcr8 when v(clkc)=0.75 rise=8 +meas tran tcr9 when v(clkc)=0.75 rise=9 +meas tran tcr10 when v(clkc)=0.75 rise=10 +meas tran tcr11 when v(clkc)=0.75 rise=11 +meas tran tcf1 when v(clkc)=0.75 fall=1 +meas tran tcf2 when v(clkc)=0.75 fall=2 +meas tran tcf3 when v(clkc)=0.75 fall=3 +meas tran tcf4 when v(clkc)=0.75 fall=4 +meas tran tcf5 when v(clkc)=0.75 fall=5 +meas tran tcf6 when v(clkc)=0.75 fall=6 +meas tran tcf7 when v(clkc)=0.75 fall=7 +meas tran tcf8 when v(clkc)=0.75 fall=8 +meas tran tcf9 when v(clkc)=0.75 fall=9 +meas tran tcf10 when v(clkc)=0.75 fall=10 +meas tran tcf11 when v(clkc)=0.75 fall=11 +.endc +.end diff --git a/specs/analog/decks/sar_logic_swap.cir b/specs/analog/decks/sar_logic_swap.cir new file mode 100644 index 0000000000..ced3a2b250 --- /dev/null +++ b/specs/analog/decks/sar_logic_swap.cir @@ -0,0 +1,56 @@ +SAR_logic with one dff's Set and Reset swapped, transistor level, PSP103 tt at 50 C: the negative control of sar_logic.t27 +* specs/analog/decks/sar_logic_swap.cir -- reference data for specs/analog/sar_logic.t27 (M4d, #10754, epic #10486). The same bench as sar_logic_ref.cir +* (golden SAR deck, uberWAVE wp2-parser/golden/top.spice, GPL-2.0, fetched at run time; ideal clks and eleven Valid pulses; the sequencer's own CK edges), +* with ONE change: the subcircuit sar_swap below is SAR_logic written out again, eleven dff on the same nets, except that the fifth dff (x5, the one that +* drives CK7) has its Reset and Set pins exchanged: Reset = Set (a constant 0) and Set = clks. While clks is high that dff is SET instead of reset, and it is +* never reset. The dff, tgate, nor and inverter are the golden deck's own. +* Paths are NOT in this file: IHP_PDK, IHP_OSDI and SAR_GOLDEN_SUBCKTS from the environment (see sar_gate_arcs.cir). +* Run: IHP_PDK= IHP_OSDI= SAR_GOLDEN_SUBCKTS= ngspice -b sar_logic_swap.cir (ngspice-47) +* It prints = : tck7r1 / tck7r2 (CK7 rises, 1st and 2nd), tck7f1 (CK7 falls), tck6r1 / tck6r2 / tck6f1 (CK6), tck11 (CK11 rises). +* What it shows, 50 % points: CK7 is high from the reset (rise 1, 0.5 ns), falls at the first Valid edge (80.1 ns -> 80.6 ns) because the stage takes its D = 0, and rises +* again at its own turn (the fifth edge); CK6, whose D is CK7, rises at the FIRST edge (it sees D = 1), falls at the second and rises at its own turn (the sixth). +.lib $IHP_PDK/ihp-sg13g2/libs.tech/ngspice/models/cornerMOSlv.lib mos_tt +.include $SAR_GOLDEN_SUBCKTS +.subckt sar_swap CK3 CK11 CK7 CK2 CK6 CK10 CK1 CK9 CK5 CK8 CK4 VDD VSS D Valid Set clks +x1 VDD VSS CK11 Valid net1 D clks Set dff +x2 VDD VSS CK10 Valid net2 CK11 clks Set dff +x3 VDD VSS CK9 Valid net3 CK10 clks Set dff +x4 VDD VSS CK8 Valid net4 CK9 clks Set dff +x5 VDD VSS CK7 Valid net5 CK8 Set clks dff +x6 VDD VSS CK6 Valid net6 CK7 clks Set dff +x7 VDD VSS CK5 Valid net7 CK6 clks Set dff +x8 VDD VSS CK4 Valid net8 CK5 clks Set dff +x9 VDD VSS CK3 Valid net9 CK4 clks Set dff +x10 VDD VSS CK2 Valid net10 CK3 clks Set dff +x11 VDD VSS CK1 Valid net11 CK2 clks Set dff +.ends +VDD vdd 0 1.5 +Vclks clks 0 PULSE(0 1.5 0n 1n 1n 30n 1u) +Vvalid valid 0 PULSE(0 1.5 80n 0.2n 0.2n 20n 40n) +Vd d 0 1.5 +Vset set 0 0 +Va a 0 0 +Vb b 0 0 +Vc c 0 0 +x7 ck3 ck11 ck7 ck2 ck6 ck10 ck1 ck9 ck5 ck8 ck4 vdd 0 d valid set clks sar_swap +x4 vdd clkc 0 clks valid ck1 a b c clock_gen +Cvalid valid 0 12f +Cck1 ck1 0 4f +Cclkc clkc 0 8f +.temp 50 +.options reltol=1e-3 +.tran 5p 520n 0 50p +.control +pre_osdi $IHP_OSDI/psp103.osdi +pre_osdi $IHP_OSDI/psp103_nqs.osdi +set numdgt=7 +run +meas tran tck11 when v(ck11)=0.75 rise=LAST +meas tran tck7r1 when v(ck7)=0.75 rise=1 +meas tran tck7r2 when v(ck7)=0.75 rise=2 +meas tran tck7f1 when v(ck7)=0.75 fall=1 +meas tran tck6r1 when v(ck6)=0.75 rise=1 +meas tran tck6r2 when v(ck6)=0.75 rise=2 +meas tran tck6f1 when v(ck6)=0.75 fall=1 +.endc +.end diff --git a/specs/analog/sar_logic.t27 b/specs/analog/sar_logic.t27 new file mode 100644 index 0000000000..38995a8f15 --- /dev/null +++ b/specs/analog/sar_logic.t27 @@ -0,0 +1,1229 @@ +// SPDX-License-Identifier: Apache-2.0 +// specs/analog/sar_logic.t27 -- the SAR ADC sequencer and its asynchronous clock, GATE LEVEL, from the netlist, against ngspice PSP103 +// M4d of the SAR ADC milestone (#10524, epic #10486, issue #10754). The digital half of the golden SAR deck (uberWAVE +// wp2-parser/golden/top.spice, GPL-2.0: read at run time, never vendored). THE LOGIC IS MODELLED AT GATE LEVEL (a gate is a delay and a +// truth table, a transmission gate is a switch with a stored node) and NOTHING HERE IS TRANSISTOR LEVEL: the transistors of the gates are in the +// ngspice decks that measure the gates and judge this model. The comparator and the DAC are not here (M4e composes them): Valid is an input. +// - +// THE CIRCUIT. SAR_logic is eleven `dff` in a chain: x1 has D = D, Q = CK11, CLK = Valid; xk has D = the CK of the stage above and Q = the next CK +// down (CK11 .. CK1); every dff has Reset = clks and Set = Set. At the top of the golden deck D = VDD, Set = 0 and clks is the track/hold clock: while +// clks is high every Q is held at 0; when it falls, each rising edge of Valid shifts a 1 one stage down. With D tied to VDD the chain FILLS, it +// does not walk: after n Valid edges CK11 .. CK(12-n) are high and stay high until clks rises (a thermometer code, not a one-hot walk). A dff is +// a master/slave latch of four `tgate`, four `nor` and two `inverter`: the master (x1 transparent while CLK is low, x9 its keeper while CLK is +// high), the slave (x3 transparent while CLK is high, x7 its keeper while CLK is low), x2 and x8 the master's NORs with Set and Reset, x4 and x5 the +// slave's, x6 the nQ inverter and x10 the clock inverter. clock_gen makes the comparator clock without a clock source: nor3(clks, Valid, CK1) -> +// delay_cell (trimmed by A, B, C; the golden top ties A = B = C = 0) -> inverter -> clkc. The clock restarts itself after each Valid pulse and stops +// once CK1 is high. The gates are the deck's own subcircuits inverter, nor, nor3, tgate and delay_cell (and nand, which neither block uses). +// - +// THE NETLIST, NOT RE-TYPED (FR-002). The instance list comes from the t27o SPICE front end: tools::t27o::spice_convert::emit_gates reads the +// hierarchy of a named subcircuit down to its gates and lists, for each, the kind and its nets (nets 0, 1, 2 .. the ports of the root, then the +// others in the order met, an internal net named .). THIS FILE holds the wiring of SAR_logic, dff and clock_gen and the two +// lines of the golden top that instantiate them, as netlist text (the facts of the circuit: which gate sits on which net; the gates carry their +// ports only, no transistors) and the model is built from what the converter returns for it; no instance table is typed here. The wiring is the +// deck's: `tri t27o` on the golden deck's subcircuit section gives the same table (the receipt is in the PR). +// - +// THE GATE MODEL. A net has the value 0, 1 or X. A gate drives its output net through a delay: when its inputs ask for a new output, the change +// is scheduled one propagation delay later, and withdrawn if the inputs revert first (an INERTIAL delay: a glitch shorter than the delay is +// swallowed). The delay of an arc is d0 + k * FO: the arc is (kind, the input pin that changed, the output edge), FO the load of the output net in +// units of ONE INVERTER INPUT (1.8 um^2 of gate): the sum of the loads of the pins on the net (an inverter, nor, nand or nor3 input is 1, a tgate's +// PMOS gate 0.75, its NMOS gate 0.25, its in and out terminals 0.15 each) plus the capacitance the top puts there (4 fF = 0.3, 8 fF = 0.6). d0 and k +// are MEASURED (specs/analog/decks/sar_gate_arcs.cir: every gate driving 1, 4 and 16 inverter inputs, ngspice-47, IHP SG13G2 PSP103, mos_tt, 50 C), +// least squares over the three loads; SL_NG_ARC pins the measured points and a test holds the fit to them. Not modelled: the slope of the input edge. +// - +// THE SWITCH. A `tgate` (CLK, nCLK, In, Out): CLK is the gate of its PMOS, nCLK the gate of its NMOS. The PMOS is on while CLK is 0, the NMOS while +// nCLK is 1. A node driven through a conducting tgate takes its driver's value; a node that no gate and no conducting tgate drives HOLDS its value +// (dynamic storage: this is how the master and the slave remember). Drive strengths: a gate's output is strong; a tgate with both devices on passes +// a strong 0 or 1; with one device on it passes strongly what that device passes well (an NMOS a 0, a PMOS a 1) and WEAKLY the other. A strong driver +// beats a weak one; two drivers of equal strength and different value, or an unknown, make X. The dff needs this: in the clock-skew window the +// slave's x3 (NMOS on first) pulls net3 low while the keeper x7 (its NMOS still on until nCLK falls) pushes a weak 1, and net3 must go low. A tgate +// is used In -> Out only in this circuit (checked by a test: every tgate In is driven by a gate or by an input). Not modelled: the threshold drop of +// a weak level (a weak 1 is a 1), charge sharing, leakage (a stored node never decays). +// - +// THE EVENT STEP. Time is an integer number of picoseconds, the quantum is 1 ps and no delay is shorter. The run advances from event time to event +// time: stimulus edges and matured gate events at one time are applied TOGETHER, the nets are resolved, then every gate is evaluated against the new +// state in table order and its change scheduled. So two events at one quantum never depend on the order of the instances. TIES: events at the same +// ps are simultaneous; two drivers that disagree at that time give X (the same rule as a contention). A tgate that keeps its value and only changes +// strength (one of its devices turns off or on) takes 1 ps: the value does not travel, and without this rule the slave's keeper still drives a strong +// 1 for the 83 ps the new value needs to arrive, and the dff goes unknown. At t = 0 every net is X; the inputs are applied, and the circuit resolves +// itself from X through the reset (a test: no net is X after the reset). +// - +// THE DELAY CELL. Its delay is ANALOG-ISH and is TABULATED, not computed: SL_DC_FALL / SL_DC_RISE hold, for each trim code 4 A + 2 B + C, the time from +// the 50 % point of its input to the 50 % point of its output when the input has been still for tens of ns (specs/analog/decks/sar_delay_cell.cir: +// nor3 -> delay_cell -> inverter as in clock_gen, eight codes). The falling output (a slow, trimmable NMOS stack) takes 8.09 ns at code 000 and +// 1.73 ns at 111, depends on how many of A, B, C are set (1, 2 and 4 give the same time), the rising output (a PMOS) takes 10.5 ns for every code. +// The delay cell's INPUT is not a gate capacitance: its long channels make the nor3 slower than 1.8 um^2 per input would, so the nor3 sees SL_DC_LOAD_R +// and SL_DC_LOAD_F inverter inputs for a rising and a falling edge, MEASURED from the same deck (the nor3's 7.43 ns and 2.71 ns at code 000), i.e. +// calibrated, not derived. The nor3's other arcs and the delay table are then tested on different stimuli (Valid, the 8 codes). +// - +// THE REFERENCE. ngspice-47, IHP SG13G2 PSP103 (the lv models, mos_tt) at 50 C (the golden deck's own temperature), transistor level, the deck's subcircuits. +// PSP103, not LEVEL=1, is the reference: the issue text says LEVEL=1, but the IHP models run here (M3b), they are what the golden deck itself uses and +// they are the better reference. Four decks in specs/analog/decks, each a bench of my own wired to the golden subcircuits: +// - sar_gate_arcs.cir: every gate driving 1, 4 and 16 inverter inputs (and the inverter driving 4 .. 32 fF): SL_ARC, SL_NG_ARC, SL_EXT_*. +// - sar_delay_cell.cir: nor3 -> delay_cell -> inverter for the eight trim codes: SL_DC_*, SL_NG_TOT_*. +// - sar_logic_ref.cir: SAR_logic and clock_gen wired as the golden top wires them (D = VDD, Set = 0, A = B = C = 0, 4 fF on CK1, 8 fF on clkc, 12 fF on +// Valid), ideal clks and eleven Valid pulses: the 50 % crossings SL_NG_TV .. SL_NG_TCF. +// - sar_logic_swap.cir: the same with the fifth dff's Set and Reset exchanged: SL_NG_SW_*. +// The decks read the golden subcircuits at run time: IHP_PDK, IHP_OSDI and SAR_GOLDEN_SUBCKTS are environment variables (no path is committed). +// SAR_GOLDEN_SUBCKTS is the subcircuit section of the fetched deck: +// sed -n '/^.subckt tracking_switch/,$p' top.spice | grep -v -i -E '^.(end|global)' > $SAR_GOLDEN_SUBCKTS +// - +// WHAT A PASS SAYS (each is a test below). (1) The converter hands this file 113 gates in 100 nets (23 inverter, 44 nor, 1 nor3, 44 tgate, 1 delay_cell), +// every tgate read In -> Out with a stored Out. (2) After the reset every CK is 0 and no net is X. (3) Eleven Valid pulses make CK11 .. CK1 rise ONCE +// each, in that order, the same order as ngspice, and they stay up; each edge is within SL_CK_BOUND_PM permille and SL_CK_ABS_PS ps of ngspice's delay +// from its Valid edge. (4) The gate arcs reproduce the 60 measured points of sar_gate_arcs.cir within SL_ARC_ABS_PS ps. (5) For each of the 8 trim +// codes the model's clks -> clkc delay, rising and falling, is within SL_DC_BOUND_PM permille of ngspice (sar_delay_cell.cir). (6) clkc rises after +// clks falls and makes one rise and one fall per Valid pulse (eleven falls, ten more rises: CK1 holds the last back), the first rise and the first fall +// within SL_CLKC_BOUND_PM permille of ngspice, the later edges within SL_REPEAT_R_BOUND_PM (rises) and SL_REPEAT_F_BOUND_PM (falls, wide: see below). +// (7) NEGATIVE CONTROL: a dff with Set and Reset swapped fails the order check, and the model shows what ngspice shows: CK7 high from the reset, falling at +// the first Valid edge and rising at its turn, CK6 rising a stage early. (8) A node only a tgate drives holds its value when the tgate turns off; a pulse +// shorter than the gate's delay is swallowed. +// - +// WHAT IT DOES NOT SAY. Not a transistor-level model of the logic, and no speed claim: nothing here is compared with how long ngspice takes. The CK +// edges are the sequencer's own: no CK net carries the DAC's load, and the comparator closes the loop with a Valid of its own, not an ideal one. THE CK +// EDGES COME OUT ABOUT 17 % EARLY (458 ps against 548 ps from the Valid edge; CK1 625 ps against 654 ps): the model has no contention slowdown in the +// skew window and no input-slope term, and the bound says so. A CK FALLING (negative control only) comes 22 % early. THE REPEATED clkc EDGES ARE NOT +// RIGHT: the delay cell is history dependent. When Valid rises for the second time ngspice has the delay cell's input at 1.05 V and its output still at +// 0.39 V, still falling (in the first cycle: 1.35 V and 0.08 V), and the output then rises in 6.1 ns, not the 10.5 ns of a settled input. A table of +// settled delays can not follow that: in the repeated cycles ngspice's falling edge comes 7.0 ns after Valid rises, the table says 13.2 ns, 83 to +// 127 % too late; the rising edges are 6 to 11 % late. SL_REPEAT_F_BOUND_PM says how wrong the model +// is allowed to be there, and the PR lists every error. A conversion needs the self-timed loop closed through the comparator; that is M4e. A weak level +// is not degraded and a stored node does not leak. The delay cell input load and the 4 fF and 8 fF equivalents are fitted, not derived (above). +// ASCII only, English only. +// phi^2 + 1/phi^2 = 3 | TRINITY + +module AnalogSarLogic; + +use tools::t27o::spice_convert::{emit_gates, NONE, ERR_LINE, GT_NGATE, GT_NNET, GT_NET0, GT_GATE0, GT_PER_KIND, GATE_SLOTS, G_INV, G_NAND, G_NOR, G_NOR3, G_TGATE, G_DELAY}; + +// ---------------------------------------------------------------- the numbers + +/// An output edge: falling or rising. The table rows below list the falling arc first. +pub const SL_FALL : usize = 0; +pub const SL_RISE : usize = 1; + +/// What a gate drives onto its output net: a strong 0, a strong 1, an unknown, nothing (a tgate that is off), a weak 0, a weak 1. +pub const SL_S0 : usize = 0; +pub const SL_S1 : usize = 1; +pub const SL_X : usize = 2; +pub const SL_Z : usize = 3; +pub const SL_W0 : usize = 4; +pub const SL_W1 : usize = 5; + +/// The arcs, one row of four per arc: the falling output's d0 (ps) and k (0.1 ps per inverter input), then the rising output's. Rows: 0 inverter; +/// 1, 2 nand A, B; 3, 4 nor A, B; 5, 6, 7 nor3 A, B, C; 8 a tgate's pass (In changes while it conducts); 9 a tgate's enable (a gate pin changes). +/// MEASURED: sar_gate_arcs.cir, delay from the input's 50 % point to the output's 50 % point, fitted over the loads of 1, 4 and 16 inverter inputs. +pub const SL_ARC : [40]usize = [ + 64, 327, 48, 719, + 91, 606, 58, 719, + 95, 607, 74, 716, + 100, 329, 113, 1446, + 73, 328, 64, 1449, + 110, 343, 239, 2155, + 108, 329, 197, 2155, + 77, 327, 80, 2159, + 40, 246, 17, 449, + 49, 297, 85, 527 +]; + +/// The measured points the rows were fitted to, ps: for each arc row, the falling output at 1, 4 and 16 inverter inputs, then the rising output +/// (so six numbers a row, in the order of the rows above). +pub const SL_NG_ARC : [60]usize = [ + 96, 196, 588, 130, 323, 1200, + 150, 336, 1061, 139, 333, 1210, + 154, 340, 1066, 156, 348, 1223, + 131, 233, 625, 272, 674, 2431, + 105, 205, 597, 223, 627, 2386, + 139, 252, 658, 462, 1092, 3689, + 140, 241, 634, 419, 1050, 3647, + 109, 208, 600, 302, 937, 3536, + 66, 136, 434, 75, 180, 738, + 76, 171, 524, 125, 312, 925 +]; + +/// The delay cell, by trim code 4 A + 2 B + C: the falling output (the input rising) and the rising output, ps. MEASURED: sar_delay_cell.cir. +pub const SL_DC_FALL : [8]usize = [8087, 5746, 5746, 4107, 5746, 4107, 4107, 1733]; +pub const SL_DC_RISE : [8]usize = [10482, 10545, 10545, 10576, 10545, 10576, 10576, 10581]; +/// The nor3 -> delay_cell -> inverter chain of sar_delay_cell.cir end to end, clks falling to clkc rising and clks rising to clkc falling, ps. +pub const SL_NG_TOT_R : [8]usize = [16147, 13592, 13592, 11875, 13592, 11875, 11875, 9669]; +pub const SL_NG_TOT_F : [8]usize = [13305, 13479, 13479, 13609, 13479, 13609, 13609, 13712]; +/// What the nor3 sees at the delay cell's input, in thousandths of an inverter input, for a rising and a falling edge (calibrated, see the header). +pub const SL_DC_LOAD_R : usize = 33380; +pub const SL_DC_LOAD_F : usize = 75700; + +/// The capacitance of the golden top on CK1 (4 fF) and clkc (8 fF), in thousandths of an inverter input: what the inverter's delay at that cap equals. +pub const SL_EXT_4F : usize = 300; +pub const SL_EXT_8F : usize = 600; + +/// ngspice's crossings (sar_logic_ref.cir), ps: Valid rising and falling (eleven pulses), clks falling, CK11 .. CK1 rising, clkc rising and falling. +pub const SL_NG_TV : [11]usize = [80100, 120100, 160100, 200100, 240100, 280100, 320100, 360100, 400100, 440100, 480100]; +pub const SL_NG_TVF : [11]usize = [100300, 140300, 180300, 220300, 260300, 300300, 340300, 380300, 420300, 460300, 500300]; +pub const SL_NG_CLKS_F : usize = 31500; +pub const SL_NG_TCK : [11]usize = [80650, 120648, 160648, 200648, 240648, 280648, 320648, 360648, 400648, 440648, 480754]; +pub const SL_NG_TCR : [11]usize = [47732, 114320, 154962, 194987, 234988, 274988, 314988, 354988, 394988, 434988, 474988]; +pub const SL_NG_TCF : [11]usize = [92096, 127307, 167062, 207052, 247052, 287052, 327052, 367052, 407052, 447052, 485907]; + +/// ngspice's crossings for the negative control (sar_logic_swap.cir: the fifth dff, which drives CK7, has Set and Reset exchanged), ps. CK7 is high from the +/// reset (its first rise, 527 ps, is the reset, not an edge of the run), falls at the first Valid edge and rises at its own turn; CK6 follows it. +pub const SL_NG_SW_CK7_F : usize = 80635; +pub const SL_NG_SW_CK7_R2 : usize = 240648; +pub const SL_NG_SW_CK6_R1 : usize = 80648; +pub const SL_NG_SW_CK6_F : usize = 120635; +pub const SL_NG_SW_CK6_R2 : usize = 280648; + +/// The bounds the tests hold the model to. CK edges: permille of ngspice's Valid -> CK delay. Arcs: ps. The delay cell: permille of the end-to-end +/// delay. clkc, the first (settled) edges: permille; the repeated edges: permille of ngspice's delay from the causing Valid edge (wide, the model is +/// wrong there and says so). +pub const SL_CK_BOUND_PM : usize = 200; +pub const SL_CK_ABS_PS : usize = 100; +/// A CK FALLING (only in the negative control: a stage takes a 0): the model's weak 1 charges the node faster than the NMOS of the real tgate, so the +/// fall comes about 22 % early, and the bound is wider. +pub const SL_FALL_BOUND_PM : usize = 250; +pub const SL_FALL_ABS_PS : usize = 150; +pub const SL_ARC_ABS_PS : usize = 25; +pub const SL_DC_BOUND_PM : usize = 60; +pub const SL_CLKC_BOUND_PM : usize = 150; +pub const SL_REPEAT_R_BOUND_PM : usize = 150; +pub const SL_REPEAT_F_BOUND_PM : usize = 1300; +/// Changes before this time (ps) are the reset settling from unknown, not edges of the run. +pub const SL_TMIN : usize = 20000; + +// ---------------------------------------------------------------- the state + +/// Capacities, and where each table sits in the state vector `ms` (zeroed by the caller, SL_SLOTS long). +pub const SL_NETS : usize = 128; +pub const SL_GATES : usize = 160; +pub const SL_SLOTS : usize = 5000; +pub const SL_VAL : usize = 0; +pub const SL_CNT : usize = 128; +pub const SL_EXT : usize = 896; +pub const SL_LDR : usize = 1024; +pub const SL_LDF : usize = 1152; +pub const SL_DRV : usize = 1280; +pub const SL_PT : usize = 1440; +pub const SL_PV : usize = 1600; +pub const SL_PREV : usize = 1760; +pub const SL_WATCH : usize = 2880; +pub const SL_NWATCH : usize = 2896; +pub const SL_NLOG : usize = 2897; +pub const SL_T : usize = 2898; +pub const SL_NSTEP : usize = 2899; +pub const SL_LOG : usize = 2900; +pub const SL_LOGMAX : usize = 640; +pub const SL_SI : usize = 4825; +pub const SL_EXTL : usize = 4830; +pub const SL_MAXSTEP : usize = 200000; + +/// The kind of gate g, and its pin p (a net), in the converter's table. +pub fn sl_kind(gt: []const usize, g: usize) -> usize { + return gt[GT_GATE0 + g * GATE_SLOTS]; +} + +pub fn sl_pin(gt: []const usize, g: usize, p: usize) -> usize { + return gt[GT_GATE0 + g * GATE_SLOTS + 1 + p]; +} + +/// The pin that is the output of a gate of this kind: the one after its inputs (inverter 1, nand and nor 2, nor3 and tgate 3, delay_cell 4). +pub fn sl_out_pin(kind: usize) -> usize { + return sl_n_in(kind); +} + +/// How many input pins a gate of this kind has (a tgate: its two gates and In). +pub fn sl_n_in(kind: usize) -> usize { + if (kind == G_INV) { return 1; } + if (kind == G_NAND or kind == G_NOR) { return 2; } + if (kind == G_NOR3 or kind == G_TGATE) { return 3; } + return 4; +} + +/// What pin p of a gate of this kind loads its net with, in thousandths of an inverter input, when the driver's output edge is `dir`. +pub fn sl_pin_load(kind: usize, p: usize, dir: usize) -> usize { + if (kind == G_INV) { + if (p == 0) { return 1000; } + return 0; + } + if (kind == G_NAND or kind == G_NOR) { + if (p < 2) { return 1000; } + return 0; + } + if (kind == G_NOR3) { + if (p < 3) { return 1000; } + return 0; + } + if (kind == G_TGATE) { + if (p == 0) { return 750; } + if (p == 1) { return 250; } + if (p == 2 or p == 3) { return 150; } + return 0; + } + if (p == 0) { + if (dir == SL_RISE) { return SL_DC_LOAD_R; } + return SL_DC_LOAD_F; + } + if (p < 4) { return 250; } + return 0; +} + +/// The row of SL_ARC for a gate of this kind whose input pin `p` changed (a tgate: p 2 is the pass arc, 0 and 1 the enable arc). +pub fn sl_row(kind: usize, p: usize) -> usize { + if (kind == G_INV) { return 0; } + if (kind == G_NAND) { return 1 + p; } + if (kind == G_NOR) { return 3 + p; } + if (kind == G_NOR3) { return 5 + p; } + if (p == 2) { return 8; } + return 9; +} + +/// The delay d0 + k * FO of one arc row for the output edge `dir` and the load `load` in thousandths of an inverter input, ps. +pub fn sl_arc(row: usize, dir: usize, load: usize) -> usize { + const base : usize = row * 4 + dir * 2; + return SL_ARC[base] + SL_ARC[base + 1] * load / 10000; +} + +// ---------------------------------------------------------------- the logic + +/// The code a gate drives for the inverse of v (X stays X). +pub fn sl_not(v: usize) -> usize { + if (v == 0) { return SL_S1; } + if (v == 1) { return SL_S0; } + return SL_X; +} + +/// NOR of three inputs (a missing input is passed as 0). +pub fn sl_nor_code(a: usize, b: usize, c: usize) -> usize { + if (a == 1 or b == 1 or c == 1) { return SL_S0; } + if (a == 0 and b == 0 and c == 0) { return SL_S1; } + return SL_X; +} + +pub fn sl_nand_code(a: usize, b: usize) -> usize { + if (a == 0 or b == 0) { return SL_S1; } + if (a == 1 and b == 1) { return SL_S0; } + return SL_X; +} + +/// What a tgate drives: pm is the value of the gate of its PMOS (CLK), nm that of its NMOS (nCLK), x its In. The PMOS is on at 0, the NMOS at 1; +/// both on pass x strongly; one on passes strongly what it passes well (an NMOS a 0, a PMOS a 1) and weakly the other; none on drives nothing. +pub fn sl_tg_code(pm: usize, nm: usize, x: usize) -> usize { + if (pm == 2 or nm == 2) { return SL_X; } + const pon : bool = (pm == 0); + const non : bool = (nm == 1); + if (!pon and !non) { return SL_Z; } + if (x == 2) { return SL_X; } + if (pon and non) { + if (x == 0) { return SL_S0; } + return SL_S1; + } + if (non) { + if (x == 0) { return SL_S0; } + return SL_W1; + } + if (x == 1) { return SL_S1; } + return SL_W0; +} + +/// What gate g wants to drive, from the values on its input nets. +pub fn sl_want(ms: []const usize, gt: []const usize, g: usize) -> usize { + const kind : usize = sl_kind(gt, g); + const v0 : usize = ms[SL_VAL + sl_pin(gt, g, 0)]; + if (kind == G_INV) { return sl_not(v0); } + const v1 : usize = ms[SL_VAL + sl_pin(gt, g, 1)]; + if (kind == G_NAND) { return sl_nand_code(v0, v1); } + if (kind == G_NOR) { return sl_nor_code(v0, v1, 0); } + const v2 : usize = ms[SL_VAL + sl_pin(gt, g, 2)]; + if (kind == G_NOR3) { return sl_nor_code(v0, v1, v2); } + if (kind == G_TGATE) { return sl_tg_code(v0, v1, v2); } + return sl_not(v0); +} + +/// The net a gate drives. +pub fn sl_out_net(gt: []const usize, g: usize) -> usize { + return sl_pin(gt, g, sl_out_pin(sl_kind(gt, g))); +} + +// ---------------------------------------------------------------- the delay of a change + +/// The trim code of a delay cell: 4 A + 2 B + C from its three pins (an unknown counts as 0). +pub fn sl_code(ms: []const usize, gt: []const usize, g: usize) -> usize { + var c : usize = 0; + if (ms[SL_VAL + sl_pin(gt, g, 1)] == 1) { c = c + 4; } + if (ms[SL_VAL + sl_pin(gt, g, 2)] == 1) { c = c + 2; } + if (ms[SL_VAL + sl_pin(gt, g, 3)] == 1) { c = c + 1; } + return c; +} + +/// The input pin that made gate g change: the first whose value differs from the one at the last evaluation (a tgate: 0 for the enable arc when +/// either gate pin changed, else 2); the first pin when none did. +pub fn sl_trigger(ms: []const usize, gt: []const usize, g: usize) -> usize { + const kind : usize = sl_kind(gt, g); + const base : usize = SL_PREV + g * 7; + var j : usize = 0; + var hit : usize = NONE; + while (j < sl_n_in(kind)) { + if (hit == NONE and ms[base + j] != ms[SL_VAL + sl_pin(gt, g, j)]) { hit = j; } + j = j + 1; + } + if (hit == NONE) { hit = 0; } + if (kind == G_TGATE and hit == 1) { hit = 0; } + return hit; +} + +/// The propagation delay, ps, of gate g changing to `want`: an unknown or a release is 1 ps; a delay cell reads its table; the others d0 + k * FO. +pub fn sl_delay(ms: []const usize, gt: []const usize, g: usize, ask: usize) -> usize { + var dir : usize = SL_FALL; + var known : bool = false; + if (ask == SL_S1 or ask == SL_W1) { + dir = SL_RISE; + known = true; + } + if (ask == SL_S0 or ask == SL_W0) { known = true; } + if (!known) { return 1; } + const kind : usize = sl_kind(gt, g); + const cur : usize = ms[SL_DRV + g]; + // a tgate that keeps its value and only changes strength (one of its devices turns off or on) takes 1 ps: the value does not travel + if (kind == G_TGATE) { + if ((cur == SL_S1 or cur == SL_W1) and (ask == SL_S1 or ask == SL_W1)) { return 1; } + if ((cur == SL_S0 or cur == SL_W0) and (ask == SL_S0 or ask == SL_W0)) { return 1; } + } + if (kind == G_DELAY) { + const c : usize = sl_code(ms, gt, g); + if (dir == SL_RISE) { return SL_DC_RISE[c]; } + return SL_DC_FALL[c]; + } + const net : usize = sl_out_net(gt, g); + var load : usize = ms[SL_LDF + net]; + if (dir == SL_RISE) { load = ms[SL_LDR + net]; } + if (kind == G_TGATE) { load = load - 150; } + const d : usize = sl_arc(sl_row(kind, sl_trigger(ms, gt, g)), dir, load); + if (d < 1) { return 1; } + return d; +} + +// ---------------------------------------------------------------- the model + +/// Look a net up by name in the converter's table (the names are lower case): its number, NONE when there is no such net. +pub fn sl_net(w: []const u8, gt: []const usize, name: []const u8) -> usize { + var n : usize = 0; + while (n < gt[GT_NNET]) { + const a : usize = gt[GT_NET0 + n * 2]; + const b : usize = gt[GT_NET0 + n * 2 + 1]; + if (b - a == name.len) { + var i : usize = 0; + while (i < name.len and w[a + i] == name[i]) { i = i + 1; } + if (i == name.len) { return n; } + } + n = n + 1; + } + return NONE; +} + +/// The model at t = 0: every net unknown, every gate driving X, nothing pending, no input driven, nothing watched. +pub fn sl_init(ms: []usize, gt: []const usize) -> void { + var n : usize = 0; + while (n < gt[GT_NNET]) { + ms[SL_VAL + n] = SL_X; + n = n + 1; + } + var g : usize = 0; + while (g < gt[GT_NGATE]) { + ms[SL_DRV + g] = SL_X; + ms[SL_PT + g] = NONE; + var j : usize = 0; + while (j < 7) { + ms[SL_PREV + g * 7 + j] = SL_X; + j = j + 1; + } + g = g + 1; + } +} + +/// The loads of every net, from the pins on it and the capacitance the top added (SL_EXTL, set by sl_ext_load before this). +pub fn sl_loads(ms: []usize, gt: []const usize) -> void { + var n : usize = 0; + while (n < gt[GT_NNET]) { + ms[SL_LDR + n] = ms[SL_EXTL + n]; + ms[SL_LDF + n] = ms[SL_EXTL + n]; + n = n + 1; + } + var g : usize = 0; + while (g < gt[GT_NGATE]) { + const kind : usize = sl_kind(gt, g); + var p : usize = 0; + while (p < 4) { + const net : usize = sl_pin(gt, g, p); + if (p < sl_n_in(kind) or (kind == G_TGATE and p == 3)) { + ms[SL_LDR + net] = ms[SL_LDR + net] + sl_pin_load(kind, p, SL_RISE); + ms[SL_LDF + net] = ms[SL_LDF + net] + sl_pin_load(kind, p, SL_FALL); + } + p = p + 1; + } + g = g + 1; + } +} + +pub fn sl_ext_load(ms: []usize, net: usize, mfo: usize) -> void { + ms[SL_EXTL + net] = mfo; +} + +/// Watch a net: every change of its value is logged with the time. +pub fn sl_watch(ms: []usize, net: usize) -> void { + ms[SL_WATCH + ms[SL_NWATCH]] = net; + ms[SL_NWATCH] = ms[SL_NWATCH] + 1; +} + +/// Set the value of net n at time t and log it if the net is watched and the value changed. +pub fn sl_set(ms: []usize, n: usize, v: usize, t: usize) -> void { + if (ms[SL_VAL + n] == v) { return; } + ms[SL_VAL + n] = v; + var i : usize = 0; + while (i < ms[SL_NWATCH]) { + if (ms[SL_WATCH + i] == n and ms[SL_NLOG] < SL_LOGMAX) { + const row : usize = SL_LOG + ms[SL_NLOG] * 3; + ms[row] = t; + ms[row + 1] = n; + ms[row + 2] = v; + ms[SL_NLOG] = ms[SL_NLOG] + 1; + } + i = i + 1; + } +} + +/// An input: net n takes the value v from now on (it has no driver in the circuit). +pub fn sl_drive(ms: []usize, n: usize, v: usize, t: usize) -> void { + ms[SL_EXT + n] = 1; + sl_set(ms, n, v, t); +} + +/// The value a net takes from the drivers counted on it: strong beats weak, equal strength and different value is X, no driver holds `old`. +pub fn sl_resolve(ms: []const usize, n: usize, old: usize) -> usize { + const b : usize = SL_CNT + n * 6; + const s0 : usize = ms[b + SL_S0]; + const s1 : usize = ms[b + SL_S1]; + const sx : usize = ms[b + SL_X]; + const w0 : usize = ms[b + SL_W0]; + const w1 : usize = ms[b + SL_W1]; + if (s0 + s1 + sx > 0) { + if (sx > 0 or (s0 > 0 and s1 > 0)) { return SL_X; } + if (s1 > 0) { return 1; } + return 0; + } + if (w0 + w1 > 0) { + if (w0 > 0 and w1 > 0) { return SL_X; } + if (w1 > 0) { return 1; } + return 0; + } + return old; +} + +/// Schedule or withdraw the change of gate g at time t (the inertial rule), then remember its inputs for the next evaluation. +pub fn sl_sched(ms: []usize, gt: []const usize, g: usize, t: usize) -> void { + const ask : usize = sl_want(ms, gt, g); + const cur : usize = ms[SL_DRV + g]; + if (ms[SL_PT + g] != NONE) { + if (ask != ms[SL_PV + g]) { + if (ask == cur) { + ms[SL_PT + g] = NONE; + } else { + ms[SL_PT + g] = t + sl_delay(ms, gt, g, ask); + ms[SL_PV + g] = ask; + } + } + } else if (ask != cur) { + ms[SL_PT + g] = t + sl_delay(ms, gt, g, ask); + ms[SL_PV + g] = ask; + } + const kind : usize = sl_kind(gt, g); + var j : usize = 0; + while (j < sl_n_in(kind)) { + ms[SL_PREV + g * 7 + j] = ms[SL_VAL + sl_pin(gt, g, j)]; + j = j + 1; + } +} + +/// One event time: the gates whose change matures now take effect, every net is resolved from what drives it, then every gate is evaluated. +pub fn sl_step(ms: []usize, gt: []const usize, t: usize) -> void { + var g : usize = 0; + while (g < gt[GT_NGATE]) { + if (ms[SL_PT + g] == t) { + ms[SL_DRV + g] = ms[SL_PV + g]; + ms[SL_PT + g] = NONE; + } + g = g + 1; + } + var n : usize = 0; + while (n < gt[GT_NNET]) { + var c : usize = 0; + while (c < 6) { + ms[SL_CNT + n * 6 + c] = 0; + c = c + 1; + } + n = n + 1; + } + g = 0; + while (g < gt[GT_NGATE]) { + const o : usize = sl_out_net(gt, g); + ms[SL_CNT + o * 6 + ms[SL_DRV + g]] = ms[SL_CNT + o * 6 + ms[SL_DRV + g]] + 1; + g = g + 1; + } + n = 0; + while (n < gt[GT_NNET]) { + if (ms[SL_EXT + n] == 0) { + sl_set(ms, n, sl_resolve(ms, n, ms[SL_VAL + n]), t); + } + n = n + 1; + } + g = 0; + while (g < gt[GT_NGATE]) { + sl_sched(ms, gt, g, t); + g = g + 1; + } +} + +/// Run to time t_end (a second call continues where the first stopped). The stimulus is nst edges (time, net, value), sorted by time; +/// edges at one time are applied together before the step. +pub fn sl_run(ms: []usize, gt: []const usize, s_t: []const usize, s_n: []const usize, s_v: []const usize, nst: usize, t_end: usize) -> void { + var si : usize = ms[SL_SI]; + var t : usize = ms[SL_T]; + var go : bool = true; + while (go) { + var nxt : usize = NONE; + if (si < nst) { nxt = s_t[si]; } + var g : usize = 0; + while (g < gt[GT_NGATE]) { + if (ms[SL_PT + g] < nxt) { nxt = ms[SL_PT + g]; } + g = g + 1; + } + if (nxt == NONE or nxt > t_end or ms[SL_NSTEP] >= SL_MAXSTEP) { + go = false; + } else { + t = nxt; + while (si < nst and s_t[si] == t) { + sl_drive(ms, s_n[si], s_v[si], t); + si = si + 1; + } + sl_step(ms, gt, t); + ms[SL_NSTEP] = ms[SL_NSTEP] + 1; + } + } + ms[SL_T] = t; + ms[SL_SI] = si; +} + +// ---------------------------------------------------------------- reading the run + +/// How many logged changes of net n went to value v at or after tmin. +pub fn sl_edges(ms: []const usize, n: usize, v: usize, tmin: usize) -> usize { + var c : usize = 0; + var i : usize = 0; + while (i < ms[SL_NLOG]) { + const row : usize = SL_LOG + i * 3; + if (ms[row + 1] == n and ms[row + 2] == v and ms[row] >= tmin) { c = c + 1; } + i = i + 1; + } + return c; +} + +/// The time of the nth (from 1) change of net n to value v at or after tmin, NONE when there is no such change. +pub fn sl_edge(ms: []const usize, n: usize, v: usize, nth: usize, tmin: usize) -> usize { + var c : usize = 0; + var i : usize = 0; + var found : usize = NONE; + while (i < ms[SL_NLOG]) { + const row : usize = SL_LOG + i * 3; + if (ms[row + 1] == n and ms[row + 2] == v and ms[row] >= tmin) { + c = c + 1; + if (c == nth) { found = ms[row]; } + } + i = i + 1; + } + return found; +} + +/// |a - b|. +pub fn sl_absdiff(a: usize, b: usize) -> usize { + if (a > b) { return a - b; } + return b - a; +} + +/// Is the model's delay within SL_CK_BOUND_PM permille and SL_CK_ABS_PS ps of ngspice's? +pub fn sl_within(got: usize, ref_ps: usize) -> bool { + return sl_err_pm(got, ref_ps) <= SL_CK_BOUND_PM and sl_absdiff(got, ref_ps) <= SL_CK_ABS_PS; +} + +/// The same for a falling CK edge (SL_FALL_BOUND_PM, SL_FALL_ABS_PS). +pub fn sl_within_fall(got: usize, ref_ps: usize) -> bool { + return sl_err_pm(got, ref_ps) <= SL_FALL_BOUND_PM and sl_absdiff(got, ref_ps) <= SL_FALL_ABS_PS; +} + +/// The error of `got` against `ref_ps` in permille of `ref_ps`. +pub fn sl_err_pm(got: usize, ask: usize) -> usize { + return sl_absdiff(got, ask) * 1000 / ask; +} + +// ---------------------------------------------------------------- the netlist text + +/// The wiring of SAR_logic, dff and clock_gen and the top's two lines, as netlist text (see the header): the gates carry their ports only. The +/// text is cut in pieces so the swapped-pins negative control can change one line. +pub const SL_DECK_A : str = "SAR logic and clock_gen of the golden SAR deck: the gates (ports only), dff, SAR_logic, clock_gen and the two lines of the top that wire them\n.subckt inverter Vin VDD VSS Vout\n.ends\n.subckt nor A B Vout VDD VSS\n.ends\n.subckt nor3 VDD VSS Vout A B C\n.ends\n.subckt tgate CLK nCLK In Out VDD VSS\n.ends\n.subckt delay_cell OUT VDD VSS IN A B C\n.ends\n.subckt dff VDD VSS Q CLK nQ D Reset Set\nx1 CLK nCLK D net1 VDD VSS tgate\nx2 Set net1 net2 VDD VSS nor\nx3 nCLK CLK net2 net3 VDD VSS tgate\nx4 Reset net3 Q VDD VSS nor\nx5 Q Set net4 VDD VSS nor\nx6 Q VDD VSS nQ inverter\nx7 CLK nCLK net4 net3 VDD VSS tgate\nx8 net2 Reset net5 VDD VSS nor\nx9 nCLK CLK net5 net1 VDD VSS tgate\nx10 CLK VDD VSS nCLK inverter\n.ends\n"; +pub const SL_DECK_B1 : str = ".subckt SAR_logic CK3 CK11 CK7 CK2 CK6 CK10 CK1 CK9 CK5 CK8 CK4 VDD VSS D Valid Set clks\nx1 VDD VSS CK11 Valid net1 D clks Set dff\nx2 VDD VSS CK10 Valid net2 CK11 clks Set dff\nx3 VDD VSS CK9 Valid net3 CK10 clks Set dff\nx4 VDD VSS CK8 Valid net4 CK9 clks Set dff\n"; +pub const SL_X5 : str = "x5 VDD VSS CK7 Valid net5 CK8 clks Set dff\n"; +pub const SL_X5_SWAPPED : str = "x5 VDD VSS CK7 Valid net5 CK8 Set clks dff\n"; +pub const SL_DECK_B2 : str = "x6 VDD VSS CK6 Valid net6 CK7 clks Set dff\nx7 VDD VSS CK5 Valid net7 CK6 clks Set dff\nx8 VDD VSS CK4 Valid net8 CK5 clks Set dff\nx9 VDD VSS CK3 Valid net9 CK4 clks Set dff\nx10 VDD VSS CK2 Valid net10 CK3 clks Set dff\nx11 VDD VSS CK1 Valid net11 CK2 clks Set dff\n.ends\n"; +pub const SL_DECK_C : str = ".subckt clock_gen VDD Vout VSS clks Valid CK1 A B C\nx1 VDD VSS net1 clks Valid CK1 nor3\nx2 net2 VDD VSS net1 A B C delay_cell\nx3 net2 VDD VSS Vout inverter\n.ends\n.subckt sar_clk VDD VSS D Set clks Valid A B C CK11 CK10 CK9 CK8 CK7 CK6 CK5 CK4 CK3 CK2 CK1 clkc\nx7 CK3 CK11 CK7 CK2 CK6 CK10 CK1 CK9 CK5 CK8 CK4 VDD VSS D Valid Set clks SAR_logic\nx4 VDD clkc VSS clks Valid CK1 A B C clock_gen\n.ends\n.end\n"; + +/// Copy the text `lit` into dst at pos; the result is the position after it. +pub fn sl_cat(dst: []u8, pos: usize, lit: []const u8) -> usize { + var i : usize = 0; + while (i < lit.len and pos + i < dst.len) { + dst[pos + i] = lit[i]; + i = i + 1; + } + return pos + i; +} + +/// The whole netlist into dst; with `swapped` the dff x5 of SAR_logic has its Set and Reset pins exchanged (the negative control). +pub fn sl_deck(dst: []u8, swapped: bool) -> usize { + var p : usize = sl_cat(dst, 0, SL_DECK_A); + p = sl_cat(dst, p, SL_DECK_B1); + if (swapped) { p = sl_cat(dst, p, SL_X5_SWAPPED); } else { p = sl_cat(dst, p, SL_X5); } + p = sl_cat(dst, p, SL_DECK_B2); + p = sl_cat(dst, p, SL_DECK_C); + return p; +} + +// ---------------------------------------------------------------- building and driving a run + +/// One stimulus edge (time, net, value) written at index k; the result is k + 1. +pub fn sl_put(s_t: []usize, s_n: []usize, s_v: []usize, k: usize, t: usize, n: usize, v: usize) -> usize { + s_t[k] = t; + s_n[k] = n; + s_v[k] = v; + return k + 1; +} + +/// The nets of the sequencer that the runs use, in `ids`: vdd, vss, d, set, clks, valid, a, b, c, then CK11 .. CK1 (9 .. 19), then clkc (20). +pub fn sl_ids(w: []const u8, gt: []const usize, ids: []usize) -> void { + ids[0] = sl_net(w, gt, "vdd"); + ids[1] = sl_net(w, gt, "vss"); + ids[2] = sl_net(w, gt, "d"); + ids[3] = sl_net(w, gt, "set"); + ids[4] = sl_net(w, gt, "clks"); + ids[5] = sl_net(w, gt, "valid"); + ids[6] = sl_net(w, gt, "a"); + ids[7] = sl_net(w, gt, "b"); + ids[8] = sl_net(w, gt, "c"); + var i : usize = 0; + while (i < 11) { + var nm : [4]u8 = [99, 107, 49, 49]; + var len : usize = 4; + if (i == 1) { nm[3] = 48; } + if (i >= 2) { + len = 3; + nm[2] = 48 + (11 - i) as u8; + } + ids[9 + i] = sl_net(w, gt, nm[0 .. len]); + i = i + 1; + } + ids[20] = sl_net(w, gt, "clkc"); +} + +/// Read the netlist, build the model of `sar_clk` and watch CK11 .. CK1 and clkc. False when the converter refuses the netlist. +pub fn sl_build(src: []u8, w: []u8, out: []u8, st: []usize, gt: []usize, ms: []usize, ids: []usize, swapped: bool) -> bool { + const len : usize = sl_deck(src, swapped); + const n : usize = emit_gates(src[0 .. len], "sar_clk", w, out, st, gt); + if (st[ERR_LINE] != 0 or n == 0) { return false; } + sl_init(ms, gt); + sl_ids(w, gt, ids); + sl_ext_load(ms, ids[19], SL_EXT_4F); + sl_ext_load(ms, ids[20], SL_EXT_8F); + sl_loads(ms, gt); + var i : usize = 0; + while (i < 12) { + sl_watch(ms, ids[9 + i]); + i = i + 1; + } + return true; +} + +/// The stimulus of sar_logic_ref.cir at its measured 50 % points: the inputs at t = 0 with clks high (the reset), clks falling, eleven Valid pulses. +pub fn sl_stim(s_t: []usize, s_n: []usize, s_v: []usize, ids: []const usize) -> usize { + var k : usize = 0; + k = sl_put(s_t, s_n, s_v, k, 0, ids[0], 1); + k = sl_put(s_t, s_n, s_v, k, 0, ids[1], 0); + k = sl_put(s_t, s_n, s_v, k, 0, ids[2], 1); + k = sl_put(s_t, s_n, s_v, k, 0, ids[3], 0); + k = sl_put(s_t, s_n, s_v, k, 0, ids[6], 0); + k = sl_put(s_t, s_n, s_v, k, 0, ids[7], 0); + k = sl_put(s_t, s_n, s_v, k, 0, ids[8], 0); + k = sl_put(s_t, s_n, s_v, k, 0, ids[5], 0); + k = sl_put(s_t, s_n, s_v, k, 0, ids[4], 1); + k = sl_put(s_t, s_n, s_v, k, SL_NG_CLKS_F, ids[4], 0); + var i : usize = 0; + while (i < 11) { + k = sl_put(s_t, s_n, s_v, k, SL_NG_TV[i], ids[5], 1); + k = sl_put(s_t, s_n, s_v, k, SL_NG_TVF[i], ids[5], 0); + i = i + 1; + } + return k; +} + +/// Do CK11 .. CK1 each rise exactly once in the whole run (a rise during the reset counts), never fall after the reset, and rise in the same order +/// as ngspice's rising edges? +pub fn sl_order_ok(ms: []const usize, ids: []const usize) -> bool { + var i : usize = 0; + while (i < 11) { + if (sl_edges(ms, ids[9 + i], 1, 0) != 1) { return false; } + if (sl_edges(ms, ids[9 + i], 0, SL_TMIN) != 0) { return false; } + i = i + 1; + } + i = 0; + while (i < 11) { + var j : usize = 0; + while (j < 11) { + const ri : usize = sl_edge(ms, ids[9 + i], 1, 1, 0); + const rj : usize = sl_edge(ms, ids[9 + j], 1, 1, 0); + if ((ri < rj) != (SL_NG_TCK[i] < SL_NG_TCK[j])) { return false; } + j = j + 1; + } + i = i + 1; + } + return true; +} + +// ---------------------------------------------------------------- tests +// Each test builds the model from the netlist text above: the converter reads it into `gt`, the model runs on `gt`. The buffers are the caller's. + +test the_gate_logic_is_the_truth_table_of_each_kind { + // inverter + assert(sl_not(0) == SL_S1 and sl_not(1) == SL_S0 and sl_not(SL_X) == SL_X); + // nor: any 1 gives 0, all 0 gives 1, else unknown; a missing input is passed as 0 + assert(sl_nor_code(0, 0, 0) == SL_S1); + assert(sl_nor_code(1, 0, 0) == SL_S0 and sl_nor_code(0, 1, 0) == SL_S0 and sl_nor_code(0, 0, 1) == SL_S0); + assert(sl_nor_code(SL_X, 1, 0) == SL_S0); + assert(sl_nor_code(SL_X, 0, 0) == SL_X); + // nand: any 0 gives 1, all 1 gives 0 + assert(sl_nand_code(1, 1) == SL_S0); + assert(sl_nand_code(0, 1) == SL_S1 and sl_nand_code(1, 0) == SL_S1 and sl_nand_code(0, 0) == SL_S1); + assert(sl_nand_code(SL_X, 0) == SL_S1); + assert(sl_nand_code(SL_X, 1) == SL_X); + // tgate (pmos gate, nmos gate, in): both on pass strongly, one on passes its good level strongly and the other weakly, none on is off + assert(sl_tg_code(0, 1, 0) == SL_S0 and sl_tg_code(0, 1, 1) == SL_S1); + assert(sl_tg_code(1, 1, 0) == SL_S0 and sl_tg_code(1, 1, 1) == SL_W1); + assert(sl_tg_code(0, 0, 1) == SL_S1 and sl_tg_code(0, 0, 0) == SL_W0); + assert(sl_tg_code(1, 0, 0) == SL_Z and sl_tg_code(1, 0, 1) == SL_Z); + assert(sl_tg_code(SL_X, 1, 0) == SL_X and sl_tg_code(0, 1, SL_X) == SL_X); +} + +test a_net_takes_the_stronger_driver_holds_when_nothing_drives_and_goes_unknown_on_a_tie { + var ms : [5000]usize = [0] ** 5000; + // net 0: a strong 0 and a weak 1 -> 0; a weak 1 alone -> 1; a strong 0 and a strong 1 -> X; nothing -> the old value + ms[SL_CNT + SL_S0] = 1; + ms[SL_CNT + SL_W1] = 1; + assert(sl_resolve(&ms, 0, 1) == 0); + ms[SL_CNT + SL_S0] = 0; + assert(sl_resolve(&ms, 0, 0) == 1); + ms[SL_CNT + SL_W0] = 1; + assert(sl_resolve(&ms, 0, 0) == SL_X); + ms[SL_CNT + SL_W1] = 0; + ms[SL_CNT + SL_W0] = 0; + assert(sl_resolve(&ms, 0, 1) == 1); + assert(sl_resolve(&ms, 0, 0) == 0); + ms[SL_CNT + SL_S0] = 1; + ms[SL_CNT + SL_S1] = 1; + assert(sl_resolve(&ms, 0, 0) == SL_X); + ms[SL_CNT + SL_S1] = 0; + ms[SL_CNT + SL_Z] = 3; + assert(sl_resolve(&ms, 0, 1) == 0); +} + +test the_converter_hands_over_113_gates_in_100_nets_with_the_ports_of_the_root_first { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + const len : usize = sl_deck(&src, false); + const n : usize = emit_gates(src[0 .. len], "sar_clk", &w, &out, &st, >); + assert(n > 0); + assert(st[ERR_LINE] == 0); + assert(gt[GT_NGATE] == 113); + assert(gt[GT_NNET] == 100); + assert(gt[GT_PER_KIND + G_INV] == 23); + assert(gt[GT_PER_KIND + G_NAND] == 0); + assert(gt[GT_PER_KIND + G_NOR] == 44); + assert(gt[GT_PER_KIND + G_NOR3] == 1); + assert(gt[GT_PER_KIND + G_TGATE] == 44); + assert(gt[GT_PER_KIND + G_DELAY] == 1); + // the ports of the root are nets 0 .. 20 in the order of its .subckt line + assert(sl_net(&w, >, "vdd") == 0); + assert(sl_net(&w, >, "vss") == 1); + assert(sl_net(&w, >, "valid") == 5); + assert(sl_net(&w, >, "ck11") == 9); + assert(sl_net(&w, >, "ck1") == 19); + assert(sl_net(&w, >, "clkc") == 20); + // an internal net is named by its instance path; the nQ of dff x3 of SAR_logic and the dff's own net3 + assert(sl_net(&w, >, "x7.net3") != NONE); + assert(sl_net(&w, >, "x7.x3.net3") != NONE); + assert(sl_net(&w, >, "x7.x3.net3") != sl_net(&w, >, "x7.net3")); + assert(sl_net(&w, >, "x4.net1") != NONE); + assert(sl_net(&w, >, "nosuchnet") == NONE); +} + +test every_tgate_is_used_from_in_to_out_and_its_out_is_a_stored_node { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + const len : usize = sl_deck(&src, false); + const n : usize = emit_gates(src[0 .. len], "sar_clk", &w, &out, &st, >); + assert(n > 0); + var tg : usize = 0; + var bad_in : usize = 0; + var driven_out : usize = 0; + var g : usize = 0; + while (g < gt[GT_NGATE]) { + if (sl_kind(>, g) == G_TGATE) { + tg = tg + 1; + const innet : usize = sl_pin(>, g, 2); + const outnet : usize = sl_pin(>, g, 3); + // is anything but a tgate driving the In net, or is it an input of the sequencer (D, Set, a CK port)? + var drv : usize = 0; + var h : usize = 0; + while (h < gt[GT_NGATE]) { + if (sl_kind(>, h) != G_TGATE and sl_out_net(>, h) == innet) { drv = drv + 1; } + if (sl_kind(>, h) != G_TGATE and sl_out_net(>, h) == outnet) { driven_out = driven_out + 1; } + h = h + 1; + } + if (drv == 0 and innet > 8) { bad_in = bad_in + 1; } + } + g = g + 1; + } + assert(tg == 44); + // the In of a tgate is a gate's output or one of the inputs vdd vss d set clks valid a b c (nets 0 .. 8); the Out is never a gate's output + assert(bad_in == 0); + assert(driven_out == 0); +} + +test the_reset_resolves_the_unknown_start_and_clears_every_ck { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5000]usize = [0] ** 5000; + var ids : [24]usize = [0] ** 24; + var s_t : [64]usize = [0] ** 64; + var s_n : [64]usize = [0] ** 64; + var s_v : [64]usize = [0] ** 64; + assert(sl_build(&src, &w, &out, &st, >, &ms, &ids, false)); + const k : usize = sl_stim(&s_t, &s_n, &s_v, &ids); + assert(k == 32); + // every net is unknown at t = 0, and none is after the reset and the first clks fall, before the first Valid edge + assert(ms[SL_VAL + ids[9]] == SL_X); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 70000); + var i : usize = 0; + while (i < 11) { + assert(ms[SL_VAL + ids[9 + i]] == 0); + i = i + 1; + } + var unknown : usize = 0; + var n : usize = 0; + while (n < gt[GT_NNET]) { + if (ms[SL_VAL + n] == SL_X) { unknown = unknown + 1; } + n = n + 1; + } + assert(unknown == 0); + // the clock has started on its own: clkc went high once clks fell + assert(ms[SL_VAL + ids[20]] == 1); +} + +test eleven_valid_pulses_make_ck11_to_ck1_rise_once_each_in_ngspices_order_within_the_bound { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5000]usize = [0] ** 5000; + var ids : [24]usize = [0] ** 24; + var s_t : [64]usize = [0] ** 64; + var s_n : [64]usize = [0] ** 64; + var s_v : [64]usize = [0] ** 64; + assert(sl_build(&src, &w, &out, &st, >, &ms, &ids, false)); + const k : usize = sl_stim(&s_t, &s_n, &s_v, &ids); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 540000); + assert(sl_order_ok(&ms, &ids)); + var i : usize = 0; + while (i < 11) { + const r : usize = sl_edge(&ms, ids[9 + i], 1, 1, SL_TMIN); + assert(r > SL_NG_TV[i]); + assert(r < SL_NG_TVF[i]); + const got : usize = r - SL_NG_TV[i]; + const ng : usize = SL_NG_TCK[i] - SL_NG_TV[i]; + assert(sl_within(got, ng)); + i = i + 1; + } + // CK1 carries the nor3 pin and 4 fF more than the others: its delay is the longest of the eleven, in the model as in ngspice + const d1 : usize = sl_edge(&ms, ids[19], 1, 1, SL_TMIN) - SL_NG_TV[10]; + const d2 : usize = sl_edge(&ms, ids[18], 1, 1, SL_TMIN) - SL_NG_TV[9]; + assert(d1 > d2); +} + +test the_asynchronous_clock_starts_after_clks_falls_and_follows_every_valid_pulse { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5000]usize = [0] ** 5000; + var ids : [24]usize = [0] ** 24; + var s_t : [64]usize = [0] ** 64; + var s_n : [64]usize = [0] ** 64; + var s_v : [64]usize = [0] ** 64; + assert(sl_build(&src, &w, &out, &st, >, &ms, &ids, false)); + const k : usize = sl_stim(&s_t, &s_n, &s_v, &ids); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 540000); + // eleven rises (after clks falls, then after the first ten Valid falls: CK1 holds the eleventh back) and eleven falls (one per Valid rise), as in ngspice + assert(sl_edges(&ms, ids[20], 1, SL_TMIN) == 11); + assert(sl_edges(&ms, ids[20], 0, SL_TMIN) == 11); + // the settled edges: the first rise from clks falling, the first fall from the first Valid rise + const r1 : usize = sl_edge(&ms, ids[20], 1, 1, SL_TMIN); + assert(r1 > SL_NG_CLKS_F); + assert(sl_err_pm(r1 - SL_NG_CLKS_F, SL_NG_TCR[0] - SL_NG_CLKS_F) <= SL_CLKC_BOUND_PM); + const f1 : usize = sl_edge(&ms, ids[20], 0, 1, SL_TMIN); + assert(f1 > SL_NG_TV[0]); + assert(sl_err_pm(f1 - SL_NG_TV[0], SL_NG_TCF[0] - SL_NG_TV[0]) <= SL_CLKC_BOUND_PM); + // the repeated edges: each from the Valid edge that causes it, with the wide bounds of the header + var i : usize = 1; + while (i < 11) { + const rr : usize = sl_edge(&ms, ids[20], 1, i + 1, SL_TMIN); + assert(rr > SL_NG_TVF[i - 1]); + assert(sl_err_pm(rr - SL_NG_TVF[i - 1], SL_NG_TCR[i] - SL_NG_TVF[i - 1]) <= SL_REPEAT_R_BOUND_PM); + const ff : usize = sl_edge(&ms, ids[20], 0, i + 1, SL_TMIN); + assert(ff > SL_NG_TV[i]); + assert(sl_err_pm(ff - SL_NG_TV[i], SL_NG_TCF[i] - SL_NG_TV[i]) <= SL_REPEAT_F_BOUND_PM); + i = i + 1; + } +} + +test the_gate_arcs_reproduce_the_measured_points_of_the_arc_deck { + var row : usize = 0; + var worst : usize = 0; + while (row < 10) { + var dir : usize = 0; + while (dir < 2) { + var li : usize = 0; + while (li < 3) { + var load : usize = 1000; + if (li == 1) { load = 4000; } + if (li == 2) { load = 16000; } + const model : usize = sl_arc(row, dir, load); + const ng : usize = SL_NG_ARC[row * 6 + dir * 3 + li]; + const e : usize = sl_absdiff(model, ng); + assert(e <= SL_ARC_ABS_PS); + if (e > worst) { worst = e; } + li = li + 1; + } + dir = dir + 1; + } + row = row + 1; + } + // the worst error is a real one, not zero: the table is a fit, not a copy + assert(worst > 0); + // a nor driving one inverter input rises slower than it falls, a nor3 slower than a nor (the series PMOS stack), and a heavier load is slower + assert(sl_arc(3, SL_RISE, 1000) > sl_arc(3, SL_FALL, 1000)); + assert(sl_arc(5, SL_RISE, 1000) > sl_arc(3, SL_RISE, 1000)); + assert(sl_arc(0, SL_RISE, 4000) > sl_arc(0, SL_RISE, 1000)); +} + +test the_delay_cell_trim_codes_give_ngspices_clks_to_clkc_delays_for_all_eight_settings { + var code : usize = 0; + var base_r : usize = 0; + while (code < 8) { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5000]usize = [0] ** 5000; + const len : usize = sl_deck(&src, false); + const n : usize = emit_gates(src[0 .. len], "clock_gen", &w, &out, &st, >); + assert(n > 0); + assert(st[ERR_LINE] == 0); + assert(gt[GT_NGATE] == 3); + sl_init(&ms, >); + const vdd : usize = sl_net(&w, >, "vdd"); + const vss : usize = sl_net(&w, >, "vss"); + const clks : usize = sl_net(&w, >, "clks"); + const valid : usize = sl_net(&w, >, "valid"); + const ck1 : usize = sl_net(&w, >, "ck1"); + const va : usize = sl_net(&w, >, "a"); + const vb : usize = sl_net(&w, >, "b"); + const vc : usize = sl_net(&w, >, "c"); + const vout : usize = sl_net(&w, >, "vout"); + sl_ext_load(&ms, vout, SL_EXT_8F); + sl_loads(&ms, >); + sl_watch(&ms, vout); + var s_t : [16]usize = [0] ** 16; + var s_n : [16]usize = [0] ** 16; + var s_v : [16]usize = [0] ** 16; + var k : usize = 0; + k = sl_put(&s_t, &s_n, &s_v, k, 0, vdd, 1); + k = sl_put(&s_t, &s_n, &s_v, k, 0, vss, 0); + k = sl_put(&s_t, &s_n, &s_v, k, 0, valid, 0); + k = sl_put(&s_t, &s_n, &s_v, k, 0, ck1, 0); + k = sl_put(&s_t, &s_n, &s_v, k, 0, va, (code / 4) % 2); + k = sl_put(&s_t, &s_n, &s_v, k, 0, vb, (code / 2) % 2); + k = sl_put(&s_t, &s_n, &s_v, k, 0, vc, code % 2); + k = sl_put(&s_t, &s_n, &s_v, k, 0, clks, 1); + k = sl_put(&s_t, &s_n, &s_v, k, 10100, clks, 0); + k = sl_put(&s_t, &s_n, &s_v, k, 90300, clks, 1); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 190000); + const r : usize = sl_edge(&ms, vout, 1, 1, 5000); + const f : usize = sl_edge(&ms, vout, 0, 1, 50000); + assert(r > 10100); + assert(f > 90300); + assert(sl_err_pm(r - 10100, SL_NG_TOT_R[code]) <= SL_DC_BOUND_PM); + assert(sl_err_pm(f - 90300, SL_NG_TOT_F[code]) <= SL_DC_BOUND_PM); + // more bits set, a shorter clks -> clkc rise (the falling output of the delay cell is trimmed by A, B and C) + if (code == 0) { base_r = r - 10100; } + if (code == 7) { assert(r - 10100 < base_r); } + code = code + 1; + } +} + +test a_node_that_only_a_tgate_drives_holds_its_value_when_the_tgate_turns_off { + var src : [1024]u8 = undefined; + var w : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5000]usize = [0] ** 5000; + // one tgate (PMOS gate en, NMOS gate nen) from d to q, and an inverter on q + const len : usize = sl_cat(&src, 0, "hold\n.subckt inverter Vin VDD VSS Vout\n.ends\n.subckt tgate CLK nCLK In Out VDD VSS\n.ends\n.subckt hold en nen d q y vdd vss\nx1 en nen d q vdd vss tgate\nx2 q vdd vss y inverter\n.ends\n.end\n"); + const n : usize = emit_gates(src[0 .. len], "hold", &w, &out, &st, >); + assert(n > 0); + assert(st[ERR_LINE] == 0); + assert(gt[GT_NGATE] == 2); + sl_init(&ms, >); + sl_loads(&ms, >); + const en : usize = sl_net(&w, >, "en"); + const nen : usize = sl_net(&w, >, "nen"); + const d : usize = sl_net(&w, >, "d"); + const q : usize = sl_net(&w, >, "q"); + const y : usize = sl_net(&w, >, "y"); + sl_watch(&ms, q); + var s_t : [16]usize = [0] ** 16; + var s_n : [16]usize = [0] ** 16; + var s_v : [16]usize = [0] ** 16; + var k : usize = 0; + k = sl_put(&s_t, &s_n, &s_v, k, 0, sl_net(&w, >, "vdd"), 1); + k = sl_put(&s_t, &s_n, &s_v, k, 0, sl_net(&w, >, "vss"), 0); + k = sl_put(&s_t, &s_n, &s_v, k, 0, d, 1); + k = sl_put(&s_t, &s_n, &s_v, k, 0, en, 0); + k = sl_put(&s_t, &s_n, &s_v, k, 0, nen, 1); + k = sl_put(&s_t, &s_n, &s_v, k, 5000, en, 1); + k = sl_put(&s_t, &s_n, &s_v, k, 5000, nen, 0); + k = sl_put(&s_t, &s_n, &s_v, k, 10000, d, 0); + k = sl_put(&s_t, &s_n, &s_v, k, 15000, en, 0); + k = sl_put(&s_t, &s_n, &s_v, k, 15000, nen, 1); + // conducting: q follows d = 1 + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 4900); + assert(ms[SL_VAL + q] == 1); + assert(ms[SL_VAL + y] == 0); + // off, then d falls: q keeps 1 (dynamic storage), the inverter still reads 1 + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 14900); + assert(ms[SL_VAL + d] == 0); + assert(ms[SL_VAL + q] == 1); + assert(ms[SL_VAL + y] == 0); + // on again: q takes the new value after the tgate's delay, y after the inverter's + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 20000); + assert(ms[SL_VAL + q] == 0); + assert(ms[SL_VAL + y] == 1); + const tq : usize = sl_edge(&ms, q, 0, 1, 1000); + assert(tq > 15000); + assert(tq < 15400); +} + +test a_pulse_shorter_than_the_delay_is_swallowed_and_a_tie_of_two_drivers_is_unknown { + var src : [1024]u8 = undefined; + var w : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5000]usize = [0] ** 5000; + // an inverter y = not a, unloaded: its fall takes d0 = 64 ps, its rise 48 ps + const len : usize = sl_cat(&src, 0, "glitch\n.subckt inverter Vin VDD VSS Vout\n.ends\n.subckt top a y vdd vss\nx1 a vdd vss y inverter\n.ends\n.end\n"); + const n : usize = emit_gates(src[0 .. len], "top", &w, &out, &st, >); + assert(n > 0); + assert(st[ERR_LINE] == 0); + sl_init(&ms, >); + sl_loads(&ms, >); + const a : usize = sl_net(&w, >, "a"); + const y : usize = sl_net(&w, >, "y"); + sl_watch(&ms, y); + var s_t : [16]usize = [0] ** 16; + var s_n : [16]usize = [0] ** 16; + var s_v : [16]usize = [0] ** 16; + var k : usize = 0; + k = sl_put(&s_t, &s_n, &s_v, k, 0, sl_net(&w, >, "vdd"), 1); + k = sl_put(&s_t, &s_n, &s_v, k, 0, sl_net(&w, >, "vss"), 0); + k = sl_put(&s_t, &s_n, &s_v, k, 0, a, 0); + // a rises at 1000 and falls again at 1030 (30 ps < 64 ps): y must not move; a rises again at 2000 and stays: y falls 64 ps later + k = sl_put(&s_t, &s_n, &s_v, k, 1000, a, 1); + k = sl_put(&s_t, &s_n, &s_v, k, 1030, a, 0); + k = sl_put(&s_t, &s_n, &s_v, k, 2000, a, 1); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 5000); + assert(sl_edges(&ms, y, 0, 500) == 1); + assert(sl_edge(&ms, y, 0, 1, 500) == 2064); + assert(ms[SL_VAL + y] == 0); +} + +test one_dff_with_set_and_reset_swapped_fails_the_order_check_that_the_right_netlist_passes { + var src : [4096]u8 = undefined; + var w : [24576]u8 = undefined; + var out : [24576]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + var ms : [5000]usize = [0] ** 5000; + var ids : [24]usize = [0] ** 24; + var s_t : [64]usize = [0] ** 64; + var s_n : [64]usize = [0] ** 64; + var s_v : [64]usize = [0] ** 64; + assert(sl_build(&src, &w, &out, &st, >, &ms, &ids, false)); + var k : usize = sl_stim(&s_t, &s_n, &s_v, &ids); + sl_run(&ms, >, &s_t, &s_n, &s_v, k, 540000); + assert(sl_order_ok(&ms, &ids)); + // the same run on the netlist whose fifth dff has Set = clks and Reset = Set + var st2 : [3000]usize = [0] ** 3000; + var gt2 : [3200]usize = [0] ** 3200; + var ms2 : [5000]usize = [0] ** 5000; + var ids2 : [24]usize = [0] ** 24; + assert(sl_build(&src, &w, &out, &st2, >2, &ms2, &ids2, true)); + k = sl_stim(&s_t, &s_n, &s_v, &ids2); + sl_run(&ms2, >2, &s_t, &s_n, &s_v, k, 540000); + assert(!sl_order_ok(&ms2, &ids2)); + // CK7 (the fifth stage's output) is the one that is wrong: set during the reset (a first rise), it falls at the first Valid edge when the stage + // takes its D = 0 (Reset is now the constant Set) and rises again in its turn: two rises and one fall after the reset, at ngspice's times + assert(sl_edges(&ms2, ids2[13], 1, 0) == 2); + assert(sl_edges(&ms2, ids2[13], 0, SL_TMIN) == 1); + const f7 : usize = sl_edge(&ms2, ids2[13], 0, 1, SL_TMIN); + assert(f7 > SL_NG_TV[0]); + assert(sl_within_fall(f7 - SL_NG_TV[0], SL_NG_SW_CK7_F - SL_NG_TV[0])); + const r7 : usize = sl_edge(&ms2, ids2[13], 1, 2, 0); + assert(r7 > SL_NG_TV[4]); + assert(sl_within(r7 - SL_NG_TV[4], SL_NG_SW_CK7_R2 - SL_NG_TV[4])); + // CK6, whose D is CK7, rises at the FIRST edge, falls at the second and rises at its own, the sixth + assert(sl_edges(&ms2, ids2[14], 1, 0) == 2); + assert(sl_edges(&ms2, ids2[14], 0, SL_TMIN) == 1); + const a6 : usize = sl_edge(&ms2, ids2[14], 1, 1, 0); + assert(a6 > SL_NG_TV[0]); + assert(sl_within(a6 - SL_NG_TV[0], SL_NG_SW_CK6_R1 - SL_NG_TV[0])); + const f6 : usize = sl_edge(&ms2, ids2[14], 0, 1, SL_TMIN); + assert(f6 > SL_NG_TV[1]); + assert(sl_within_fall(f6 - SL_NG_TV[1], SL_NG_SW_CK6_F - SL_NG_TV[1])); + const b6 : usize = sl_edge(&ms2, ids2[14], 1, 2, 0); + assert(b6 > SL_NG_TV[5]); + assert(sl_within(b6 - SL_NG_TV[5], SL_NG_SW_CK6_R2 - SL_NG_TV[5])); + // the other stages are untouched: CK11 rises once, on time + assert(sl_edges(&ms2, ids2[9], 1, 0) == 1); +} diff --git a/specs/tools/t27o/spice_convert.t27 b/specs/tools/t27o/spice_convert.t27 index e081bc0a76..81f461c657 100644 --- a/specs/tools/t27o/spice_convert.t27 +++ b/specs/tools/t27o/spice_convert.t27 @@ -55,8 +55,8 @@ // 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: the inventory counts and names, and the cell below reads flat decks and -// refuses an instance (wiring a hierarchy is M4d, #10754). +// resolved through the port maps by the inventory: it counts and names, and the cell below reads flat decks and +// refuses an instance; the gates below (M4d, #10754) wire a hierarchy. // -- // 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 @@ -109,6 +109,24 @@ // - 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. // -- +// The gates (M4d, #10754). `emit_gates` reads the hierarchy of ONE named subcircuit down to its gates, from the netlist alone, and lists each gate with the +// nets on its pins. A gate is an instance of a subcircuit called inverter, nand, nor, nor3, tgate or delay_cell (the golden SAR deck's names); everything +// above the gates must be X cards of subcircuits. What the walk does: +// - Each gate row lists its pins in the order of the table in pin_matches (inputs, output, supplies; a tgate: the gate of its PMOS, the gate of its NMOS, +// in, out, vdd, vss), found by the NAMES of the gate subcircuit's ports, so a gate written with its ports in another order gives the same row. +// - Nets 0, 1, 2 ... are the ports of the root in the order of its .subckt line (a root port is the net itself). A node name that is a port of its level +// is followed out through the X card that opened the level, up to the root; `0` is ground, one net for the deck; any other name is internal to its +// level and is called . (x7.x3.net3), so a net1 at two levels is two nets. Names are lower case. +// - Refused with `line N: reason` and the card, like the rest of the reader: a subcircuit that is not defined, an element that is not an X card +// (a resistor, a transistor outside a gate), a gate lacking a pin its kind needs, m other than 1 on a gate or a level, a subcircuit that instantiates itself, +// a deck larger than the tables (256 nets, 256 gates), and whatever the scanner refuses first. +// - The caller passes a second state vector `gt` (GT_SLOTS slots, zeroed): the gate table, read by sar_logic.t27 (analog M4d), which builds its gate-level +// model of SAR_logic and clock_gen from it. The text output (`gates of : N instances, M nets`, the kinds, a line a net, a line a gate) is for a +// person. The command line flag `tri t27o deck --emit-gates ` is NOT wired yet (cmd_t27o is being edited by the Zig front end, #10765): the +// hand-written glue is a follow-up of a few lines; the generated Rust of this spec has been run on the whole golden deck by a scratch harness. +// - Traps met: t27b refuses a repeated `pos + n` whose local is declared in the same function (`FnDecl(reference CSE hoist)`): it is spelled once as `stop`; +// a helper that only reads `st` becomes `impl AsRef` in the generated Rust and the caller's `&mut` is moved into it: port_at, def_port, pin_port and +// gate_kind write GATE_HIT; the words `at` and `P` (in a message and a test deck) are rust_convert declarations and pulled it in again. // 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 @@ -218,6 +236,8 @@ pub const GROUP_END : usize = 67; pub const N_BSRC : usize = 68; pub const OPT_METHOD : usize = 69; pub const OPT_MAXORD : usize = 70; +/// The answer of the last gate helper (port_at, def_port, pin_port, gate_kind): the write keeps the state vector a &mut in the generated Rust. +pub const GATE_HIT : usize = 71; pub const FIXED : usize = 80; /// The cell workspace (see "The cell"): CELL_SLOTS slots. @@ -345,6 +365,12 @@ 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; +/// Why a deck is not a gate-level netlist (M4d): the subcircuit asked for is not in the deck, an element sits +/// outside a gate, a gate lacks a pin its kind needs, a gate carries m other than 1. +pub const Y_GATE_TOP : usize = 34; +pub const Y_GATE_LEAF : usize = 35; +pub const Y_GATE_PINS : usize = 36; +pub const Y_GATE_M : usize = 37; // The bytes the reader looks at, as numbers (a char literal is a Rust char in gen-rust). pub const B_TAB : u8 = 9; @@ -1596,6 +1622,10 @@ pub fn put_why(w: []const u8, st: []usize, out: []u8) -> void { 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 (why == Y_GATE_TOP) { puts(st, out, "the subcircuit to read as gates is not defined in the deck"); } + if (why == Y_GATE_LEAF) { puts(st, out, "not gate level: an element that is not an instance of a gate or of a subcircuit"); } + if (why == Y_GATE_PINS) { puts(st, out, "a gate subcircuit lacks a pin its kind needs"); } + if (why == Y_GATE_M) { puts(st, out, "m on a gate or on a level is not read in a gate level netlist (write 1 or none)"); } if (tok != NONE and k != NONE) { puts(st, out, ": "); put_tok(w, st, out, tok, st[CARD0 + k * CARD_SLOTS + C_B]); @@ -2339,6 +2369,441 @@ pub fn emit_cell(src: []const u8, w: []u8, out: []u8, st: []usize) -> usize { return st[W]; } +// ---------------------------------------------------------------- the gates (M4d, #10754) + +/// The gate kinds a hierarchy is read down to: the subcircuits named inverter, nand, nor, nor3, tgate and +/// delay_cell. A gate row lists its pins in the order of the table in pin_matches, not in the order of the +/// deck's .subckt line, so the same gate written with its ports in another order gives the same row. +pub const G_INV : usize = 0; +pub const G_NAND : usize = 1; +pub const G_NOR : usize = 2; +pub const G_NOR3 : usize = 3; +pub const G_TGATE : usize = 4; +pub const G_DELAY : usize = 5; +pub const G_KINDS : usize = 6; +/// The pins of the widest gate (delay_cell), and the slots of a gate row: the kind, G_PINS nets (NONE for a +/// pin the kind does not have), then where the instance name sits in the work buffer. +pub const G_PINS : usize = 7; +pub const GATE_SLOTS : usize = 10; +/// The gate workspace, the caller's second state vector (GT_SLOTS slots, zeroed): the counters, one row of +/// the level stack per depth (the definition, the X card that opened the level, the length of its path), +/// the net table (where each net's name sits in the work buffer) and the gate table. +pub const GT_SLOTS : usize = 3200; +pub const GT_NGATE : usize = 0; +pub const GT_NNET : usize = 1; +pub const GT_POOL : usize = 2; +pub const GT_LAST : usize = 3; +pub const GT_PER_KIND : usize = 4; +pub const GT_STK : usize = 16; +pub const GT_NET0 : usize = 128; +pub const GT_MAXNET : usize = 256; +pub const GT_GATE0 : usize = 640; +pub const GT_MAXGATE : usize = 256; +/// In the work buffer, after the path scratch of the flattener: GT_PATH bytes for the instance path of the +/// level being read, GT_NAME for the net name being built, then the pool of the names kept. +pub const GT_PATH : usize = 480; +pub const GT_NAME : usize = 640; + +/// How many pins a gate of this kind has. +pub fn pin_count(kind: usize) -> usize { + if (kind == G_INV) { return 4; } + if (kind == G_NAND) { return 5; } + if (kind == G_NOR) { return 5; } + if (kind == G_NOR3) { return 6; } + if (kind == G_TGATE) { return 6; } + return 7; +} + +/// Is w[a..b] the j-th word of the space-separated list `names`, ignoring case? +pub fn pin_word(w: []const u8, a: usize, b: usize, names: []const u8, j: usize) -> bool { + var s : usize = 0; + var n : usize = 0; + while (n < j and s < names.len) { + while (s < names.len and names[s] != B_SPACE) { s = s + 1; } + s = s + 1; + n = n + 1; + } + var e : usize = s; + while (e < names.len and names[e] != B_SPACE) { e = e + 1; } + if (s >= names.len or b < a or e - s != b - a) { return false; } + var i : usize = 0; + while (i < e - s) { + if (lower(w[a + i]) != names[s + i]) { return false; } + i = i + 1; + } + return true; +} + +/// Is w[a..b] the name of pin j of a gate of this kind? The inputs come first, then the output, then the +/// supplies; a tgate lists the gate of its PMOS (clk), the gate of its NMOS (nclk), then in, out, vdd, vss. +pub fn pin_matches(kind: usize, j: usize, w: []const u8, a: usize, b: usize) -> bool { + if (kind == G_INV) { return pin_word(w, a, b, "vin vout vdd vss", j); } + if (kind == G_NAND) { return pin_word(w, a, b, "a b vout vdd vss", j); } + if (kind == G_NOR) { return pin_word(w, a, b, "a b vout vdd vss", j); } + if (kind == G_NOR3) { return pin_word(w, a, b, "a b c vout vdd vss", j); } + if (kind == G_TGATE) { return pin_word(w, a, b, "clk nclk in out vdd vss", j); } + return pin_word(w, a, b, "in a b c out vdd vss", j); +} + +/// The kind of gate the definition d is, NONE when its name is none of the six. +pub fn gate_kind(w: []const u8, st: []usize, d: usize) -> usize { + const a : usize = st[DEF0 + d * DEF_SLOTS + D_NAME_A]; + const b : usize = st[DEF0 + d * DEF_SLOTS + D_NAME_B]; + var kind : usize = NONE; + if (ci_is(w, a, b, "inverter")) { kind = G_INV; } + if (ci_is(w, a, b, "nand")) { kind = G_NAND; } + if (ci_is(w, a, b, "nor3")) { kind = G_NOR3; } + if (ci_is(w, a, b, "nor")) { kind = G_NOR; } + if (ci_is(w, a, b, "tgate")) { kind = G_TGATE; } + if (ci_is(w, a, b, "delay_cell")) { kind = G_DELAY; } + st[GATE_HIT] = kind; + return kind; +} + +/// Is w[a..b] the text `top`, ignoring the case of both? +pub fn top_is(w: []const u8, a: usize, b: usize, top: []const u8) -> bool { + if (b < a or b - a != top.len) { return false; } + var k : usize = 0; + while (k < top.len) { + if (lower(w[a + k]) != lower(top[k])) { return false; } + k = k + 1; + } + return true; +} + +/// Where the i-th port of the definition def starts in the work buffer, NONE when it has fewer. +pub fn port_at(w: []const u8, st: []usize, def: usize, i: usize) -> usize { + const k0 : usize = st[DEF0 + def * DEF_SLOTS + D_FIRST]; + const e : usize = st[CARD0 + k0 * CARD_SLOTS + C_B]; + const t0 : usize = tk(w, st[CARD0 + k0 * CARD_SLOTS + C_A], e); + const nm : usize = tk(w, tk_end(w, t0, e), e); + const r : usize = word_at(w, tk_end(w, nm, e), e, i); + st[GATE_HIT] = r; + return r; +} + +/// Which port of the definition def is named w[a..b], NONE when none is. +pub fn def_port(w: []const u8, st: []usize, def: usize, a: usize, b: usize) -> usize { + const e : usize = st[CARD0 + st[DEF0 + def * DEF_SLOTS + D_FIRST] * CARD_SLOTS + C_B]; + var hit : usize = NONE; + var i : usize = 0; + while (hit == NONE and i < st[DEF0 + def * DEF_SLOTS + D_NPORTS]) { + const p : usize = port_at(w, st, def, i); + if (p != NONE and same_ci(w, a, b, p, tk_end(w, p, e))) { hit = i; } + i = i + 1; + } + st[GATE_HIT] = hit; + return hit; +} + +/// Which port of the definition def is pin j of a gate of this kind, NONE when it has none. +pub fn pin_port(w: []const u8, st: []usize, def: usize, kind: usize, j: usize) -> usize { + const e : usize = st[CARD0 + st[DEF0 + def * DEF_SLOTS + D_FIRST] * CARD_SLOTS + C_B]; + var hit : usize = NONE; + var i : usize = 0; + while (hit == NONE and i < st[DEF0 + def * DEF_SLOTS + D_NPORTS]) { + const p : usize = port_at(w, st, def, i); + if (p != NONE and pin_matches(kind, j, w, p, tk_end(w, p, e))) { hit = i; } + i = i + 1; + } + st[GATE_HIT] = hit; + return hit; +} + +/// The net whose name is in the work buffer from c0 for n bytes: its number if it was seen, else a new one. +pub fn net_intern(w: []u8, st: []usize, gt: []usize, k: usize, c0: usize, n: usize) -> usize { + var i : usize = 0; + while (i < gt[GT_NNET]) { + if (same_ci(w, gt[GT_NET0 + i * 2], gt[GT_NET0 + i * 2 + 1], c0, c0 + n)) { return i; } + i = i + 1; + } + const pos : usize = gt[GT_POOL]; + const stop : usize = pos + n; + if (gt[GT_NNET] >= GT_MAXNET or stop > w.len) { + reject(st, Y_SIZE, k, NONE); + return 0; + } + var j : usize = 0; + while (j < n) { + w[pos + j] = w[c0 + j]; + j = j + 1; + } + const id : usize = gt[GT_NNET]; + gt[GT_NET0 + id * 2] = pos; + gt[GT_NET0 + id * 2 + 1] = stop; + gt[GT_POOL] = stop; + gt[GT_NNET] = id + 1; + return id; +} + +/// The number of the net the node name w[a0..b0] is, as seen from the level `depth` of the walk (card k is +/// where a refusal is reported). A name that is a port of its level is followed out through the X card that +/// opened the level, up to the root, where a port of the root is the net itself; `0` is ground, one net +/// for the whole deck; any other name is internal to its level and is called .. +pub fn net_id(w: []u8, st: []usize, gt: []usize, k: usize, a0: usize, b0: usize, depth: usize) -> usize { + var a : usize = a0; + var b : usize = b0; + var d : usize = depth; + var mode : usize = 0; + while (mode == 0) { + if (a >= b) { + mode = 1; + } else if (b == a + 1 and w[a] == B_ZERO) { + mode = 1; + } else { + const p : usize = def_port(w, st, gt[GT_STK + d * 3], a, b); + if (p == NONE or d == 0) { + mode = 2; + } else { + const card : usize = gt[GT_STK + d * 3 + 1]; + const e2 : usize = st[CARD0 + card * CARD_SLOTS + C_B]; + const na : usize = word_at(w, tk_end(w, st[CARD0 + card * CARD_SLOTS + C_A], e2), e2, p); + if (na == NONE) { + mode = 1; + } else { + a = na; + b = tk_end(w, na, e2); + d = d - 1; + } + } + } + } + const c0 : usize = st[WEND] + GT_PATH; + var n : usize = 0; + if (mode == 1) { + w[c0] = B_ZERO; + n = 1; + } else { + const pl : usize = gt[GT_STK + d * 3 + 2]; + if (pl + (b - a) > GT_NAME) { + reject(st, Y_SIZE, k, NONE); + return 0; + } + var i : usize = 0; + while (i < pl) { + w[c0 + i] = w[st[WEND] + i]; + i = i + 1; + } + i = 0; + while (a + i < b) { + w[c0 + pl + i] = lower(w[a + i]); + i = i + 1; + } + n = pl + (b - a); + } + return net_intern(w, st, gt, k, c0, n); +} + +/// One gate: the X card k on the level `depth`, an instance of the definition d, which is a gate of this +/// kind. Its row holds the kind and the net of each pin, in the order of the pin table, and its name. +pub fn gate_add(w: []u8, st: []usize, gt: []usize, k: usize, d: usize, kind: usize, plen: usize, depth: usize) -> void { + const g : usize = gt[GT_NGATE]; + if (g >= GT_MAXGATE) { + reject(st, Y_SIZE, k, NONE); + return; + } + const e : usize = st[CARD0 + k * CARD_SLOTS + C_B]; + const row : usize = GT_GATE0 + g * GATE_SLOTS; + gt[row] = kind; + var j : usize = 0; + while (j < G_PINS) { + gt[row + 1 + j] = NONE; + j = j + 1; + } + j = 0; + while (j < pin_count(kind)) { + const p : usize = pin_port(w, st, d, kind, j); + if (p == NONE) { + reject(st, Y_GATE_PINS, k, NONE); + } else { + const t : usize = word_at(w, tk_end(w, st[CARD0 + k * CARD_SLOTS + C_A], e), e, p); + if (t != NONE) { + gt[row + 1 + j] = net_id(w, st, gt, k, t, tk_end(w, t, e), depth); + } + } + j = j + 1; + } + const nm : usize = tk(w, st[CARD0 + k * CARD_SLOTS + C_A], e); + const nl : usize = tk_end(w, nm, e) - nm; + const pos : usize = gt[GT_POOL]; + const stop : usize = pos + plen + nl; + if (stop > w.len) { + reject(st, Y_SIZE, k, NONE); + return; + } + var i : usize = 0; + while (i < plen) { + w[pos + i] = w[st[WEND] + i]; + i = i + 1; + } + i = 0; + while (i < nl) { + w[pos + plen + i] = lower(w[nm + i]); + i = i + 1; + } + gt[row + 8] = pos; + gt[row + 9] = stop; + gt[GT_POOL] = stop; + gt[GT_PER_KIND + kind] = gt[GT_PER_KIND + kind] + 1; + gt[GT_NGATE] = g + 1; +} + +/// Walk the cards lo..hi that belong to the definition `scope`, which is level `depth` (the root is 0). Every +/// card must be an X card: an instance of a gate is listed, an instance of another subcircuit opens a level +/// below, anything else (a resistor, a transistor outside a gate) is refused. +pub fn gate_walk(w: []u8, st: []usize, gt: []usize, scope: usize, lo: usize, hi: usize, plen: usize, depth: usize) -> void { + gt[GT_STK + depth * 3] = scope; + gt[GT_STK + depth * 3 + 2] = plen; + var k : usize = lo; + while (k < hi) { + const a : usize = st[CARD0 + k * CARD_SLOTS + C_A]; + const e : usize = st[CARD0 + k * CARD_SLOTS + C_B]; + const t0 : usize = tk(w, a, e); + if (st[CARD0 + k * CARD_SLOTS + C_SCOPE] == scope and w[t0] != B_DOT) { + var cls : usize = X_NONE; + if (lower(w[t0]) == 120) { cls = x_class(w, st, k); } + if (cls != X_SUB) { + reject(st, Y_GATE_LEAF, k, NONE); + } else { + const d : usize = st[DEF_HIT]; + const kind : usize = gate_kind(w, st, d); + if (x_mult(w, st, k) != 1) { + reject(st, Y_GATE_M, k, NONE); + } else if (kind != NONE) { + gate_add(w, st, gt, k, d, kind, plen, depth); + } else { + const nl : usize = tk_end(w, t0, e) - t0; + var open : bool = false; + var j : usize = 0; + while (j <= depth) { + if (gt[GT_STK + j * 3] == d) { open = true; } + j = j + 1; + } + if (open) { + reject(st, Y_RECUR, k, st[XA]); + } else if (depth + 1 >= MAX_DEPTH) { + reject(st, Y_DEPTH, k, st[XA]); + } else if (plen + nl + 1 > GT_PATH) { + reject(st, Y_SIZE, k, NONE); + } else { + var i : usize = 0; + while (i < nl) { + w[st[WEND] + plen + i] = lower(w[t0 + i]); + i = i + 1; + } + w[st[WEND] + plen + nl] = B_DOT; + gt[GT_STK + (depth + 1) * 3 + 1] = k; + gate_walk(w, st, gt, d, st[DEF0 + d * DEF_SLOTS + D_FIRST] + 1, st[DEF0 + d * DEF_SLOTS + D_LAST], plen + nl + 1, depth + 1); + } + } + } + } + k = k + 1; + } +} + +/// The name of a gate kind. +pub fn put_kind(st: []usize, out: []u8, kind: usize) -> void { + if (kind == G_INV) { puts(st, out, "inverter"); } + else if (kind == G_NAND) { puts(st, out, "nand"); } + else if (kind == G_NOR) { puts(st, out, "nor"); } + else if (kind == G_NOR3) { puts(st, out, "nor3"); } + else if (kind == G_TGATE) { puts(st, out, "tgate"); } + else { puts(st, out, "delay_cell"); } +} + +/// The gate table as text: the counts, the gates of each kind, one line a net and one a gate. +pub fn put_gates(w: []const u8, st: []usize, gt: []usize, out: []u8, root: usize) -> void { + puts(st, out, "gates of "); + put_lower(w, st, out, st[DEF0 + root * DEF_SLOTS + D_NAME_A], st[DEF0 + root * DEF_SLOTS + D_NAME_B]); + puts(st, out, ": "); + put_num(st, out, gt[GT_NGATE]); + puts(st, out, " instances, "); + put_num(st, out, gt[GT_NNET]); + puts(st, out, " nets\nkinds:"); + var q : usize = 0; + while (q < G_KINDS) { + puts(st, out, " "); + put_kind(st, out, q); + puts(st, out, "="); + put_num(st, out, gt[GT_PER_KIND + q]); + q = q + 1; + } + put(st, out, B_NL); + var n : usize = 0; + while (n < gt[GT_NNET]) { + puts(st, out, "net "); + put_num(st, out, n); + puts(st, out, " "); + put_lower(w, st, out, gt[GT_NET0 + n * 2], gt[GT_NET0 + n * 2 + 1]); + put(st, out, B_NL); + n = n + 1; + } + var g : usize = 0; + while (g < gt[GT_NGATE]) { + const row : usize = GT_GATE0 + g * GATE_SLOTS; + puts(st, out, "gate "); + put_num(st, out, g); + puts(st, out, " "); + put_lower(w, st, out, gt[row + 8], gt[row + 9]); + puts(st, out, " "); + put_kind(st, out, gt[row]); + var j : usize = 0; + while (j < pin_count(gt[row])) { + puts(st, out, " "); + put_num(st, out, gt[row + 1 + j]); + j = j + 1; + } + put(st, out, B_NL); + g = g + 1; + } +} + +/// `tri t27o deck.cir --emit-gates `: read the hierarchy of the subcircuit `top` down to its gates and +/// write the gate table, or the refusal (`line N: reason`, the card). `w` and `st` are the caller's, as for +/// inventory; `gt` is the gate workspace (GT_SLOTS slots, zeroed). The nets 0, 1, 2 ... are the ports of `top` +/// in the order of its .subckt line; the other nets are numbered in the order the walk meets them. The result +/// is the number of bytes the output needs. +pub fn emit_gates(src: []const u8, top: []const u8, w: []u8, out: []u8, st: []usize, gt: []usize) -> usize { + 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); + resolve(w, st); + var root : usize = NONE; + var d : usize = 0; + while (d < st[NDEF]) { + if (root == NONE and st[DEF0 + d * DEF_SLOTS + D_PARENT] == NONE and top_is(w, st[DEF0 + d * DEF_SLOTS + D_NAME_A], st[DEF0 + d * DEF_SLOTS + D_NAME_B], top)) { root = d; } + d = d + 1; + } + if (st[ERR_LINE] == 0 and root == NONE) { reject_at(st, Y_GATE_TOP, 1, NONE, NONE); } + if (st[ERR_LINE] == 0 and st[WEND] + GT_PATH + GT_NAME + 64 > w.len) { reject_at(st, Y_SIZE, 1, NONE, NONE); } + if (st[ERR_LINE] == 0) { + const k0 : usize = st[DEF0 + root * DEF_SLOTS + D_FIRST]; + gt[GT_POOL] = st[WEND] + GT_PATH + GT_NAME; + gt[GT_STK] = root; + gt[GT_STK + 1] = NONE; + gt[GT_STK + 2] = 0; + var i : usize = 0; + while (i < st[DEF0 + root * DEF_SLOTS + D_NPORTS]) { + const p : usize = port_at(w, st, root, i); + gt[GT_LAST] = net_id(w, st, gt, k0, p, tk_end(w, p, st[CARD0 + k0 * CARD_SLOTS + C_B]), 0); + i = i + 1; + } + gate_walk(w, st, gt, root, k0 + 1, st[DEF0 + root * DEF_SLOTS + D_LAST], 0, 0); + } + if (st[ERR_LINE] != 0) { + put_reject(w, st, out); + } else { + put_gates(w, st, gt, out, root); + } + 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. @@ -3265,3 +3730,146 @@ test b_sources_ic_and_the_integration_options_are_read_by_the_inventory_too { assert(n > 0); assert(d[ERR_LINE] == 2 and d[ERR_WHY] == Y_FIELDS); } + +// ---------------------------------------------------------------- tests of the gates (M4d, #10754) +// A two-level hierarchy of the shape SAR_logic / dff have: the gates (an inverter, a nor and a transmission +// gate, each a subcircuit of transistors), a latch of five gates, and a chain of two latches. + +pub const DECK_GATES : str = "gate level mini deck\n.subckt inverter Vin VDD VSS Vout\nXM1 Vout Vin VDD VDD sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1\nXM2 Vout Vin VSS VSS sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1\n.ends\n.subckt nor A B Vout VDD VSS\nXM1 Vout A VSS VSS sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1\nXM2 Vout B VSS VSS sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1\nXM3 Vout B net1 VDD sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1\nXM4 net1 A VDD VDD sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1\n.ends\n.subckt tgate CLK nCLK In Out VDD VSS\nXM1 Out CLK In VDD sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1\nXM2 In nCLK Out VSS sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1\n.ends\n.subckt latch VDD VSS Q CLK nQ D Reset\nx1 CLK nCLK D net1 VDD VSS tgate\nx2 Reset net1 net2 VDD VSS nor\nx3 nCLK CLK net2 Q VDD VSS tgate\nx6 Q VDD VSS nQ inverter\nx10 CLK VDD VSS nCLK inverter\n.ends\n.subckt chain CK2 CK1 VDD VSS D Valid Reset\nx1 VDD VSS CK1 Valid net1 D Reset latch\nx2 VDD VSS CK2 Valid net2 CK1 Reset latch\n.ends\n.end\n"; + +/// Does gate g of the table hold this kind and these six pin slots (NONE for a pin the kind lacks)? +pub fn gate_is(gt: []usize, g: usize, kind: usize, p0: usize, p1: usize, p2: usize, p3: usize, p4: usize, p5: usize) -> bool { + const row : usize = GT_GATE0 + g * GATE_SLOTS; + if (gt[row] != kind) { return false; } + return gt[row + 1] == p0 and gt[row + 2] == p1 and gt[row + 3] == p2 and gt[row + 4] == p3 and gt[row + 5] == p4 and gt[row + 6] == p5; +} + +test a_two_level_hierarchy_is_read_down_to_its_gates { + var w : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + const n : usize = emit_gates(DECK_GATES, "chain", &w, &out, &st, >); + assert(n > 0); + assert(st[ERR_LINE] == 0); + assert(exit_code(&st) == 0); + assert(gt[GT_NGATE] == 10); + assert(gt[GT_NNET] == 15); + assert(gt[GT_PER_KIND + G_INV] == 4); + assert(gt[GT_PER_KIND + G_NAND] == 0); + assert(gt[GT_PER_KIND + G_NOR] == 2); + assert(gt[GT_PER_KIND + G_NOR3] == 0); + assert(gt[GT_PER_KIND + G_TGATE] == 4); + assert(gt[GT_PER_KIND + G_DELAY] == 0); + // the first latch: clock Valid, D the chain's D, Q the chain's CK1, its own nclk, net1 and net2 below x1 + assert(gate_is(>, 0, G_TGATE, 5, 7, 4, 8, 2, 3)); + assert(gate_is(>, 1, G_NOR, 6, 8, 9, 2, 3, NONE)); + assert(gate_is(>, 2, G_TGATE, 7, 5, 9, 1, 2, 3)); + assert(gate_is(>, 3, G_INV, 1, 10, 2, 3, NONE, NONE)); + assert(gate_is(>, 4, G_INV, 5, 7, 2, 3, NONE, NONE)); + // the second latch takes the first one's Q (CK1) as D and drives CK2; its nQ is the chain's net2 + assert(gate_is(>, 5, G_TGATE, 5, 11, 1, 12, 2, 3)); + assert(gate_is(>, 6, G_NOR, 6, 12, 13, 2, 3, NONE)); + assert(gate_is(>, 7, G_TGATE, 11, 5, 13, 0, 2, 3)); + assert(gate_is(>, 8, G_INV, 0, 14, 2, 3, NONE, NONE)); + assert(gate_is(>, 9, G_INV, 5, 11, 2, 3, NONE, NONE)); +} + +test the_gate_table_is_printed_with_the_names_the_hierarchy_gives_the_nets { + var w : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + const n : usize = emit_gates(DECK_GATES, "CHAIN", &w, &out, &st, >); + assert(st[ERR_LINE] == 0); + assert(has_text(n, &out, "gates of chain: 10 instances, 15 nets\nkinds: inverter=4 nand=0 nor=2 nor3=0 tgate=4 delay_cell=0\nnet 0 ck2\nnet 1 ck1\nnet 2 vdd\nnet 3 vss\nnet 4 d\nnet 5 valid\nnet 6 reset\nnet 7 x1.nclk\nnet 8 x1.net1\nnet 9 x1.net2\nnet 10 net1\n")); + assert(has_text(n, &out, "net 14 net2\ngate 0 x1.x1 tgate 5 7 4 8 2 3\ngate 1 x1.x2 nor 6 8 9 2 3\n")); + assert(has_text(n, &out, "gate 3 x1.x6 inverter 1 10 2 3\ngate 4 x1.x10 inverter 5 7 2 3\ngate 5 x2.x1 tgate 5 11 1 12 2 3\n")); + assert(has_text(n, &out, "gate 9 x2.x10 inverter 5 11 2 3\n")); +} + +test pins_are_listed_in_the_order_of_the_table_not_of_the_subckt_line_and_zero_is_ground { + var w : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + // ports written in another order than the table's, and the supplies tied to a net and to 0 + const deck : str = "reorder\n.subckt nor Vout B A VSS VDD\nXM1 Vout A VSS VSS sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1\n.ends\n.subckt inverter Vout VDD Vin VSS\nXM1 Vout Vin VDD VDD sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1\n.ends\n.subckt top PA QA RA\nx1 y RA QA 0 vdd nor\nx2 PA vdd RA 0 inverter\n.ends\n.end\n"; + const n : usize = emit_gates(deck, "top", &w, &out, &st, >); + assert(st[ERR_LINE] == 0); + assert(gt[GT_NGATE] == 2); + assert(gt[GT_NNET] == 6); + assert(gate_is(>, 0, G_NOR, 1, 2, 3, 4, 5, NONE)); + assert(gate_is(>, 1, G_INV, 2, 0, 4, 5, NONE, NONE)); + assert(has_text(n, &out, "net 3 y\nnet 4 vdd\nnet 5 0\n")); + // the same inverter with its input and output swapped is another row: the table is not blind to the wiring + var st2 : [3000]usize = [0] ** 3000; + var gt2 : [3200]usize = [0] ** 3200; + const deck2 : str = "swapped\n.subckt inverter Vout VDD Vin VSS\nXM1 Vout Vin VDD VDD sg13_lv_pmos w=3.0u l=0.45u ng=1 m=1\n.ends\n.subckt top PA RA\nx2 RA vdd PA 0 inverter\n.ends\n.end\n"; + const n2 : usize = emit_gates(deck2, "top", &w, &out, &st2, >2); + assert(n2 > 0); + assert(st2[ERR_LINE] == 0); + assert(gate_is(>2, 0, G_INV, 0, 1, 2, 3, NONE, NONE)); + assert(!gate_is(>2, 0, G_INV, 1, 0, 2, 3, NONE, NONE)); +} + +test a_hierarchy_that_is_not_gate_level_is_refused_with_its_line { + var w : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + // no such subcircuit + var n : usize = emit_gates(DECK_GATES, "nosuch", &w, &out, &st, >); + assert(st[ERR_WHY] == Y_GATE_TOP); + assert(exit_code(&st) == 1); + assert(out_is(n, &out, 0, "line 1: the subcircuit to read as gates is not defined in the deck\n")); + // a resistor beside the gates + var st2 : [3000]usize = [0] ** 3000; + var gt2 : [3200]usize = [0] ** 3200; + n = emit_gates("leaf\n.subckt inverter Vin VDD VSS Vout\n.ends\n.subckt top a b\nx1 a vdd 0 b inverter\nR1 a b 1k\n.ends\n.end\n", "top", &w, &out, &st2, >2); + assert(st2[ERR_LINE] == 6); + assert(st2[ERR_WHY] == Y_GATE_LEAF); + assert(out_is(n, &out, 0, "line 6: not gate level: an element that is not an instance of a gate or of a subcircuit\n card: R1 a b 1k\n")); + // a transistor that is not inside a gate + var st3 : [3000]usize = [0] ** 3000; + var gt3 : [3200]usize = [0] ** 3200; + n = emit_gates("leaf\n.subckt top a b\nXM1 b a 0 0 sg13_lv_nmos w=1.0u l=0.45u ng=1 m=1\n.ends\n.end\n", "top", &w, &out, &st3, >3); + assert(n > 0); + assert(st3[ERR_LINE] == 3 and st3[ERR_WHY] == Y_GATE_LEAF); + // a gate that lacks a pin its kind needs (the inverter's output is called Vo) + var st4 : [3000]usize = [0] ** 3000; + var gt4 : [3200]usize = [0] ** 3200; + n = emit_gates("pins\n.subckt inverter Vin VDD VSS Vo\n.ends\n.subckt top a b\nx1 a vdd 0 b inverter\n.ends\n.end\n", "top", &w, &out, &st4, >4); + assert(n > 0); + assert(st4[ERR_LINE] == 5 and st4[ERR_WHY] == Y_GATE_PINS); + // m on a gate, and on a level + var st5 : [3000]usize = [0] ** 3000; + var gt5 : [3200]usize = [0] ** 3200; + n = emit_gates("m\n.subckt inverter Vin VDD VSS Vout\n.ends\n.subckt top a b\nx1 a vdd 0 b inverter m=2\n.ends\n.end\n", "top", &w, &out, &st5, >5); + assert(n > 0); + assert(st5[ERR_LINE] == 5 and st5[ERR_WHY] == Y_GATE_M); + var st6 : [3000]usize = [0] ** 3000; + var gt6 : [3200]usize = [0] ** 3200; + n = emit_gates("m\n.subckt inverter Vin VDD VSS Vout\n.ends\n.subckt cell a b\nx1 a vdd 0 b inverter\n.ends\n.subckt top a b\nx1 a b cell m=3\n.ends\n.end\n", "top", &w, &out, &st6, >6); + assert(n > 0); + assert(st6[ERR_LINE] == 8 and st6[ERR_WHY] == Y_GATE_M); + // a subcircuit that instantiates itself + var st7 : [3000]usize = [0] ** 3000; + var gt7 : [3200]usize = [0] ** 3200; + n = emit_gates("loop\n.subckt cell a\nx1 a cell\n.ends\n.subckt top a\nx1 a cell\n.ends\n.end\n", "top", &w, &out, &st7, >7); + assert(n > 0); + assert(st7[ERR_LINE] == 3 and st7[ERR_WHY] == Y_RECUR); +} + +test the_scanner_refuses_a_deck_before_the_gates_are_read { + var w : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var st : [3000]usize = [0] ** 3000; + var gt : [3200]usize = [0] ** 3200; + // x2 names a subcircuit that is not defined: the refusal is the scanner's, on the earliest line + const n : usize = emit_gates("undefined\n.subckt inverter Vin VDD VSS Vout\n.ends\n.subckt top a b\nx1 a vdd 0 b inverter\nx2 a b nosuchgate\n.ends\n.end\n", "top", &w, &out, &st, >); + assert(n > 0); + assert(st[ERR_LINE] == 6); + assert(st[ERR_WHY] == Y_UNDEF); + assert(gt[GT_NGATE] == 0); +}