基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理

这篇具有很好参考价值的文章主要介绍了基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理

1. 为什么要解决跨时钟处理问题

慢时钟到快时钟一般都不需要处理,关键需要解决从快时钟到慢时钟的问题,因为可能会漏信号或者失真,比如:
基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理

2.解决办法

第一种办法是开环解决方案,也就是人为设置目标信号脉宽大于1.5倍的周期。但是容易和设计要求冲突
所以第二个大方法是闭环解决方案,也就是从改善同步方式:最基础的是二级、三级寄存器。但是还是会在极端情况下出现失真,并且需要满足:
【1】级联的寄存器必须使用同一个采样时钟;
【2】发送端时钟域寄存器输出和接收端异步时钟域级联寄存器输入之间不能有任何其他组合逻辑;
【3】同步器中级联的寄存器中除了最后一个寄存器外所有的寄存器只能有一个扇出,即其只能驱动下一级寄存器的输入
于是就有了将控制信号当作使能信号进行传递(单比特的话,这个使能信号可以是信号本身):
基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理

我的理解就是弄一个使能信号当作两个时钟域的信使,来告诉对方是不是开始了读/写数据了。
为了进一步进行多比特信号的跨时钟处理,干脆就拿地址作为同步信号(下图中的wptr和rptr),用RAM作为数据的缓存区,用不同时钟域给的空/满作为数据输出/输入的标识。传输的过程需要格雷码和二进制的转换。
这一方法被称为FIFO结果处理多比特跨时钟域信号。
基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理

3.实现代码

