CPU in 40 lines of SV¶
This project builds a small 16-bit CPU in five incremental steps. Each step is complete, and adds only few new lines. Programs are written directly in SV testbench. Full CPU here.
Interactive 3D preview unavailable in this browser. Open the GLB file directly.
Quickstart¶
make fresh # if you havent started the container
make enter # to enter the running container
make sim gtkwave DESIGN=cpu_3_store_data # simulate the step 3 design & view waveform
# Ctrl+C to exit gtkwave
make sim gtkwave DESIGN=cpu_factorial # run factorial on the final CPU
make gds show_layout DESIGN=cpu_factorial # Run GDS flow
Example programs:
Incremental evolution¶
Level |
Feature |
RTL |
|---|---|---|
|
Simple memory with zero-latency read and 1-cycle-latency write |
|
|
Just a counter to load instructions (PC) |
|
|
Sixteen registers and |
|
|
|
|
|
|
|
|
|
CPU Design¶
Only 7 opcodes:
LOAD=0,STORE=1,MOVE=2,ADD=3,SUB=4,MUL=5, andJNZ=6.Two instruction formats:
Address type:
LOAD, STORE, JNZtake anaddress and register index (i_reg)Register type:
MOVE, ADD, SUB, MULtake indices of three registers. Two source (i_rs1, i_rs2) and one destinationi_rd.
JNZjumps to theaddrwhenregs[i_reg]is nonzero.
| Instructions | Format | 4 Bits [15:12] | 4 Bits [11:8] | 4 Bits [7:4] | 4 Bits [3:0] |
|---|---|---|---|---|---|
LOAD, STORE, JNZ |
Address | addr |
i_reg |
opcode |
|
MOVE, ADD, SUB, MUL |
Register | i_rs2 |
i_rs1 |
i_rd |
opcode |
Reading Instructions¶
Each instruction field is 4-bits, so it becomes a character when displayed as hex, making it easy to read binary. Read right to left (little endian). e.g.
0x 1250 : 0=LOAD regs[5] <- dmem[0x12]
0x 0123 : 3=ADD regs[2] <- regs[1] + regs[0]
Waveform of Fibonacci program:

Full CPU (40 LOC)¶
module cpu (
input logic clk, reset,
output logic [7 :0] imem_addr, dmem_addr,
input logic [15:0] imem_rdata, dmem_rdata,
output logic [15:0] dmem_wdata,
output logic dmem_wen
);
logic [7:0] pc, addr;
enum logic [3:0] {LOAD, STORE, MOVE, ADD, SUB, MUL, JNZ} opcode;
logic [ 3:0] i_rs1, i_rs2, i_rd, i_reg;
logic [15:0] regs [16];
logic [15:0] reg_1, reg_2;
always_comb begin
imem_addr = pc;
{addr , i_reg, opcode} = imem_rdata;
{i_rs2, i_rs1, i_rd , opcode} = imem_rdata;
dmem_addr = addr;
dmem_wdata = regs[i_reg];
dmem_wen = opcode == STORE;
reg_1 = regs[i_rs1];
reg_2 = regs[i_rs2];
end
always_ff @(posedge clk)
if (reset) begin
pc <= '0;
for (int i = 0; i < 16; i++) regs[i] <= '0;
end else begin
pc <= pc + 1'b1;
case (opcode)
LOAD: regs[i_reg] <= dmem_rdata;
MOVE: regs[i_rd ] <= reg_1;
ADD : regs[i_rd ] <= reg_1 + reg_2;
SUB : regs[i_rd ] <= reg_1 - reg_2;
MUL : regs[i_rd ] <= reg_1 * reg_2;
JNZ : if (regs[i_reg] != '0) pc <= addr;
default: ;
endcase
end
endmodule