The end-to-end test for the ICESOC SoC: bitstream load + CPU instruction fetch + custom-instruction decode + eFPGA accelerator + result writeback + memory store. Verifies the full chain.
Makefile— build + sim driver. Same flow as bare-fabric Test/ but pulls in the SoC RTL (rtl_icesoc_with_cores/*.v) on top ofTile/andFabric/.top_tb.v— testbench (~620 lines). Five phases:- Bitstream load (fast) — uses
tb_sw_forceoverride to driveSelfWriteData/Strobedirectly into the eFPGA Config FSM, ~250us instead of UART's ~20s. - SoC stress (16 hand patterns) —
tb_drive_opsoverrides drive operand busses; verifies SoC interconnect (interleave + bit-reverse) over hand-crafted patterns. - SoC random stress (200 patterns) — same as above but with
$randomfills. - CPU firmware Phase A — forces a 9-instruction RISC-V program into SRAM and lets the CPU run it. Three back-to-back
efpga op=2instructions (captureresult_c= constant0xDEADBEEF) into x5/x6/x7, thensweach register toSRAM[0x100/0x104/0x108]. - CPU firmware Phase B — two
efpga op=0instructions (captureresult_a=E_OPA + E_OPB):- First with
rs1=0, rs2=0→ expected ~0 - Second with
rs1=0x10000000, rs2=0x555→ expected0x80000555(bit-level prediction:(rs1[28:0] << 3) + rs2).
- First with
- Bitstream load (fast) — uses
mem.hex— legacy 6-instruction CPU program (UART-to-mem encoded). The testbench's Phase 4/5 forces SRAM directly, bypassing this.icesoc_waveforms.gtkw— GTKWave save file (signal selection / timestamps for debugging).build_fabulous_fabric.tcl— TCL helper (currently unused).makehex.py— bitstream binary → hex converter.
source ~/oss-cad-suite/environment # if not already in your shell
cd ICESOC_FABulous_user_project/icesoc_sim/Test
make build_test_design # synth + PnR + bit_gen
make run_icesoc_simulation \
VVP_ARGS="+bitstream_hex=build_icesoc/top.hex +mem_hex=mem.hex" # ~5s, no FSTDefault Makefile uses FST. Don't run with FST unless you're prepared to wait hours — UART simulation + waveform writes is the slow combination.
| Phase | What's exercised |
|---|---|
| 1 | Config FSM accepts SelfWrite frames; bitstream lands correctly in fabric latches. |
| 2-3 | SoC interconnect: CPU operand busses → row-interleave + bit-reverse → fabric input pads → fabric computation → fabric output pads → de-interleave → CPU result busses. 216 patterns × 6 outputs. |
| 4 | CPU instruction fetch from SRAM; FlexBex custom-instruction decode (opcode 0x0B); eFPGA accelerator FSM dispatch; register writeback to multiple rd values (x5, x6, x7); CPU sw data writes through inter → SRAM Port 0. |
| 5 | Operand-dependent fabric computation: rs1/rs2 register values flow to fabric → ADD computed → result returns to register → store. Verifies operands actually drive the fabric (not stuck constants). |
| Bits | Field |
|---|---|
[6:0] |
opcode = 0x0B |
[11:7] |
rd (destination register) |
[13:12] |
operator: 00=result_a (W:XOR/E:ADD), 01=result_b (W:AND/E:SUB), 10=result_c (W:OR/E:DEADBEEF), 11=write_strobe (config write) |
[14] |
unused |
[19:15] |
rs1 → eFPGA_operand_a |
[24:20] |
rs2 → eFPGA_operand_b |
[28:25] |
delay (cycles to wait before result capture; 15 = wait for eFPGA_fpga_done) |
[31:29] |
unused |
Example efpga x5, x1, x2, op=2, delay=2:
rd=5 (00101),rs1=1 (00001),rs2=2 (00010),op=10,delay=0010- Encoding =
delay<<25 | rs2<<20 | rs1<<15 | op<<12 | rd<<7 | 0x0B=0x0420A28B
When the CPU executes efpga with rs1, rs2:
E_OPA[35] = eFPGA_en (1 during op)
E_OPA[34:3] = rs1[31:0] (CPU operand_a)
E_OPA[2:0] = {irq_id_2_o[1:0], irq_ack_2_o} (typically 0)
E_OPB[31:0] = rs2[31:0] (CPU operand_b)
E_OPB[33:32] = eFPGA_delay[1:0] (lower 2 bits of 4-bit delay)
E_OPB[35:34] = eFPGA_operator[1:0]
The fabric computes E_RES0 = E_OPA + E_OPB (etc.) on the full 36 bits, and the CPU sees [31:0] of that. So:
result_a[31:0] = ((rs1[28:0] << 3) | irq) + rs2[31:0] (mod 2^32)
≈ (rs1[28:0] << 3) + rs2 (assuming irq = 0)
That's why Phase B's (rs1=0x10000000, rs2=0x555) produces 0x80000555 exactly:
rs1[28:0] = 0x10000000,<< 3=0x80000000+ rs2 = 0x80000000 + 0x555 = 0x80000555✓
Both ibex_top (core 1) and flexbex_ibex_core (core 2) boot at PC=0x80 and execute from SRAM. Only the FlexBex decodes opcode 0x0B; ibex_top sees it as illegal and traps (wandering into garbage memory). The verification only depends on FlexBex's writes landing in SRAM — ibex's confused execution may briefly write garbage to mem[0x100] before FlexBex overwrites with 0xDEADBEEF, which is what we observed in the trace.
If you wanted to silence ibex_top, you'd either disable it via reset or branch it to a tight loop before the eFPGA opcode (e.g., differentiate by reading mhartid).
Final state of the verification:
- 16/16 hand stress patterns pass
- 200/200 random stress patterns pass
- 3/3 Phase A constant-result sentinels (DEADBEEF) match
- Phase B:
mem[0x10C] = 0x00000000,mem[0x110] = 0x80000555— both passing soft assertions
End-to-end CPU↔eFPGA round-trip verified.