Skip to main content

logicaffeine_compile/codegen_sva/
coproc_soc.rs

1//! coproc_soc — the ULX3S-flashable SoC wrapper around `coproc_core`.
2//!
3//! `coproc_core` issues BADD/BSUB/BMUL/CMERGE to the synthesized coprocessor blocks and retires
4//! to a 256-bit register file, but its `rout[255:0]` port is 262 pins — impossible on a board.
5//! This wrapper makes it real hardware: hold the core in reset, run it to halt, then stream the
6//! four RESULT registers (R2..R5) out over a single UART TX line, MSB byte first, each frame
7//! preceded by a 0xA5 sync byte. A host (`scripts/coproc-host.py`) reads the 1 + 128 bytes and
8//! checks them against the software model (`run_core`) — the on-silicon proof of a CPU-issued
9//! coprocessor op.
10//!
11//! The RTL is FIXED logic (a UART-TX sequencer FSM); only the readout shape depends on the core
12//! (four 256-bit result registers). The testbench's expected registers are DERIVED from
13//! `run_core`, so a change to the coprocessor semantics regenerates the golden in lockstep — no
14//! hand-typed hex. The wrapper .v, its testbench, and the board constraints are drift-locked; the
15//! iverilog PASS + the routed `coproc_soc.bit` are recorded in `benchmarks/silicon/reports.json`.
16
17use super::coproc_core::{run_core, COPROC_A, COPROC_B, COPROC_PROGRAM};
18
19/// The result registers streamed out, in order: R2=BADD, R3=BSUB, R4=BMUL, R5=CMERGE.
20pub const RESULT_REGS: [u8; 4] = [2, 3, 4, 5];
21
22/// Emit the flashable SoC wrapper: `coproc_core` + a 7-state UART-TX readout FSM. External pins
23/// (`clk`, `rst`, `uart_tx`, `led[7:0]`) fit the ULX3S; `CLKS_PER_BIT` = clk_hz / baud (217 for
24/// 25 MHz / 115200). The RTL is operand-independent — the core carries the operands internally.
25pub fn emit_coproc_soc_verilog() -> String {
26    r#"// coproc_soc — a ULX3S-flashable SoC wrapper around coproc_core.
27//
28// coproc_core has a 262-pin interface (rout[255:0]) that cannot map to a board. This wrapper
29// makes it real hardware: it runs coproc_core (which ISSUES BADD/BSUB/BMUL/CMERGE to the
30// synthesized coprocessor blocks and retires to its register file), then streams the four
31// RESULT registers (R2=BADD, R3=BSUB, R4=BMUL, R5=CMERGE) out over a UART TX line, MSB byte
32// first, each frame preceded by a 0xA5 sync byte. A host reads the 4x32 = 128 bytes and checks
33// them against the software model (run_core) — the on-silicon proof of a CPU-issued coproc op.
34//
35// UART: 8N1, one stop bit, LSB-first data. CLKS_PER_BIT = clk_hz / baud (217 for 25 MHz/115200).
36// External pins fit the ULX3S: clk, rst, uart_tx, led. (Loadable programs over UART RX = later.)
37module coproc_soc #(parameter CLKS_PER_BIT = 217) (
38  input        clk,
39  input        rst,        // active-high synchronous reset
40  output reg   uart_tx,
41  output [7:0] led
42);
43  // ---- the coprocessor-issuing core ----
44  reg         core_rst;
45  reg  [2:0]  rsel;
46  wire [255:0] rout;
47  wire         halted;
48  coproc_core u_core(.clk(clk), .rst(core_rst), .rsel(rsel), .rout(rout), .halted(halted));
49
50  // ---- readout + UART-TX sequencer ----
51  localparam S_RESET = 3'd0,  // hold the core in reset for a few cycles
52             S_RUN   = 3'd1,  // release, wait for halted
53             S_LOAD  = 3'd2,  // latch the next byte to send
54             S_START = 3'd3,  // UART start bit
55             S_DATA  = 3'd4,  // 8 data bits
56             S_STOP  = 3'd5,  // UART stop bit
57             S_DONE  = 3'd6;
58  reg [2:0]  state;
59  reg [15:0] clk_cnt;
60  reg [2:0]  bit_idx;
61  reg [7:0]  tx_byte;
62  reg [7:0]  byte_idx;      // 0..128 : one 0xA5 sync + 4 regs x 32 bytes = 129 bytes total
63  reg [3:0]  rst_cnt;
64
65  // byte_idx layout: 0 = sync (0xA5); then group g = (byte_idx-1)/32 selects reg 2+g,
66  // position p = (byte_idx-1)%32 selects the MSB-first byte of that 256-bit register.
67  wire        is_sync = (byte_idx == 8'd0);
68  wire [6:0]  n       = byte_idx - 8'd1;            // 0..127 once past the sync byte
69  wire [1:0]  grp     = n[6:5];                     // 0..3  -> reg 2..5
70  wire [4:0]  pos     = n[4:0];                     // 0..31 -> MSB-first byte index
71  wire [7:0]  reg_byte = rout[(31 - pos) * 8 +: 8]; // pos 0 = most-significant byte
72
73  assign led = {halted, byte_idx[6:0]};            // crude progress on the LEDs
74
75  always @(posedge clk) begin
76    if (rst) begin
77      state <= S_RESET; core_rst <= 1'b1; uart_tx <= 1'b1; clk_cnt <= 16'd0;
78      bit_idx <= 3'd0; byte_idx <= 8'd0; rsel <= 3'd2; rst_cnt <= 4'd0; tx_byte <= 8'd0;
79    end else begin
80      case (state)
81        S_RESET: begin
82          core_rst <= 1'b1; uart_tx <= 1'b1;
83          if (rst_cnt == 4'd8) begin core_rst <= 1'b0; state <= S_RUN; end
84          else rst_cnt <= rst_cnt + 4'd1;
85        end
86        S_RUN: begin
87          core_rst <= 1'b0;
88          rsel <= 3'd2 + grp;                 // present the register for the first data byte
89          if (halted) state <= S_LOAD;
90        end
91        S_LOAD: begin
92          rsel    <= 3'd2 + grp;
93          tx_byte <= is_sync ? 8'hA5 : reg_byte;
94          clk_cnt <= 16'd0; bit_idx <= 3'd0;
95          state   <= S_START;
96        end
97        S_START: begin
98          uart_tx <= 1'b0;                    // start bit
99          if (clk_cnt == CLKS_PER_BIT - 1) begin clk_cnt <= 16'd0; state <= S_DATA; end
100          else clk_cnt <= clk_cnt + 16'd1;
101        end
102        S_DATA: begin
103          uart_tx <= tx_byte[bit_idx];        // LSB-first
104          if (clk_cnt == CLKS_PER_BIT - 1) begin
105            clk_cnt <= 16'd0;
106            if (bit_idx == 3'd7) state <= S_STOP;
107            else bit_idx <= bit_idx + 3'd1;
108          end else clk_cnt <= clk_cnt + 16'd1;
109        end
110        S_STOP: begin
111          uart_tx <= 1'b1;                     // stop bit
112          if (clk_cnt == CLKS_PER_BIT - 1) begin
113            clk_cnt <= 16'd0;
114            if (byte_idx == 8'd128) state <= S_DONE;   // sync + 128 data bytes sent
115            else begin byte_idx <= byte_idx + 8'd1; state <= S_LOAD; end
116          end else clk_cnt <= clk_cnt + 16'd1;
117        end
118        S_DONE: begin uart_tx <= 1'b1; end
119        default: state <= S_DONE;
120      endcase
121    end
122  end
123endmodule
124"#
125    .to_string()
126}
127
128/// Emit the self-checking testbench. The expected result registers are DERIVED from `run_core`
129/// (not hand-typed): the tb decodes the UART TX stream, reconstructs the sync byte + 128 payload
130/// bytes, and checks each MSB-first byte against the model's retired R2..R5. PASS iff every byte
131/// matches — the software≡RTL proof over the serial framing.
132pub fn emit_coproc_soc_testbench() -> String {
133    let regs = run_core(&COPROC_A, &COPROC_B, COPROC_PROGRAM);
134    let tags = ["BADD", "BSUB", "BMUL", "CMERGE"];
135    let mut exp = String::new();
136    for (i, &r) in RESULT_REGS.iter().enumerate() {
137        exp.push_str(&format!(
138            "    exp_r[{r}] = {}; // {}\n",
139            hex256(&regs[r as usize]),
140            tags[i],
141        ));
142    }
143    format!(
144        r#"// tb_coproc_soc — drive coproc_soc, decode its UART TX stream, and check the four result
145// registers against the software model (run_core). PASS iff every streamed byte matches.
146`timescale 1ns/1ps
147module tb_coproc_soc;
148  localparam CPB  = 8;    // CLKS_PER_BIT for fast simulation
149  localparam CLKP = 10;   // clock period (ns)
150
151  reg  clk = 0, rst = 1;
152  wire uart_tx;
153  wire [7:0] led;
154  coproc_soc #(.CLKS_PER_BIT(CPB)) dut(.clk(clk), .rst(rst), .uart_tx(uart_tx), .led(led));
155  always #(CLKP/2) clk = ~clk;
156
157  reg [255:0] exp_r [2:5];
158  reg [7:0]   got [0:128];
159  reg [255:0] tmp;
160  reg [7:0]   exp_byte, rb;
161  integer i, g, p, errors;
162
163  // Receive one 8N1 byte (LSB first) from the TX line by mid-bit sampling.
164  task uart_rx_byte(output [7:0] data);
165    integer k;
166    begin
167      @(negedge uart_tx);                       // start-bit edge
168      #(CPB * CLKP + (CPB * CLKP) / 2);         // advance to the middle of data bit 0
169      for (k = 0; k < 8; k = k + 1) begin
170        data[k] = uart_tx;
171        #(CPB * CLKP);
172      end
173    end
174  endtask
175
176  initial begin
177    // the four retired result registers, {{limb3,limb2,limb1,limb0}}, from run_core(COPROC_A,B):
178{exp}
179    #(3 * CLKP) rst = 0;
180    errors = 0;
181    for (i = 0; i <= 128; i = i + 1) begin
182      uart_rx_byte(rb);
183      got[i] = rb;
184    end
185
186    if (got[0] !== 8'hA5) begin errors = errors + 1; $display("FAIL sync byte: %h", got[0]); end
187    for (i = 0; i < 128; i = i + 1) begin
188      g = i / 32; p = i % 32;
189      tmp = exp_r[2 + g];
190      exp_byte = tmp[(31 - p) * 8 +: 8];
191      if (got[1 + i] !== exp_byte) begin
192        errors = errors + 1;
193        $display("FAIL byte %0d (R%0d MSB-pos %0d): got %h exp %h", i, 2 + g, p, got[1 + i], exp_byte);
194      end
195    end
196
197    if (errors == 0)
198      $display("PASS: coproc_soc streamed R2..R5 over UART, matching run_core (sync + 128 bytes)");
199    else
200      $display("FAIL: %0d UART byte mismatches", errors);
201    $finish;
202  end
203endmodule
204"#,
205        exp = exp.trim_end(),
206    )
207}
208
209/// Emit the ULX3S (LFE5U-85F caBGA381) board constraints: 25 MHz clock (G2), reset = FIRE1 (R1),
210/// UART TX → FTDI RXD (L4), 8 LEDs. Sites from the board's `ulx3s_v20.lpf`.
211pub fn emit_ulx3s_coproc_lpf() -> String {
212    let leds = ["B2", "C2", "C1", "D2", "D1", "E2", "E1", "H3"];
213    let mut locate = String::new();
214    let mut iobuf = String::new();
215    for (i, site) in leds.iter().enumerate() {
216        locate.push_str(&format!("LOCATE COMP \"led[{i}]\" SITE \"{site}\";\n"));
217        iobuf.push_str(&format!(
218            "IOBUF PORT \"led[{i}]\" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;\n"
219        ));
220    }
221    format!(
222        r#"# ULX3S (LFE5U-85F caBGA381) constraints for coproc_soc — the coprocessor-issuing core with a
223# UART result-readout, flashable and verifiable on real hardware. Sites from the board's
224# tools/prjtrellis/examples/ulx3s/ulx3s_v20.lpf.
225SYSCONFIG CONFIG_IOVOLTAGE=3.3 COMPRESS_CONFIG=ON MCCLK_FREQ=62 MASTER_SPI_PORT=DISABLE SLAVE_SPI_PORT=DISABLE SLAVE_PARALLEL_PORT=DISABLE;
226
227# 25 MHz oscillator
228LOCATE COMP "clk" SITE "G2";
229IOBUF PORT "clk" PULLMODE=NONE IO_TYPE=LVCMOS33;
230FREQUENCY PORT "clk" 25 MHZ;
231
232# reset = FIRE1 button (active-high: press to reset + re-stream)
233LOCATE COMP "rst" SITE "R1";
234IOBUF PORT "rst" PULLMODE=DOWN IO_TYPE=LVCMOS33;
235
236# UART TX -> FTDI RXD (the FPGA transmits; read it on the host's /dev/ttyUSB* at 115200)
237LOCATE COMP "uart_tx" SITE "L4";
238IOBUF PORT "uart_tx" PULLMODE=UP IO_TYPE=LVCMOS33 DRIVE=4;
239
240# 8 LEDs — crude progress / halted indicator
241{locate}{iobuf}"#,
242        locate = locate,
243        iobuf = iobuf,
244    )
245}
246
247fn hex256(x: &[u64; 4]) -> String {
248    format!(
249        "256'h{:016x}_{:016x}_{:016x}_{:016x}",
250        x[3], x[2], x[1], x[0]
251    )
252}
253
254/// The 129-byte serial frame the SoC streams: a 0xA5 sync byte, then R2..R5 (256-bit each),
255/// most-significant byte first. This is exactly what `scripts/coproc-host.py` reconstructs and
256/// what `tb_coproc_soc.v` samples off the wire — the single definition of the readout order.
257pub fn expected_stream(regs: &[[u64; 4]; 8]) -> Vec<u8> {
258    let mut out = Vec::with_capacity(1 + 128);
259    out.push(0xA5);
260    for &r in &RESULT_REGS {
261        let reg = regs[r as usize];
262        for pos in 0..32u32 {
263            // MSB byte first: pos 0 is the most-significant byte of the 256-bit register.
264            let byte_from_top = pos;
265            let bit_hi = 255 - byte_from_top * 8;
266            let limb = (bit_hi / 64) as usize;
267            let within = bit_hi % 64; // top bit of this byte inside its limb
268            let byte = (reg[limb] >> (within - 7)) as u8;
269            out.push(byte);
270        }
271    }
272    out
273}
274
275#[cfg(test)]
276#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/coproc_soc/tests.rs"]
277mod tests;