FlexRISC16 is a custom 16-bit RISC processor implemented in Verilog HDL using a five-stage instruction pipeline.
The processor was designed with a focus on understanding and implementing fundamental pipelined CPU concepts including:
- Five-stage instruction pipelining
- Register-based datapath
- ALU operations
- Immediate instructions
- Load/store operations
- Conditional and unconditional branches
- Data forwarding
- Load-use hazard detection
- Pipeline stalls
- Pipeline flushing
- Status register handling
- Write-back control
- Performance monitoring
- Automated self-checking verification
- FPGA implementation on the PYNQ-Z2
The design was developed and verified using Xilinx Vivado and was successfully programmed onto a PYNQ-Z2 FPGA development board.
- Overview
- Features
- Processor Architecture
- Five-Stage Pipeline
- Pipeline Datapath
- Instruction Flow
- Hazard Handling
- Status Register
- Memory System
- Performance Counters
- RTL Modules
- Verification
- Simulation Results
- Waveform Analysis
- Synthesized Hardware
- Synthesis Results
- Timing Results
- FPGA Implementation
- PYNQ-Z2 Interface
- Project Structure
- How to Run Simulation
- How to Build for PYNQ-Z2
- Future Improvements
- Author
FlexRISC16 is a 16-bit pipelined RISC processor designed from the RTL level.
Instead of executing one complete instruction before starting the next instruction, FlexRISC16 divides instruction execution into multiple pipeline stages.
The five stages are:
IF → ID → EX → MEM → WB
where:
IF = Instruction Fetch
ID = Instruction Decode
EX = Execute
MEM = Memory Access
WB = Write Back
Pipeline registers separate each stage:
IF
│
▼
IF/ID
│
▼
ID
│
▼
ID/EX
│
▼
EX
│
▼
EX/MEM
│
▼
MEM
│
▼
MEM/WB
│
▼
WB
The processor additionally contains forwarding and hazard detection logic to maintain correct execution when instructions depend on previous instructions.
- 16-bit datapath
- 16-bit program counter
- Register-based architecture
- Five-stage pipeline
- Synchronous sequential logic
- Hardwired zero register
- Separate instruction and data memories
- Immediate instruction support
- Load/store support
- Branch and jump instructions
Supported ALU operations include:
- ADD
- SUB
- AND
- OR
- XOR
- NOT
- CMP
- SHL
- SHR
- MOV
- IF/ID pipeline register
- ID/EX pipeline register
- EX/MEM pipeline register
- MEM/WB pipeline register
- Data forwarding
- Load-use hazard detection
- Pipeline stall generation
- Pipeline flush
- Branch handling
- Status-register based branch conditions
- Automated self-checking testbench
- 22 functional checks
- Register verification
- Memory verification
- Forwarding verification
- Load-use stall verification
- Branch verification
- Status-register verification
- R0 verification
- Automated simulation log
- Automated CSV result generation
- Target: PYNQ-Z2
- FPGA: Xilinx Zynq-7000 XC7Z020
- System clock: 125 MHz
- Board LEDs used for debug output
- Push button used as reset
- Two switches used for debug selection
The high-level FlexRISC16 architecture is shown below.
The Program Counter provides the address of the next instruction.
The instruction memory returns the corresponding instruction.
The PC is also incremented to obtain the next sequential instruction address.
Main components:
Program Counter
Instruction Memory
PC + 1 logic
The instruction decoder extracts:
Opcode
Destination Register
Source Register 1
Source Register 2
The register file supplies source operands.
The immediate generator produces immediate values when required.
The hazard detection unit examines dependencies between instructions.
Main components:
Instruction Decoder
Register File
Immediate Generator
Hazard Detection Unit
Pipeline Control
The EX stage performs the actual arithmetic or logical operation.
The ALU receives operands through the operand selection and forwarding logic.
The EX stage also handles:
Arithmetic
Logical operations
Shifts
Comparisons
Effective address calculation
Branch evaluation
Main components:
ALU Control
ALU Operand Mux
Forwarding Unit
Forwarding Muxes
ALU
Branch Unit
Status Register
Load and store instructions access the data memory during this stage.
For a load:
Address → Data Memory → Loaded Data
For a store:
Address + Store Data → Data Memory
The final result is selected and written into the register file.
The write-back multiplexer selects the appropriate result source.
Possible sources include:
ALU result
Memory data
The synthesized RTL datapath generated by Vivado is shown below.
The synthesized design contains the logic required to implement the processor pipeline, including:
- Pipeline registers
- Multiplexers
- ALU logic
- Control logic
- Forwarding logic
- Branch logic
- Memory interface
- Register file
- Hazard handling
The schematic provides an implementation-level view of the RTL design.
A typical sequence of instructions progresses through the pipeline as follows:
Cycle 1 2 3 4 5 6 7
------------------------------------------------
Instr 1 IF ID EX MEM WB
Instr 2 IF ID EX MEM WB
Instr 3 IF ID EX MEM WB
Instr 4 IF ID EX MEM WB
Instr 5 IF ID EX MEM WB
After the pipeline is filled, multiple instructions are processed simultaneously.
This increases instruction throughput compared with a non-pipelined implementation.
Pipeline execution introduces data and control hazards.
FlexRISC16 implements hardware mechanisms to detect and resolve these hazards.
A data dependency can occur when an instruction requires a result that has not yet been written back to the register file.
Example:
ADD R3, R1, R2
SUB R4, R3, R5
The SUB instruction needs the result produced by ADD.
Instead of waiting for the result to reach the register file, FlexRISC16 forwards the result from later pipeline stages back to the EX stage.
The forwarding paths include:
EX/MEM ──────────────┐
│
▼
EX
▲
│
MEM/WB ──────────────┘
The forwarding unit generates control signals for the two ALU operands.
The simulation verified forwarding functionality with:
Forwarding events: 4
A load instruction creates a special dependency because the required data becomes available only after the memory stage.
Example:
LOAD R12, ...
ADD R13, R12, ...
The dependent instruction cannot always be resolved using normal EX-stage forwarding.
FlexRISC16 therefore detects the dependency and introduces a pipeline stall.
The hazard controller generates:
stall_pc
stall_if_id
flush_id_ex
Conceptually:
LOAD
│
▼
Hazard Detection
│
├── Stall PC
├── Stall IF/ID
└── Flush ID/EX
The verification testbench confirmed the load-use stall behavior.
Total stalls during the complete verification program:
1
FlexRISC16 supports conditional and unconditional control-flow instructions.
The implemented branch operations include:
BEQ
BNE
JMP
When a branch is taken, the incorrect sequential instructions are removed from the pipeline through flushing/control logic.
The verification program tested:
BEQ taken
BEQ fallthrough
BNE taken
BNE fallthrough
JMP
The final verification recorded:
Branches : 6
Branches Taken : 4
The processor includes a status register for ALU condition flags.
The stored flags are:
Carry
Zero
Negative
Overflow
The status register prevents conditional branches from using unrelated live ALU outputs.
The branch logic instead uses the registered status information corresponding to the appropriate instruction.
This is particularly important for sequences involving:
CMP
↓
Branch
where the comparison result must remain available while the following branch is evaluated.
The verification testbench includes a dedicated status/branch dependency check.
FlexRISC16 contains:
Instruction memory stores the processor program.
The simulation program is provided in:
memory/program.mem
The instruction memory is initialized from this program file.
The data memory is used for:
LOAD
STORE
operations.
The verification environment checks both the stored value and the value loaded back from memory.
One of the verification tests checks:
mem[11] = 0x000d
followed by verification of the loaded value.
The processor includes hardware performance counters.
The following counters are available:
cycle_count
instruction_count
stall_count
branch_count
branch_taken_count
forwarding_count
These counters provide information about processor execution and pipeline behavior.
For the final verification program:
Cycles : 52
Instructions Retired: 39
Stalls : 1
Branches : 6
Branches Taken : 4
Forwarding Events : 4
The processor is divided into modular RTL components.
| Module | Function |
|---|---|
program_counter.v |
Program counter |
instruction_memory.v |
Instruction storage |
if_id_register.v |
IF/ID pipeline register |
instruction_decoder.v |
Instruction decoding |
immediate_generator.v |
Immediate value generation |
register_file.v |
General-purpose register file |
hazard_detection_unit.v |
Load-use hazard detection |
pipeline_control.v |
Pipeline control |
id_ex_register.v |
ID/EX pipeline register |
forwarding_unit.v |
Forwarding control |
forwarding_mux.v |
Operand forwarding |
alu_operand_mux.v |
ALU operand selection |
alu_control.v |
ALU operation control |
alu.v |
Arithmetic and logical operations |
branch_unit.v |
Branch decision logic |
status_register.v |
ALU status flags |
ex_mem_register.v |
EX/MEM pipeline register |
data_memory.v |
Data memory |
mem_wb_register.v |
MEM/WB pipeline register |
write_back_mux.v |
Write-back result selection |
performance_counter.v |
Execution counters |
pipeline_datapath.v |
Complete pipeline datapath |
pipeline_processor_top.v |
Processor top-level |
FlexRISC16 uses an automated self-checking testbench:
sim/tb_pipeline_selfcheck.v
The testbench verifies processor behavior automatically rather than requiring manual waveform inspection for every operation.
The verification covers:
- ALU operations
- Data forwarding
- Immediate operations
- Load
- Store
- Load-use hazard
- Branches
- Status register behavior
- Jump
- Sequential execution
- Zero register behavior
- Performance counters
The final simulation produced:
========================================
Cycles: 52
Instructions retired: 39
Stalls: 1
Branches: 6 (taken 4)
Forwards: 4
----------------------------------------
ALL 22 CHECKS PASSED
========================================
Final result:
22 PASSED
0 FAILED
The testbench therefore completed the complete verification suite successfully.
| # | Test | Result |
|---|---|---|
| 1 | ADD forwarding | PASS |
| 2 | SUB forwarding | PASS |
| 3 | AND | PASS |
| 4 | OR | PASS |
| 5 | XOR | PASS |
| 6 | NOT | PASS |
| 7 | SHL | PASS |
| 8 | SHR | PASS |
| 9 | Immediate sign extension | PASS |
| 10 | LOAD | PASS |
| 11 | Load-use stall | PASS |
| 12 | ADD before STORE | PASS |
| 13 | Forwarded STORE data | PASS |
| 14 | Loaded stored value | PASS |
| 15 | BEQ taken | PASS |
| 16 | BEQ fallthrough | PASS |
| 17 | BNE taken | PASS |
| 18 | BNE fallthrough | PASS |
| 19 | STATUS branch dependency | PASS |
| 20 | JMP | PASS |
| 21 | Sequential continuation | PASS |
| 22 | R0 hardwired zero | PASS |
The testbench can generate simulation results automatically.
Generated files:
output/generated_files/
├── simulation_results.csv
└── simulation_results.log
The log file contains the human-readable verification output.
The CSV file provides structured test results that can be used for:
- Documentation
- Result analysis
- Spreadsheet processing
- Regression testing
- Future automated verification
The repository contains waveform screenshots demonstrating the major pipeline mechanisms.
This waveform provides an overall view of processor execution, performance counters, program counter, instructions, ALU results, and write-back behavior.
This waveform demonstrates instructions progressing through the different pipeline stages.
This waveform shows the operation of the pipeline registers:
IF/ID
ID/EX
EX/MEM
MEM/WB
The forwarding waveform demonstrates the forwarding control signals used to resolve data dependencies.
The important signals include:
forward_a
forward_b
and the associated source/destination register information.
This waveform demonstrates the load-use hazard mechanism.
Important signals include:
id_ex_mem_read
id_ex_rd
if_id_rs1
if_id_rs2
stall_pc
stall_if_id
flush_id_ex
The waveform demonstrates that the pipeline inserts a stall when the dependency cannot be resolved through normal forwarding.
This waveform demonstrates branch decisions and the interaction between the status register and branch unit.
The tested operations include:
BEQ
BNE
JMP
The RTL was synthesized using Xilinx Vivado targeting:
xc7z020clg400-1
The synthesized design was successfully generated.
The Vivado-generated schematic is included in:
docs/architecture/synthesized_datapath.png
This provides an implementation-level representation of the processor datapath and control logic.
The current synthesis utilization is:
| Resource | Used | Available | Utilization |
|---|---|---|---|
| Slice LUTs | 393 | 53,200 | 0.74% |
| LUT as Logic | 329 | 53,200 | 0.62% |
| LUT as Memory | 64 | 17,400 | 0.37% |
| Slice Registers | 311 | 106,400 | 0.29% |
| F7 Muxes | 80 | 26,600 | 0.30% |
| F8 Muxes | 16 | 13,300 | 0.12% |
| Block RAM | 0 | 140 | 0.00% |
| DSP | 0 | 220 | 0.00% |
| BUFG | 1 | 32 | 3.13% |
The design uses a small fraction of the available programmable logic resources of the XC7Z020 device.
The FPGA design uses a 125 MHz clock constraint.
Clock Period : 8.00 ns
Target : 125 MHz
Current implementation timing:
| Parameter | Result |
|---|---|
| Worst Negative Slack | -0.343 ns |
| Total Negative Slack | -0.343 ns |
| Worst Hold Slack | +0.078 ns |
| Total Hold Slack | 0.000 ns |
| Failing Setup Endpoints | 1 |
| Failing Hold Endpoints | 0 |
Setup : NOT YET MET
Hold : MET
The current design has a small setup timing violation of:
0.343 ns
Timing optimization is therefore identified as a future improvement.
Possible future work includes:
- Reducing combinational depth
- Optimizing forwarding paths
- Optimizing branch logic
- Register placement optimization
- Pipeline timing optimization
- Vivado physical optimization
FlexRISC16 was targeted to the:
PYNQ-Z2
development board.
The target FPGA is:
Xilinx Zynq-7000
XC7Z020CLG400-1
A dedicated FPGA wrapper is used instead of exposing the complete processor debug interface.
This is necessary because the simulation processor top contains a large number of debug and performance signals, while the physical board has a limited number of user I/O pins.
The FPGA wrapper therefore exposes only:
clk
rst
sw[1:0]
led[3:0]
while the complete processor remains internally connected.
clk
Board clock:
125 MHz
Clock period:
8 ns
rst
Connected to:
PYNQ-Z2 btn[0]
The reset signal initializes the processor and associated pipeline state.
sw[1:0]
The switches are used for selecting the debug output nibble.
The processor's 16-bit debug result can be viewed in four-bit portions.
Conceptually:
sw = 00 → debug_writeback[3:0]
sw = 01 → debug_writeback[7:4]
sw = 10 → debug_writeback[11:8]
sw = 11 → debug_writeback[15:12]
led[3:0]
The selected debug nibble is displayed using the four LEDs.
This provides a simple physical indication that the processor is operating on the FPGA.
The hardware top-level is:
fpga_top
The processor itself remains:
pipeline_processor_top
The relationship is:
fpga_top
│
▼
pipeline_processor_top
│
▼
pipeline_datapath
│
┌────────┼────────┐
▼ ▼ ▼
IF EX MEM
│ │ │
└────────┼────────┘
▼
WB
The FPGA wrapper prevents the large simulation/debug interface from becoming physical FPGA I/O.
FlexRISC16/
│
├── constant/
│ └── cpu_constants.vh
│
├── docs/
│ │
│ ├── architecture/
│ │ ├── pipeline_architecture.png
│ │ └── synthesized_datapath.png
│ │
│ ├── synthesis/
│ │ ├── utilization.md
│ │ └── timing.md
│ │
│ └── waveforms/
│ ├── waveform_overview.png
│ ├── waveform_pipeline_flow.png
│ ├── waveform_pipeline_registers.png
│ ├── waveform_forwarding.png
│ ├── waveform_load_use_stall.png
│ └── waveform_branch_status.png
│
├── memory/
│ └── program.mem
│
├── output/
│ └── generated_files/
│ ├── simulation_results.csv
│ └── simulation_results.log
│
├── rtl/
│ ├── alu.v
│ ├── alu_control.v
│ ├── alu_operand_mux.v
│ ├── branch_unit.v
│ ├── data_memory.v
│ ├── ex_mem_register.v
│ ├── forwarding_mux.v
│ ├── forwarding_unit.v
│ ├── hazard_detection_unit.v
│ ├── id_ex_register.v
│ ├── if_id_register.v
│ ├── immediate_generator.v
│ ├── instruction_decoder.v
│ ├── instruction_memory.v
│ ├── mem_wb_register.v
│ ├── performance_counter.v
│ ├── pipeline_control.v
│ ├── pipeline_datapath.v
│ ├── pipeline_processor_top.v
│ ├── program_counter.v
│ ├── register_file.v
│ ├── status_register.v
│ └── write_back_mux.v
│
├── sim/
│ └── tb_pipeline_selfcheck.v
│
├── .gitignore
├── LICENSE
└── README.md
Open the FlexRISC16 project in Xilinx Vivado.
Make sure the simulation top is:
tb_pipeline_selfcheck
The processor top used by the testbench is:
pipeline_processor_top
Ensure:
memory/program.mem
is available to the instruction memory module.
The testbench program should correspond to the expected verification sequence.
In Vivado:
Flow Navigator
↓
Simulation
↓
Run Simulation
↓
Run Behavioral Simulation
The self-checking testbench will execute automatically.
A successful run should end with:
========================================
Cycles: 52
Instructions retired: 39
Stalls: 1
Branches: 6 (taken 4)
Forwards: 4
----------------------------------------
ALL 22 CHECKS PASSED
========================================
For FPGA implementation, use:
fpga_top
as the synthesis/implementation top-level.
The target part is:
xc7z020clg400-1
Add the PYNQ-Z2 constraints file and ensure the following ports are constrained:
clk
rst
sw[1:0]
led[3:0]
Then run:
Synthesis
↓
Implementation
↓
Generate Bitstream
After generating the bitstream, open:
Hardware Manager
and program the PYNQ-Z2 FPGA.
The FPGA wrapper provides a reduced hardware debug interface.
The processor internally generates:
debug_pc
debug_instruction
debug_alu_result
debug_memory_data
debug_writeback
along with performance counters.
These signals remain useful during simulation.
For FPGA operation, the wrapper reduces the interface to:
Switches → Debug Selection
│
▼
Debug Writeback
│
▼
LEDs
Current verification status:
+--------------------------------------+
| FlexRISC16 Verification |
+--------------------------------------+
| Functional Checks : 22 / 22 |
| Passed : 22 |
| Failed : 0 |
| Cycles : 52 |
| Instructions Retired : 39 |
| Pipeline Stalls : 1 |
| Branches : 6 |
| Branches Taken : 4 |
| Forwarding Events : 4 |
+--------------------------------------+
Status:
FUNCTIONAL VERIFICATION: PASSED
The design has been synthesized and programmed onto the PYNQ-Z2.
The FPGA resource utilization is low, with the current design using approximately:
0.74% LUTs
0.29% Registers
0% BRAM
0% DSP
The current implementation has a small setup timing violation at the 125 MHz target.
Therefore:
RTL Functionality : PASSED
Simulation Verification : PASSED
Synthesis : PASSED
FPGA Programming : COMPLETED
Setup Timing : OPTIMIZATION REQUIRED
The current FlexRISC16 implementation provides a functional five-stage pipelined processor.
Potential improvements include:
Resolve the current:
WNS = -0.343 ns
setup violation.
The current branch handling can be extended with:
- Static branch prediction
- Dynamic branch prediction
- Branch target buffer
- Reduced branch penalty
Additional instructions could be added, including:
SLT
SLLI
SRLI
ADDI
ANDI
ORI
XORI
and additional memory addressing modes.
The current register organization can be extended to support a larger register file if the instruction encoding is redesigned.
Additional performance analysis can be performed using:
CPI
IPC
Branch penalty
Stall percentage
Forwarding frequency
Future versions could expose internal processor signals through:
- Vivado Integrated Logic Analyzer
- AXI interface
- UART
- OLED/display interface
- PYNQ Python interface
A future version can connect the processor to the Zynq Processing System and allow software running on the ARM processor to interact with FlexRISC16.
[✓] 16-bit RISC datapath
[✓] ALU implementation
[✓] Register file
[✓] Instruction decoder
[✓] Instruction memory
[✓] Data memory
[✓] Five-stage pipeline
[✓] Pipeline registers
[✓] Data forwarding
[✓] Load-use hazard detection
[✓] Pipeline stall
[✓] Branch handling
[✓] Status register
[✓] Performance counters
[✓] Automated self-checking testbench
[✓] 22/22 functional checks
[✓] Simulation result logging
[✓] CSV result generation
[✓] Vivado synthesis
[✓] PYNQ-Z2 FPGA programming
[ ] Setup timing optimization
[ ] Advanced branch prediction
[ ] Extended instruction set
[ ] Hardware/software integration
- Verilog HDL
- Xilinx Vivado
- Vivado Simulator / XSim
- PYNQ-Z2
- Xilinx Zynq-7000 FPGA







