logicaffeine_compile/codegen_sva/
coproc_soc.rs1use super::coproc_core::{run_core, COPROC_A, COPROC_B, COPROC_PROGRAM};
18
19pub const RESULT_REGS: [u8; 4] = [2, 3, 4, 5];
21
22pub 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
128pub 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(®s[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
209pub 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
254pub 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 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; 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;