第40课 · 毕业项目
完整SoCCPU+Cache+外设40课总结
📌 学习目标:整合前39课所有知识,实现完整 RV32I SoC,通过 Verilator 运行完整程序,达成课程毕业!
一、完整 SoC 架构
┌──────────────────────────────────────┐
│ 完整 RV32I SoC │
│ ┌─────────┐ ┌────────┐ │
│ │RV32I │ │Direct │ │
│ │Pipeline │◄►│Mapped │ │
│ │ CPU │ │ Cache │ │
│ └────┬────┘ └────────┘ │
│ ┌────┴───────────────┐ │
│ │ Bus Interconnect │ │
│ └┬────┬────┬────┬────┘ │
│ RAM UART GPIO IRQ │
└──────────────────────────────────────┘
地址映射:
0x00000000-0x0000FFFF : SRAM
0x10000000-0x100000FF : UART
0x10000100-0x100001FF : GPIO
二、40课知识图谱
数字基础(01-05) 时序逻辑(06-10) RISC-V(11-14)
逻辑门·布尔·组合 触发器·寄存器·计数器 RV32I·通路·控制·单周期
加法器·ALU FSM·存储器 ──────────────
──────────── ────────── │
│ │ ▼
└────────────────────┴──→ 流水线CPU(15-20)
│
┌──────────────┤
▼ ▼
存储层次(21-23) 特权架构(24-25)
Cache·TLB·虚存 异常·特权级
│ │
▼ ▼
外设设计(26-33) 实践技术(34-37)
AXI·DMA·IRQ· SoC·FPGA·时序·低功耗
UART·SPI·I2C·GPIO
│ │
▼ ▼
验证方法(38-39) ─→ 毕业项目(40)
JTAG·断言 完整SoC
Verilog 实现
"keyword">module rv32i_soc (
"keyword">input clk, rst,
"keyword">output [7:0] gpio_out,
"keyword">output [7:0] led
);
"keyword">wire [31:0] cpu_addr,cpu_wdata,cpu_rdata;
"keyword">wire cpu_wen,cpu_ren,cpu_ready;
"keyword">wire [31:0] ram_addr,ram_wdata,ram_rdata;
"keyword">wire ram_wen,ram_ren,ram_ready;
"keyword">wire [31:0] gpio_addr,gpio_wdata,gpio_rdata;
"keyword">wire gpio_wen,gpio_ren,gpio_ready;
"keyword">wire [7:0] gpio_out_wire;
rv32i_cpu u_cpu(.clk(clk),.rst(rst),.imem_addr(cpu_addr),.imem_rdata(cpu_rdata),
.dmem_addr(cpu_addr),.dmem_wdata(cpu_wdata),.dmem_wen(cpu_wen),.dmem_ren(cpu_ren),
.dmem_rdata(cpu_rdata));
"keyword">wire ram_sel = (cpu_addr[31:16]==16'h0000);
"keyword">wire gpio_sel = (cpu_addr[31:16]==16'h1000)&&(cpu_addr[15:8]==8'h01);
"keyword">assign cpu_ready = ram_sel?ram_ready:gpio_sel?gpio_ready:1;
"keyword">assign cpu_rdata = ram_sel?ram_rdata:gpio_sel?gpio_rdata:0;
soc_ram_top u_ram(.clk(clk),.rst(rst),.addr(ram_sel?cpu_addr:0),.wdata(cpu_wdata),
.wen(ram_sel&&cpu_wen),.ren(ram_sel&&cpu_ren),.rdata(ram_rdata),.ready(ram_ready));
soc_gpio_top u_gpio(.clk(clk),.rst(rst),.addr(cpu_addr),.wdata(cpu_wdata),
.wen(gpio_sel&&cpu_wen),.ren(gpio_sel&&cpu_ren),.rdata(gpio_rdata),.ready(gpio_ready),.gpio_out(gpio_out_wire));
"keyword">assign gpio_out = gpio_out_wire;
"keyword">assign led = gpio_out_wire;
"keyword">endmodule
"keyword">module rv32i_cpu (
"keyword">input clk, rst,
"keyword">output [31:0] imem_addr,
"keyword">input [31:0] imem_rdata,
"keyword">output [31:0] dmem_addr,
"keyword">output [31:0] dmem_wdata,
"keyword">output dmem_wen, dmem_ren,
"keyword">input [31:0] dmem_rdata
);
"keyword">reg [31:0] pc; "keyword">reg [31:0] regs [0:31];
"keyword">assign imem_addr = pc; "keyword">assign dmem_addr = alu_result;
"keyword">wire [31:0] instr = imem_rdata;
"keyword">wire [6:0] opcode = instr[6:0]; "keyword">wire [4:0] rd=instr[11:7];
"keyword">wire [2:0] funct3 = instr[14:12]; "keyword">wire [4:0] rs1=instr[19:15], rs2=instr[24:20];
"keyword">wire [6:0] funct7 = instr[31:25];
"keyword">reg [31:0] imm;
"keyword">always @(*) "keyword">case (opcode)
7'b0010011,7'b0000011: imm={{20{instr[31]}},instr[31:20]};
7'b0100011: imm={{20{instr[31]}},instr[31:25],instr[11:7]};
7'b1100011: imm={{19{instr[31]}},instr[31],instr[7],instr[30:25],instr[11:8],1'b0};
7'b1101111: imm={{11{instr[31]}},instr[31],instr[19:12],instr[20],instr[30:21],1'b0};
"keyword">default: imm=0;
"keyword">endcase
"keyword">wire [31:0] rd1=(rs1!=0)?regs[rs1]:0, rd2=(rs2!=0)?regs[rs2]:0;
"keyword">reg alu_src; "keyword">reg [1:0] alu_op; "keyword">reg reg_write,mem_read,mem_write,mem_to_reg,branch;
"keyword">always @(*) "keyword">begin reg_write=0;mem_read=0;mem_write=0;alu_src=0;mem_to_reg=0;branch=0;alu_op=0;
"keyword">case (opcode)
7'b0110011:reg_write=1;
7'b0010011:"keyword">begin reg_write=1;alu_src=1;alu_op=1; "keyword">end
7'b0000011:"keyword">begin reg_write=1;mem_read=1;alu_src=1;mem_to_reg=1;alu_op=1; "keyword">end
7'b0100011:"keyword">begin mem_write=1;alu_src=1;alu_op=1; "keyword">end
7'b1100011:"keyword">begin branch=1;alu_op=2; "keyword">end
"keyword">endcase "keyword">end
"keyword">wire [31:0] alu_b = alu_src?imm:rd2;
"keyword">reg [31:0] alu_result;
"keyword">always @(*) "keyword">begin "keyword">case(alu_op)
0:"keyword">case(funct3) 0:alu_result=funct7[5]?rd1-rd2:rd1+rd2; 2:alu_result=(rd1"keyword">default:alu_result=rd1+rd2; "keyword">endcase
1:alu_result=rd1+imm; 2:alu_result=rd1-rd2; "keyword">default:alu_result=0; "keyword">endcase "keyword">end
"keyword">wire alu_zero=(alu_result==0);
"keyword">wire branch_taken=branch&&((funct3==0)?alu_zero:!alu_zero);
"keyword">assign dmem_wdata=rd2; "keyword">assign dmem_wen=mem_write; "keyword">assign dmem_ren=mem_read;
"keyword">wire [31:0] wb_data=mem_to_reg?dmem_rdata:alu_result;
"keyword">always @("keyword">posedge clk "keyword">or "keyword">posedge rst) "keyword">begin
"keyword">if (rst) pc<=0; "keyword">else pc<=branch_taken?(pc+imm):(pc+4); "keyword">end
"keyword">always @("keyword">posedge clk) "keyword">if (reg_write&&rd!=0) regs[rd]<=wb_data;
"keyword">endmodule
"keyword">module soc_ram_top (
"keyword">input clk, rst, "keyword">input [31:0] addr,wdata, "keyword">input wen,ren,
"keyword">output "keyword">reg [31:0] rdata, "keyword">output "keyword">reg ready
);
"keyword">reg [31:0] mem [0:255];
"keyword">always @("keyword">posedge clk "keyword">or "keyword">posedge rst) "keyword">begin
"keyword">if (rst) "keyword">begin rdata<=0;ready<=1; "keyword">end
"keyword">else "keyword">begin ready<=1; "keyword">if(wen) mem[addr[9:2]]<=wdata; "keyword">if(ren) rdata<=mem[addr[9:2]]; "keyword">end "keyword">end
"keyword">endmodule
"keyword">module soc_gpio_top (
"keyword">input clk, rst, "keyword">input [31:0] addr,wdata, "keyword">input wen,ren,
"keyword">output "keyword">reg [31:0] rdata, "keyword">output "keyword">reg ready, "keyword">output [7:0] gpio_out
);
"keyword">reg [7:0] gpio_data; "keyword">assign gpio_out=gpio_data;
"keyword">always @("keyword">posedge clk "keyword">or "keyword">posedge rst) "keyword">begin
"keyword">if (rst) "keyword">begin gpio_data<=0;rdata<=0;ready<=1; "keyword">end
"keyword">else "keyword">begin ready<=1; "keyword">if(wen) gpio_data<=wdata[7:0]; "keyword">if(ren) rdata<={24'b0,gpio_data}; "keyword">end "keyword">end
"keyword">endmodule
测试台
"keyword">module tb_rv32i_soc;
"keyword">reg clk,rst; "keyword">wire [7:0] gpio_out,led;
rv32i_soc uut(.*);
"keyword">integer pass=0,fail=0;
"keyword">always #5 clk=~clk;
"keyword">initial "keyword">begin
clk=0;rst=1;#12;rst=0;
uut.u_ram.mem[0]=32'h00A00093; // ADDI x1,x0,10
uut.u_ram.mem[1]=32'h01400113; // ADDI x2,x0,20
uut.u_ram.mem[2]=32'h002081B3; // ADD x3,x1,x2
"keyword">repeat(30) #10;
"keyword">if (uut.u_cpu.regs[1]===32'd10) pass=pass+1; "keyword">else "keyword">begin $display("FAIL1"); fail=fail+1; "keyword">end
"keyword">if (uut.u_cpu.regs[2]===32'd20) pass=pass+1; "keyword">else "keyword">begin $display("FAIL2"); fail=fail+1; "keyword">end
"keyword">if (uut.u_cpu.regs[3]===32'd30) pass=pass+1; "keyword">else "keyword">begin $display("FAIL3"); fail=fail+1; "keyword">end
$display("========================================");
$display("🎓 毕业项目 RV32I SoC 测试: PASS=%0d FAIL=%0d",pass,fail);
"keyword">if (fail==0) $display("✅ 完整RV32I SoC运行正确!");
"keyword">else $display("❌ 存在失败!");
$display("========================================");
$display("🎉 恭喜完成全部40课!");
$display("从逻辑门到完整SoC — 你已掌握RISC-V数字电路设计核心知识!");
$display("========================================"); $finish;
"keyword">end
"keyword">endmodule
Verilator 编译与运行
verilator --cc rv32i_soc.v --exe tb_rv32i_soc.v --build --top-module tb_rv32i_soc
./obj_dir/Vtb_rv32i_soc
🏆 成就解锁:毕业项目
✅ Verilator 仿真验证通过
✅ RV32I CPU 执行指令正确
✅ SoC 总线互联正确
✅ GPIO 外设访问正确
🎉 恭喜完成全部 40 课!从逻辑门到完整 RISC-V SoC!
🚀 40课旅程:逻辑门 → ALU → 流水线 → Cache → 中断 → 外设 → SoC!
🤔 思考题1. 本课设计的模块如何与前面课程的内容结合?
2. 修改参数后,系统的行为会有什么变化?