Small FPGA system-on-chip with a custom RV32I-style processor, memory-mapped I/O, and bare-metal firmware examples.
Overview • Features • Get started • Memory map • Firmware • Simulation
HolySoC is a compact FPGA SoC built around a custom single-cycle, RV32I-style 32-bit processor. The design targets the Digilent Basys 3 board and connects the CPU to instruction memory, data memory, switches, push buttons, LEDs, a four-digit seven-segment display, and a 115200-baud UART through memory-mapped I/O.
Note
This is a learning-oriented hardware project, not a drop-in replacement for a production RISC-V core. The RTL is designed to be readable, inspectable, and easy to extend.
The top-level HolySoC module wires together the processor core, RAM, address decoder, board I/O registers, debounced button input, and UART peripherals. The Basys 3 board clock runs at 100 MHz. The CPU and data-path peripherals use a divided 1 MHz clock, while UART and button debouncing stay on the board clock.
Basys 3 board
├─ 100 MHz clock, reset, switches, buttons
├─ HolySoC top-level RTL
│ ├─ RV32I-style single-cycle CPU
│ ├─ 1024-word instruction memory
│ ├─ 1024-word data memory
│ ├─ MMIO address decoder
│ ├─ LED and seven-segment output registers
│ ├─ button synchronizer, debouncer, and event latch
│ └─ UART RX/TX
└─ firmware loaded from instruction.mem
- Custom 32-bit processor core with program counter, register file, ALU, immediate generator, branch logic, load extension, and write-back muxes.
- RV32I-style instruction support for arithmetic, logic, shifts, loads, stores, branches, jumps,
LUI, andAUIPCflows used by the examples. - Harvard-style memory split with instruction memory loaded from
instruction.memand separate data RAM. - Memory-mapped board I/O for switches, LEDs, buttons, seven-segment segments, seven-segment digit enables, and UART.
- Button event capture with synchronization, approximately 10 ms debounce filtering, and latched rising-edge flags.
- UART peripheral with 115200-baud transmit and receive from the 100 MHz board clock.
- Example firmware in C, generated assembly, and machine-code text files.
- Simulation testbenches for core modules and the full SoC.
.
├── sources_1/new/ Synthesizable Verilog RTL and instruction memory image
├── sim_1/new/ Verilog simulation testbenches
├── constrs_1/new/ Basys 3 pin and clock constraints
├── game_1/ Example C programs, assembly, and machine-code files
├── img/ Documentation images
└── README.md
- Digilent Basys 3 FPGA board, or the equivalent
xc7a35tcpg236-1Artix-7 target - AMD/Xilinx Vivado
- Optional: RV32I-capable bare-metal GCC toolchain for rebuilding example programs
- Optional: Verilog simulator for running the included testbenches
Important
This repository does not currently include a Vivado .xpr project or a scripted build flow. Create a Vivado RTL project and add the source, constraint, and memory files manually.
git clone https://github.com/ArmmyC/HolySoC.git
cd HolySoC- Create a new Vivado RTL project for the Basys 3 board, or select device
xc7a35tcpg236-1. - Add every
.vfile insources_1/new/as design sources. - Add
sources_1/new/instruction.memas a memory initialization file. - Add every
.xdcfile inconstrs_1/new/as constraints. - Set
HolySoCas the synthesis top module. - Run synthesis, implementation, and bitstream generation.
- Program the Basys 3 board.
Warning
InstructionMemory.v currently uses an absolute local path in $readmemh. Replace that string with instruction.mem, or with the memory file path used by your Vivado project, before running the design on another machine.
The CPU is organized as a single-cycle datapath. RV32I_Core.v connects the main control unit, instruction memory, register file, ALU, immediate extender, branch target adder, load extender, and muxes for PC and register write-back.
The control unit decodes the major RV32I instruction families used by the project:
| Instruction family | Opcode | Notes |
|---|---|---|
| R-type ALU | 0110011 |
Register-register arithmetic, logic, shifts, and comparisons |
| I-type ALU | 0010011 |
Register-immediate arithmetic and logic |
| Loads | 0000011 |
Byte, halfword, word, signed and unsigned extension |
| Stores | 0100011 |
Byte, halfword, and word writes |
| Branches | 1100011 |
Equality and signed/unsigned comparison branches |
JAL |
1101111 |
PC-relative jump and link |
JALR |
1100111 |
Register-relative jump and link |
LUI |
0110111 |
Upper-immediate write-back |
AUIPC |
0010111 |
PC plus upper immediate |
| Address | Access | Peripheral | Value |
|---|---|---|---|
0x00000000 and above |
Read/write | Data RAM | 1024 x 32-bit words, indexed by address bits [11:2] |
0x10000000 |
Read | Switches | Basys 3 switches in bits [15:0] |
0x10000004 |
Write | LEDs | Basys 3 LEDs from bits [15:0] |
0x10000008 |
Read/write | Buttons | Read latched rising-edge flags in bits [3:0]; any write clears them |
0x1000000C |
Write | Seven-segment segments | Segment outputs from bits [6:0] |
0x10000010 |
Write | Seven-segment digits | Digit enables from bits [3:0] |
0x10000014 |
Read | UART status | Bit 0: RX ready, bit 1: TX busy |
0x10000018 |
Read/write | UART data | Read received byte or write transmit byte in bits [7:0] |
The Basys 3 seven-segment segment and digit outputs are active low.
Instruction memory is loaded from sources_1/new/instruction.mem. The file contains one 32-bit hexadecimal instruction per line and is addressed as 1024 words starting at 0x00000000.
The game_1/ directory contains example programs:
| File | Purpose |
|---|---|
game_LED.c |
LED timing example controlled by switches and button events |
Test.c |
Basic LED and seven-segment output test |
*.s |
Generated RV32I assembly |
*.txt |
Machine-code output used as a reference for instruction.mem |
Example MMIO usage:
#define ADDR_SWITCHES ((volatile unsigned int *)0x10000000)
#define ADDR_LEDS ((volatile unsigned int *)0x10000004)
unsigned int switches = *ADDR_SWITCHES;
*ADDR_LEDS = switches;To load a different program, build it for RV32I with the ILP32 ABI and a bare-metal entry point at address zero, convert the executable instructions into the same one-word-per-line hexadecimal format, then replace instruction.mem.
Note
Firmware build automation and a linker script are not included yet. The exact compile, link, and conversion commands depend on the RISC-V toolchain you install.
Testbenches are available in sim_1/new/:
| Testbench | Scope |
|---|---|
tb_ALU.v |
Steps through ALU control values |
tb_InstructionMemory.v |
Reads the first instruction words |
tb_RV32I_Core.v |
Clocks and resets the processor core |
tb_HolySoC.v |
Clocks and resets the top-level SoC |
Add the desired testbench as a Vivado simulation source and set it as the simulation top.
Caution
The included testbenches are waveform-oriented, not self-checking. Some interfaces have evolved, so strict simulators may require small port-list updates before a testbench runs cleanly.
- No checked-in Vivado project file or automated build script.
InstructionMemory.vneeds its$readmemhpath made portable before use outside the original workspace.- Testbenches are useful for waveform inspection, but they are not a complete regression suite.
- Firmware examples are minimal and do not include a full linker script or build pipeline.
- The CPU is intended as a readable educational core and has not been presented as formally verified RV32I compliance.
- Replace the absolute instruction memory path with a portable project-relative path.
- Add a Vivado TCL build script for project creation, synthesis, implementation, and bitstream generation.
- Add a small firmware linker script and repeatable
makeflow for C-to-instruction.membuilds. - Convert key testbenches into self-checking tests.
- Document supported instruction behavior with directed tests for every implemented opcode family.