`timescale 1ns / 1ns
  module ASFIFO#
	(
		parameter WIDTH = 16,		// FIFO数据总线位宽
		parameter PTR   = 4			// FIFO存储深度(bit数,深度只能是2^n个)
	)
	(
		// write interface
		input					wrclk	,		// 写时钟
		input					wr_rst_n,		// 写指针复位
		input	[WIDTH-1:0]		wr_data	,		// 写数据总线
		input					wr_en	,		// 写使能
		output  reg				wr_full	,		// 写满标志
 
		//read interface
		input					rdclk	,		// 读时钟
		input					rd_rst_n,		// 读指针复位
		input					rd_en	,		// 读使能
		output	[WIDTH-1:0]		rd_data	,		// 读数据输出
		output	reg				rd_empty		// 读空标志
    );
    
    
 
 
	// 写时钟域信号定义
	reg [PTR:0] wr_bin		;					// 二进制写地址
	reg [PTR:0] wr_gray		;					// 格雷码写地址
	reg [PTR:0] rd_gray_ff1 ;					// 格雷码读地址同步寄存器1
	reg [PTR:0] rd_gray_ff2 ;					// 格雷码读地址同步寄存器2
	reg [PTR:0] rd_bin_wr	;					// 同步到写时钟域的二进制读地址
 
 
	// 读时钟域信号定义
	reg [PTR:0] rd_bin		;					// 二进制读地址
	reg [PTR:0] rd_gray		;					// 格雷码读地址
	reg [PTR:0] wr_gray_ff1 ;					// 格雷码写地址同步寄存器1
	reg [PTR:0] wr_gray_ff2 ;					// 格雷码写地址同步寄存器2
	reg [PTR:0] wr_bin_rd	;					// 同步到读时钟域的二进制写地址
 
 
	// 解格雷码电路循环变量
	integer i ;
	integer j ;
 
 
	// DPRAM控制信号
	wire  				dpram_wr_en		 ;		// DPRAM写使能
	wire [PTR-1:0]		dpram_wr_addr    ;		// DPRAM写地址
	wire [WIDTH-1:0] 	dpram_wr_data	 ;		// DPRAM写数据
	wire  				dpram_rd_en		 ;		// DPRAM读使能
	wire [PTR-1:0]		dpram_rd_addr    ;		// DPRAM读地址
	wire [WIDTH-1:0] 	dpram_rd_data	 ;		// DPRAM读数据
 
 
 
	// ******************************** 写时钟域 ******************************** //
	// 二进制写地址递增
	always @(posedge wrclk or posedge wr_rst_n) begin
		if (!wr_rst_n) begin
			wr_bin <= 'b0;
		end
		else if ( wr_en == 1'b1 && wr_full == 1'b0 ) begin
			wr_bin <= wr_bin + 1'b1;
		end
		else begin
			wr_bin <= wr_bin;
		end
	end
 
 
	// 写地址:二进制转格雷码
	always @(posedge wrclk or posedge wr_rst_n) begin
		if (!wr_rst_n) begin
			wr_gray <= 'b0;
		end
		else begin
			wr_gray <= { wr_bin[PTR], wr_bin[PTR:1] ^ wr_bin[PTR-1:0] };
		end
	end
	// 格雷码读地址同步至写时钟域
	always @(posedge wrclk or posedge wr_rst_n) begin
		if(!wr_rst_n) begin
			rd_gray_ff1 <= 'b0;
			rd_gray_ff2 <= 'b0;
		end
		else begin
			rd_gray_ff1 <= rd_gray;
			rd_gray_ff2 <= rd_gray_ff1;
		end
	end
	// 同步后的读地址解格雷
	always @(*) begin
		rd_bin_wr[PTR] = rd_gray_ff2[PTR];
		for ( i=PTR-1; i>=0; i=i-1 )
			rd_bin_wr[i] = rd_bin_wr[i+1] ^ rd_gray_ff2[i];
	end
	// 写时钟域产生写满标志
	always @(*) begin
		if( (wr_bin[PTR] != rd_bin_wr[PTR]) && (wr_bin[PTR-1:0] == rd_bin_wr[PTR-1:0]) ) begin
			wr_full = 1'b1;
		end
		else begin
			wr_full = 1'b0;
		end
	end
	// ******************************** 读时钟域 ******************************** //
	always @(posedge rdclk or posedge rd_rst_n) begin
		if (!rd_rst_n) begin
			rd_bin <= 'b0;
		end
		else if ( rd_en == 1'b1 && rd_empty == 1'b0 ) begin
			rd_bin <= rd_bin + 1'b1;
		end
		else begin
			rd_bin <= rd_bin;
		end
	end
	// 读地址:二进制转格雷码
	always @(posedge rdclk or posedge rd_rst_n) begin
		if (!rd_rst_n) begin
			rd_gray <= 'b0;
		end
		else begin
			rd_gray <= { rd_bin[PTR], rd_bin[PTR:1] ^ rd_bin[PTR-1:0] };
		end
	end
 
 
	// 格雷码写地址同步至读时钟域
	always @(posedge rdclk or posedge rd_rst_n) begin
		if(!rd_rst_n) begin
			wr_gray_ff1 <= 'b0;
			wr_gray_ff2 <= 'b0;
		end
		else begin
			wr_gray_ff1 <= wr_gray;
			wr_gray_ff2 <= wr_gray_ff1;
		end
	end
 
 
	// 同步后的写地址解格雷
	always @(*) begin
		wr_bin_rd[PTR] = wr_gray_ff2[PTR];
		for ( j=PTR-1; j>=0; j=j-1 )
			wr_bin_rd[j] = wr_bin_rd[j+1] ^ wr_gray_ff2[j];
	end
 
 
	// 读时钟域产生读空标志
	always @(*) begin
		if( wr_bin_rd == rd_bin )
			rd_empty = 1'b1;
		else
			rd_empty = 1'b0;
	end
 
 
	// RTL双口RAM例化
	DPRAM
		# ( .WIDTH(16), .DEPTH(16), .ADDR(4) )
		U_DPRAM
		(
			.wr_clk		(wrclk		 	),
			.rd_clk		(rdclk			),
			.rd_rst_n   (rd_rst_n		),
			.wr_rst_n   (wr_rst_n       ),
			.wr_en 		(dpram_wr_en	),
			.rd_en 		(dpram_rd_en	),
			.wr_data 	(dpram_wr_data	),
			.rd_data 	(dpram_rd_data	),  //唯一输出output
			.wr_addr 	(dpram_wr_addr	),
			.rd_addr 	(dpram_rd_addr	)
		);
 
 
	// 产生DPRAM读写控制信号
	assign dpram_wr_en   = ( wr_en == 1'b1 && wr_full == 1'b0 )? 1'b1 : 1'b0;
	assign dpram_wr_data = wr_data;
	assign dpram_wr_addr = wr_bin[PTR-1:0];
 
	assign dpram_rd_en   = ( rd_en == 1'b1 && rd_empty == 1'b0 )? 1'b1 : 1'b0;
	assign rd_data = dpram_rd_data;
	assign dpram_rd_addr = rd_bin[PTR-1:0];

endmodule

其中的DPRAM就是一个数据缓存区,根据wr_en&~wr_full来作为写操作使能,控制数据写入RAM中。RAM模块定义如下:

`timescale 1ns / 1ns

