一种解决总线同步问题的方法是使用一个保持寄存器和握手信号”,这也就是“先异步暂存,后同步写入”的方法
下面介绍握手方式进行异步时钟域的通信。
所谓握手,即通信双方使用了专用控制信号进行状态指示。这个控制信号既有发送域给接收域的,也有接收域给发送域的,有别于的单向控制信号检测方式。
使用握手协议方式处理跨时钟域数据传输,只需要对双方的握手信号(req和ack)分别使用脉冲检测方法进行同步。在具体 实现中,假设req、ack、data总线在初始化时都处于无效状态,发送域先把数据放入总线,随后发送有效的req信号给接收域。接收域在检测到有效的req信号后 锁存数据总线,然后回送一个有效的ack信号表示读取完成应答。发送域在检测到有效ack信号后撤销当前的req信号,接收域在检测到req撤销后也相应撤销ack信号,此时完成一次正常 握手通信。此后,发送域可以继续开始下一次握手通信,如此循环。该方式能够使接收到的数据稳定可靠,有效的避免了亚稳态的出现,但控制信号握手检测会消耗通信双方较多的时间。
我分别编写了发送时钟域和接收时钟域的代码进行测试,用到两组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; endend//数据处理状态机
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; endcaseendassign 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; endend//数据处理状态机
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; endcaseend具体方案 : 假设req,ack,data在初始化时都处于无效状态, 发送域先把数据放入总线,随后发送有效的req信号给接收域. 接收域在检测到有效的req信号后锁存数据总线,然后回送一个有效的ack信号表示读取完成应答. 发送域在检测到有效ack信号后撤销当前的req信号, 接收域在检测到req撤销后也相应撤销ack信号, 此时完成一次正常握手通信. 此后,发送域可以继续开始下一次握手通信.
验证 : 经过验证此模块可以实现数据从快时钟域发送至慢时钟域(发送端时钟:120M,接收端时钟:1M), 也可以实现数据从慢时钟域发送至快时钟域(发送端时钟:1M,接收端时钟120M),两个MEM中的数据完全一致。
1.本文部分素材来源网络,版权归原作者所有,如涉及作品版权问题,请与我联系删除。
2.未经原作者允许不得转载本文内容,否则将视为侵权;
3.转载或者引用本文内容请注明来源及原作者;
4.对于不遵守此声明或者其他违法使用本文内容者,本人依法保留追究权等。
下面是我的个人微信公众号,关注【一个早起的程序员】精彩系列文章每天不断。