Skip to content

Commit 6d15271

Browse files
4-Bit Up/Down Counter 🧮
1 parent d044aa8 commit 6d15271

File tree

6 files changed

+1190
-0
lines changed

6 files changed

+1190
-0
lines changed

examples/4bit_counter/4bit_counter.ffpga

Lines changed: 945 additions & 0 deletions
Large diffs are not rendered by default.

examples/4bit_counter/README.md

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# 🧮 4-Bit Up/Down Counter on Vicharak Shrike-Lite
2+
3+
4+
5+
## 1. Project Overview & Objective
6+
7+
This project implements a **4-bit up/down counter in Verilog** on the **Vicharak Shrike-Lite** FPGA dev board and demonstrates how the **RP2040 microcontroller reads FPGA-generated data in real time** using MicroPython.
8+
9+
The goal is to help the readers understand:
10+
11+
- Synchronous digital logic design in Verilog
12+
- FPGA pin-level signal exposure
13+
- Parallel data transfer from FPGA to MCU
14+
- Hardware–software co-design using MCU + FPGA boards
15+
16+
This counter is intentionally simple so the **communication mechanism** is easy to observe and reason about.
17+
18+
---
19+
20+
## 2. System Description (FPGA ↔ RP2040)
21+
22+
### FPGA
23+
- Implements a **4-bit synchronous counter**
24+
- Supports **up and down counting**
25+
- Uses a clock divider to slow the count rate (~1 Hz)
26+
- Continuously drives the counter value on GPIO pins
27+
28+
### RP2040
29+
- Programs the FPGA bitstream
30+
- Samples FPGA GPIO outputs
31+
- Reconstructs the counter value
32+
- Displays output in **binary and hexadecimal**
33+
34+
---
35+
36+
## 3. FPGA Verilog Design (Go Configure Software Hub)
37+
38+
> The bitstream for the Renesas FPGA in Shrike-Lite can only be generated in the Go Configure Software hub for now. So once it is installed in your pc, Select the appropriate fpga part [`SLG47910V (Rev BB)`] and create new project. Once created, double click on the fpga core square to open ForgeFPGA Workshop window and paste the verilog code from `./ffpga/src/counter.v` into main.v and click on synthesize button.
39+
40+
### Top-Level Module
41+
42+
~~~verilog
43+
(* top *) module counter(
44+
(* iopad_external_pin, clkbuf_inhibit *) input clk,
45+
(* iopad_external_pin *) input nreset,
46+
(* iopad_external_pin *) input up_down,
47+
(* iopad_external_pin *) output [3:0] out_oe,
48+
(* iopad_external_pin *) output osc_en,
49+
(* iopad_external_pin *) output [3:0] count
50+
);
51+
~~~
52+
53+
54+
- `clk` : Clock input to the FPGA
55+
- `nreset` : **Synchronous active-low reset**
56+
- `up_down`: Controls counting direction
57+
- `count` : 4-bit counter output
58+
59+
Output control:
60+
61+
~~~verilog
62+
assign out_oe = 4'b1111; // to configure count signal pins as outputs
63+
assign osc_en = 1'b1; // to enable clock
64+
assign count = counter_reg; // Counter Output
65+
~~~
66+
67+
---
68+
69+
### Timing & Counter Logic
70+
71+
A **26-bit register (`time_steps`)** is used as a clock divider:
72+
73+
~~~verilog
74+
if (time_steps >= 26'd49_999_999)
75+
~~~
76+
77+
- Counter updates roughly **once per second**
78+
- `up_down = 1` → increment
79+
- `up_down = 0` → decrement
80+
- 4-bit width naturally causes wrap-around
81+
82+
This demonstrates clean **synchronous sequential logic**.
83+
84+
---
85+
86+
## 4. Pin Connections
87+
88+
### Counter Data Lines (FPGA → RP2040)
89+
90+
| Signal | FPGA Pin | RP2040 Pin |
91+
|-----------|----------|------------|
92+
| `count[0]` | GPIO3 | GPIO2 |
93+
| `count[1]` | GPIO4 | GPIO1 |
94+
| `count[2]` | GPIO5 | GPIO3 |
95+
| `count[3]` | GPIO6 | GPIO0 |
96+
97+
These four lines form a **4-bit parallel data bus** from FPGA → RP2040.
98+
99+
100+
101+
### Control Signals
102+
103+
| Signal | FPGA Pin | Description |
104+
|--------|----------|-------------|
105+
| `nreset` | GPIO2 | Synchronous active-low reset |
106+
| `up_down` | GPIO7 | Controls up/down counting |
107+
108+
109+
### I/O Planner (in Go Configure Software Hub)
110+
>Tick the following check boxes in I/O Planner after synthesizing the verilog code.
111+
112+
- [x] CLK
113+
- [x] GPIO
114+
- [x] OSC_ctrl
115+
116+
>Then, configure the ports as shown below. Finally, save all files and click on Generate Bitstream.
117+
118+
| FUNCTION | DIRECTION | PORT |
119+
|--------|----------|-------------|
120+
| `OSC_CLK` | Input | clk |
121+
| `GPIO02_IN` | Input | updown |
122+
| `GPIO03_OUT` | Output | count[0] |
123+
| `GPIO03_OE` | Output | out_oe[0] |
124+
| `GPIO04_OUT` | Output | count[1] |
125+
| `GPIO04_OE` | Output | out_oe[1] |
126+
| `GPIO05_OUT` | Output | count[2] |
127+
| `GPIO05_OE` | Output | out_oe[2] |
128+
| `GPIO06_OUT` | Output | count[3] |
129+
| `GPIO06_OE` | Output | out_oe[4] |
130+
| `GPIO07_IN` | Input | nreset |
131+
| `OSC_EN` | Output | osc_en |
132+
133+
> The Bitstream file can be found in the project root folder as `.\ffpga\build\bitstream\FPGA_bitstream_MCU.bin`. You can rename it to any name but make sure to keep the `.bin` file extension. Don't forget to upload the bitstream file to shrike's rp2040 file system either by copy-pasting it through file explorer or by uploading using thonny directly.
134+
135+
## 5. RP2040 MicroPython Implementation
136+
137+
### Flashing the FPGA
138+
139+
~~~python
140+
import shrike
141+
shrike.flash("4bit_counter.bin")
142+
~~~
143+
144+
---
145+
146+
### Reading and Decoding Counter Value
147+
148+
~~~python
149+
cntr_pins = [2, 1, 3, 0]
150+
counter = [Pin(pin, Pin.IN) for pin in cntr_pins]
151+
~~~
152+
153+
Each pin corresponds to one counter bit.
154+
The RP2040 reconstructs the value using bit shifts:
155+
156+
~~~python
157+
value += (1 << i)
158+
~~~
159+
160+
Formatted output:
161+
162+
~~~python
163+
binary_str = "{:04b}".format(value)
164+
hex_str = "{:X}".format(value)
165+
print(f"Binary: {binary_str} | Hex: 0x{hex_str}")
166+
~~~
167+
168+
>Connect the Shrike board and Open Thonny. Select MicroPython (RP2040) Interpreter along with correct COM Port at the bottom right corner of the Thonny window. Create a new file and paste the code from `.\firmware\micropython\counter_test.py` into it. Save it and click on run button in the top row to run the code on the Shrike board. Output is printed to the Shell.
169+
170+
---
171+
172+
## 6. Hardware Diagram & Significance
173+
174+
![4-bit Up/Down Counter Block Diagram](./media/4Bit_UPDown_Counter.png)
175+
176+
177+
*Block-level view of the Up/Down Counter showing the FPGA-generated 4-bit counter, parallel data bus to RP2040, and control signals.*
178+
179+
### Result:
180+
181+
![OUTPUT](./media/Result.png)
182+
183+
*Serial Output from RP2040 showing the up-counting, reset, and down-counting functionality of the counter.*
184+
185+
>Note: Noise and Interference on the GPIO input pins of the FPGA is also considered as a logic high (1). Thus, Physical connection of input pins to 3.3V to stay in up-counting mode or to stay out of reset is simply not required in this scenario.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
(* top *) module counter(
2+
(* iopad_external_pin, clkbuf_inhibit *) input clk,
3+
(* iopad_external_pin *) input nreset,
4+
(* iopad_external_pin *) input up_down,
5+
(* iopad_external_pin *) output [3:0] out_oe,
6+
(* iopad_external_pin *) output osc_en,
7+
(* iopad_external_pin *) output [3:0] count
8+
);
9+
10+
reg [3:0] counter_reg;
11+
reg [25:0] time_steps;
12+
13+
assign out_oe = 4'b1111;
14+
assign osc_en = 1'b1;
15+
assign count = counter_reg;
16+
17+
always @(posedge clk or negedge nreset) begin
18+
if (!nreset) begin
19+
counter_reg <= 4'h0;
20+
time_steps <= 26'd0;
21+
end else begin
22+
if (time_steps >= 26'd49_999_999) begin
23+
time_steps <= 26'd0;
24+
if (up_down)
25+
counter_reg <= counter_reg + 4'd1;
26+
else
27+
counter_reg <= counter_reg - 4'd1;
28+
end else begin
29+
time_steps <= time_steps + 26'd1;
30+
end
31+
end
32+
end
33+
endmodule
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import shrike
2+
from machine import Pin
3+
from time import sleep
4+
5+
shrike.flash("4bit_counter.bin")
6+
7+
cntr_pins = [2,1,3,0]
8+
9+
counter = [Pin(pin, Pin.IN) for pin in cntr_pins]
10+
11+
previous = 0
12+
13+
while True:
14+
value = 0
15+
# Read each button and shift its value into the correct bit position
16+
for i in range(4):
17+
if counter[i].value():
18+
value += (1 << i)
19+
if(value != previous):
20+
# Format strings
21+
binary_str = "{:04b}".format(value) # 4-digit binary
22+
hex_str = "{:X}".format(value) # Uppercase Hex
23+
24+
print(f"Binary: {binary_str} | Hex: 0x{hex_str}")
25+
previous = value
26+
sleep(0.1) # Small delay for stability
27+
97 KB
Loading
111 KB
Loading

0 commit comments

Comments
 (0)