module DPRAM#(parameter WIDTH = 8 ,parameter DEPTH = 16,parameter ADDR = 4)(
	input wr_clk,
	input rd_clk,
    input rd_rst_n,
    input wr_rst_n,
	input wr_en,
	input rd_en,
	input [WIDTH-1:0]wr_data,
	output reg [WIDTH-1:0]rd_data,
	input [ADDR-1:0]wr_addr,
	input[ADDR-1:0]rd_addr
);
	reg [WIDTH-1:0] memory[DEPTH-1:0]; 
	
	//写
	always@(posedge wr_clk)begin
	if (!wr_rst_n) begin
	   memory[wr_addr] <= 0;
	   end
	else if(wr_en) begin
		memory[wr_addr] <= wr_data;
		end
	else   begin
		memory[wr_addr] <= memory[wr_addr];
		end
	end

	//读
	always@(posedge rd_clk)begin
	if(! rd_rst_n) begin
	   rd_data <= 0;
	   end
	if(rd_en)  begin
		rd_data <= memory[rd_addr];
		end
	else   begin
		rd_data <= rd_data;
		end
	end
endmodule

testbench如下:

`timescale 1ns / 1ns
module ASFIFO_tb;
	parameter WIDTH = 16;
	parameter PTR   = 4;
	// 写时钟域tb信号定义
	reg					wrclk		;
	reg					wr_rst_n	;
	reg	[WIDTH-1:0]		wr_data		;
	reg 				wr_en		;
	wire				wr_full		;
	// 读时钟域tb信号定义
	reg					rdclk		;
	reg					rd_rst_n	;
	wire [WIDTH-1:0]	rd_data		;
	reg					rd_en		;
	wire				rd_empty	;
	// testbench自定义信号
	reg					init_done	;		// testbench初始化结束
	// FIFO初始化
	initial	begin
		// 输入信号初始化
		wr_rst_n  = 1	;
		rd_rst_n  = 1	;
		wrclk 	  = 0	;
		rdclk 	  = 0	;
		wr_en 	  = 1	;
		rd_en 	  = 1	;
		wr_data   = 'b0 ;
		init_done = 0	;
 
		// FIFO复位
		#30 wr_rst_n = 0;
			rd_rst_n = 0;
		#30 wr_rst_n = 1;
			rd_rst_n = 1;
 
		// 初始化完毕
		#30 init_done = 1;
	end
  
	// 写时钟
	always
		#2 wrclk = ~wrclk;
 
	// 读时钟
	always
		#4 rdclk = ~rdclk;
  
	// 写入数据自增
	always @(posedge wrclk) begin
		if(init_done) begin
			if( wr_full == 1'b0 )
				wr_data <= wr_data + 1;
			else
				wr_data <= wr_data;
		end
		else begin
			wr_data <= 'b0;
		end
	end
  
 	// 异步fifo例化
	ASFIFO
		# ( .WIDTH(16), .PTR(4) )
		U_ASFIFO
		(
			.wrclk	 	(wrclk		),
			.wr_rst_n	(wr_rst_n	),
			.wr_data	(wr_data	),
			.wr_en		(wr_en		),
			.wr_full	(wr_full	),
 
			.rdclk		(rdclk		),
			.rd_rst_n	(rd_rst_n	),
			.rd_data	(rd_data	),
			.rd_en		(rd_en		),
			.rd_empty	(rd_empty	)
		);
 
 
endmodule

对应的框架图自己重新画了一遍,思路清晰很多。
基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理
看时序图的时候,可以将RAM模块的端口也画出来,方便看地址变化:
基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理
时序图:
基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理
上图中,上下两个读写使能wr_en和rd_en分别表示DPRAM例划前后的:

assign dpram_wr_en   = ( wr_en == 1'b1 && wr_full == 1'b0 )? 1'b1 : 1'b0;
assign dpram_rd_en   = ( rd_en == 1'b1 && rd_empty == 1'b0 )? 1'b1 : 1'b0;

3.1 细节一:写地址和同步读地址的比较

通过时序图,可以看出写地址是与两帧(相对于wr_clk时钟)前的同步读地址相比较。
基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理
上图可以看出当写地址为6时,读地址的前两帧才是6,因为为了仿真亚稳态出现,读地址过来对比是经过了两级触发器。
基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理

3.2 RAM的具体数据情况

指针所指的时刻为上时序图中黄线时刻,也就是wr_full第一次变为1时。
从代码中可以看出RAM例划前地址为5位,例划后只取4位,现在明白了原因:
第一位用来判断是写指针超过读指针一圈了(满标识:第一位地址相反,其余相同),还是写指针和读指针在一起(空标识:5位地址全部相反)。
基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理

3.3 实时性的要求

在testbench中,有这么一段,意思就是如果该RAM已满时,就不自增数据(我的理解就是不添加新的数据了):

	// 写入数据自增
	always @(posedge wrclk) begin
		if(init_done) begin
			if( wr_full == 1'b0 )
				wr_data <= wr_data + 1;
			else
				wr_data <= wr_data;
		end
		else begin
			wr_data <= 'b0;
		end
	end

但实际情况很有可能是实时处理,数据是源源不断传来,所以还是在满足快时钟同步至慢时钟的不漏报情况下,就需要衡量最长持续数据传输长度和RAM容积大小。当持续传输数据有n个时,就需要至少m*n的RAM。m=快时钟频率/慢时钟频率

参考:
https://www.codenong.com/cs105834073/
https://blog.csdn.net/qq_40807206/article/details/109555162
转格雷码https://blog.csdn.net/jingfengvae/article/details/51691124文章来源地址https://www.toymoban.com/news/detail-436691.html

到了这里,关于基于vivado(语言Verilog)的FPGA学习(5)——跨时钟处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于vivado+Verilog FPGA开发 — GT收发器

    代码规范:Verilog 代码规范_verilog代码编写规范-CSDN博客 开发流程:FPGA基础知识----第二章 FPGA 开发流程_fpga 一个项目的整个流程-CSDN博客   源码下载:GitHub - Redamancy785/FPGA-Learning-Record: 项目博客:https://blog.csdn.net/weixin_51460407 零、低速通信接口的缺陷 1、同步通信要求传输数据

    2024年04月17日
    浏览(64)
  • Verilog语言fpga小脚丫数字时钟(整点报时,调时,显示秒钟等功能)

    学弟加油!                                                                       ———来自科大焯人 最近刚好学习了数电有关知识,就做了这个项目(闹钟过于繁琐就没有做了) 希望给还在学习的大伙一点参考,完整代码在最后 在这里先附上两串代码分别是d

    2024年02月07日
    浏览(48)
  • 基于FPGA的AES加密解密vivado仿真,verilog开发,包含testbench

    目录 1.算法描述 2.仿真效果预览 3.verilog核心程序 4.完整verilog          AES, 高级加密标准, 是采用区块加密的一种标准, 又称Rijndael加密法. 严格上来讲, AES和Rijndael又不是完全一样, AES的区块长度固定为128比特, 秘钥长度可以是128, 192或者256. Rijndael加密法可以支持更大范围的区

    2024年02月01日
    浏览(57)
  • 基于vivado+Verilog FPGA开发 — 基于AD9767高速DAC的DDS信号发生器

    目录  一、功能定义 二、设计输入  1、主模块 2、DDS模块 3、 按键消抖模块 三、功能仿真  四、综合优化 五、布局布线 六、时序仿真 七、板级调试  代码规范:Verilog 代码规范_verilog代码编写规范-CSDN博客 开发流程:FPGA基础知识----第二章 FPGA 开发流程_fpga 一个项目的整个

    2024年03月18日
    浏览(77)
  • 【FPGA异步时钟域约束方法】——Vivado时钟组约束

    【FPGA异步时钟域约束方法】——Vivado时钟组约束 在 FPGA 设计过程中,由于存在多时钟域的情况,需要采取一定的策略来处理时序约束问题。其中,异步时钟域是一种常见的设计需求,为了保证设计时序的正确性和稳定性,我们需要使用 Vivado 提供的时钟组约束方法来对异步时

    2024年02月04日
    浏览(50)
  • 基于fpga的图像处理之图像灰度化处理(Vivado+Modelsim+Matlab联合仿真验证)

    微信公众号上线,搜索公众号 小灰灰的FPGA ,关注可获取相关源码,定期更新有关FPGA的项目以及开源项目源码,包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 源码工程链接 https://download.csdn.net/download/m0_50111463/88529260

    2024年02月10日
    浏览(61)
  • FPGA时钟资源与设计方法——Xilinx(Vivado)

    1.时钟资源包括:时钟布线、时钟缓冲器(BUFGBUFRBUFIO)、时钟管理器(MMCM/PLL)。 2.时钟类型有三种:全局时钟,可以驱动整个内核上的同步逻辑;局部时钟,可以驱动特定和相邻区域的逻辑;IO时钟,可以驱动某个IO的特定逻辑。 3.混合模式时钟管理器(MMCM)和数字时钟管理

    2024年02月22日
    浏览(56)
  • 基于Verilog 语言开发的FPGA密码锁工程

    基于Verilog 语言开发的FPGA密码锁工程。 通过矩阵键盘输入按键值。 输入12修改密码,13清除密码,可以修改原来默认的密码,修改时首先要输入当前密码进行验证,正确后才能更新当前密码,否则修改不成功。 修改结束后按键15,确认修改成功。 也直接使用默认密码作为最终

    2024年02月10日
    浏览(59)
  • 基于FPGA的信号处理算法,FFT法相差检测verilog实现

    基于FPGA的信号处理算法,FFT法相差检测verilog实现 。 1.硬件平台:altera芯片 2.软件平台:Quartusii 13.1 Verilog 3.实现功能:检测两路正弦波的相位差 基于FPGA的信号处理算法,FFT法相差检测verilog实现 引言 在现代通信系统中,信号处理算法在实时性和精度方面面临着巨大的挑战。

    2024年04月16日
    浏览(60)
  • FPGA(Verilog)时钟无缝切换设计与验证

    时钟切换基本模型,本文围绕“ 基本组合电路切换、解决前毛刺切换、解决后毛刺切换 ”三方面完成时钟无缝切换。 组合逻辑切换,本质就是二选一多路器 如下图,CLK_SEL 0与1分别控制时钟CLK_A CLK_B输出。 组合逻辑输出只跟当前输入状态有关,CLK_SEL异步不可控导致输出毛刺

    2023年04月10日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包