logicaffeine_compile/codegen_sva/
scratchpad_rtl.rs1pub const CW_BITS: usize = 72; pub const DATA_BITS: usize = 64;
21const HAMMING_HI: usize = 71; const CHECK_BITS: usize = 7; fn is_check_pos(pos: usize) -> bool {
26 pos != 0 && (pos & (pos - 1)) == 0
27}
28
29fn data_index_at(pos: usize) -> Option<usize> {
33 if pos == 0 || is_check_pos(pos) {
34 return None;
35 }
36 let mut di = 0;
37 for p in 1..=HAMMING_HI {
38 if !is_check_pos(p) {
39 if p == pos {
40 return Some(di);
41 }
42 di += 1;
43 }
44 }
45 None
46}
47
48#[derive(Clone, Copy, PartialEq, Eq, Debug)]
50pub enum Decode {
51 NoError,
53 Corrected(usize),
55 CorrectedParityBit,
57 DoubleError,
60}
61
62pub fn encode(data: u64) -> u128 {
65 let mut cw = [false; CW_BITS];
66 for pos in 1..=HAMMING_HI {
68 if let Some(di) = data_index_at(pos) {
69 cw[pos] = (data >> di) & 1 == 1;
70 }
71 }
72 for i in 0..CHECK_BITS {
75 let cp = 1usize << i;
76 let mut parity = false;
77 for pos in 1..=HAMMING_HI {
78 if pos != cp && (pos & cp) != 0 {
79 parity ^= cw[pos];
80 }
81 }
82 cw[cp] = parity;
83 }
84 let mut op = false;
86 for pos in 1..=HAMMING_HI {
87 op ^= cw[pos];
88 }
89 cw[0] = op;
90 pack(&cw)
91}
92
93pub fn decode(cw_bits: u128) -> (u64, Decode) {
95 let cw = unpack(cw_bits);
96 let mut syndrome = 0usize;
98 for i in 0..CHECK_BITS {
99 let cp = 1usize << i;
100 let mut parity = false;
101 for pos in 1..=HAMMING_HI {
102 if pos & cp != 0 {
103 parity ^= cw[pos];
104 }
105 }
106 if parity {
107 syndrome |= cp;
108 }
109 }
110 let odd_errors = (0..CW_BITS).fold(false, |acc, p| acc ^ cw[p]);
112
113 let mut fixed = cw;
114 let verdict = match (syndrome, odd_errors) {
115 (0, false) => Decode::NoError,
116 (0, true) => Decode::CorrectedParityBit, (s, true) if s <= HAMMING_HI => {
118 fixed[s] ^= true; Decode::Corrected(s)
120 }
121 _ => Decode::DoubleError, };
123 let mut data = 0u64;
124 for pos in 1..=HAMMING_HI {
125 if let Some(di) = data_index_at(pos) {
126 if fixed[pos] {
127 data |= 1u64 << di;
128 }
129 }
130 }
131 (data, verdict)
132}
133
134fn pack(cw: &[bool; CW_BITS]) -> u128 {
135 let mut v = 0u128;
136 for (i, &b) in cw.iter().enumerate() {
137 if b {
138 v |= 1u128 << i;
139 }
140 }
141 v
142}
143
144fn unpack(v: u128) -> [bool; CW_BITS] {
145 let mut cw = [false; CW_BITS];
146 for (i, slot) in cw.iter_mut().enumerate() {
147 *slot = (v >> i) & 1 == 1;
148 }
149 cw
150}
151
152fn check_bit_terms(i: usize) -> Vec<usize> {
155 let cp = 1usize << i;
156 let mut terms = Vec::new();
157 for pos in 1..=HAMMING_HI {
158 if pos != cp && (pos & cp) != 0 {
159 if let Some(di) = data_index_at(pos) {
160 terms.push(di);
161 }
162 }
163 }
164 terms
165}
166
167fn exceeds(bits: &[String], c: usize) -> String {
171 if c >> bits.len() != 0 {
172 return "1'b0".to_string();
173 }
174 let mut acc: Option<String> = None;
175 for (i, b) in bits.iter().enumerate() {
176 acc = match (c >> i & 1 == 1, acc) {
177 (true, None) => None,
178 (true, Some(a)) => Some(format!("({b} & {a})")),
179 (false, None) => Some(b.clone()),
180 (false, Some(a)) => Some(format!("({b} | {a})")),
181 };
182 }
183 acc.unwrap_or_else(|| "1'b0".to_string())
184}
185
186pub fn emit_secded_verilog() -> String {
191 let mut w = String::new();
192 w.push_str("// secded_enc — SECDED(72,64) encoder (Hamming SEC + overall parity).\n");
195 w.push_str("// GENERATED from the same incidence as codegen_sva::scratchpad_rtl::encode.\n");
196 w.push_str("module secded_enc(input [63:0] din, output [71:0] cw);\n");
197 for pos in 1..=HAMMING_HI {
199 if let Some(di) = data_index_at(pos) {
200 w.push_str(&format!(" wire p{pos} = din[{di}];\n"));
201 }
202 }
203 let reduce = |terms: &[usize]| {
206 let t: Vec<String> = terms.iter().map(|d| format!("din[{d}]")).collect();
207 format!("^{{{}}}", t.join(", "))
208 };
209 for i in 0..CHECK_BITS {
210 w.push_str(&format!(" wire p{} = {};\n", 1usize << i, reduce(&check_bit_terms(i))));
211 }
212 let mut count = [1usize; DATA_BITS];
215 for i in 0..CHECK_BITS {
216 for d in check_bit_terms(i) {
217 count[d] += 1;
218 }
219 }
220 let overall: Vec<usize> = (0..DATA_BITS).filter(|&d| count[d] % 2 == 1).collect();
221 w.push_str(&format!(" wire p0 = {};\n", reduce(&overall)));
222 w.push_str(" assign cw = {");
224 for pos in (0..=HAMMING_HI).rev() {
225 w.push_str(&format!("p{pos}"));
226 if pos != 0 {
227 w.push_str(", ");
228 }
229 }
230 w.push_str("};\nendmodule\n\n");
231
232 w.push_str("// secded_dec — SECDED(72,64) decoder: corrects any single-bit error,\n");
234 w.push_str("// detects any double-bit error (double_err), recovers the 64 data bits.\n");
235 w.push_str("module secded_dec(input [71:0] cw_in, output [63:0] dout,\n");
236 w.push_str(" output corrected, output double_err);\n");
237 for pos in 0..=HAMMING_HI {
239 w.push_str(&format!(" wire q{pos} = cw_in[{pos}];\n"));
240 }
241 for i in 0..CHECK_BITS {
243 let cp = 1usize << i;
244 let terms: Vec<String> = (1..=HAMMING_HI)
245 .filter(|p| p & cp != 0)
246 .map(|p| format!("q{p}"))
247 .collect();
248 w.push_str(&format!(" wire s{i} = ^{{{}}};\n", terms.join(", ")));
249 }
250 w.push_str(&format!(
251 " wire [{hi}:0] syndrome = {{{bits}}};\n",
252 hi = CHECK_BITS - 1,
253 bits = (0..CHECK_BITS)
254 .rev()
255 .map(|i| format!("s{i}"))
256 .collect::<Vec<_>>()
257 .join(", ")
258 ));
259 w.push_str(" wire odd_err = ^cw_in;\n");
261 w.push_str(" wire syn_nz = |syndrome;\n");
262 let syndrome_bits: Vec<String> = (0..CHECK_BITS).map(|i| format!("s{i}")).collect();
265 w.push_str(&format!(
266 " wire beyond = {};\n",
267 exceeds(&syndrome_bits, HAMMING_HI)
268 ));
269 w.push_str(" assign corrected = odd_err & ~beyond;\n");
270 w.push_str(" assign double_err = syn_nz & (~odd_err | beyond);\n");
271 for pos in 1..=HAMMING_HI {
273 if let Some(di) = data_index_at(pos) {
274 w.push_str(&format!(
275 " wire f{di} = q{pos} ^ (odd_err & (syndrome == {pos})); // pos {pos}\n"
276 ));
277 }
278 }
279 w.push_str(" assign dout = {");
280 for di in (0..DATA_BITS).rev() {
281 w.push_str(&format!("f{di}"));
282 if di != 0 {
283 w.push_str(", ");
284 }
285 }
286 w.push_str("};\nendmodule\n");
287 w
288}
289
290pub fn emit_double_buffer_verilog() -> String {
294 r#"// double_buffer — ping-pong scratchpad bank select. Compute reads read_bank while
295// the DMA fills fill_bank; a swap flips both. read_bank != fill_bank by construction.
296module double_buffer(input clk, input rst, input swap,
297 output reg read_bank, output fill_bank);
298 assign fill_bank = ~read_bank;
299 always @(posedge clk) begin
300 if (rst) read_bank <= 1'b0;
301 else if (swap) read_bank <= ~read_bank;
302 end
303endmodule
304"#
305 .to_string()
306}
307
308pub fn emit_secded_testbench() -> String {
312 r#"// tb_secded — SECDED(72,64) encode -> inject fault -> decode, vs the oracle.
313`timescale 1ns/1ps
314module tb_secded;
315 reg [63:0] din, edout; reg [71:0] mask; reg ecorr, edbl;
316 integer fd, r, errs = 0, n = 0;
317 wire [71:0] cw; wire [63:0] dout; wire corrected, double_err;
318 secded_enc enc(.din(din), .cw(cw));
319 wire [71:0] cw_corrupt = cw ^ mask;
320 secded_dec dec(.cw_in(cw_corrupt), .dout(dout), .corrected(corrected), .double_err(double_err));
321 initial begin
322 fd = $fopen("secded_vec.hex", "r");
323 while (!$feof(fd)) begin
324 r = $fscanf(fd, "%h %h %h %b %b\n", din, mask, edout, ecorr, edbl);
325 if (r == 5) begin
326 #1;
327 if (double_err !== edbl) begin errs=errs+1; $display("FAIL row %0d: double_err %b!=%b", n, double_err, edbl); end
328 else if (!edbl && dout !== edout) begin errs=errs+1; $display("FAIL row %0d: data %h!=%h", n, dout, edout); end
329 n = n + 1;
330 end
331 end
332 if (errs == 0) $display("PASS: %0d SECDED fault cases match the oracle (SEC + DED)", n);
333 $finish;
334 end
335endmodule
336"#
337 .to_string()
338}
339
340#[cfg(test)]
341#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/scratchpad_rtl/tests.rs"]
342mod tests;