Skip to main content

logicaffeine_compile/codegen_sva/
handshake_rtl.rs

1//! Handshake primitives of the spatial IR in silicon: the bounded FIFO (`Handshake::Fifo`), the counting
2//! semaphore (`Handshake::Semaphore`'s ready counter / valid flag), the arrive-all barrier
3//! (`Handshake::Barrier`) and the one-writer multicast bus (a `Group`). Each emitter is paired with a trace
4//! testbench whose expected values come from the Rust model of the same primitive, and the FIFO's bound is
5//! proven by k-induction with our own certified SAT — the sequential analogue of the combinational proofs
6//! the datapaths carry.
7//!
8//! `emit_fifo_verilog("chan_fifo", 8, 32)` is byte-identical to `chan_rtl::emit_chan_fifo_verilog()`: the
9//! language Pipe IS the parameterized FIFO at its recorded parameters (locked).
10
11use std::collections::VecDeque;
12
13use logicaffeine_proof::bmc::InductionOutcome;
14
15use super::rtl::{NextState, Register, TransitionSystem};
16use super::sva_to_verify::{BitVecBoundedOp, BoundedExpr};
17
18/// Bits needed to count `0..=n`.
19fn count_bits(n: usize) -> u32 {
20    (usize::BITS - n.leading_zeros()).max(1)
21}
22
23/// Bits needed to index `0..n`.
24fn index_bits(n: usize) -> u32 {
25    if n <= 1 {
26        1
27    } else {
28        (usize::BITS - (n - 1).leading_zeros()).max(1)
29    }
30}
31
32fn pad_to(s: &str, col: usize) -> String {
33    let mut out = s.to_string();
34    while out.len() < col {
35        out.push(' ');
36    }
37    out
38}
39
40/// A bounded FIFO of `depth` entries of `width` bits: FIFO order, `full` refuses a sender, `empty` refuses
41/// a receiver, `dout` is the oldest buffered value. Power-of-two depths wrap their pointers naturally; other
42/// depths compare explicitly.
43pub fn emit_fifo_verilog(name: &str, depth: usize, width: usize) -> String {
44    assert!(depth >= 1 && width >= 1);
45    let cw = count_bits(depth);
46    let pw = index_bits(depth);
47    let pow2 = depth.is_power_of_two();
48    let w1 = width - 1;
49    let mut s = String::new();
50    s.push_str(&format!(
51        "// {name} — the language Pipe (bounded FIFO channel) in silicon.\n"
52    ));
53    s.push_str("// FIFO order, bounded (full refuses a sender, empty refuses a receiver) — the discipline of\n");
54    s.push_str("// logicaffeine_runtime::channel::Chan. Proven ≡ the software channel by chan_fifo_matches_the_runtime_channel.\n");
55    s.push_str(&format!("// DEPTH = {depth}, WIDTH = {width}.\n"));
56    s.push_str(&format!("module {name}(\n"));
57    let port = |kind: &str, range: &str, ident: &str, tail: &str| -> String {
58        let head = if range.is_empty() {
59            format!("  {kind}")
60        } else {
61            format!(
62                "  {kind}{}{range}",
63                if kind == "input" { "  " } else { " " }
64            )
65        };
66        format!("{}{ident}{tail}\n", pad_to(&head, 20))
67    };
68    s.push_str(&port("input", "", "clk", ","));
69    s.push_str(&port("input", "", "rst", ","));
70    s.push_str(&port("input", "", "push", ","));
71    s.push_str(&port("input", &format!("[{w1}:0]"), "din", ","));
72    s.push_str(&port("input", "", "pop", ","));
73    s.push_str(&port(
74        "output",
75        &format!("[{w1}:0]"),
76        "dout",
77        ",     // combinational: the oldest buffered value (FIFO front)",
78    ));
79    s.push_str(&port(
80        "output",
81        "",
82        "pushed",
83        ",   // this cycle's push was accepted (buffer had room)",
84    ));
85    s.push_str(&port(
86        "output",
87        "",
88        "popped",
89        ",   // this cycle's pop was accepted (buffer was non-empty)",
90    ));
91    s.push_str(&port("output", "", "empty", ","));
92    s.push_str(&port("output", "", "full", ","));
93    s.push_str(&port("output", &format!("[{}:0]", cw - 1), "count", ""));
94    s.push_str(");\n");
95    let mem_decl = format!("  reg [{w1}:0] ");
96    let reg_decl = |bits: u32| pad_to(&format!("  reg [{}:0]", bits - 1), mem_decl.len());
97    s.push_str(&format!("{mem_decl}mem [0:{}];\n", depth - 1));
98    s.push_str(&format!(
99        "{}// 0..{depth}\n",
100        pad_to(&format!("{}cnt;", reg_decl(cw)), 30)
101    ));
102    let wrap = if pow2 {
103        format!("wrap mod {depth}")
104    } else {
105        format!("wrap at {depth} (explicit compare)")
106    };
107    s.push_str(&format!(
108        "{}// {wrap}\n",
109        pad_to(&format!("{}head, tail;", reg_decl(pw)), 30)
110    ));
111    s.push_str(&format!("  assign empty  = (cnt == {cw}'d0);\n"));
112    s.push_str(&format!("  assign full   = (cnt == {cw}'d{depth});\n"));
113    s.push_str("  assign popped = pop  && !empty;\n");
114    s.push_str("  assign pushed = push && !full;\n");
115    s.push_str("  assign dout   = mem[head];\n");
116    s.push_str("  assign count  = cnt;\n");
117    s.push_str("  always @(posedge clk) begin\n");
118    s.push_str("    if (rst) begin\n");
119    s.push_str(&format!(
120        "      cnt <= {cw}'d0; head <= {pw}'d0; tail <= {pw}'d0;\n"
121    ));
122    s.push_str("    end else begin\n");
123    if pow2 {
124        s.push_str(&format!("      if (popped) head <= head + {pw}'d1;\n"));
125        s.push_str(&format!(
126            "      if (pushed) begin mem[tail] <= din; tail <= tail + {pw}'d1; end\n"
127        ));
128    } else {
129        let last = depth - 1;
130        s.push_str(&format!(
131            "      if (popped) head <= (head == {pw}'d{last}) ? {pw}'d0 : head + {pw}'d1;\n"
132        ));
133        s.push_str(&format!("      if (pushed) begin mem[tail] <= din; tail <= (tail == {pw}'d{last}) ? {pw}'d0 : tail + {pw}'d1; end\n"));
134    }
135    s.push_str(&format!(
136        "      cnt <= cnt - (popped ? {cw}'d1 : {cw}'d0) + (pushed ? {cw}'d1 : {cw}'d0);\n"
137    ));
138    s.push_str("    end\n  end\nendmodule\n");
139    s
140}
141
142/// `0 ≤ cnt ≤ depth` for EVERY push/pop stream, by k-induction over the FIFO's count register exactly as
143/// the RTL computes it (`popped = pop ∧ cnt≠0`, `pushed = push ∧ cnt≠depth`).
144pub fn fifo_bound_certified(depth: usize, width: usize) -> bool {
145    let _ = width;
146    let cw = count_bits(depth);
147    let cnt = || Box::new(BoundedExpr::BitVecVar("cnt".into(), cw));
148    let k = |v: u64| {
149        Box::new(BoundedExpr::BitVecConst {
150            width: cw,
151            value: v,
152        })
153    };
154    let bv = |op, l: Box<BoundedExpr>, r: Box<BoundedExpr>| {
155        Box::new(BoundedExpr::BitVecBinary {
156            op,
157            left: l,
158            right: r,
159        })
160    };
161    let eqk = |v: u64| *bv(BitVecBoundedOp::Eq, cnt(), k(v));
162    let and = |a: BoundedExpr, b: BoundedExpr| BoundedExpr::And(Box::new(a), Box::new(b));
163    let not = |a: BoundedExpr| BoundedExpr::Not(Box::new(a));
164    let popped = and(BoundedExpr::Var("pop".into()), not(eqk(0)));
165    let pushed = and(BoundedExpr::Var("push".into()), not(eqk(depth as u64)));
166    let next = NextState::Ite {
167        cond: popped,
168        then_: Box::new(NextState::Ite {
169            cond: pushed.clone(),
170            then_: Box::new(NextState::Simple(*cnt())),
171            else_: Box::new(NextState::Simple(*bv(BitVecBoundedOp::Sub, cnt(), k(1)))),
172        }),
173        else_: Box::new(NextState::Ite {
174            cond: pushed,
175            then_: Box::new(NextState::Simple(*bv(BitVecBoundedOp::Add, cnt(), k(1)))),
176            else_: Box::new(NextState::Simple(*cnt())),
177        }),
178    };
179    let bound_ok = not(*bv(BitVecBoundedOp::ULt, k(depth as u64), cnt()));
180    let ts = TransitionSystem {
181        registers: vec![Register {
182            name: "cnt".into(),
183            width: cw,
184            init: Some(0),
185            next,
186        }],
187        property: bound_ok,
188    };
189    ts.prove_invariant(1) == InductionOutcome::Proven
190}
191
192fn splitmix(state: &mut u64) -> u64 {
193    *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
194    let mut z = *state;
195    z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
196    z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
197    z ^ (z >> 31)
198}
199
200/// A `width`-bit hex literal from a seeded stream (works past 64 bits).
201fn hex_value(state: &mut u64, width: usize) -> String {
202    let digits = width.div_ceil(4);
203    let mut s = String::new();
204    for i in 0..digits {
205        let nib = (splitmix(state) & 0xF) as u32;
206        let nib = if i == 0 {
207            let top_bits = width - (digits - 1) * 4;
208            nib & ((1u32 << top_bits) - 1)
209        } else {
210            nib
211        };
212        s.push(std::char::from_digit(nib, 16).unwrap());
213    }
214    s
215}
216
217/// Scripted push/pop trace against a `VecDeque` model: every cycle the DUT's `pushed`, `popped`, `count` and
218/// (when popped) `dout` must equal the model's. Prints `PASS:` or the first mismatch.
219pub fn emit_fifo_trace_testbench(
220    name: &str,
221    depth: usize,
222    width: usize,
223    steps: usize,
224    seed: u64,
225) -> String {
226    let mut st = seed.wrapping_mul(0xA24B_AED4_963E_E407).wrapping_add(1);
227    let mut model: VecDeque<String> = VecDeque::new();
228    let mut lines = Vec::new();
229    let w1 = width - 1;
230    let cw = count_bits(depth);
231    for _ in 0..steps {
232        let r = splitmix(&mut st);
233        let push = (r & 3) != 0;
234        let pop = ((r >> 2) & 3) == 0 || ((r >> 4) & 1) == 1 && model.len() > depth / 2;
235        let din = hex_value(&mut st, width);
236        let popped = pop && !model.is_empty();
237        let pushed = push && model.len() < depth;
238        // `count` and `dout` are read BEFORE the clock edge, so the expectation is the state the step starts
239        // from; `pushed`/`popped` are the combinational accept flags of the step itself.
240        let count_before = model.len();
241        let dout = if popped {
242            model.pop_front().unwrap()
243        } else {
244            String::new()
245        };
246        if pushed {
247            model.push_back(din.clone());
248        }
249        lines.push((push, pop, din, pushed, popped, dout, count_before));
250    }
251    let mut s = String::new();
252    s.push_str(&format!("`timescale 1ns/1ps\nmodule tb_{name};\n  reg clk = 0, rst = 1, push = 0, pop = 0;\n  reg [{w1}:0] din = 0;\n  wire [{w1}:0] dout;\n  wire pushed, popped, empty, full;\n  wire [{}:0] count;\n", cw - 1));
253    s.push_str(&format!("  {name} dut(.clk(clk), .rst(rst), .push(push), .din(din), .pop(pop), .dout(dout), .pushed(pushed), .popped(popped), .empty(empty), .full(full), .count(count));\n"));
254    s.push_str("  integer fails = 0;\n  always #5 clk = ~clk;\n");
255    s.push_str("  task step(input p, input q, input [{w}:0] d, input ep, input eq, input [{w}:0] ed, input integer ec, input integer cyc);\n".replace("{w}", &w1.to_string()).as_str());
256    s.push_str("    begin\n      push = p; pop = q; din = d;\n      #1;\n");
257    s.push_str("      if (pushed !== ep || popped !== eq || count !== ec[{c}:0] || (eq && dout !== ed)) begin\n        $display(\"FAIL at cycle %0d: pushed=%b/%b popped=%b/%b count=%0d/%0d dout=%h/%h\", cyc, pushed, ep, popped, eq, count, ec, dout, ed);\n        fails = fails + 1;\n      end\n".replace("{c}", &(cw - 1).to_string()).as_str());
258    s.push_str("      @(posedge clk); #1;\n    end\n  endtask\n");
259    s.push_str("  initial begin\n    @(posedge clk); #1 rst = 0;\n");
260    for (i, (push, pop, din, pushed, popped, dout, count_before)) in lines.iter().enumerate() {
261        let ed = if dout.is_empty() {
262            "0".to_string()
263        } else {
264            dout.clone()
265        };
266        s.push_str(&format!("    step(1'b{}, 1'b{}, {width}'h{din}, 1'b{}, 1'b{}, {width}'h{ed}, {count_before}, {i});\n", *push as u8, *pop as u8, *pushed as u8, *popped as u8));
267    }
268    s.push_str("    if (fails == 0) $display(\"PASS: fifo trace\"); else $display(\"FAIL: %0d mismatches\", fails);\n    $finish;\n  end\nendmodule\n");
269    s
270}
271
272/// A counting semaphore of `width` bits: `set` loads `set_val` (the owner's reset), else `inc` adds `inc_n`
273/// (a remote increment); `ge` is `count >= thresh` — the set / increment / wait-threshold discipline every
274/// dataflow mesh's semaphores implement.
275pub fn emit_semaphore_verilog(name: &str, width: usize) -> String {
276    let w1 = width - 1;
277    let mut s = String::new();
278    s.push_str(&format!(
279        "// {name} — a counting semaphore ({width} bits): set wins over inc in the same cycle.\n"
280    ));
281    s.push_str(&format!("module {name}(\n  input clk,\n  input rst,\n  input inc,\n  input [{w1}:0] inc_n,\n  input set,\n  input [{w1}:0] set_val,\n  input [{w1}:0] thresh,\n  output [{w1}:0] count,\n  output ge\n);\n"));
282    s.push_str(&format!(
283        "  reg [{w1}:0] cnt;\n  assign count = cnt;\n  assign ge = (cnt >= thresh);\n"
284    ));
285    s.push_str(&format!("  always @(posedge clk) begin\n    if (rst) cnt <= {width}'d0;\n    else if (set) cnt <= set_val;\n    else if (inc) cnt <= cnt + inc_n;\n  end\nendmodule\n"));
286    s
287}
288
289/// Scripted set/inc/threshold trace against the Rust model of the semaphore.
290pub fn emit_semaphore_trace_testbench(name: &str, width: usize) -> String {
291    let w1 = width - 1;
292    let mask: u64 = if width >= 64 {
293        u64::MAX
294    } else {
295        (1u64 << width) - 1
296    };
297    let mut st = 0x5EED_u64;
298    let mut cnt: u64 = 0;
299    let mut script = Vec::new();
300    for i in 0..120 {
301        let r = splitmix(&mut st);
302        let (set, set_val, inc, inc_n, thresh) = match r % 5 {
303            0 => (true, (r >> 8) & 7, false, 0, (r >> 16) & 7),
304            1 | 2 => (false, 0, true, 1 + ((r >> 8) & 3), (r >> 16) & 7),
305            3 => (true, 0, true, 1, (r >> 16) & 7),
306            _ => (false, 0, false, 0, (r >> 16) & 7),
307        };
308        let ge_before = cnt >= thresh;
309        if set {
310            cnt = set_val & mask;
311        } else if inc {
312            cnt = (cnt + inc_n) & mask;
313        }
314        script.push((i, set, set_val, inc, inc_n, thresh, ge_before, cnt));
315    }
316    let mut s = String::new();
317    s.push_str(&format!("`timescale 1ns/1ps\nmodule tb_{name};\n  reg clk = 0, rst = 1, inc = 0, set = 0;\n  reg [{w1}:0] inc_n = 0, set_val = 0, thresh = 0;\n  wire [{w1}:0] count;\n  wire ge;\n"));
318    s.push_str(&format!("  {name} dut(.clk(clk), .rst(rst), .inc(inc), .inc_n(inc_n), .set(set), .set_val(set_val), .thresh(thresh), .count(count), .ge(ge));\n"));
319    s.push_str("  integer fails = 0;\n  always #5 clk = ~clk;\n  initial begin\n    @(posedge clk); #1 rst = 0;\n");
320    for (i, set, set_val, inc, inc_n, thresh, ge_before, after) in script {
321        s.push_str(&format!("    set = {}; set_val = {set_val}; inc = {}; inc_n = {inc_n}; thresh = {thresh}; #1;\n", set as u8, inc as u8));
322        s.push_str(&format!("    if (ge !== 1'b{}) begin $display(\"FAIL step {i}: ge=%b expected {}\", ge); fails = fails + 1; end\n", ge_before as u8, ge_before as u8));
323        s.push_str(&format!("    @(posedge clk); #1;\n    if (count !== {width}'d{after}) begin $display(\"FAIL step {i}: count=%0d expected {after}\", count); fails = fails + 1; end\n"));
324    }
325    s.push_str("    if (fails == 0) $display(\"PASS: semaphore trace\"); else $display(\"FAIL: %0d mismatches\", fails);\n    $finish;\n  end\nendmodule\n");
326    s
327}
328
329/// An arrive-all barrier over `n` parties: arrivals are sticky; the cycle every party has arrived, `released`
330/// pulses for one cycle and the barrier re-arms.
331pub fn emit_barrier_verilog(name: &str, n: usize) -> String {
332    assert!(n >= 1);
333    let n1 = n - 1;
334    let mut s = String::new();
335    s.push_str(&format!("// {name} — an arrive-all barrier over {n} parties (sticky arrivals, one-cycle released, re-arms).\n"));
336    s.push_str(&format!("module {name}(\n  input clk,\n  input rst,\n  input [{n1}:0] arrive,\n  output released\n);\n"));
337    s.push_str(&format!("  reg [{n1}:0] seen;\n  reg rel;\n  wire all_in = &(seen | arrive);\n  assign released = rel;\n"));
338    s.push_str(&format!("  always @(posedge clk) begin\n    if (rst) begin seen <= {n}'d0; rel <= 1'b0; end\n    else if (all_in) begin seen <= {n}'d0; rel <= 1'b1; end\n    else begin seen <= seen | arrive; rel <= 1'b0; end\n  end\nendmodule\n"));
339    s
340}
341
342/// Staggered arrivals over several rounds: `released` pulses exactly once per round, on the cycle after the
343/// last party arrives, and never otherwise.
344pub fn emit_barrier_trace_testbench(name: &str, n: usize) -> String {
345    let n1 = n - 1;
346    let mut st = 0xBA77_u64;
347    let mut s = String::new();
348    s.push_str(&format!("`timescale 1ns/1ps\nmodule tb_{name};\n  reg clk = 0, rst = 1;\n  reg [{n1}:0] arrive = 0;\n  wire released;\n  {name} dut(.clk(clk), .rst(rst), .arrive(arrive), .released(released));\n  integer fails = 0;\n  always #5 clk = ~clk;\n  initial begin\n    @(posedge clk); #1 rst = 0;\n"));
349    for round in 0..4 {
350        // Model: sticky set; released the cycle after the last arrival.
351        let mut seen: u64 = 0;
352        let full: u64 = if n >= 64 { u64::MAX } else { (1u64 << n) - 1 };
353        let mut order: Vec<usize> = (0..n).collect();
354        for i in (1..n).rev() {
355            let j = (splitmix(&mut st) as usize) % (i + 1);
356            order.swap(i, j);
357        }
358        let mut idx = 0;
359        while seen != full {
360            // Arrive one or two parties this cycle, sometimes none.
361            let r = splitmix(&mut st);
362            let mut arrive: u64 = 0;
363            let how_many = if r % 4 == 0 {
364                0
365            } else if r % 4 == 1 && idx + 1 < n {
366                2
367            } else {
368                1
369            };
370            for _ in 0..how_many {
371                if idx < n {
372                    arrive |= 1u64 << order[idx];
373                    idx += 1;
374                }
375            }
376            let completes = (seen | arrive) == full;
377            s.push_str(&format!("    arrive = {n}'h{arrive:X}; #1;\n    if (released !== 1'b0) begin $display(\"FAIL round {round}: early released\"); fails = fails + 1; end\n    @(posedge clk); #1;\n"));
378            if completes {
379                s.push_str(&format!("    arrive = {n}'d0; if (released !== 1'b1) begin $display(\"FAIL round {round}: no released after the last arrival\"); fails = fails + 1; end\n    @(posedge clk); #1;\n    if (released !== 1'b0) begin $display(\"FAIL round {round}: released did not drop\"); fails = fails + 1; end\n"));
380                seen = full;
381            } else {
382                seen |= arrive;
383                s.push_str(&format!("    if (released !== 1'b0) begin $display(\"FAIL round {round}: released before all arrived\"); fails = fails + 1; end\n"));
384            }
385        }
386    }
387    s.push_str("    if (fails == 0) $display(\"PASS: barrier trace\"); else $display(\"FAIL: %0d mismatches\", fails);\n    $finish;\n  end\nendmodule\n");
388    s
389}
390
391/// A one-writer multicast bus: on `send`, the next cycle every one of the `n` receivers sees `push` with the
392/// same `dout` — the multicast landing of a `Group` (each receiver pushes it into its own FIFO).
393pub fn emit_mcast_bus_verilog(name: &str, width: usize, n: usize) -> String {
394    let (w1, n1) = (width - 1, n - 1);
395    let mut s = String::new();
396    s.push_str(&format!(
397        "// {name} — a one-writer multicast bus to {n} receivers ({width} bits), registered.\n"
398    ));
399    s.push_str(&format!("module {name}(\n  input clk,\n  input rst,\n  input send,\n  input [{w1}:0] din,\n  output [{n1}:0] push,\n  output [{w1}:0] dout\n);\n"));
400    s.push_str(&format!("  reg strobe;\n  reg [{w1}:0] data;\n  assign push = {{{n}{{strobe}}}};\n  assign dout = data;\n"));
401    s.push_str(&format!("  always @(posedge clk) begin\n    if (rst) begin strobe <= 1'b0; data <= {width}'d0; end\n    else begin strobe <= send; if (send) data <= din; end\n  end\nendmodule\n"));
402    s
403}
404
405/// Sends, idles and back-to-back sends: every receiver's `push` and `dout` follow the writer one cycle later.
406pub fn emit_mcast_bus_trace_testbench(name: &str, width: usize, n: usize) -> String {
407    let (w1, n1) = (width - 1, n - 1);
408    let mut st = 0xB05_u64;
409    let mut s = String::new();
410    s.push_str(&format!("`timescale 1ns/1ps\nmodule tb_{name};\n  reg clk = 0, rst = 1, send = 0;\n  reg [{w1}:0] din = 0;\n  wire [{n1}:0] push;\n  wire [{w1}:0] dout;\n  {name} dut(.clk(clk), .rst(rst), .send(send), .din(din), .push(push), .dout(dout));\n  integer fails = 0;\n  always #5 clk = ~clk;\n  initial begin\n    @(posedge clk); #1 rst = 0;\n"));
411    let all_ones = format!(
412        "{n}'h{:X}",
413        if n >= 64 { u64::MAX } else { (1u64 << n) - 1 }
414    );
415    let mut last_sent: Option<String> = None;
416    for i in 0..60 {
417        let r = splitmix(&mut st);
418        let send = r % 3 != 0;
419        let din = hex_value(&mut st, width);
420        s.push_str(&format!(
421            "    send = {}; din = {width}'h{din};\n    @(posedge clk); #1;\n",
422            send as u8
423        ));
424        if send {
425            s.push_str(&format!("    if (push !== {all_ones} || dout !== {width}'h{din}) begin $display(\"FAIL step {i}: push=%h dout=%h\", push, dout); fails = fails + 1; end\n"));
426            last_sent = Some(din);
427        } else {
428            s.push_str(&format!("    if (push !== {n}'d0) begin $display(\"FAIL step {i}: spurious push\"); fails = fails + 1; end\n"));
429            if let Some(prev) = &last_sent {
430                s.push_str(&format!("    if (dout !== {width}'h{prev}) begin $display(\"FAIL step {i}: dout drifted\"); fails = fails + 1; end\n"));
431            }
432        }
433    }
434    s.push_str("    if (fails == 0) $display(\"PASS: mcast bus trace\"); else $display(\"FAIL: %0d mismatches\", fails);\n    $finish;\n  end\nendmodule\n");
435    s
436}