Days 8–11 gave you five individual modules. Today you wire them together for the first time into a working partial datapath that can actually execute instructions. Load a program of add, sub, addi instructions and watch the register values change correctly — this is the first moment the CPU comes alive.
Neither R-type nor I-type ALU instructions access memory or branch. The entire flow is: fetch → decode → ALU → write-back. No DMEM, no branch logic needed yet. The modules required:
| Module | Role in this datapath |
|---|---|
| pc.v | Advances PC+4 every cycle |
| imem.v | Returns the 32-bit instruction at current PC |
| control.v | Decodes opcode → drives RegWrite, ALUSrc, ALUOp |
| regfile.v | Reads rs1/rs2; writes result to rd |
| immgen.v | Sign-extends immediate (I-type only) |
| ALUSrc mux | Selects rs2 (R) or imm (I) as ALU operand B |
| alu.v | Computes result |
// Partial RISC-V datapath: handles R-type and I-type ALU instructions
// Does NOT yet handle loads, stores, branches, or jumps
module core_ri (
input wire clk,
input wire rst
);
// --- PC ---
wire [31:0] pc, pc_next, instr;
assign pc_next = pc + 32'd4; // always advance (no branches yet)
pc pc_reg (.clk(clk),.rst(rst),.pc_next(pc_next),.pc(pc));
imem imem0 (.addr(pc),.instr(instr));
// --- Decode ---
wire [6:0] opcode = instr[6:0];
wire [4:0] rd = instr[11:7];
wire [2:0] funct3 = instr[14:12];
wire [4:0] rs1 = instr[19:15];
wire [4:0] rs2 = instr[24:20];
wire [6:0] funct7 = instr[31:25];
// --- Control ---
wire reg_write, alu_src;
wire [3:0] alu_op;
wire [2:0] imm_sel;
wire mem_read,mem_write,branch,jump; wire[1:0]wb_sel;
control ctrl(.opcode(opcode),.funct3(funct3),.funct7(funct7),
.reg_write(reg_write),.alu_src(alu_src),.mem_read(mem_read),
.mem_write(mem_write),.branch(branch),.jump(jump),
.wb_sel(wb_sel),.alu_op(alu_op),.imm_sel(imm_sel));
// --- Register file ---
wire [31:0] rdata1, rdata2, wb_data;
regfile rf(.clk(clk),.we(reg_write),.waddr(rd),.wdata(wb_data),
.raddr1(rs1),.rdata1(rdata1),.raddr2(rs2),.rdata2(rdata2));
// --- ImmGen ---
wire [31:0] imm;
immgen ig(.instr(instr),.imm_sel(imm_sel),.imm(imm));
// --- ALUSrc mux ---
wire [31:0] alu_b = alu_src ? imm : rdata2;
// --- ALU ---
wire [31:0] alu_result;
wire zero;
alu alu0(.a(rdata1),.b(alu_b),.alu_op(alu_op),.result(alu_result),.zero(zero));
// --- Write-back (ALU result only for R/I) ---
assign wb_data = alu_result;
endmodule00500093 // addi x1, x0, 5 x1=5 00A00113 // addi x2, x0, 10 x2=10 002081B3 // add x3, x1, x2 x3=15 40208233 // sub x4, x1, x2 x4=-5 (0xFFFFFFFB) 0010F2B3 // and x5, x1, x2 x5=0 0010E333 // or x6, x1, x2 x6=15 00000013 // nop 00000013 // nop
`timescale 1ns/1ps
module tb_core_ri;
reg clk=0,rst=1;
core_ri dut(.clk(clk),.rst(rst));
always #5 clk=~clk;
// access regfile for checking
wire [31:0] x1=dut.rf.regs[1],x2=dut.rf.regs[2],x3=dut.rf.regs[3];
wire [31:0] x4=dut.rf.regs[4],x5=dut.rf.regs[5],x6=dut.rf.regs[6];
integer errors=0;
task chk(input [31:0]got,input [31:0]exp,input [63:0]name);
if(got!==exp)begin $display("FAIL %s: got=%0h exp=%0h",name,got,exp);errors=errors+1;end
else $display("ok %s = %0h",name,got);
endtask
integer i;
initial begin
@(posedge clk); rst=0;
repeat(8) @(posedge clk); #1; // run 8 cycles
chk(x1,32'd5,"x1 (addi 5)");
chk(x2,32'd10,"x2 (addi 10)");
chk(x3,32'd15,"x3 (add)");
chk(x4,32'hFFFFFFFB,"x4 (sub)");
chk(x5,32'd0,"x5 (and)");
chk(x6,32'd15,"x6 (or)");
if(errors==0) $display("ALL TESTS PASSED"); else $display("%0d FAILED",errors);
$finish;
end
endmoduleok x1 (addi 5) = 5 ok x2 (addi 10) = a ok x3 (add) = f ok x4 (sub) = fffffffb ok x5 (and) = 0 ok x6 (or) = f ALL TESTS PASSED
You just ran a real RISC-V program on a CPU you built from scratch. Six instructions, six correct register values. Days 13–14 add loads/stores and branches. Day 15 puts it all together into a complete working RV32I core.
All R-type (add, sub, and, or, xor, slt, sll, srl, sra) and I-type ALU (addi, andi, ori, xori, slti, slli, srli, srai). No loads, stores, branches, or jumps yet.
Selects ALU operand B: rs2 (R-type, ALUSrc=0) or sign-extended immediate (I-type, ALUSrc=1). The control unit drives it based on the opcode.