Skip to content

Commit c86b6f0

Browse files
Poojan-84dpks2003
authored andcommitted
An example of 4-bit cpu on Shrike Lite
1 parent d2d4d3f commit c86b6f0

File tree

6 files changed

+1599
-0
lines changed

6 files changed

+1599
-0
lines changed

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)