目录
两个模块之间要完成数据的安全传输,必须要一方确定能够发送有效的数据、另一方能够接受有效的数据,但是当其中有一个模块处于忙等无法处理新数据的状态时,另一方的时序就要做出调整,这个过程就是握手。
两个模块的握手方式有许多,例如AMBA、FIFO、等等,本文旨在对这些握手时序进行归纳总结,就暂时叫它标准握手时序吧
UVM:事务级建模(Transaction Level Modeling, TLM)1.0 通信标准
高级高性能总线(Advanced High-performance Bus, AHB)
高级外围总线(Advanced Peripheral Bus, APB)
AXI总线概述
【FPGA基础篇】XilinxFIFO详细解析
1. 基本概念
实际上,模块之间通信可以看作是事务的传输,而事务的传输是基于发起和响应请求的。
下面介绍事务传输的基本元素
1.1. 端口信号
首先是端口信号,握手涉及到的端口信号有很多,但基本走不开下面这几个
● addr:表示访问的地址
● data:表示访问的数据
● valid:表示data是否为有效,与data时序对齐
● rd_en:表示数据流是否开启 读
注意一次写事务传输中,valid和wr_en一般只会有其中一个
● ready:表示是否 可被访问
● ack:表示是否 访问完成
其他握手中的信号,本质其实还是上面这几个信号,所以基于以上几个信号给出握手的一些特点。
例如FIFO中的full和empty,本质上其实就是ready嘛
1.2. 控制流:Master 与 Slave
针对模块而言,发起事务请求的是Master,响应事务请求的是Slave
那么在事务传输中,怎么看谁是Master、谁是Slave呢?
● 控制流方向:rd_en、addr的传输方向,ready、ack传输方向的反向
对,就是看rd_en和addr的方向,这些信号表示谁读写、读写的地址在哪,所以这些信号的发起者就是Master
而ready与ack的是反馈信号,所以这些信号的发起者就是Slave。
1.3. 数据流:读 与 写
表示Master发起的请求数据流方向,如果数据是从Master到Slave就是写,否则就是读,所以不难的处下面的结论
● 数据流方向: data、valid的传输方向
实际上任何数据交换都可以转化成Master向Slave的写过程,或者是Master向Slave的读过程。例如UART看上去是UART_TX发射、UART_RX接收,但本质上都是Master(UART_TX)向Slave(UART_RX)写过程
那么读写握手的时序是如何的呢?下面介绍
2. 写握手
即Master向地址addr中写入data的握手过程,如图所示
Master和Slave的时序图都是这个
2.1. Slave (被)写
咱先分析Slave,因为Slave是接受控制的一方,会比较简单。
Slave的工作是在状态准备好时,将数据写入自己,准备不好时就忽略data。哎,是不是闻到了状态机的味道了?而划分Slave是否准备好的信号恰好是ready
● ready为高:表示Slave可以接受数据写入,并一直检测valid。
一旦valid为高则表明data可用,将data缓存成data_r,将ready拉低。
并且只有满足valid && ready
时,ack为高,表示成功写入
● ready为低:表示Slave暂时不可接受数据写入,Slave将一直基于变量data_r进行操作,操作完成时拉高ready。
时序图注意addr、data和valid三者对齐
UART_TX为例
以状态机的角度思考下面代码
//ready状态转换
always@(posedge clk) begin
if(!rstn)
ready <= 1'b0;
else if(cnt == 0)
ready <= 1'b1;
else if(valid && ready)
ready <= 1'b0;
end
always@(posedge clk) begin
if(!rstn)
data_r <= 18'b1;
else if(ready) begin //ready为高时,看valid判断是否缓存
if(valid)
data_r <= {1'b1,data,1'b0};
else
data_r <= data_r;
end
else if(&baud_cnt) //ready为低时,看baud_cnt判断是否移位
data_r <= (data_r >> 1);
else
data_r <= data_r;
end
always@(posedge clk) begin
if(!rstn)
ack <= 1'b0;
else if(ready && valid) //ready为高时,看valid判断是否缓存
ack <= 1'b1;
end
assign tx = data_r[0];
使用状态机时注意是否时序是对齐的,例如
if(cur_state == S1) valid <= 1'b1;
这句话中,valid置位和cur_state变化差一拍,非阻塞赋值导致的。
2.2. Master 写
然后看Master这边,Master也是当数据准备好时等待Slave接受,未准备好就去计算。
所以Master也是基于状态机,而这回指示状态的是valid,表示是否等待
● valid有效时:表示data有效,且data信号保持展宽,并一直检测ready。
当ready为高时,表明data被对方接受到,valid拉低。
注意这个valid只拉高一拍,防止Slave那边多次写入
● valid无效时:计算新的data,当有新的data产生时,拉高valid。
时序图与Slave那边类似
UART_RX为例
always@(posedge clk) begin
if(!rstn)
valid <= 1'b0;
else if(cnt == 10) begin
valid <= 1'b1;
else if(valid && ready)
valid <= 1'b0;
end
always@(posedge clk) begin
if(!rstn)
data <= 10'b0
else if(cnt == 10)
data <= data;
else
data <= {rx,data[9:1]};
end
3. 读握手
如下图,注意多了个信号rd_en
时序图
3.1. Slave (被)读
Slave的状态还是取决于ready,准备好时就看rd_en看要不要读出数据给Master
● ready为高:表示Slave可以接受数据被读出,并一直检测rd_en
一旦rd_en为高则表明Master要求Slave读出,则根据地址addr读出数据data,且valid只拉高一拍。
并且只有满足ready && rd_en
时,ack为高,表示成功读出
● ready为低:表示Slave暂时不可接受数据读出,Slave将干自己的事,完成后拉高ready。
注意时序上是ready拉低的同一拍驱动的data并拉高valid
UART_RX为例
例码如下:
always@(posedge clk) begin
if(!rstn)
ready <= 1'b0;
else if(cnt == 0)
ready <= 1'b1;
else if(rd_en && ready)
ready <= 1'b0;
end
always@(posedge clk) begin
if(!rstn) begin
data <= 16'b0;
valid <= 1'b0;
end
else if(ready && rd_en) begin
data <= mem[addr];
valid <= 1'b1;
end
else begin
data <= data;
valid <= 1'b0;
end
end
always@(posedge clk) begin
if(!rstn)
ack <= 1'b0;
else if(ready && rd_en)
ack <= 1'b1;
else
ack <= 1'b0;
end
3.2. Master 读
读的话,Master将rd_en拉高就表示需要Slave给它返数据了,此时要等待Slave给它的数据,有了数据就拉低rd_en。
即
● rd_en有效时:表示需要数据,与addr时序对齐,并一直检测ready。
当ready为高时,表明此处读请求被对方收到,rd_en拉低。
● rd_en无效时:检测valid,当valid有效时获取读到的数据,然后该干嘛干嘛。
注意rd_en拉低的时刻时检测到ready为高的时刻,而不是检测到valid为高的时刻。
确实valid为高时,Master才确定收到了数据。但是这样rd_en会多拉高一拍,如果多出的那一拍Slave那边ready还是高,就会多读了一次!
UART_TX为例
例码如下:
always@(posedge clk) begin
if(!rstn)
rd_en <= 1'b0;
else if(cnt == 0)
rd_en <= 1'b1;
else if(rd_en && ready)
rd_en <= 1'b0;
end
always@(posedge clk) begin
if(!rstn)
data_r <= 16'b0;
else if(valid)
data_r <= data;
end
4. 案例分析
上述的这些握手时序,可以套用到很多常见的握手情景,例如模块间交互、跨时钟域传输等可以直接套用,而与IP的交互不过就是少了哪个信号呀、信号名变了呀这种,但依旧在标准握手时序这个框架下,咱们来看看。
如下表表示哪些信号可以看作是等价的。
标准握手时序 | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
写握手 | 读握手 | ||||||||||||
addr | data | valid | ready | ack | addr | data | valid | rd_en | ready | ack | |||
异步FIFO | 写端 | wdata | 等价 | ||||||||||
wr_en | 等价 | ||||||||||||
full | 取非关系 | ||||||||||||
读端 | rdata | 等价 | |||||||||||
valid | 等价 | ||||||||||||
rd_en | 等价 | ||||||||||||
empty | 取非关系 | ||||||||||||
Single-port RAM | 单端 | addra | 写时等价 | 读时等价 | |||||||||
dina | 写时等价 | ||||||||||||
wea | 写时等价 | 读时取非 | |||||||||||
dout | 等价 | ||||||||||||
ena | |||||||||||||
Simple Dual-port RAM | 写端 | dina | 等价 | ||||||||||
addra | 等价 | ||||||||||||
wea | 等价 | ||||||||||||
ena | |||||||||||||
读端 | addrb | 等价 | |||||||||||
enb | |||||||||||||
doutb | 等价 | ||||||||||||
True Dual-port RAM | 任意一端 | dina | 写时等价 | ||||||||||
addra | 写时等价 | 读时等价 | |||||||||||
wea | 写时等价 | 读时取非 | |||||||||||
ena | |||||||||||||
douta | 等价 |
需要说明的是RAM的wea和APB的pwrite类似,高表示写低表示读。
4.1. APB
看一下APB协议跟标准握手时序有什么关系。
APB 写时序非常类似于RAM的写握手,如下图,其中PADDR就是写握手中的addr、PWRITE就是写握手中的valid、PWDATA就是写握手中的data、PREADY就是写握手中的ready信号。
那其他信号是什么呢?PENABLE,使能信号,与RAM中的ena是一样的,可以看作是Slave的开关。PSEL,片选信号,类似于SPI中的csn。PENABLE和PSEL可看做是在标准写握手基础上的扩展。
APB写最快也是两个周期,这是因为APB要求第一拍必须将PENABLE拉低,所以在PENABLE和PSEL同时拉高时,写入PWDATA也最快一拍,这个与标准写握手是一样的。
再看读时序。如下图,其中PADDR就是读握手中的addr、PWRITE就是读握手中的rd_en、PRDATA就是读握手中的data、PREADY就是写握手中的valid信号。
注意APB读中的PREADY与PRDATA是时序对齐的,与标准读握手中的ready不一样。
在PENABLE和PSEL同时拉高时,读出PRDATA最快是两拍,这个与标准读握手是一样的