uP16 · 5-Stage RISC CPU

MSN-003 · 2025 · verilog, fpga, rtl

5-stage pipelined 16-bit RISC CPU written in Verilog and synthesized on an Artix-7 FPGA. Register file and program counter readable live on hardware via Vivado.

uP16 is a 5-stage pipelined 16-bit RISC processor implemented in Verilog. The design runs on an Artix-7 FPGA with data hazards resolved via forwarding, and the register file and program counter are readable live on hardware through Vivado.

ISA

The instruction word is 18 bits wide, R-format only: 4-bit opcode, 3-bit destination register, 3-bit source register, 8-bit function/immediate field. Eight general-purpose registers (R0–R7) with R0 hardwired to zero. Separate address spaces: a 1024×18-bit single-port instruction ROM and a 1024×16-bit single-port data RAM. The 16-bit PC increments by 1 each cycle unless a branch redirects it.

Pipeline and hazard handling

uP16 datapath — PC, IF/ID, register file, ID/EX, ALU with forwarding MUXes (S4/S5 driven by DF Control), EX/MEM, MEM/WB pipeline registers, and write-back path

The five stages are IF, ID, EX, MEM, and WB. Data forwarding resolves read-after-write hazards between adjacent and back-to-back instructions without inserting stalls. Branch instructions (beq, bne, blt, bgt) resolve in EX using a signed PC-relative offset, flushing the two in-flight instructions when the branch is taken.

Simulation waveform — all 8 registers and IF_currPC advancing through the test program. Forwarding paths active between EX and MEM stages.

Instruction set

ALU operations: add, sub, and, or, nand, nor, xor, not, mov. Memory: lw and sw with an 8-bit signed immediate offset. Immediate loads: lli sign-extends an 8-bit value into the lower byte, lui merges an 8-bit upper immediate with the low byte of a source register. Four conditional branch instructions compare two registers with a signed PC-relative target.

Timing closure

Initial synthesis failed timing: WNS −0.455 ns, TNS −0.702 ns, 2 failing endpoints across 408 total.

Timing report before fix — WNS −0.455 ns, TNS −0.702 ns, timing constraints not met

After reworking the critical path the design closed cleanly: WNS +0.045 ns, zero failing endpoints.

Timing report after fix — WNS +0.045 ns, all user timing constraints met

Post-fix simulation waveform — same test program, all register values correct after timing rework

Synthesized and deployed to an Artix-7 xc7a35tcmpg236-1. Register file contents and the program counter are readable live in Vivado during execution, making it possible to step through programs and verify forwarding paths are resolving correctly at each pipeline stage.

What this built

Designing the forwarding unit made the cost of every pipeline decision concrete in a way that reading about it doesn't. You can reason abstractly about RAW hazards, but wiring up the MUX select logic for EX→EX and MEM→EX paths and then watching the waveform show a wrong value forces you to understand exactly when each stage has committed its result and when it hasn't.

The timing closure work was a similar forcing function. A failing WNS tells you the critical path is too long, but Vivado's timing report makes you trace it — through which gates, across which register boundaries — before you know what to cut. Reworking the path from −0.455 ns to +0.045 ns was not a large margin, and finding it required understanding the relationship between logic depth, fanout, and slack in a way that static analysis alone doesn't give you.

The split instruction/data memory architecture (Harvard model) also clarified something about memory access patterns: the pipeline never stalls on an instruction fetch waiting for a data read because the two can't conflict. That constraint shapes the ISA — it's why lw and sw use base+offset addressing rather than anything that would require a second memory access during fetch.

Stack

- Verilog-2001 - Artix-7 xc7a35tcmpg236-1 FPGA - Vivado Design Suite