标准握手时序

两个模块之间要完成数据的安全传输,必须要一方确定能够发送有效的数据、另一方能够接受有效的数据,但是当其中有一个模块处于忙等无法处理新数据的状态时,另一方的时序就要做出调整,这个过程就是握手。

两个模块的握手方式有许多,例如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的交互不过就是少了哪个信号呀、信号名变了呀这种,但依旧在标准握手时序这个框架下,咱们来看看。

如下表表示哪些信号可以看作是等价的。

标准握手时序
写握手 读握手
addrdatavalidreadyackaddrdatavalidrd_enreadyack
异步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最快是两拍,这个与标准读握手是一样的

在这里插入图片描述

  • 1
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Starry丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值