我们已经编写了单周期处理器, 并了解了设备如何工作
但处理器如何与其他模块通信?
本次课内容
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(从设备)
其实背后藏着一套通信协议
需要添加valid(有效)信号, 通信协议如下
Q: 如何避免处理器执行了无效指令?
A: 处理器是个状态机!
需要添加ready(就绪)信号, 通信协议如下
这就是异步总线
Decoupled
模板,
通过元编程轻松实现异步总线接口
Decoupled
模板自带valid和readymaster和slave需要根据握手信号的情况来实现约定的总线协议
# master
+-+ valid = 0
| v valid = 1
1. idle -------> 2. wait_ready <-+
^ ready = 1 | | | ready = 0
+-----------------+ +----+
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.fire, s_idle, s_wait_ready) // fire = valid & ready
))
// ...
}
+-----+ 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) }
}
}
读操作是最基本的需求
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都是类似上述特性
由于电气特性, 实际中很多存储器(例如DRAM)的读延迟大于CPU的1周期
新需求
这就需要握手信号!
+-----+ 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向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 -+
1. 将写地址和写数据分开
2. 分组, 并将wmask改名为wstrb
<--- bresp -+
<--- bvalid B
bready ---> -+
我们得到了手册上的AXI-Lite总线规范!
CPU中IFU需要从内存取指, LSU需要读写内存中的数据
+-----+ +---------+
| IFU | ----> | |
+-----+ | | +-----+
| Arbiter | ----> | MEM |
+-----+ | | +-----+
| LSU | ----> | |
+-----+ +---------+
Arbiter = 状态机
更多的信号和特性
大家一定要RTFM
总线是传统课本上一个相对抽象的概念
AXI的细节对初学者来说并不少
大家首先要端正学习心态
先从简单的特性开始实现:
pmem_read()
,
并延迟一周期返回读出的数据
pmem_read()
/pmem_write()
,
并延迟一周期返回读出的数据/写回复
严格来说, 这个CPU有点像多周期了