第34课 · SoC集成
SoC总线互联地址解码
📌 学习目标:理解 SoC 系统集成方法,实现 CPU + 外设总线互联和地址解码器,通过 Verilator 验证多外设访问。
一、SoC 架构
┌─────────┐
│ CPU │
└────┬────┘
┌────┴────┐
│地址解码器│
└┬───┬───┬┘
│ │ │
RAM UART GPIO
地址映射:
0x0000_0000 - 0x0000_FFFF : RAM
0x1000_0000 - 0x1000_00FF : UART
0x1000_0100 - 0x1000_01FF : GPIO
二、地址解码器
根据 CPU 地址选择对应外设,实现地址空间隔离。
Verilog 实现
"keyword">module soc_interconnect (
"keyword">input clk, rst,
"keyword">input [31:0] cpu_addr, cpu_wdata,
"keyword">input cpu_wen, cpu_ren,
"keyword">output "keyword">reg [31:0] cpu_rdata,
"keyword">output "keyword">reg cpu_ready,
"keyword">output "keyword">reg [31:0] ram_addr, ram_wdata,
"keyword">output "keyword">reg ram_wen, ram_ren,
"keyword">input [31:0] ram_rdata, "keyword">input ram_ready,
"keyword">output "keyword">reg [31:0] uart_addr, uart_wdata,
"keyword">output "keyword">reg uart_wen, uart_ren,
"keyword">input [31:0] uart_rdata, "keyword">input uart_ready,
"keyword">output "keyword">reg [31:0] gpio_addr, gpio_wdata,
"keyword">output "keyword">reg gpio_wen, gpio_ren,
"keyword">input [31:0] gpio_rdata, "keyword">input gpio_ready
);
"keyword">wire ram_sel = (cpu_addr[31:16]==16'h0000);
"keyword">wire uart_sel = (cpu_addr[31:16]==16'h1000)&&(cpu_addr[15:8]==8'h00);
"keyword">wire gpio_sel = (cpu_addr[31:16]==16'h1000)&&(cpu_addr[15:8]==8'h01);
"keyword">always @(*) "keyword">begin
ram_wen=0;ram_ren=0;uart_wen=0;uart_ren=0;gpio_wen=0;gpio_ren=0;
ram_addr=cpu_addr;ram_wdata=cpu_wdata;
uart_addr=cpu_addr;uart_wdata=cpu_wdata;
gpio_addr=cpu_addr;gpio_wdata=cpu_wdata;
cpu_rdata=0;cpu_ready=0;
"keyword">if (ram_sel) "keyword">begin ram_wen=cpu_wen;ram_ren=cpu_ren;cpu_rdata=ram_rdata;cpu_ready=ram_ready; "keyword">end
"keyword">else "keyword">if (uart_sel) "keyword">begin uart_wen=cpu_wen;uart_ren=cpu_ren;cpu_rdata=uart_rdata;cpu_ready=uart_ready; "keyword">end
"keyword">else "keyword">if (gpio_sel) "keyword">begin gpio_wen=cpu_wen;gpio_ren=cpu_ren;cpu_rdata=gpio_rdata;cpu_ready=gpio_ready; "keyword">end
"keyword">else cpu_ready=1;
"keyword">end
"keyword">endmodule
"keyword">module soc_ram (
"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 tb_soc_interconnect;
"keyword">reg clk,rst; "keyword">reg [31:0] cpu_addr,cpu_wdata; "keyword">reg cpu_wen,cpu_ren;
"keyword">wire [31:0] cpu_rdata; "keyword">wire cpu_ready;
"keyword">wire [31:0] ram_addr,ram_wdata; "keyword">wire ram_wen,ram_ren; "keyword">wire [31:0] ram_rdata; "keyword">wire ram_ready;
"keyword">wire [31:0] uart_addr,uart_wdata; "keyword">wire uart_wen,uart_ren; "keyword">wire [31:0] uart_rdata; "keyword">wire uart_ready;
"keyword">wire [31:0] gpio_addr,gpio_wdata; "keyword">wire gpio_wen,gpio_ren; "keyword">wire [31:0] gpio_rdata; "keyword">wire gpio_ready;
soc_interconnect uut(.*);
soc_ram u_ram(.clk(clk),.rst(rst),.addr(ram_addr),.wdata(ram_wdata),.wen(ram_wen),.ren(ram_ren),.rdata(ram_rdata),.ready(ram_ready));
soc_ram u_uart(.clk(clk),.rst(rst),.addr(uart_addr),.wdata(uart_wdata),.wen(uart_wen),.ren(uart_ren),.rdata(uart_rdata),.ready(uart_ready));
soc_ram u_gpio(.clk(clk),.rst(rst),.addr(gpio_addr),.wdata(gpio_wdata),.wen(gpio_wen),.ren(gpio_ren),.rdata(gpio_rdata),.ready(gpio_ready));
"keyword">integer pass=0,fail=0;
"keyword">always #5 clk=~clk;
"keyword">initial "keyword">begin
clk=0;rst=1;cpu_addr=0;cpu_wdata=0;cpu_wen=0;cpu_ren=0;#12;rst=0;#10;
cpu_addr=32'h0;cpu_wdata=32'hAAAA;cpu_wen=1;#10;cpu_wen=0;#10;
cpu_ren=1;#10;cpu_ren=0;
"keyword">if (cpu_rdata===32'hAAAA) pass=pass+1; "keyword">else "keyword">begin $display("FAIL1"); fail=fail+1; "keyword">end
cpu_addr=32'h10000100;cpu_wdata=32'hCCCC;cpu_wen=1;#10;cpu_wen=0;#10;
cpu_ren=1;#10;cpu_ren=0;
"keyword">if (cpu_rdata===32'hCCCC) pass=pass+1; "keyword">else "keyword">begin $display("FAIL2"); fail=fail+1; "keyword">end
$display("========================================");
$display("SoC互联测试: PASS=%0d FAIL=%0d",pass,fail);
"keyword">if (fail==0) $display("✅ SoC总线互联+地址解码全部正确!"); "keyword">else $display("❌ 存在失败!");
$display("========================================"); $finish;
"keyword">end
"keyword">endmodule
Verilator 编译与运行
verilator --cc soc_interconnect.v --exe tb_soc_interconnect.v --build --top-module tb_soc_interconnect
./obj_dir/Vtb_soc_interconnect
📌 扩展阅读
本课的核心概念在实际工程中有广泛应用:
- 在芯片设计中,模块化设计方法至关重要——每个 IP 核都有清晰的接口定义
- 仿真验证是确保设计正确性的关键步骤,Verilator 是工业界广泛使用的开源工具
- 时序约束和综合优化直接影响芯片的性能和功耗表现
- 形式验证和断言检查可以显著提高设计可靠性,减少流片后的 bug
🔧 调试技巧
在开发本课模块时,常见问题和解决方法:
- 仿真不收敛:检查组合逻辑是否形成了环路(combinational loop)
- 时序违例:添加流水线寄存器切断关键路径,或降低工作频率
- 资源超标:使用资源共享、时分复用等技术减少硬件开销
- 功能不正确:先写测试台(testbench),用断言检查关键信号
📊 性能指标
衡量本课模块性能的关键指标:
| 指标 | 含义 | 目标 |
| 延迟(Latency) | 从输入到输出的周期数 | 尽可能小 |
| 吞吐量(Throughput) | 每周期处理的数据量 | 尽可能大 |
| 面积(Area) | 占用的 LUT/FF 资源 | 在性能满足下最小化 |
| 功耗(Power) | 动态 + 静态功耗 | 在性能满足下最小化 |
🔗 与其他课程的联系
本课内容在整个 RISC-V 数字电路课程中的位置:
- 第01-05课的组合逻辑是所有硬件模块的基础
- 第06-10课的时序逻辑提供了寄存器和状态机的设计方法
- 第11-20课的CPU设计是 SoC 的核心处理单元
- 第21-23课的存储层次为数据访问提供速度和容量平衡
- 第24-28课的异常与中断使系统能响应外部事件
- 第29-33课的外设接口连接 CPU 与外部世界
- 第34-37课的系统集成将所有模块整合为完整 SoC
- 第38-40课的验证与毕业确保设计正确可靠
🏆 成就解锁:SoC集成
✅ Verilator 仿真验证通过
✅ 地址解码逻辑正确
✅ RAM/UART/GPIO访问正确
✅ 多外设总线互联正确
🤔 思考题1. 本课设计的模块如何与前面课程的内容结合?
2. 修改参数后,系统的行为会有什么变化?