“一种解决总线同步问题的方法是使用一个保持寄存器和握手信号”,这也就是“先异步暂存,后同步写入”的方法
下面介绍握手方式进行异步时钟域的通信。
图2是一个基本的握手通信方式。所谓握手,即通信双方使用了专用控制信号进行状态指示。这个控制信号既有发送域给接收域的,也有接收域给发送域的,有别于的单向控制信号检测方式。
图2 握手通信原理
使用握手协议方式处理跨时钟域数据传输,只需要对双方的握手信号(req和ack)分别使用脉冲检测方法进行同步。在具体 实现中,假设req、ack、data总线在初始化时都处于无效状态,发送域先把数据放入总线,随后发送有效的req信号给接收域。接收域在检测到有效的req信号后 锁存数据总线,然后回送一个有效的ack信号表示读取完成应答。发送域在检测到有效ack信号后撤销当前的req信号,接收域在检测到req撤销后也相应撤销ack信号,此时完成一次正常 握手通信。此后,发送域可以继续开始下一次握手通信,如此循环。该方式能够使接收到的数据稳定可靠,有效的避免了亚稳态的出现,但控制信号握手检测会消耗通信双方较多的时间。以上所述的通信流程如图3所示。
图3 握手通信流程
我分别编写了发送时钟域和接收时钟域的代码进行测试,用到两组MEM,以便于观察实验结果,进一步加深对基本握手方式解决跨时钟域问题的认识。
发送端代码:
//接收域应答信号ack采用两级寄存器同步,便于时序收敛
always @ (negedge rst_n or posedge t_clk)
begin
if(rst_n == 1'b0)
begin
ack_reg1 <= 1'b0;
ack_reg2 <= 1'b0;
end
else
begin
ack_reg1 <= ack;
ack_reg2 <= ack_reg1;
end
end
//数据处理状态机
always @ (negedge rst_n or posedge t_clk)
begin
if(rst_n == 1'b0)
begin
data_buf <= 'b0;
tr_state <= TR_IDLE;
TR_MEM_Addr <= 0;
end
else
case(tr_state)
TR_IDLE : //初始化状态
begin
req <= 1'b0;
TR_MEM_Addr <= 0;
tr_state <= SND_DATA_REQ;
end
SND_DATA_REQ: //送数据到总线上和发送请求信号
begin
data_buf <= TR_MEM[TR_MEM_Addr];
req <= 1'b1;//发送请求信号,请求信号为高,表示请求接收
TR_MEM_Addr <= TR_MEM_Addr + 1;
tr_state <= CHK_ACK_ACTIVE;
end
CHK_ACK_ACTIVE ://检测应答信号为高,释放请求信号
begin
if(ack_reg2 == 1'b1) //便于时序收敛
begin
req <= 1'b0; //释放请求信号
tr_state <= CHK_COMM_END;
end
else
tr_state <= CHK_ACK_ACTIVE;
end
CHK_COMM_END : //检测握手通信结束,如果应答信号被释放,一次握手结束
begin
if(ack_reg2 == 1'b0)
tr_state <= SND_DATA_REQ;
else
tr_state <= CHK_COMM_END;
end
default : tr_state <= TR_IDLE;
endcase
end
assign dout = data_buf;
接收端代码:
//发送域请求信号req采用两级寄存器同步,便于时序收敛
always @ (negedge rst_n or posedge r_clk)
begin
if(rst_n == 1'b0)
begin
req_reg1 <= 1'b0;
req_reg2 <= 1'b0;
end
else
begin
req_reg1 <= req;
req_reg2 <= req_reg1;
end
end
//数据处理状态机
always @ (negedge rst_n or posedge r_clk)
begin
if(rst_n == 1'b0)
begin
re_state <= RE_IDLE;
ack <= 1'b0;
RE_MEM_Addr <= 0;
end
else
case(re_state)
RE_IDLE : //初始化状态
begin
RE_MEM_Addr <= 0;
re_state <= CHK_REQ_ACTIVE;
end
CHK_REQ_ACTIVE : //检测发送端的数据发送请求信号
begin
if(req_reg2 == 1'b1) //如果有请求信号,接收数据
begin
RE_MEM[RE_MEM_Addr] <= din;//接收数据存放到MEM
ack <= 1'b1;//检测到请求信号,发送接收端应答信号
RE_MEM_Addr <= RE_MEM_Addr + 1; //接收数据存储器地址累加
re_state <= CHK_REQ_RELEASE;
end
else
re_state <= CHK_REQ_ACTIVE;
end
CHK_REQ_RELEASE : //检测请求信号释放,释放应答信号
begin
if(req_reg2 == 1'b0)
begin
ack <= 1'b0; //释放应答信号,一次握手通信结束
re_state <= CHK_REQ_ACTIVE;
end
else
re_state <= CHK_REQ_RELEASE;
end
default : re_state <= RE_IDLE;
endcase
end
具体方案 : 假设req,ack,data在初始化时都处于无效状态, 发送域先把数据放入总线,随后发送有效的req信号给接收域. 接收域在检测到有效的req信号后锁存数据总线,然后回送一个有效的ack信号表示读取完成应答. 发送域在检测到有效ack信号后撤销当前的req信号, 接收域在检测到req撤销后也相应撤销ack信号, 此时完成一次正常握手通信. 此后,发送域可以继续开始下一次握手通信.
验证 : 经过验证此模块可以实现数据从快时钟域发送至慢时钟域(发送端时钟:120M,接收端时钟:1M), 也可以实现数据从慢时钟域发送至快时钟域(发送端时钟:1M,接收端时钟120M),两个MEM中的数据完全一致。
用户1857338 2015-12-25 17:11
用户1831470 2015-3-12 12:01
用户1404666 2012-11-17 21:19