我们已经编写了单周期处理器, 并了解了设备如何工作
但处理器如何与其他模块通信?
本次课内容
In computer architecture, a bus is a communication system that transfers data between
components inside a computer, or between computers. This expression covers all
related hardware components (wire, optical fiber, etc.) and software, including
communication protocols.
没错, 以下这些都属于广义的总线概念:
这次课我们学习狭义的总线
大家在单周期处理器里面就是这样做的
主动发起通信的模块叫master(主设备), 响应通信的模块叫slave(从设备)
其实背后藏着一套通信协议
IFU并非每周期都能取到指令
需要添加valid(有效)信号, 指示何时发送有效的指令, 通信协议如下
Q: 如何避免处理器执行了无效指令?
A: 处理器是个状态机!
如果IDU并非每周期都能译码指令
需要添加ready(就绪)信号, 通信协议如下
这就是异步总线
Decoupled
模板,
通过元编程轻松实现异步总线接口
Decoupled
模板自带valid和readymaster和slave需要根据握手信号的情况来实现约定的总线协议
# master的状态转移图
+-+ valid = 0
| v valid = 1
1. idle ----------------> 2. wait_ready <-+
^ | | | ready = 0
+--------------------------+ +----+
ready = 1
class IFU extends Module {
val io = IO(new Bundle { val out = Decoupled(new Message) })
val s_idle :: s_wait_ready :: Nil = Enum(2)
val state = RegInit(s_idle)
state := MuxLookup(state, s_idle)(List(
s_idle -> Mux(io.out.valid, s_wait_ready, s_idle),
s_wait_ready -> Mux(io.out.ready, s_idle, s_wait_ready)
))
io.out.valid := 有指令需要发送
// ...
}
+-----+ inst ---> +-----+ ... ---> +-----+ ... ---> +-----+
| IFU | valid ---> | IDU | valid ---> | EXU | valid ---> | WBU |
+-----+ <--- ready +-----+ <--- ready +-----+ <--- ready +-----+
一个观察: 不同微结构的处理器, 只是模块间的通信协议不同
+--------------+
+-------------> | Controller | <--------------+
| +--------------+ |
| ^ ^ |
v v v v
+-----+ inst +-----+ ... +-----+ ... +-----+
| IFU | ------> | IDU | ------> | EXU | ------> | WBU |
+-----+ +-----+ +-----+ +-----+
采用基于握手的分布式控制可以统一不同微结构的处理器设计!
class NPC extends Module {
val io = // ...
val ifu = Module(new IFU)
val idu = Module(new IDU)
val exu = Module(new EXU)
val wbu = Module(new WBU)
StageConnect(ifu.io.out, idu.io.in)
StageConnect(idu.io.out, exu.io.in)
StageConnect(exu.io.out, wbu.io.in)
// ...
}
object StageConnect {
def apply[T <: Data](left: DecoupledIO[T], right: DecoupledIO[T]) = {
val arch = "single"
// 为展示抽象的思想, 此处代码省略了若干细节
if (arch == "single") { right.bits := left.bits }
else if (arch == "multi") { right <> left }
else if (arch == "pipeline") { right <> RegEnable(left, left.fire) }
else if (arch == "ooo") { right <> Queue(left, 16) }
}
}
轻松对处理器微结构进行 “升级”
大部分同学的回答都说 “是”, 因为大部分同学很可能被教科书所约束
我们的实践经验(Chisel):
这体现了设计模式的优势
对初学者的启发: 大家能学习的比教科书中的内容多得多
系统总线 = 连接处理器和存储器以及设备之间的总线
读操作是最基本的需求
pmem_read()
没有读延迟,
实际上不存在这样的存储器器件这就是只读存储器(ROM, Read-Only Memory), 其通信协议如下
新需求: 如何支持写操作?
+-----+ raddr[log2(N)-1:0] ---> +-----+
| | <--- rdata[31:0] | |
| | waddr[log2(N)-1:0] ---> | |
| CPU | wdata[31:0] ---> | MEM |
| | wen ---> | |
| | wmask[3:0] ---> | |
+-----+ +-----+
需要添加新信号:
通信协议 - wen有效时, M[waddr]
更新为
若同时读写同一地址, 读出结果需要RTFM(有可能undefined)
+---+ +---+ +---+ +---+
| | | | | | | |
----+ +---+ +---+ +---+ +---+
/------\
waddr ------------ addr -----------------
\------/
+------+
wen | |
------------+ +-----------------
/------\
wmask ------------ 1111 -----------------
\------/
/----------------------\
raddr ---- addr ---------
\----------------------/
/------\/------\/------\
rdata ------------ old XXXXXX new -
\------/\------/\------/
SRAM和FPGA中的Block RAM都是类似上述特性
读延迟为1周期的前提: 能够使用与处理器制造相同的工艺进行生产
更多成本更低的存储器通常采用存储密度更大的工艺来制造(如DRAM)
新需求
这就需要握手信号!
+-----+ raddr[log2(N)-1:0] ---> +-----+
| | rvalid ---> | |
| | <--- rready | |
| | <--- rdata[31:0] | |
| CPU | waddr[log2(N)-1:0] ---> | MEM |
| | wdata[31:0] ---> | |
| | wen ---> | |
| | wmask[3:0] ---> | |
+-----+ +-----+
添加rvalid(也充当了ren的作用)和rready, 实现读请求raddr的握手
新问题
+-----+ araddr[log2(N)-1:0] ---> +-----+
| | arvalid ---> | |
| | <--- arready | |
| | <--- rdata[31:0] | |
| | <--- rvalid | |
| CPU | rready ---> | MEM |
| | waddr[log2(N)-1:0] ---> | |
| | wdata[31:0] ---> | |
| | wen ---> | |
| | wmask[3:0] ---> | |
+-----+ +-----+
读出的数据rdata也需要握手
a
在一次读数据过程中, master和slave都需要等待两次握手
+-----+ araddr[log2(N)-1:0] ---> +-----+
| | arvalid ---> | |
| | <--- arready | |
| | <--- rdata[31:0] | |
| | <--- rvalid | |
| CPU | rready ---> | MEM |
| | waddr[log2(N)-1:0] ---> | |
| | wdata[31:0] ---> | |
| | wmask[3:0] ---> | |
| | wvalid ---> | |
+-----+ <--- wready +-----+
同理, 写请求也需要握手
屏蔽通信双方的处理延迟
通信的一方无法得知另一方处于什么状态
有了握手信号, 双方均无需关心上述细节, 只要等待握手即可
读写请求可能会出错, 例如超过存储区间的边界
+-----+ araddr[log2(N)-1:0] ---> +-----+
| | arvalid ---> | |
| | <--- arready | |
| | <--- rdata[31:0] | |
| | <--- rresp[1:0] | |
| | <--- rvalid | |
| | rready ---> | |
| CPU | waddr[log2(N)-1:0] ---> | MEM |
| | wdata[31:0] ---> | |
| | wmask[3:0] ---> | |
| | wvalid ---> | |
| | <--- wready | |
| | <--- bresp[1:0] | |
| | <--- bvalid | |
+-----+ bready ---> +-----+
通过rresp和bresp(b表示backward)向master回复读写操作是否成功
araddr ---> araddr ---> araddr ---> -+
arvalid ---> arvalid ---> arvalid ---> AR
<--- arready <--- arready <--- arready -+
<--- rdata <--- rdata
<--- rresp <--- rresp <--- rdata -+
<--- rvalid <--- rvalid <--- rresp |
rready ---> 1 rready ---> 2 <--- rvalid R
waddr ---> ===> awaddr ---> ===> rready ---> -+
wdata ---> awvalid ---> *
wmask ---> <--- awready * awaddr ---> -+
wvalid ---> wdata ---> awvalid ---> AW
<--- wready wmask ---> <--- awready -+
<--- bresp wvalid --->
<--- bvalid <--- wready wdata ---> -+
bready ---> <--- bresp wstrb ---> |
<--- bvalid wvalid ---> W
bready ---> <--- wready -+
<--- bresp -+
<--- bvalid B
bready ---> -+
总线协议要求读数据在读地址握手后至少1周期后返回
因此需要将NPC升级成多周期处理器后, 才能正确地接入总线
如果想获得更高的主频, 还需要在多个模块之间添加寄存器暂存信号
死锁(deadlock): 系统卡死
master和slave都在等待对方先将握手信号置1
活锁(livelock): 局部看没卡死, 全局看没进展
master和slave都在试探性地握手, 但试探失败后又都取消握手
解决方法: 对握手信号的行为添加额外的约束, 具体RTFM
CPU中IFU需要从内存取指, LSU需要读写内存中的数据
+-----+ +---------+
| IFU | ----> | |
+-----+ | | +-----+
| Arbiter | ----> | MEM |
+-----+ | | +-----+
| LSU | ----> | |
+-----+ +---------+
在复杂系统中, 调度策略还需要考虑
多周期处理器还很简单, 随着系统的复杂度上升, 大家就知道厉害了
真实的计算机系统中并不仅仅只有存储器, 还有其他设备
pmem_read()
和pmem_write()
实现Xbar根据请求地址将请求转发给不同的下游(设备或另一个Xbar)
Arbiter和Xbar可合并成多进多出的Xbar(也称Interconnect, 总线桥等)
+-----+ +---------+ +------+ +-----+
| IFU | ----> | | | | ----> | UART| [0x1000_0000, 0x1000_0fff)
+-----+ | | | | +-----+
| Arbiter | ----> | Xbar |
+-----+ | | | | +-----+
| LSU | ----> | | | | ----> | SRAM| [0x8000_0000, 0x80ff_ffff)
+-----+ +---------+ +------+ +-----+
0x1000_0000
,
UART当作是读操作来处理, 返回一个数据
RISC-V提供两种物理内存检查机制
完整的AXI总线规范包含更多的信号和特性
大家一定要RTFM
总线是传统课本上一个相对抽象的概念
AXI的细节对初学者来说并不少
大家要认识到学习的规律