SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
0ba037b verified
|
Raw History Blame Contribute Delete
4.44 kB

FPGA build

Runs rtl/bit_accelerator.sv on real FPGA boards. The core's 64-bit buses do not fit on package pins, so rtl/fpga_top.sv wraps it with:

  • 256 × 64-bit words of block RAM (byte addresses 0-2047). Any other address answers with mem_fault, so the error path works on hardware.
  • A UART command interface, 115200 baud 8N1.
Board FPGA Clock Top Constraints
iCEBreaker iCE40UP5K-SG48 12 MHz boards/icebreaker_top.sv boards/icebreaker.pcf
ULX3S ECP5 LFE5U-25F CABGA381 25 MHz boards/ulx3s_top.sv boards/ulx3s.lpf

LEDs: iCEBreaker red = last operation reported an error, green = heartbeat. ULX3S led[0] = error, led[1] = busy, led[7] = heartbeat. The button resets.

Build and program

Ubuntu 24.04: sudo apt-get install yosys nextpnr-ice40 nextpnr-ecp5 fpga-icestorm fpga-trellis iverilog verilator.

make icebreaker          # build/icebreaker.bin
make prog-icebreaker     # iceprog
make ulx3s               # build/ulx3s.bit (ULX3S_DEVICE=12k|25k|45k|85k to match your board)
make prog-ulx3s          # openFPGALoader --board ulx3s

Then, from the host (cd host && cargo build --release):

bitacc --port /dev/ttyUSB1 ping                 # pong
bitacc --port /dev/ttyUSB1 write 0 0x8000000000000001
bitacc --port /dev/ttyUSB1 exec get 0 63        # 1
bitacc --port /dev/ttyUSB1 exec toggle 0 5      # 1
bitacc --port /dev/ttyUSB1 read 0               # 0x8000000000000021
bitacc --port /dev/ttyUSB1 exec get 4096 0      # error (outside the RAM)
bitacc --port /dev/ttyUSB1 selftest 10000       # acceptance test, must report 0 mismatches

The iCEBreaker's UART is the FTDI's second interface (usually /dev/ttyUSB1). On the ULX3S it is the only FTDI port (usually /dev/ttyUSB0).

selftest writes random data to all 256 words. It then runs random operations (including out-of-range addresses and undefined opcodes) and checks every status and the final RAM contents against a host model of the RTL.

Protocol

All multi-byte fields are little-endian.

Host → FPGA FPGA → host
'P' 'B' ping
'W' idx[1] data[8] 'K' write word
'R' idx[1] data[8] read word
'X' op[1] base[8] offset[8] 0xA0 | error<<1 | bit run one operation
anything else '?'

Results

Yosys 0.33, nextpnr 0.6, place-and-route seeds 1-5:

Board Logic Block RAM Fmax (worst of 5 seeds) Board clock
iCEBreaker 1138 / 5280 LC (21%) 4 / 30 39.06 MHz 12 MHz
ULX3S 25F 1171 / 24288 LUT4 (4%), 725 FF 2 / 56 DP16KD 73.22 MHz 25 MHz

Verification

Check Command Result
Lint, both board tops make lint verilator -Wall clean
UART-level testbench on RTL, Icarus + Verilator make sim 0 failures (7554 checks under Icarus, 7407 under Verilator; their random streams differ)
Same testbench on the iCE40 gate-level netlist (with the SB_RAM40_4K model) make gl-sim-ice40 7554 checks, 0 failures
Same testbench on the ECP5 gate-level netlist make gl-sim-ecp5 7554 checks, 0 failures
Host tool against the RTL through a pseudo-terminal make cosim selftest 2000 ops, 0 mismatches

The testbench drives the serial line bit by bit. It checks pings, unknown commands, all 256 words, directed operations (word crossing, last bit of RAM, out-of-range reads, undefined opcodes) and 300 random operations against a reference model. It also checks that TX idles high from power-up. Six of seven hand-made faults in fpga_top/uart_tx are detected. The seventh (no write-fault response) cannot occur: a write only follows a successful read of the same word.

Two problems were found and fixed on the way:

  • TX powered up low. FPGA flip-flops power up low, so TX started low and a host would read a junk byte.
  • Testbench race. The testbench released reset on a clock edge, so the gate-level netlist saw a partial reset.

Limits:

  • Not run on a physical board. Pin assignments are taken from each board's reference constraints and have not been checked against a board.
  • ECP5 block RAM is not covered at gate level. Yosys 0.33 ships DP16KD only as a black box, so gl-sim-ecp5 maps the memory to LUT RAM (-nobram). The DP16KD mapping is covered by place-and-route only. The iCE40 gate-level run does include block RAM.