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

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

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

慢时钟到快时钟一般都不需要处理,关键需要解决从快时钟到慢时钟的问题,因为可能会漏信号或者失真,比如:
在这里插入图片描述

2.解决办法

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

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

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

对应的框架图自己重新画了一遍,思路清晰很多。
在这里插入图片描述
看时序图的时候,可以将RAM模块的端口也画出来,方便看地址变化:
在这里插入图片描述
时序图:
在这里插入图片描述
上图中,上下两个读写使能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时钟)前的同步读地址相比较。
在这里插入图片描述
上图可以看出当写地址为6时,读地址的前两帧才是6,因为为了仿真亚稳态出现,读地址过来对比是经过了两级触发器。
在这里插入图片描述

3.2 RAM的具体数据情况

指针所指的时刻为上时序图中黄线时刻,也就是wr_full第一次变为1时。
从代码中可以看出RAM例划前地址为5位,例划后只取4位,现在明白了原因:
第一位用来判断是写指针超过读指针一圈了(满标识:第一位地址相反,其余相同),还是写指针和读指针在一起(空标识: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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/8118.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python零基础自学

很多零基础想做程序员的同学&#xff0c;最开始接触的基本上都是 Python 作为常年霸榜的 “最好上手的编程语言” ——Python&#xff0c;深受互联网大厂的喜爱。 而很多小伙伴反应&#xff0c;在刚开始学Python时遇到不少问题&#xff1a; 比如找不到学习资源&#xff0c;不…

Linux系统centos7关闭防火墙命令

CentOS 7使用的防火墙是firewalld&#xff0c;要关闭防火墙可以使用以下命令&#xff1a; 1. 停止firewalld服务&#xff1a; systemctl stop firewalld 2. 禁止firewalld服务开机启动&#xff1a; systemctl disable firewalld 3. 查看firewalld服务状态&#xff1a; sys…

java 线程池

一.简单的线程池设计&#xff1a; 线程池的执行示意图&#xff1a; 二. 线程池的核心参数&#xff1a; 三.线程池的处理流程&#xff1a; 四.线程池的阻塞队列&#xff1a; 1.基于数组的有界阻塞队列 2.基于链表的有界阻塞队列 3.基于链表的无界阻塞队列 4.同步移交阻塞队列…

2003-2019年各省专利申请和授权量数据/2003-2019年31省专利申请和授权量数据

2003-2019年各省专利申请和授权量数据/2003-2019年31省专利申请量和授权量数据 2003-2019年各省专利申请和授权量数据/2003-2019年31省专利申请和授权量数据 1、时间&#xff1a;2003-2019年 2、来源&#xff1a;国家知识产权专利数据库 3、指标&#xff1a;专利申请数、发…

数据结构和算法(一):复杂度、数组、链表、栈、队列

从广义上来讲&#xff1a;数据结构就是一组数据的存储结构 &#xff0c; 算法就是操作数据的方法 数据结构是为算法服务的&#xff0c;算法是要作用在特定的数据结构上的。 10个最常用的数据结构&#xff1a;数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie树 10…

办公协作效率想提质增效,可借助开源大数据工具!

在信息爆炸式发展的今天&#xff0c;提升办公协作效率&#xff0c;让各部门的信息有效互通起来&#xff0c;做好数据管理&#xff0c;已经成为众企业提升竞争力的方式方法。那么&#xff0c;如果想要提升办公效率&#xff0c;就需要了解开源大数据工具了。在数字化发展进程中&a…

《扬帆优配》西藏地震!美史上最严排放新规将出台,美股收涨

当地时间周四&#xff0c;美股遍及收高&#xff0c;科技股领涨。因耶稣受难日&#xff0c;美股4月7日&#xff08;周五&#xff09;休市&#xff0c;周四为美股本周最终一个买卖日&#xff0c;从本周状况来看&#xff0c;纳指与标普500指数均录得跌幅&#xff0c;别离跌1.1%和0…

回归预测 | MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测

回归预测 | MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测 目录回归预测 | MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测效果一览基本介绍程序设计参考资料效果一览 基本介绍 MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测 粒子…

第十四届蓝桥杯题解

声明&#xff1a;以下都无法确定代码的正确性&#xff0c;是赛时代码&#xff0c;希望大家见谅&#xff01;思路可以参考&#xff0c;等后续可以评测之后再去修改博客内错误&#xff0c;也希望大家能够指正错误&#xff01; 试题A&#xff1a;日期统计 分析&#xff1a;这道题…

45-Dockerfile-ARG/ENV指令

AGR/ENV指令前言ARG作用格式说明生效范围使用示例ENV作用格式说明使用环境变量使用示例ARG 和 ENV 的区别前言 本篇来学习下Dockerfile中的AGR/ENV指令 ARG 作用 定义一个可以在构建镜像时使用的变量 格式 ARG <name>[<default value>]说明 在执行 docker b…

【数据结构】AVL树

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《数据结构与算法》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; AVL树&#x1f332;AVL树&#x1f334;AVL树的插入&#x1f334;AVL树的旋转左单旋右单旋左…

Springboot基础学习之(十三):通过代码实现对数据库的增删该查操作(数据库:mysql)

Springboot这个系列实现的案例&#xff1a;员工后台管理系统 之前讲解的内容是前后端的交互&#xff0c;并没有涉及到数据库。把员工信息放置在一个数组中&#xff0c;实现的方法则是对数组的增删改查操作&#xff0c;但是从今天开始&#xff0c;实现的功能则是在数据库的基础上…

看完这篇 教你玩转渗透测试靶机vulnhub——My File Server: 2

Vulnhub靶机My File Server: 2渗透测试详解Vulnhub靶机介绍&#xff1a;Vulnhub靶机下载&#xff1a;Vulnhub靶机安装&#xff1a;Vulnhub靶机漏洞详解&#xff1a;①&#xff1a;信息收集&#xff1a;②&#xff1a;FTP匿名登入③&#xff1a;SSH私钥登入④&#xff1a;SMB共享…

Mysql一条多表关联SQL把CPU打爆了,如何优化

今天是清明假期的第三天&#xff0c;收到同事的求助&#xff0c;DB的CPU被打爆了&#xff01; 查看监控&#xff0c;CPU已经被打爆100% 登录mysql&#xff0c;DB无锁阻塞&#xff0c;元凶是一个异常sql,存在39个并发执行。 SQL的明细如下&#xff1a; select TEMPSALE.USER_ID…

ChatGPT 被大面积封号,到底发生什么了?

意大利数据保护机表示 OpenAI 公司不但非法收集大量意大利用户个人数据&#xff0c;没有设立检查 ChatGPT 用户年龄的机制。 ChatGPT 似乎正在遭遇一场滑铁卢。 3月31日&#xff0c; 大量用户在社交平台吐槽&#xff0c;自己花钱开通的 ChatGPT 账户已经无法登录&#xff0c;更…

港科夜闻|香港科大(广州)熊辉教授获委任为协理副校长(知识转移)

关注并星标每周阅读港科夜闻建立新视野 开启新思维1、香港科大(广州)熊辉教授获委任为协理副校长(知识转移)。在任期间&#xff0c;熊教授将为香港科大知识转移战略发展提供全面领导&#xff0c;鼓励和促进教师、学生和校友之间的知识转移&#xff0c;促进创业发展、技术研究及…

数据仓库、数据集市、数据湖,你的企业更适合哪种数据管理架构?

建设企业级数据平台&#xff0c;首先需要了解企业数据&#xff0c;确认管理需求&#xff0c;并选择一个数据管理架构。那么面对纷繁复杂的数据来源&#xff0c;多元化的数据结构&#xff0c;以及他们的管理使用需求&#xff0c;企业数据平台建设该从何处入手呢&#xff1f;哪个…

机器学习笔记之正则化(二)权重衰减角度(直观现象)

机器学习笔记之正则化——权重衰减角度[直观现象]引言回顾&#xff1a;拉格朗日乘数法角度观察正则化过拟合的原因&#xff1a;模型参数的不确定性正则化约束权重的取值范围L1L_1L1​正则化稀疏权重特征的过程权重衰减角度观察正则化场景构建权重衰减的描述过程权重衰减与过拟合…

ChatGPT常用prompts汇总

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

一个开源人的心酸哭诉

编者按&#xff1a;这篇文章比较长&#xff0c;但值得一读。从这篇文章&#xff0c;你可以看到一些开源开发者的内心活动&#xff0c;看看他们的热情、抱负、无奈、心酸、愤怒、失望和痛斥&#xff1b;看看他们如何指责、讽刺、乞求、羞辱那些使用他们作品而无视他们痛苦的人&a…