Research-Stack/6-Documentation/FPGA_PROGRAMMING_GUIDE.md
Brandon Schneider 7a6df586b2 docs: runbook, FPGA programming guide, disaster recovery, API docs
- RUNBOOK.md: k3s/FPGA/Tailscale/GPU/DNS ops procedures
- FPGA_PROGRAMMING_GUIDE.md: SUBLEQ format, memory map, 3 examples
- DISASTER_RECOVERY.md: backup/restore for all components
- API_DOCS.md: dashboard, credential, registry, jobs, blobs APIs
2026-05-29 01:26:41 -05:00

363 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# FPGA Programming Guide — SUBLEQ on the Blitter
**Last updated:** 2026-05-29
**Board:** Sipeed Tang Nano 9K (Gowin GW1NR-LV9)
**CPU:** Blitter6502OISC (One Instruction Set Computer — SUBLEQ)
---
## SUBLEQ Instruction Format
The Blitter implements a **SUBLEQ** (Subtract and Branch if Less-than-or-Equal to Zero) CPU.
Each instruction is **3 words** (3 × 16-bit = 6 bytes):
```
[src] [dst] [next]
```
**Semantics:**
```
mem[dst] = mem[dst] - mem[src]
if mem[dst] <= 0:
PC = next
else:
PC = PC + 3
```
**Special addresses:**
- If `next == 0` (or `next == PC`): **HALT**
- If `src == 0`: reads zero (constant source)
- If `dst == IO_ADDR`: writes to I/O
---
## Memory Map ($0000$FFFF)
The SUBLEQ address space is 16-bit (64K words). The memory map is divided into regions:
| Address Range | Size | Function |
|---------------|------|----------|
| `$0000$0FFF` | 4K words | Program + data (BRAM) |
| `$1000$7FFF` | 28K words | Extended data (if available) |
| `$8000$800F` | 16 words | Q16 LUT result registers |
| `$8010` | 1 word | Voltage controller mode |
| `$8011` | 1 word | Scale space parameter |
| `$8020$8025` | 6 words | HiGHS pivot registers |
| `$FF00` | 1 word | UART TX data register |
| `$FF01` | 1 word | UART TX status (bit 0 = busy) |
| `$FF02` | 1 word | UART RX data register |
| `$FF03` | 1 word | UART RX status (bit 0 = data available) |
| `$FFF0` | 1 word | LED output (bits 0-5 = led[0:5]) |
| `$FFF1` | 1 word | Button input (bit 0 = user_btn) |
---
## Q16 LUT ($8000$8025)
The Q16 LUT is a hardware-accelerated fixed-point arithmetic unit. It operates on Q16.16 values (16-bit integer, 16-bit fraction; total 1.0 = 65536).
### Q16 Operations
Write operands to the LUT registers, then read the result:
| Address | Register | Function |
|---------|----------|----------|
| `$8000` | OP_A (lo) | Operand A, low word |
| `$8001` | OP_A (hi) | Operand A, high word |
| `$8002` | OP_B (lo) | Operand B, low word |
| `$8003` | OP_B (hi) | Operand B, high word |
| `$8004` | OPCODE | Operation selector (0-7) |
| `$8008` | RESULT (lo) | Result, low word |
| `$8009` | RESULT (hi) | Result, high word |
### Opcodes
| Code | Operation | Latency |
|------|-----------|---------|
| 0 | A + B | 2 cycles (74ns @ 27MHz) |
| 1 | A - B | 2 cycles |
| 2 | A × B | 2 cycles |
| 3 | A ÷ B | 2 cycles |
| 4 | √A | 2 cycles |
| 5 | \|A\| | 2 cycles |
| 6 | min(A, B) | 2 cycles |
| 7 | max(A, B) | 2 cycles |
### Q16.16 Encoding
```
value = integer_part × 65536 + fraction_part
Examples:
1.0 = 65536 (0x00010000)
0.5 = 32768 (0x00008000)
3.14 = 205887 (0x000323D7)
-1.0 = -65536 (0xFFFF0000)
```
---
## Voltage Controller ($8010)
The voltage controller manages BRAM access modes:
| Mode | Value | Description |
|------|-------|-------------|
| STORE | 0 | Direct memory read/write |
| COMPUTE | 1 | Q16 LUT computation mode |
| APPROX | 2 | Approximate computation (fast) |
| MORPHIC | 3 | Morphic field mode |
```subleq
; Set voltage controller to COMPUTE mode
; Write 1 to address $8010
```
---
## Scale Space ($8011)
The scale space parameter controls Gaussian kernel selection:
| Value | σ (sigma) | Kernel Bank |
|-------|-----------|-------------|
| 0 | 0.25 | Bank 0 |
| 1 | 0.50 | Bank 1 |
| 2 | 0.75 | Bank 2 |
| 3 | 1.00 | Bank 3 |
---
## HiGHS Pivot Registers ($8020$8025)
3-stage simplex pipeline interface for hardware-accelerated LP solving:
| Address | Register | Function |
|---------|----------|----------|
| `$8020` | PIVOT_ROW | Row index |
| `$8021` | PIVOT_COL | Column index |
| `$8022` | PIVOT_VAL (lo) | Pivot value, low |
| `$8023` | PIVOT_VAL (hi) | Pivot value, high |
| `$8024` | PIVOT_CTRL | Control/status |
| `$8025` | PIVOT_RESULT | Result/iteration count |
---
## Example Programs
### 1. Blink LED
Blink LED 0 in a loop:
```subleq
; Program at address 0
; Toggle LED 0 by XOR with 1
; mem[100] = 1 (constant)
; mem[101] = LED address ($FFF0)
; mem[102] = current LED state
; mem[103] = 0 (zero constant)
; Instruction 0: sub 103 from 102, store in 102 (clear 102)
addr 0: 103 102 3 ; mem[102] = mem[102] - mem[103] = 0
; Instruction 3: sub 103 from LED, store in LED (clear LED)
addr 3: 103 2545 6 ; mem[$FFF0] = mem[$FFF0] - 0
; Instruction 6: sub 100 from LED, store in LED (set bit 0)
addr 6: 100 2545 9 ; mem[$FFF0] = mem[$FFF0] - 1
; Instruction 9: delay loop
addr 9: 104 104 12 ; mem[104] = mem[104] - 1
addr 12: 104 104 0 ; if mem[104] <= 0, jump to 0 (restart)
; Data
addr 100: 1 ; toggle mask
addr 101: 0 ; unused
addr 102: 0 ; LED state
addr 103: 0 ; zero
addr 104: 50000 ; delay counter
```
**Assembled binary:**
```
0064 0066 0003
0067 09F1 0006
0064 09F1 0009
0068 0068 000C
0068 0068 0000
0001 0000 0000 0000 C350
```
### 2. Q16 Addition
Add two Q16.16 values using the hardware LUT:
```subleq
; Write operands to Q16 LUT, read result
; mem[200] = operand A = 3.14 (Q16: 205887 = 0x000323D7)
; mem[201] = operand B = 2.72 (Q16: 178258 = 0x0002B8F2)
; Write A low word to $8000
addr 0: 200 32768 3 ; mem[$8000] = mem[200] (A low)
; Write A high word to $8001
addr 3: 201 32769 6 ; mem[$8001] = 0 (A high)
; Write B low word to $8002
addr 6: 202 32770 9 ; mem[$8002] = mem[202] (B low)
; Write B high word to $8003
addr 9: 203 32771 12 ; mem[$8003] = 0 (B high)
; Set opcode to 0 (add)
addr 12: 204 32772 15 ; mem[$8004] = 0
; Read result low from $8008
addr 15: 204 32776 18 ; mem[$8008] -> read
; Store result to mem[210]
addr 18: 32776 210 21 ; mem[210] = result low
; HALT
addr 21: 0 0 0
; Data
addr 200: 23D7 ; A low (3.14)
addr 201: 0003 ; A high
addr 202: B8F2 ; B low (2.72)
addr 203: 0002 ; B high
addr 204: 0000 ; opcode 0 (add)
```
### 3. UART Send Character
Send 'A' (0x41) over UART:
```subleq
; Wait for TX to be not busy, then send character
; mem[300] = 0x41 ('A')
; mem[301] = 0 (zero)
; mem[302] = UART TX status address ($FF01)
; mem[303] = UART TX data address ($FF00)
; Check TX status (poll loop)
addr 0: 302 304 3 ; mem[304] = mem[$FF01]
addr 3: 304 304 6 ; mem[304] -= mem[304] (test if zero)
addr 6: 304 304 9 ; if <= 0 (not busy), continue
addr 9: 301 304 0 ; else, reset and retry
; Send character
addr 12: 300 303 15 ; mem[$FF00] = 0x41
; HALT
addr 15: 0 0 0
; Data
addr 300: 0041 ; 'A'
addr 301: 0000 ; zero
addr 302: FF01 ; UART TX status
addr 303: FF00 ; UART TX data
addr 304: 0000 ; temp
```
---
## Loading Programs via UART
### Using Python
```python
import serial
import struct
# Load assembled program (array of 16-bit words)
program = [0x0064, 0x0066, 0x0003, ...] # assembled instructions
# Connect to FPGA UART
ser = serial.Serial('/dev/ttyUSB0', 115384, timeout=1)
# Send program: each word as 2 bytes, big-endian
for word in program:
ser.write(struct.pack('>H', word))
ser.close()
```
### Using openFPGALoader (SRAM load)
For quick iteration (non-persistent):
```bash
# Load bitstream to SRAM (lost on power cycle)
openFPGALoader -b tangnano9k --sram research_stack_top.fs
# Load to flash (persistent)
openFPGALoader -b tangnano9k research_stack_top.fs
```
---
## Reading Results
### Via UART
```python
import serial
ser = serial.Serial('/dev/ttyUSB0', 115384, timeout=5)
# Read result bytes
data = ser.read(2) # 1 word = 2 bytes
result = struct.unpack('>H', data)[0]
print(f"Result: {result} (0x{result:04X})")
ser.close()
```
### Via LEDs
Read the 6 LEDs (pins 10-16) as a 6-bit value from `led[0:5]`.
- LED 0 = bit 0 (rightmost)
### Via Memory Dump
After HALT, the UART TX beacon outputs the full memory contents.
Connect a serial terminal and observe the dump.
---
## Build Toolchain
```bash
# Synthesis
cd 4-Infrastructure/hardware && bash build_research_stack.sh
# Simulation
cd /tmp/fpga_sim_full && ./obj_dir/sim_top
# Flash
openFPGALoader -b tangnano9k research_stack_top.fs
```
### Tool Versions
| Tool | Version |
|------|---------|
| Yosys | 0.64 |
| nextpnr-himbaechel | 0.10-75 |
| gowin_pack | latest |
| Verilator | 5.048 |
| openFPGALoader | latest |
---
## Timing
- **Clock:** 27 MHz (37.04 ns period)
- **Achieved Fmax:** 195.92 MHz (7.2× margin)
- **Q16 LUT latency:** 2 cycles (74 ns)
- **MAX_CYCLES:** 1,000,000