Skip to main content

logicaffeine_compile/codegen_sva/
infinichip_rtl.rs

1//! infinichip_rtl — the integrated **Infinichip** memory subsystem, taken all the way to a
2//! signoff-clean Sky130 GDSII.
3//!
4//! Stage 1 tapes out the command player alone (`codegen_sva::dram::dram_executor`). Stage 2 —
5//! here — composes the *product*: one `infinichip` top-level that wires three INDEPENDENTLY
6//! CERTIFIED sub-blocks into a single manufacturable die:
7//!   • the **command player** — the deterministic DRAM command-stream executor (`dram_executor`);
8//!     its plan-word decode and countdown issue-timing are proven by our OWN SAT in
9//!     `codegen_sva::dram`;
10//!   • the **SECDED(72,64) ECC** on the data path (`secded_enc`/`secded_dec`, `codegen_sva::
11//!     scratchpad_rtl`) — single-error-correct, double-error-detect, exhaustively verified and
12//!     Hamming-SAT-proven;
13//!   • the **double-buffer** (`double_buffer`) — ping-pong bank select whose safety invariant
14//!     `read_bank != fill_bank` holds by construction.
15//!
16//! Because each sub-block carries its own proof, the integration's remaining obligation is that
17//! the WIRING composes correctly — discharged by the self-checking `tb_infinichip` iverilog
18//! testbench (command stream issues, ECC corrects a single fault, banks stay disjoint), and the
19//! data-integrity of the composed ECC path is re-checked here against the certified SECDED oracle.
20//!
21//! The emitted `infinichip.v` is SELF-CONTAINED (every sub-module definition is inlined), so the
22//! OpenLane/Sky130 flow tapes it out from a single PDK-agnostic source, exactly like the other
23//! blocks: `scripts/openlane-run.sh infinichip clk <period_ns> infinichip.v`.
24
25use super::dram::emit_dram_executor_verilog;
26use super::scratchpad_rtl::{emit_double_buffer_verilog, emit_secded_verilog};
27
28/// The `infinichip` top-level module string: the structural wrapper that instantiates the four
29/// certified sub-blocks and exposes their composed interface (command stream, ECC write/read
30/// paths, double-buffer). It adds no datapath logic, only a register at each codec's die-side
31/// boundary: the write path registers the data word before the encoder, the read path registers
32/// the decoder's verdict, so each codec's logic sits between one port and one register and
33/// carries one external delay, never both, and a word crosses either path in one edge.
34fn infinichip_top() -> &'static str {
35    r#"// infinichip — the integrated deterministic memory subsystem (Stage 2 tape-out top).
36// A STRUCTURAL composition of four independently-certified sub-blocks, registered at the
37// ECC boundaries:
38//   dram_executor  — the command player (in-order countdown FSM; decode + issue timing
39//                    certified by our SAT in codegen_sva::dram),
40//   secded_enc/dec — SECDED(72,64) ECC on the data path (single-error-correct, double-error-
41//                    detect; exhaustively verified + Hamming-SAT-proven),
42//   double_buffer  — ping-pong bank select (compute never reads the DMA-filled bank;
43//                    read_bank != fill_bank by construction).
44// Each sub-block carries its own proof; tb_infinichip certifies the wiring composes correctly.
45module infinichip(
46  input clk,
47  input rst,
48  // command player — compile-time DRAM command stream in, issued commands out
49  input [63:0] cmd_word,
50  input cmd_valid,
51  output cmd_ack,
52  output issue,
53  output [2:0] op,
54  output [4:0] bank,
55  output [31:0] row,
56  // ECC write path — 64-bit data in, its 72-bit protected codeword out one edge later
57  input [63:0] wr_data,
58  output [71:0] wr_cw,
59  // ECC read path — 72-bit codeword in, corrected 64-bit data + status out one edge later
60  input [71:0] rd_cw,
61  output reg [63:0] rd_data,
62  output reg rd_corrected,
63  output reg rd_double_err,
64  // scratchpad double buffer
65  input swap,
66  output read_bank,
67  output fill_bank
68);
69  reg  [63:0] wr_word;
70  wire [63:0] rd_word;
71  wire        rd_fix, rd_bad;
72  dram_executor u_cmd (.clk(clk), .rst(rst), .cmd_word(cmd_word), .cmd_valid(cmd_valid),
73                       .cmd_ack(cmd_ack), .issue(issue), .op(op), .bank(bank), .row(row));
74  secded_enc    u_enc (.din(wr_word), .cw(wr_cw));
75  secded_dec    u_dec (.cw_in(rd_cw), .dout(rd_word), .corrected(rd_fix),
76                       .double_err(rd_bad));
77  double_buffer u_dbuf(.clk(clk), .rst(rst), .swap(swap),
78                       .read_bank(read_bank), .fill_bank(fill_bank));
79  always @(posedge clk) begin
80    wr_word       <= wr_data;
81    rd_data       <= rd_word;
82    rd_corrected  <= rd_fix;
83    rd_double_err <= rd_bad;
84  end
85endmodule
86"#
87}
88
89/// Emit the SELF-CONTAINED `infinichip.v`: every certified sub-block definition inlined, followed
90/// by the structural top. One PDK-agnostic file → tapes out on Sky130 with no cross-file deps.
91pub fn emit_infinichip_verilog() -> String {
92    let mut v = String::new();
93    v.push_str(&emit_dram_executor_verilog());
94    v.push('\n');
95    v.push_str(&emit_secded_verilog());
96    v.push('\n');
97    v.push_str(&emit_double_buffer_verilog());
98    v.push('\n');
99    v.push_str(infinichip_top());
100    v
101}
102
103/// Emit a SELF-CHECKING integration testbench: it drives all three datapaths through the composed
104/// `infinichip` top and asserts (1) the ECC read path corrects a single-bit fault and recovers the
105/// data one edge after the codeword arrives, (2) the double-buffer banks stay disjoint across a
106/// swap, and (3) the command player issues a loaded command with the exact decoded payload. Prints
107/// PASS/FAIL — no external vectors needed.
108pub fn emit_infinichip_testbench() -> String {
109    r#"// tb_infinichip — self-checking integration of the composed memory subsystem.
110`timescale 1ns/1ps
111module tb_infinichip;
112  reg clk = 0; always #5 clk = ~clk;
113  reg rst = 1;
114  reg [63:0] cmd_word = 0; reg cmd_valid = 0;
115  wire cmd_ack, issue; wire [2:0] op; wire [4:0] bank; wire [31:0] row;
116  reg [63:0] wr_data = 64'hDEAD_BEEF_1234_5678;
117  wire [71:0] wr_cw;
118  reg [71:0] rd_cw = 0;
119  wire [63:0] rd_data; wire rd_corrected, rd_double_err;
120  reg swap = 0; wire read_bank, fill_bank;
121  integer errors = 0;
122  reg saw_issue = 0;
123
124  infinichip dut(.clk(clk), .rst(rst), .cmd_word(cmd_word), .cmd_valid(cmd_valid),
125                 .cmd_ack(cmd_ack), .issue(issue), .op(op), .bank(bank), .row(row),
126                 .wr_data(wr_data), .wr_cw(wr_cw),
127                 .rd_cw(rd_cw), .rd_data(rd_data), .rd_corrected(rd_corrected),
128                 .rd_double_err(rd_double_err),
129                 .swap(swap), .read_bank(read_bank), .fill_bank(fill_bank));
130
131  // A delta-0 command issues the cycle after it loads; latch and check its payload.
132  always @(posedge clk) if (issue) begin
133    saw_issue <= 1'b1;
134    if (op !== 3'd1 || bank !== 5'd3 || row !== 32'd7) errors = errors + 1;
135  end
136
137  initial begin
138    repeat (3) @(posedge clk);
139    rst = 0;
140    @(posedge clk);
141
142    // (1) ECC read path corrects a single-bit fault (position 7 = a Hamming data bit), its
143    //     verdict registered one edge after the codeword arrives.
144    #1 rd_cw = wr_cw ^ (72'b1 << 7);
145    @(posedge clk); #1;
146    if (rd_data !== wr_data)    begin errors = errors + 1; $display("FAIL: ECC did not recover data"); end
147    if (rd_corrected !== 1'b1)  begin errors = errors + 1; $display("FAIL: ECC did not flag corrected"); end
148    if (rd_double_err !== 1'b0) begin errors = errors + 1; $display("FAIL: ECC false double-error"); end
149
150    // (2) Double-buffer disjointness holds now and across a swap.
151    if (fill_bank !== ~read_bank) begin errors = errors + 1; $display("FAIL: banks not disjoint"); end
152    swap = 1; @(posedge clk); swap = 0; @(posedge clk);
153    if (fill_bank !== ~read_bank) begin errors = errors + 1; $display("FAIL: banks not disjoint after swap"); end
154
155    // (3) Command player issues a loaded (delta-0) command with the exact payload.
156    cmd_word = {24'd0, 3'd1, 5'd3, 32'd7}; cmd_valid = 1;
157    repeat (6) @(posedge clk);
158    cmd_valid = 0;
159    repeat (2) @(posedge clk);
160    if (!saw_issue) begin errors = errors + 1; $display("FAIL: command player never issued"); end
161
162    if (errors == 0)
163      $display("PASS: infinichip integration - command player + SECDED ECC + double buffer");
164    else
165      $display("FAIL: %0d integration errors", errors);
166    $finish;
167  end
168endmodule
169"#
170    .to_string()
171}
172
173#[cfg(test)]
174#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/infinichip_rtl/tests.rs"]
175mod tests;