题解 | #使用握手信号实现跨时钟域数据传输#

使用握手信号实现跨时钟域数据传输

http://www.nowcoder.com/practice/2bf1b28a4e634d1ba447d3495134baac

解题思路:

       在两个不同时钟域之间进行数据传输,根据两个时钟域的频率快慢,可以分为多种情况,分别由对应的解决方法。而最通用的方法是使用握手信号,所谓握手信号即加入一些指示信号,在两个模块间确认数据已经被接受之后再进行下一个数据的传输。一般来说,发送端随数据发出一个数据有效信号,或者称为数据请求接受信号data_req,接收端在data_req有效时,采集数据data,进行缓存或者其他处理,所以要保证在data_req有效期间,也就是还没收到data_ack确认信号之前,传输的数据data不能发生变化。然后接收端发送一个数据确认信号data_ack,告知发送端,数据已经接受,可以开始下一个数据的传输,发送端在接收到data_ack之后,撤销data_req,并可以改变数据,为下一步传输做准备。

解题过程:

       首先是数据发送端,发送数据的标志是data_req拉高,在data_req拉高期间,data需要保持不变,一直到接收端完成数据的接收,即接收端发送data_ack确认信号。所以取data_ack的上升沿信号作为data_req撤销和data_out改变的指示信号。

       always @ (posedge clk_a or negedge rst_n)

              if (!rst_n)

              begin

                     data_ack_reg_1 <= 0;

                     data_ack_reg_2 <= 0;

              end

              else

              begin

                     data_ack_reg_1 <= data_ack;

                     data_ack_reg_2 <= data_ack_reg_1;

              end

             

       always @ (posedge clk_a or negedge rst_n)

              if (!rst_n)

              begin

                     data <= 0;

              end

              else if(data_ack_reg_1 && !data_ack_reg_2)

              begin    

                     data <= data +1;

              end

              else begin

                     data <= data;

              end

同时在data_ack有效之后,开始计数五个时钟,之后发送新的数据,也就是再一次拉高data_req.

       always @ (posedge clk_a or negedge rst_n)

              if (!rst_n)

                     cnt <= 0;

              else if (data_ack_reg_1 && !data_ack_reg_2)   

                     cnt <= 0;

              else if (data_req)

                     cnt <= cnt;

              else

                     cnt <= cnt+1;

                    

       always @ (posedge clk_a or negedge rst_n)

              if (!rst_n)

                     data_req <= 0;

              else if (cnt == 3'd4)    

                     data_req <= 1'b1;

              else if (data_ack_reg_1 && !data_ack_reg_2)

                     data_req <= 1'b0;

              else

                     data_req <= data_req;

       接收端的逻辑较为简单,首先是探测data_req的电平,如果data_req为高,表示有数据正在传输,则保存该时刻的数据,然后拉高data_ack告知发送端数据已经接收,直到发送端撤销data_req

              reg data_req_reg_1;

       reg data_req_reg_2;

       reg [2:0]data_in_reg;

       always @ (posedge clk_b or negedge rst_n)

              if (!rst_n)

              begin

                     data_req_reg_1 <= 0;

                     data_req_reg_2 <= 0;

              end

              else

              begin

                     data_req_reg_1 <= data_req;

                     data_req_reg_2 <= data_req_reg_1;

              end

       always @ (posedge clk_b or negedge rst_n)

              if (!rst_n)

                     data_ack <= 0;

              else if (data_req_reg_1)

                     data_ack <= 1;

              else  data_ack <=0 ;

      

       always @ (posedge clk_b or negedge rst_n)

              if (!rst_n)

                     data_in_reg <= 0;

              else if (data_req_reg_1 && !data_req_reg_2)

                     data_in_reg <= data;

              else  data_in_reg <= data_in_reg ;   

仿真结果:

在仿真平台中,将两个模块分别例化并连接。得到如下结果:

如图所示,每次发送端的data_req拉高,接收端采集到data_req的上升沿后,将输出数据data同步到接收端,同时接收端发送data_ack信号告知发送端,数据已接受。在数据被成功接受之后,发送端才改变数值,同时拉低data_req。保证了数据传输的有效性。

注意到每次数据传输完成之后五个时钟,才再一次拉高data_req,开始下一个数据的传输,符合题目要求。

参考答案
`timescale 1ns/1ns

module data_driver(
	input clk_a,
	input rst_n,
	input data_ack,
	output reg [3:0]data,
	output reg data_req
	);
	reg data_ack_reg_1;
	reg data_ack_reg_2;
	reg [9:0] cnt;
	always @ (posedge clk_a or negedge rst_n)
		if (!rst_n) 
		begin
			data_ack_reg_1 <= 0;
			data_ack_reg_2 <= 0;
		end
		else
		begin
			data_ack_reg_1 <= data_ack;
			data_ack_reg_2 <= data_ack_reg_1;
		end
		
	always @ (posedge clk_a or negedge rst_n)
		if (!rst_n) 
		begin
			data <= 0;
		end
		else if(data_ack_reg_1 && !data_ack_reg_2)
		begin	
			data <= data+1;
		end
		else begin
			data <= data;
		end
//同时在data_ack有效之后,开始计数五个时钟,之后发送新的数据,也就是再一次拉高data_req.
	always @ (posedge clk_a or negedge rst_n)
		if (!rst_n) 
			cnt <= 0;
		else if (data_ack_reg_1 && !data_ack_reg_2)	
			cnt <= 0;
		else if (data_req)
			cnt <= cnt;
		else 
			cnt <= cnt+1;
			
	always @ (posedge clk_a or negedge rst_n)
		if (!rst_n) 
			data_req <= 0;
		else if (cnt == 3'd4)	
			data_req <= 1'b1;
		else if (data_ack_reg_1 && !data_ack_reg_2)
			data_req <= 1'b0;
		else 
			data_req <= data_req;

endmodule

module data_receiver(
	input clk_b,
	input rst_n,
	output reg data_ack,
	input [3:0]data,
	input data_req
	);
	
	reg [3:0]data_in_reg;
	reg data_req_reg_1;
	reg data_req_reg_2;
	always @ (posedge clk_b or negedge rst_n)
		if (!rst_n) 
		begin
			data_req_reg_1 <= 0;
			data_req_reg_2 <= 0;
		end
		else
		begin
			data_req_reg_1 <= data_req;
			data_req_reg_2 <= data_req_reg_1;
		end

	always @ (posedge clk_b or negedge rst_n)
		if (!rst_n)
			data_ack <= 0;
		else if (data_req_reg_1)
			data_ack <= 1;
		else  data_ack <=0 ;
	
	always @ (posedge clk_b or negedge rst_n)
		if (!rst_n)
			data_in_reg <= 0;
		else if (data_req_reg_1 && !data_req_reg_2)
			data_in_reg <= data;
		else  data_in_reg <= data_in_reg ;	

endmodule			


如果关于此次题单有问题或者反馈意见,欢迎加入牛客用户反馈群沟通~

全部评论
怎么知道data_ack_reg_1信号就稳定了呢?跨钟打一拍信号就能直接用?
7 回复 分享
发布于 2022-09-11 15:12 上海
有问题的,取data_ack的上升沿信号作为data_req撤销和data_out改变的指示信号,如果clkb时钟远大于clka,那么a刚发完,b就能处理结束,会导致data_ack在a看起来一直为高。
3 回复 分享
发布于 2023-07-18 23:37 广东
2级DFF同步的第一级还能用来做组合逻辑判断的吗。。。起码也要打3拍,用2、3拍做上升沿的判断
2 回复 分享
发布于 2023-09-30 16:11 陕西
他第一个数据什么时候开始发呀
点赞 回复 分享
发布于 2025-06-26 20:54 陕西
这参考答案,把频率相差十倍,就失败了
点赞 回复 分享
发布于 2025-06-02 11:43 陕西
虽然通过了,但是你这么写data不就加到15了吗,题目要求data是0-7循环
点赞 回复 分享
发布于 2023-12-16 16:14 湖北
代码通过不了测试,怎么精华了?
点赞 回复 分享
发布于 2023-07-27 10:27 江苏
我想知道题目中没说的那些题目要求怎么知道的?
点赞 回复 分享
发布于 2023-05-06 15:26 安徽
请问这是全握手?还是半握手?
点赞 回复 分享
发布于 2023-02-21 08:42 山东
这样是否达到了 0-7循环发送的效果?
点赞 回复 分享
发布于 2023-02-15 16:36 广东

相关推荐

评论
28
5
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务