Skip to content

Commit e9e8436

Browse files
committed
2 parents bc8371e + 03ebf53 commit e9e8436

File tree

34 files changed

+6211
-6
lines changed

34 files changed

+6211
-6
lines changed

docs/getting_started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ We have already generated and hosted a bitstream to blink led [here](https://git
102102

103103
Checkout guide to learn how to generate your own fpga design [here](./generating_your_first_bitstream.md).
104104

105-
Onces you have done this go to arduino and hit compile your compilation should finish without error if error occurs don't we have s discord hope on there.
105+
Once you have done this, open Arduino IDE and click on Compile. The compilation should complete without any errors. If you encounter any errors, don’t worry—we have a Discord community. Hop in there and we’ll help you out.
106106

107107
If the compilation has been done without any error then it's time to connect the board in boot mode " PRESS THE BOOT BUTTON WHILE CONNECTING THE BOARD WITH PC" ( this should be done only the first time of setting up if arduino are if you have programmed the board with any other way last time).
108108

docs/introduction.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ The beauty of this setup is that you can reprogram the FPGA an unlimited number
2525
As discussed earlier, we’ll need to learn how compilation works for both systems: the FPGA and the micro-controller (RP2350/RP2040).
2626

2727
The binary files that the FPGA understands are called **bitstreams**.
28-
Follow the guide here to learn how to generate a bitstream for the FPGA: [Generating Your First Bitstream](https://vicharak-in.github.io/shrike-lite/generating_your_first_bitstream.html)
28+
Follow the guide here to learn how to generate a bitstream for the FPGA:
29+
[Generating Your First Bitstream](https://vicharak-in.github.io/shrike/generating_your_first_bitstream.html)
2930

3031
Once you have the bitstream, you’re ready to load it into the FPGA through a microcontroller (RP2040) program.
3132

3233
We’ll start with a simple blink-LED example to say hello to the world of hardware.
33-
Along the way, we’ll also set up the required software and toolchain.
34+
Along the way, we’ll also set up the required software and toolchain.

docs/reading_list.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ This is a compilation of the resources and references suggested for Shrike and F
2626

2727

2828
## 3. Communities to Join
29-
1. [ULX3S](https://discord.gg/WwWtuk5w)
30-
2. [TinyVision](https://discord.gg/RQXEF59m)
31-
3. [1bitsquared](https://discord.gg/nrnGxCuF)
29+
1. ULX3S
30+
2. TinyVision
31+
3. 1bitsquared

examples/Vector-4/README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Vector - 4
2+
3+
This project implements a custom **4-bit Soft-Core CPU** on the Vicharak's **Shrike Lite** board.
4+
5+
Instead of using physical buttons and LEDs, this CPU is controlled entirely via **SPI**. The RP2040 acts as the master, sending instructions, loading memory, and single-stepping the clock.
6+
7+
---
8+
9+
## System Architecture
10+
11+
The system is a hybrid design:
12+
* **RP2040 (Master):** Handles the high-level logic, user interface, and controls the CPU execution.
13+
* **FPGA (Slave):** Contains the CPU core, memory (RAM/ROM), and ALU.
14+
15+
### Specifications
16+
| Feature | Detail |
17+
| :--- | :--- |
18+
| **Data Width** | 4-bit (Nibble) |
19+
| **Address Space** | 16 lines of Program Memory, 16 slots of Data Memory |
20+
| **Clocking** | Manual Stepping via SPI (Synchronous to FPGA System Clock) |
21+
| **Interface** | 8-bit SPI Packet (Command + Payload) |
22+
23+
---
24+
<img width="2816" height="1536" alt="Vector-4 Schematic" src="https://github.com/user-attachments/assets/e860ca24-936c-403d-bab5-7254de5bde37" />
25+
26+
## The SPI Interface
27+
28+
To interact with the CPU, the RP2040 sends **8-bit packets** over SPI. The FPGA replies simultaneously with the current CPU state.
29+
30+
### Input Packet (RP2040 -> FPGA)
31+
| Bit [7:4] | Bit [3:2] | Bit [1] | Bit [0] |
32+
| :---: | :---: | :---: | :---: |
33+
| **DATA Payload** | **INSTRUCTION** | **RESET** | **STEP** |
34+
| 4-bit Value | Mode Selector | 1 = Reset CPU | 1 = Execute Cycle |
35+
36+
* **DATA Payload:** The number to be loaded into memory or the PC.
37+
* **INSTRUCTION:**
38+
* `00` **LOADPROG**: Write payload to Program Memory at current PC.
39+
* `01` **LOADDATA**: Write payload to Data Memory at current PC.
40+
* `10` **SETRUNPT**: Set the Program Counter (PC) to the payload value.
41+
* `11` **RUNPROG**: Normal execution mode.
42+
* **RESET:** Active High. Clears PC, Registers, and Memory.
43+
* **STEP:** Rising-edge trigger. The CPU executes **one cycle** per packet where this bit is `1`.
44+
45+
### Output Packet (FPGA -> RP2040)
46+
| Bit [7:4] | Bit [3:0] |
47+
| :---: | :---: |
48+
| **REGVAL** | **PC** |
49+
| Current Accumulator Value | Current Program Counter |
50+
51+
---
52+
53+
## Instruction Set Architecture (ISA)
54+
55+
The CPU supports 16 operations. These opcodes are stored in the **Program Memory**.
56+
57+
| Opcode | Name | Description |
58+
| :--- | :--- | :--- |
59+
| `0` | **LOAD** | `Reg = Data[PC]` (Immediate Load) |
60+
| `1` | **STORE** | `Data[Address] = Reg` (Indirect Store) |
61+
| `2` | **ADD** | `Reg = Reg + Data[PC]` |
62+
| `3` | **MUL** | `Reg = Reg * Data[PC]` |
63+
| `4` | **SUB** | `Reg = Reg - Data[PC]` |
64+
| `5` | **SHIFTL** | Left Shift |
65+
| `6` | **SHIFTR** | Right Shift |
66+
| `7` | **JUMPTOIF**| Jump to `Data[PC]` if the MSB of Input (Bit 7) is High |
67+
| `8` | **LOGICAND**| Logical AND (`&&`) |
68+
| `9` | **LOGICOR** | Logical OR (`\|\|`) |
69+
| `10`| **EQUALS** | `Reg = (Reg == Data[PC])` |
70+
| `11`| **NEQ** | `Reg = (Reg != Data[PC])` |
71+
| `12`| **BITAND** | Bitwise AND (`&`) |
72+
| `13`| **BITOR** | Bitwise OR (`\|`) |
73+
| `14`| **LOGICNOT**| Logical NOT (`!`) |
74+
| `15`| **BITNOT** | Bitwise NOT (`~`) |
75+
76+
---
77+
78+
## Hardware Connections
79+
80+
This design was tested using the following connections between the Shrike Lite FPGA (Slave) and the RP2040 (Master).
81+
82+
### Top Module Interface
83+
These signals correspond to the top-level Verilog module (`top.v`).
84+
85+
| Signal | Direction | Description |
86+
|---------------|-----------|--------------------------------------|
87+
| `clk` | In | System clock (50 MHz typical) |
88+
| `clk_en` | Out | Clock enable (always 1) |
89+
| `rst_n` | In | Reset Pin (active low) |
90+
| `spi_ss_n` | In | Input target select signal (active low) |
91+
| `spi_sck` | In | Input SPI clock signal |
92+
| `spi_mosi` | In | Input from controller (Master Out) |
93+
| `spi_miso` | Out | Output to controller (Master In) |
94+
95+
### Pin Mapping Table
96+
97+
| Signal Function | FPGA Pin (GPIO) | RP2040 Pin | Direction |
98+
| :--- | :---: | :---: | :--- |
99+
| **SPI Clock** | 3 | 2 | RP2040 Output -> FPGA Input |
100+
| **Chip Select** | 4 | 1 | RP2040 Output -> FPGA Input |
101+
| **MOSI** | 5 | 3 | RP2040 Output -> FPGA Input |
102+
| **MISO** | 6 | 0 | FPGA Output -> RP2040 Input |
103+
| **Reset** | 18 | 14 | RP2040 Output -> FPGA Input |
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
from machine import Pin, SPI
2+
import time
3+
4+
# --- PIN DEFINITIONS ---
5+
SCK = 2
6+
CS = 1
7+
MOSI = 3
8+
MISO = 0
9+
RST = 14
10+
11+
# --- INSTRUCTION SET ---
12+
OP_LOAD = 0
13+
OP_STORE = 1
14+
OP_ADD = 2
15+
OP_MUL = 3
16+
OP_SUB = 4
17+
OP_SHIFTL = 5
18+
OP_SHIFTR = 6
19+
OP_JUMPTOIF = 7
20+
OP_LOGICAND = 8
21+
OP_LOGICOR = 9
22+
OP_EQUALS = 10
23+
OP_NEQ = 11
24+
OP_BITAND = 12
25+
OP_BITOR = 13
26+
OP_LOGICNOT = 14
27+
OP_BITNOT = 15
28+
29+
# --- MODES ---
30+
MODE_LOADPROG = 0
31+
MODE_LOADDATA = 1
32+
MODE_SETRUNPT = 2
33+
MODE_RUNPROG = 3
34+
35+
# --- SETUP ---
36+
print("\n=== 4-BIT CPU FINAL VERIFICATION ===")
37+
38+
reset_pin = Pin(RST, Pin.OUT, value=1)
39+
40+
def hard_reset():
41+
reset_pin.value(0)
42+
time.sleep(0.05)
43+
reset_pin.value(1)
44+
time.sleep(0.1)
45+
46+
# Lowered baudrate to 50kHz for maximum stability
47+
cs = Pin(CS, Pin.OUT, value=1)
48+
spi = SPI(0, baudrate=50_000, polarity=0, phase=0, bits=8, firstbit=SPI.MSB,
49+
sck=Pin(SCK), mosi=Pin(MOSI), miso=Pin(MISO))
50+
51+
# --- HELPERS ---
52+
53+
def send_packet(data, instr, reset, step):
54+
packet = 0
55+
packet |= (data & 0x0F) << 4
56+
packet |= (instr & 0x03) << 2
57+
packet |= (reset & 0x01) << 1
58+
packet |= (step & 0x01) << 0
59+
60+
tx = bytes([packet])
61+
rx = bytearray(1)
62+
63+
cs.value(0)
64+
time.sleep(0.005) # Increased delay for stability
65+
spi.write_readinto(tx, rx)
66+
time.sleep(0.005)
67+
cs.value(1)
68+
time.sleep(0.02)
69+
return rx[0]
70+
71+
def read_state():
72+
resp = send_packet(0, 0, 0, 0)
73+
reg = resp >> 4
74+
pc = resp & 0x0F
75+
return pc, reg
76+
77+
def write_prog(addr, opcode):
78+
send_packet(addr, MODE_SETRUNPT, 0, 1)
79+
send_packet(opcode, MODE_LOADPROG, 0, 1)
80+
81+
def write_data(addr, val):
82+
send_packet(addr, MODE_SETRUNPT, 0, 1)
83+
send_packet(val, MODE_LOADDATA, 0, 1)
84+
85+
def check(test_name, expected_reg, expected_pc=None):
86+
pc, reg = read_state()
87+
pc_match = True if expected_pc is None else (pc == expected_pc)
88+
reg_match = (reg == expected_reg)
89+
90+
if pc_match and reg_match:
91+
print(f" [PASS] {test_name}")
92+
else:
93+
print(f" [FAIL] {test_name}")
94+
print(f" Got PC:{pc} REG:{reg}")
95+
print(f" Exp PC:{expected_pc if expected_pc is not None else 'Any'} REG:{expected_reg}")
96+
97+
def peek_data(addr):
98+
"""Safely reads data at addr by temporarily swapping Opcode to LOAD"""
99+
# 1. Save current opcode (we assume we know what it is or overwrite it later)
100+
# Since we are in a test, we will just restore what we expect.
101+
102+
# 2. Write LOAD opcode to this address
103+
write_prog(addr, OP_LOAD)
104+
105+
# 3. Execute it
106+
send_packet(addr, MODE_SETRUNPT, 0, 1)
107+
send_packet(0, MODE_RUNPROG, 0, 1)
108+
109+
# 4. Read Result
110+
pc, reg = read_state()
111+
return reg
112+
113+
# --- TESTS ---
114+
115+
def test_arithmetic():
116+
print("\n--- Test 1: Arithmetic ---")
117+
118+
# 1. SETUP DATA
119+
write_data(0, 3)
120+
write_data(1, 2)
121+
write_data(2, 3)
122+
write_data(3, 2) # CHANGED: Using 2 for MUL test (2*2=4)
123+
124+
# 2. SETUP PROGRAM
125+
write_prog(0, OP_LOAD)
126+
write_prog(1, OP_ADD)
127+
write_prog(2, OP_SUB)
128+
write_prog(3, OP_MUL)
129+
130+
# 3. RUN
131+
send_packet(0, MODE_SETRUNPT, 0, 1)
132+
133+
send_packet(0, MODE_RUNPROG, 0, 1)
134+
check("LOAD 3", 3)
135+
136+
send_packet(0, MODE_RUNPROG, 0, 1)
137+
check("ADD 2 (3+2=5)", 5)
138+
139+
send_packet(0, MODE_RUNPROG, 0, 1)
140+
check("SUB 3 (5-3=2)", 2)
141+
142+
# DEBUG: Verify Data[3] using the "Peek" method
143+
# This checks the memory without running MUL
144+
val_at_3 = peek_data(3)
145+
print(f" [DEBUG] Data[3] read as: {val_at_3} (Expected 2)")
146+
147+
# RESTORE Program[3] to MUL (because peek_data changed it to LOAD)
148+
write_prog(3, OP_MUL)
149+
150+
# Restore REG to 2 (Peek corrupted it)
151+
write_data(15, 2) # scratchpad
152+
write_prog(15, OP_LOAD)
153+
send_packet(15, MODE_SETRUNPT, 0, 1)
154+
send_packet(0, MODE_RUNPROG, 0, 1) # Reg is now 2
155+
156+
# Now set PC to 3 and Run MUL
157+
send_packet(3, MODE_SETRUNPT, 0, 1)
158+
send_packet(0, MODE_RUNPROG, 0, 1)
159+
check("MUL 2 (2*2=4)", 4)
160+
161+
def test_logic():
162+
print("\n--- Test 2: Logic & Bitwise ---")
163+
write_data(0, 5)
164+
write_data(1, 3)
165+
166+
write_prog(0, OP_LOAD)
167+
write_prog(1, OP_BITAND)
168+
169+
send_packet(0, MODE_SETRUNPT, 0, 1)
170+
send_packet(0, MODE_RUNPROG, 0, 1)
171+
send_packet(0, MODE_RUNPROG, 0, 1)
172+
check("BITAND (5 & 3 = 1)", 1)
173+
174+
# BITOR
175+
write_prog(0, OP_LOAD)
176+
write_prog(1, OP_BITOR)
177+
send_packet(0, MODE_SETRUNPT, 0, 1)
178+
send_packet(0, MODE_RUNPROG, 0, 1)
179+
send_packet(0, MODE_RUNPROG, 0, 1)
180+
check("BITOR (5 | 3 = 7)", 7)
181+
182+
def test_shifts():
183+
print("\n--- Test 3: Bit Shifts ---")
184+
write_data(0, 1)
185+
write_data(1, 2)
186+
write_prog(0, OP_LOAD)
187+
write_prog(1, OP_SHIFTL)
188+
189+
send_packet(0, MODE_SETRUNPT, 0, 1)
190+
send_packet(0, MODE_RUNPROG, 0, 1)
191+
send_packet(0, MODE_RUNPROG, 0, 1)
192+
check("SHIFTL (1 << 2 = 4)", 4)
193+
194+
def test_memory():
195+
print("\n--- Test 4: Memory (STORE) ---")
196+
write_data(0, 9)
197+
write_data(1, 5)
198+
write_data(2, 0)
199+
200+
write_prog(0, OP_LOAD)
201+
write_prog(1, OP_STORE)
202+
write_prog(2, OP_LOAD)
203+
write_prog(5, OP_LOAD)
204+
205+
send_packet(0, MODE_SETRUNPT, 0, 1)
206+
send_packet(0, MODE_RUNPROG, 0, 1)
207+
send_packet(0, MODE_RUNPROG, 0, 1)
208+
send_packet(0, MODE_RUNPROG, 0, 1)
209+
210+
send_packet(5, MODE_SETRUNPT, 0, 1)
211+
send_packet(0, MODE_RUNPROG, 0, 1)
212+
213+
check("STORE/LOAD Roundtrip", 9)
214+
215+
def test_jump():
216+
print("\n--- Test 5: Branching (JUMPTOIF) ---")
217+
218+
# 1. CLEANUP
219+
write_data(15, 0)
220+
write_prog(15, OP_LOAD)
221+
send_packet(15, MODE_SETRUNPT, 0, 1)
222+
send_packet(0, MODE_RUNPROG, 0, 1)
223+
224+
# 2. SETUP JUMP TEST
225+
write_data(0, 5)
226+
write_prog(0, OP_JUMPTOIF)
227+
228+
# CASE A: Jump NOT taken
229+
send_packet(0, MODE_SETRUNPT, 0, 1)
230+
send_packet(0, MODE_RUNPROG, 0, 1)
231+
check("Jump Not Taken (PC->1)", 0, expected_pc=1)
232+
233+
# CASE B: Jump TAKEN
234+
send_packet(0, MODE_SETRUNPT, 0, 1)
235+
send_packet(8, MODE_RUNPROG, 0, 1)
236+
check("Jump Taken (PC->5)", 0, expected_pc=5)
237+
238+
# --- RUN ---
239+
hard_reset()
240+
test_arithmetic()
241+
test_logic()
242+
test_shifts()
243+
test_memory()
244+
test_jump()
245+
print("\n=== All Tests Completed ===")

0 commit comments

Comments
 (0)