上一篇博文中注释了通用寄存器reg.v模块,现在来介绍总线模块rib.v:
另外,在最后一个章节中会上传额外添加详细注释的工程代码,完全开源,如有需要可自行下载。
(这个RIB总线协议为工程原作者自定义的总线协议)
目录
0 RISC-V SoC注解系列文章目录
1. rib.v功能
2. rib总线接口注解
3. 总线工作机制
3.1. 总线仲裁机制
3.2. 主设备选择访问从设备
4. 对RIB总线的理解
参考:
零、RISC-V SoC软核笔记详解——前言
一、RISC-V SoC内核注解——取指
二、RISC-V SoC内核注解——译码
三、RISC-V SoC内核注解——执行
四、RISC-V SoC内核注解——除法(试商法)
五、RISC-V SoC内核注解——中断
六、RISC-V SoC内核注解——通用寄存器
七、RISC-V SoC内核注解——总线
八、RISC-V SoC外设注解——GPIO
九、RISC-V SoC外设注解——SPI接口
十、RISC-V SoC外设注解——timer定时器
十一、RISC-V SoC外设注解——UART模块(终篇)
设想一下一个没有总线的SoC,处理器核与外设之间的连接是怎样的。可能会如下图所示:
可见,处理器核core直接与每个外设进行交互。假设一个外设有一条地址总线和一条数据总线,总共有N个外设,那么处理器核就有N条地址总线和N条数据总线,而且每增加一个外设就要修改(改动还不小)core的代码。有了总线之后,处理器核只需要一条地址总线和一条数据总线,大大简化了处理器核与外设之间的连接。
目前已经有不少成熟、标准的总线,比如AMBA、wishbone、AXI等。设计CPU时大可以直接使用其中某一种,以节省开发时间。但是为了追求简单,tinyriscv并没有使用这些总线,而是自主设计了一种名为RIB(RISC-V Internal Bus)的总线。RIB总线支持多主多从连接,但是同一时刻只支持一主一从通信。RIB总线上的各个主设备之间采用固定优先级仲裁机制。
input wire clk,
input wire rst,
// master 0 interface
input wire[`MemAddrBus] m0_addr_i, // 主设备0读、写地址
input wire[`MemBus] m0_data_i, // 主设备0写数据
output reg[`MemBus] m0_data_o, // 主设备0读取到的数据
input wire m0_req_i, // 主设备0访问请求标志
input wire m0_we_i, // 主设备0写标志
// master 1 interface
input wire[`MemAddrBus] m1_addr_i, // 主设备1读、写地址
input wire[`MemBus] m1_data_i, // 主设备1写数据
output reg[`MemBus] m1_data_o, // 主设备1读取到的数据
input wire m1_req_i, // 主设备1访问请求标志
input wire m1_we_i, // 主设备1写标志
...
// slave 0 interface
output reg[`MemAddrBus] s0_addr_o, // 从设备0读、写地址
output reg[`MemBus] s0_data_o, // 从设备0写数据
input wire[`MemBus] s0_data_i, // 从设备0读取到的数据
output reg s0_we_o, // 从设备0写标志
// slave 1 interface
output reg[`MemAddrBus] s1_addr_o, // 从设备1读、写地址
output reg[`MemBus] s1_data_o, // 从设备1写数据
input wire[`MemBus] s1_data_i, // 从设备1读取到的数据
output reg s1_we_o, // 从设备1写标志
...
output reg hold_flag_o // 暂停流水线标志
主设备的优先级仲裁和主设备选择访问从设备各对应一个always(*)组合逻辑模块:
首先,由各主机向总线发送访问请求req:
// 主设备请求信号
//m0_req_i:来自执行模块ex.v的访问请求信号
//m1_req_i:pc_reg发来的读指令地址请求,一直为1,保持请求状态
//m2_req_i:jtag的操作请求
//m3_req_i:串口下载模块的请求信号
assign req = {m3_req_i, m2_req_i, m1_req_i, m0_req_i};
然后由总线的仲裁机制对主机的访问请求进行仲裁,如下代码所示:
// 仲裁逻辑
// 固定优先级仲裁机制
// 优先级由高到低:主设备3,主设备0,主设备2,主设备1
always @ (*) begin
if (req[3]) begin //m3_req_i:串口下载模块的请求信号
grant = grant3;
hold_flag_o = `HoldEnable; //只需要暂停pc寄存器
end else if (req[0]) begin //m0_req_i:来自执行模块ex.v的访问请求信号
grant = grant0;
hold_flag_o = `HoldEnable;
end else if (req[2]) begin //m2_req_i:jtag的操作请求
grant = grant2;
hold_flag_o = `HoldEnable;
end else begin //m1_req_i:pc_reg发来的读指令地址请求,一直为1,保持请求状态
grant = grant1;
hold_flag_o = `HoldDisable;
end
end
主设备的优先级仲裁always(*)组合逻辑模块:
通过if_else根据优先级的顺序选择主设备进行相应的访问操作,对于主设备的仲裁,主设备优先级顺序为:uart串口下载、ex.v执行模块、jtag模块、pc_reg取指模块。
对这个优先级顺序的理解:
(前三个“主设备uart、ex.v、jtag”访问总线,需要暂停流水线)
主设备选择访问从设备always(*)组合逻辑模块:
grant0: begin //主设备0
case (m0_addr_i[31:28])
slave_0: begin
s0_we_o = m0_we_i; //主设备0的写使能信号——>从设备
s0_addr_o = {{4'h0}, {m0_addr_i[27:0]}};//从设备0的地址
s0_data_o = m0_data_i; //主设备0数据写入——>从设备0
m0_data_o = s0_data_i; //从设备0数据写入——>主设备0
end
slave_1: begin
s1_we_o = m0_we_i;
s1_addr_o = {{4'h0}, {m0_addr_i[27:0]}};
s1_data_o = m0_data_i;
m0_data_o = s1_data_i;
end
...
以一个主从和RIB之间的接口为例: