A fully functional 32-bit single-cycle RISC-V (RV32I) processor, designed in Verilog and verified through simulation, synthesis, and implementation in Xilinx Vivado 2025.2, targeting an Artix-7 (xc7a35tftg256-1) device. The design implements and verifies all 37 base RV32I instructions.
S. Vignesh · P. Vineeth Goud
- Overview
- Architecture
- Repository Structure
- Module Descriptions
- Instruction Set Coverage
- Simulation & Verification
- Vivado Implementation Results
- How to Run
- Constraints
- License
This project implements a single-cycle, non-pipelined RV32I processor core that fetches, decodes, and executes one instruction per clock cycle. It supports the full RV32I base integer instruction set: R-type, I-type, S-type, B-type, U-type, and J-type instructions, including all arithmetic/logic, shift, comparison, load/store (byte, halfword, word — signed & unsigned), branch, and jump instructions.
The design is self-checked by a comprehensive testbench (tb.v) that executes and verifies 104 instructions covering all 37 RV32I opcodes, checking:
- The PC value at every fetch
- The exact instruction encoding fetched from ROM
- The architectural register write (or memory write, for stores) resulting from each instruction
The design has been synthesized, placed, and routed in Vivado targeting a Xilinx Artix-7 (xc7a35tftg256-1) part, with all timing constraints met. All results in this repository (simulation, synthesis, and implementation) were produced entirely within Vivado's simulator and implementation flow — the design has not been deployed onto a physical FPGA board.
The processor follows the classic single-cycle datapath: an instruction is fetched from ROM using the Program Counter, decoded by the control unit, operands are read from the register file, the ALU (or branch comparator) computes a result, data memory is optionally accessed, and the result is written back to the register file — all within a single clock cycle.
Fig. 1 — Architecture of the Single-Cycle RISC-V Processor.
Datapath walkthrough (per the diagram above):
- Fetch — The
Program Counter(clocked by aClock Divideroffclk) addressesInstruction Memory, which returns the 32-bit instruction.Next PC(PC + 4, via an adder) and the branch/jump target compete at apc_sel-controlled mux to produce the next PC value. - Decode — The instruction feeds the
Control Unit, which decodesopcode/fun3/fun7into all downstream control signals:pc_sel, wr_en, sign_sel, rd1_sel, rd2_sel, branch_sel, alu_ctl, dm_wr, dm_sel, dm_sel1. - Register read & immediate generation —
rs1/rs2fields index theGeneral Purpose Registerfile (rd1,rd2outputs); theImm Value Block(controlled bysign_sel) sign/zero-extends the immediate for I/S/B/U/J formats. - Operand select — Two muxes choose the ALU's operands: operand A is
rd1orPC(for AUIPC/branches), and operand B isrd2or the extended immediate (for ALU-immediate/loads/stores). - Execute — The
ALU(controlled byalu_ctl) performs the arithmetic/logic operation; in parallel, theBranch Comparatorevaluatesbr_eq/br_gtfromrd1/rd2, gated bybranch_sel, to drivepc_selfor taken branches. - Memory access — The ALU result addresses
Data Memory. Writes are qualified bydm_wrand formatted perInst[14:12](byte/half/word — the store-formatting logic). Reads pass through anExtension Block(Inst[14:12]selects sign/zero-extension forlb/lh/lw/lbu/lhu). - Writeback — A
dm_selmux chooses between the ALU result and loaded/extended memory data; a furtherdm_sel1mux chooses between that value andPC+4(forJAL/JALRlink register writeback). The selectedresultis written back into the register file whenwr_enis asserted. - External I/O (as drawn) —
f_in[7:0]feeds the input data locationdata_mem[0], andf_outexposes the data atdata_mem[1]— a simple external data-memory-mapped I/O hook used in this version of the datapath diagram.
Note: this figure documents the general single-cycle RV32I datapath concept used to design the RTL. The actual Verilog module/port names in this repository map 1:1 onto the blocks above (e.g.
control_unit.v= Control Unit,registerfile.v= General Purpose Register,branch_comparator.v= Branch Comparator,load_unit.v/store_unit.v= the load/store side of the Extension Block,extend.v= Imm Value Block).
Key datapath signals:
| Signal | Width | Description |
|---|---|---|
pc |
32 | Current program counter (output, testbench-visible) |
instr |
32 | Fetched instruction (output, testbench-visible) |
result |
32 | Register writeback value (output, testbench-visible) |
alu_ctl |
4 | ALU operation selector, generated by control unit |
rd1_sel / rd2_sel |
1 | Selects ALU operand A source (PC vs rs1) and B source (imm vs rs2) |
dm_sel / dm_sel1 |
1 | Selects writeback data: ALU result / load data / PC+4 (for JAL/JALR) |
is_jalr |
1 | Clears bit 0 of the jump target for JALR per the RISC-V spec |
br_taken |
1 | Branch-condition result from branch_comparator |
.
├── codes/
│ ├── constraint_file/
│ │ └── constr.xdc # Vivado timing & I/O constraints
│ ├── sim/
│ │ └── tb.v # Self-checking testbench (104 instructions)
│ └── source/
│ ├── ALU.v # 32-bit ALU (add/sub/logic/shift/compare)
│ ├── adder.v # Generic 32-bit adder (used for PC+4)
│ ├── branch_comparator.v # Evaluates BEQ/BNE/BLT/BGE/BLTU/BGEU
│ ├── control_unit.v # Main single-cycle decoder / control FSM
│ ├── data_memory.v # 1024 x 32-bit byte-writable data RAM
│ ├── extend.v # Immediate sign-extension unit (I/S/B/U/J)
│ ├── inst_Mem.v # 128 x 32-bit instruction ROM (test program)
│ ├── load_unit.v # LB/LH/LW/LBU/LHU byte/halfword extraction
│ ├── mux.v # Generic 2:1 32-bit multiplexer
│ ├── program_counter.v # PC register (active-low synchronous reset)
│ ├── registerfile.v # 32 x 32-bit register file (x0 hardwired to 0)
│ ├── risc_top.v # Top-level module wiring the full datapath
│ └── store_unit.v # SB/SH/SW data formatting & byte-enable logic
│
├── outputs/
│ ├── architecture_diagram.png # Fig. 1 — single-cycle datapath block diagram
│ ├── TCL Console Output.txt # Full XSim simulation log (104 instructions, 350/350 checks)
│ └── waveform/
│ ├── Waveforms.png # XSim waveform capture
│ └── top_tb_behav.wcfg # Vivado waveform configuration
│
└── reports/
├── synth/ # Post-synthesis reports
│ ├── risc_top_utilization_synth.rpt
├ ├── Sythesis_output.png
│ └── Post_Synthesis.png
└── imp/ # Post-implementation reports
├── risc_top_utilization_placed.rpt
├── risc_top_control_sets_placed.rpt
├── risc_top_timing_summary_routed.rpt
├── risc_top_power_routed.rpt
├── risc_top_bus_skew_routed.rpt
├── risc_top_route_status.rpt
├── risc_top_io_placed.rpt
├── risc_top_drc_opted.rpt
├── risc_top_drc_routed.rpt
├── risc_top_methodology_drc_routed.rpt
├── risc_top_clock_utilization_routed.rpt
├── Post_Implementation.png
├── Setup_time.png
├── Hold_Time.png
├── Pulse_Width.png
└── power.png
| Module | Responsibility |
|---|---|
risc_top.v |
Top-level integration — instantiates and wires every submodule into the single-cycle datapath. |
program_counter.v |
32-bit PC register; synchronously clears to 0 on active-low reset. |
inst_Mem.v |
128-word instruction ROM, asynchronously read, pre-loaded with the 108-instruction test program. |
control_unit.v |
Combinational decoder mapping opcode/fun3/fun7 → ALU control, mux selects, and write-enable signals for all 11 RV32I opcode classes (R, I-ALU, Load, Store, Branch, JAL, JALR, LUI, AUIPC). |
registerfile.v |
32 × 32-bit register file with two asynchronous read ports and one synchronous write port; x0 is hardwired to zero on read and write. |
extend.v |
Produces the correctly sign/zero-extended immediate for I/S/B/U/J instruction formats. |
ALU.v |
Performs add, subtract, XOR/OR/AND, shift-left/right (logical & arithmetic), and both signed/unsigned "set-less-than" comparisons. |
branch_comparator.v |
Independently evaluates all six RV32I branch conditions (BEQ/BNE/BLT/BGE/BLTU/BGEU) directly from fun3. |
data_memory.v |
1024 × 32-bit byte-writable RAM with asynchronous read and per-byte write-enable (be[3:0]). |
store_unit.v |
Formats store data and computes byte-enables for SB/SH/SW based on address alignment. |
load_unit.v |
Extracts and sign/zero-extends the addressed byte/halfword/word for LB/LH/LW/LBU/LHU. |
mux.v, adder.v |
Generic reusable building blocks used throughout the datapath. |
tb.v |
Self-checking testbench; drives 104 instructions and independently checks PC, instruction encoding, and register/memory writeback for every one. Compatible with both Icarus Verilog and Vivado XSim. |
All 37 RV32I base instructions are implemented and verified:
| Category | Instructions |
|---|---|
| R-type (ALU) | add, sub, sll, slt, sltu, xor, srl, sra, or, and |
| I-type (ALU-immediate) | addi, slti, sltiu, xori, ori, andi, slli, srli, srai |
| Loads | lb, lh, lw, lbu, lhu |
| Stores | sb, sh, sw |
| Branches | beq, bne, blt, bge, bltu, bgeu |
| Jumps | jal, jalr |
| Upper-immediate | lui, auipc |
The testbench exercises loops, forward/backward branches (taken & not-taken), function calls via jal/jalr, and a full sweep of the register file and data memory at the end of the run.
tb.v is a self-checking, PC-anchored testbench — each instruction is verified three independent ways:
- PC check — the PC at fetch time must match the expected address (catches control-flow bugs).
- Encoding check — the fetched 32-bit instruction word must match the expected encoding (catches ROM/addressing bugs).
- Destination-register check — the value written back to
rdmust match the expected architectural result (catches datapath bugs).
Stores are verified with a final data-memory sweep, and the entire register file is swept at the end of the run. The testbench also includes:
- A watchdog timer (200 µs) to catch a hung or runaway PC.
- A PC X-detector to flag any unknown/undefined state.
- Compile-time switches:
`DUMP_VCD(enable VCD waveform dump),`QUIET(suppress per-instruction trace),`STOP_ON_FAIL(halt simulation on first failure).
Sample waveform capture from Vivado XSim, showing clk, rst, inst_out, alu_out, pc_out, and the pass/fail scoreboard counters advancing correctly:
clk/rst toggling, pc_out/inst_out/alu_out advancing each cycle in lock-step, pass_count incrementing every check, fail_count staying at 0 throughout.
Actual Vivado XSim TCL console output (run all), simulating all 104 instructions end-to-end:
============================================================
RISC-V RV32I Single-Cycle Core - Full ISA Regression
37 base instructions, self-checking, PC-anchored
============================================================
reset held 3 cycles: PC=0, regfile cleared ok
[ 1] PC=0x000 IR=0xabcde0b7 lui x1,0xABCDE x1 <= 0xabcde000 ok
[ 2] PC=0x004 IR=0x00001117 auipc x2,0x00001 x2 <= 0x00001004 ok
...
[104] PC=0x1ac IR=0x00200313 addi x6,x0,2 x6 <= 0x00000002 ok
---- final data memory ----
dmem[64] = 0x12345678 ok
dmem[65] = 0x89abcdef ok
dmem[66] = 0x00ef0078 ok
dmem[67] = 0xcdef5678 ok
---- final register file ----
x0 = 0x00000000 ok
...
x31 = 0x00000184 ok
============================================================
Instructions executed : 104
Checks passed : 350
Checks failed : 0
------------------------------------------------------------
RESULT: PASS - all 37 RV32I instructions verified
============================================================
Every one of the 104 executed instructions, all 4 final data-memory words, and all 32 final register values were checked — 350/350 checks passed, 0 failed. This includes independent PC, instruction-encoding, and register/memory-writeback checks for every instruction, plus loop iterations (bne x30,x0,loop taken 3×), all 6 branch conditions (taken and not-taken), and both jal/jalr control-flow paths (function call/return and unconditional jump). The full console log is available in outputs/TCL Console Output.txt.
Target device: xc7a35tftg256-1 (Artix-7) | Tool: Vivado v.2025.2 | Clock: sys_clk, 20 ns period (50 MHz)
These results come from Vivado's synthesis and implementation flow — simulation-only; the design was not deployed onto physical hardware.
| Metric | Value |
|---|---|
| Worst Negative Slack (Setup, WNS) | 0.779 ns (met) |
| Total Negative Slack (Setup, TNS) | 0 ns |
| Worst Hold Slack (WHS) | 0.275 ns (met) |
| Total Hold Slack (THS) | 0 ns |
| Worst Pulse Width Slack (WPWS) | 8.750 ns (met) |
| Setup endpoints | 7136 (0 failing) |
| Hold endpoints | 7136 (0 failing) |
| Pulse-width endpoints | 1537 (0 failing) |
All user-specified timing constraints are met at a 20 ns (50 MHz) clock period, with ~0.78 ns of positive setup slack.
The critical path runs from p1/pc_reg[7] through the instruction decode logic, register file read, ALU, and the distributed-RAM-based data memory, into the register file write port — 18 logic levels, dominated by routing delay (≈77%) as expected for a wide single-cycle datapath on a small Artix-7 part.
Vivado's implemented timing report confirms 0 failing endpoints across all three checks (7136 setup, 7136 hold, 1537 pulse-width endpoints).
Post-Synthesis:
| Resource | Used | Available | Utilization % |
|---|---|---|---|
| LUT | 1988 | 20800 | 9.56% |
| LUTRAM | 512 | 9600 | 5.33% |
| FF | 1024 | 41600 | 2.46% |
| IO | 98 | 170 | 57.65% |
| BUFG | 1 | 32 | 3.13% |
Post-Implementation (Placed & Routed):
| Resource | Used | Available | Utilization % |
|---|---|---|---|
| LUT | 1960 | 20800 | 9.42% |
| LUTRAM | 512 | 9600 | 5.33% |
| FF | 1024 | 41600 | 2.46% |
| IO | 98 | 170 | 57.65% |
| BUFG | 1 | 32 | 3.13% |
The design uses 0 Block RAMs and 0 DSPs — both inst_Mem and data_memory are implemented entirely as distributed LUT RAM (RAMS64E), which Vivado's methodology checker flags (SYNTH-5, informational) as a timing-driven mapping choice rather than an error.
| Metric | Value |
|---|---|
| Total On-Chip Power | 0.161 W |
| Dynamic Power | 0.090 W |
| Static (Device) Power | 0.070 W |
| Junction Temperature | 25.8 °C |
| Thermal Margin | 59.2 °C (12.1 W) |
| Confidence Level | Medium (activity partially estimated) |
# Compile all source files + testbench
iverilog -g2005 -o sim.vvp codes/source/ALU.v codes/source/adder.v \
codes/source/branch_comparator.v codes/source/control_unit.v \
codes/source/data_memory.v codes/source/extend.v codes/source/inst_Mem.v \
codes/source/load_unit.v codes/source/mux.v codes/source/program_counter.v \
codes/source/registerfile.v codes/source/risc_top.v codes/source/store_unit.v \
codes/sim/tb.v
# Run the simulation
vvp sim.vvpOptional compile-time defines:
iverilog -g2005 -DDUMP_VCD -o sim.vvp codes/source/*.v codes/sim/tb.v # dump riscv_tb.vcd for GTKWave
iverilog -g2005 -DQUIET -o sim.vvp codes/source/*.v codes/sim/tb.v # suppress per-instruction trace, summary only
iverilog -g2005 -DSTOP_ON_FAIL -o sim.vvp codes/source/*.v codes/sim/tb.v # halt on first mismatch# From the Vivado Tcl console, or via a project you create in the GUI:
xvlog codes/source/*.v
xelab top_tb -s tb_sim
xsim tb_sim -runallTo reproduce the implementation results above:
- Create a new RTL project in Vivado, add all files under
codes/source/andcodes/sim/tb.vas the simulation-only testbench. - Add
codes/constraint_file/constr.xdcas the constraints file. - Set the target part to
xc7a35tftg256-1. - Run Synthesis → Implementation in Vivado to reproduce the timing, utilization, and power reports in
reports/.
codes/constraint_file/constr.xdc defines:
- Primary clock:
sys_clk, 20 ns period, on portclk. - Input delay on
reset: 5 ns max / 1 ns min relative tosys_clk. - False paths from
resetand to the debug/observability outputsresult[*],instr[*],pc[*](these are exposed purely for testbench/waveform visibility and are not part of any real timing-critical interface). - Basic configuration properties (
CFGBVS,CONFIG_VOLTAGE, bitstream compression, SPI bus width/config rate) for Artix-7 SPI configuration.
This project is licensed under the MIT License — see the LICENSE file for details.
- S. Vignesh
- P. Vineeth Goud





