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

news2024/11/16 21:31:39

基于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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于HTML5智慧监所三维可视化安防管控系统

前言 物联网技术的发展使云计算技术得到了迅猛的发展及广泛的应用&#xff0c;智能体系的创建已经成为监狱发展的必然趋势。 智慧监狱的创建、智能化管理的推行是监狱管理的创新&#xff0c;也是监狱整体工作水平提升的具体体现。 建设背景 近年来&#xff0c;司法部不断加大…

jumpserver设置密码强度

1、点击系统设置 – 点击安全设置 2、设置密码强弱规则

【SVN】window SVN安装使用教程(服务器4.3.4版本/客户端1.11.0版本)

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…

为什么建企业网站对企业来说非常重要?

随着互联网的飞速发展&#xff0c;建企业网站已经成为了企业重要的一部分。企业网站是企业与外界沟通的重要渠道&#xff0c;对于企业的品牌形象、市场推广和销售业绩都有着不可替代的作用。本文将从以下几个方面&#xff0c;阐述为什么建企业网站对企业来说非常重要&#xff0…

Spring5学习总结(五)Spring5的新特性Log4j2@Nullable注解支持函数式风格支持JUnit5

Spring5学习总结&#xff08;五&#xff09;Spring5的新特性/Log4j2/Nullable注解/支持函数式风格/支持JUnit5 整个 Spring5 框架的代码基于 Java8&#xff0c;运行时兼容 JDK9&#xff0c;许多不建议使用的类和方法在代码库中删除 一、支持整合Log4j2 Spring 5.0 框架自带了…

【Java笔试强训】day26编程题

目录 编程题快到碗里来跳台阶问题 编程题 快到碗里来 import java.math.BigDecimal; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);while (sc.hasNext()) {BigDecimal n sc.nextBigDecimal();B…

【leetcode速通java版】04——哈希表

前 言 &#x1f349; 作者简介&#xff1a;半旧518&#xff0c;长跑型选手&#xff0c;立志坚持写10年博客&#xff0c;专注于java后端 ☕专栏简介&#xff1a;代码随想录leetcode速通训练营java版本 &#x1f330; 文章简介&#xff1a;哈希表理论&#xff0c;leetcodeT242,T3…

jekyll+GithubPage搭建一个免费的个人网站

文章目录 Jekyll环境搭建windows安装RUBY、gem、Jekyll用Jekyll搭建本地博客 用jekyll模板搭建githubpage Jekyll环境搭建 windows安装RUBY、gem、Jekyll 安装ruby RUBY安装包下载地址&#xff1a;https://rubyinstaller.org/downloads/&#xff0c;一路默认选项next即可。 最…

热闻丨ChatGPT会替代你我吗?让它写了封情书后,我得到答案

ChatGPT毕竟不是人 2023年的科技圈儿被ChatGPT占据&#xff0c;上线仅仅两个月&#xff0c;活跃用户就突破一亿。上知天文下知地理&#xff0c;ChatGPT以它的强大功能让许多人生出疑问&#xff1a;ChatGPT会替代你我吗&#xff1f; 记者挑选了一些尖锐问题进行询问&#xff0…

【碳达峰碳中和】高校用电智慧监管平台的构建

摘 要&#xff1a;介绍了当前高校用电存在的问题&#xff0c;进行了原因分析&#xff0c;由此提出建立高校用电智慧监管平台。对高校用电智慧监管平台的构架进行设计&#xff0c;运用物联网技术&#xff0c;实现各回路实时自主控制&#xff0c;并细化管理权限&#xff0c;实现…

【5天打卡】学习Lodash的第一天——初体验

大家好&#xff0c;最近&#xff0c;我在学习Lodash这个工具库。Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。一款优秀的 JavaScript 工具库&#xff0c;里面包含了大量的工具函数。适用于常见浏览器以及 Node.js 等。 所以我们一起来学习Lodash&#xff0c…

7. 堆的简单学习

7. 堆 7.1 堆的定义 堆是计算机科学中一类特殊的数据结构的统称&#xff0c;堆通常可以被看做是一棵完全二叉树的数组实现。 堆的特性&#xff1a; 它是完全二叉树&#xff0c;除了树的最后一层结点不需要是满的&#xff0c;其它的每一层从左到右都是满的&#xff0c;如果最…

持续集成 在 Linux 上搭建 Jenkins,自动构建接口测试

本篇把从 0 开始搭建 Jenkins 的过程分享给大家&#xff0c;希望对小伙伴们有所帮助。 文章目录 在 Linux 上安装 Jenkins 在 Linux 上安装 Git 在 Linux 上安装 Python 在 Linux 上安装 Allure 配置 Jenkins jenkins 赋能 - 使用邮箱发送测试报告 jenkins 赋能 - 优化测试报告…

比ChatGPT更好用的Claude来了

比ChatGPT更好用的Claude来了&#xff0c;不需要魔法上网&#xff01;&#xff01;&#xff01; claude官网 点击 add to slack slack跟discord有点类似&#xff0c;先要去slack注册账号 登录之后就添加创建一个工作区 添加 不过现在已经停止添加了&#xff0c;会出现App u…

mySQL1(4/17)

目录 1. 2.字符集 3. 4.update搭配order by 和limit 5.drop table 和delete from 表名 6.主键 primary key 7.修改表 8.外键的父与子的约束关系 9.案例 10.把一个表的内容完全复制到另一个空表中去 11.更复杂的查询 12.group by 分组 13.联合查询 14.内连接和外连…

世界读书日-多读书,读好书!

我们为什么要读书&#xff0c;它可以让你在看到落日余晖的时候脑海中浮现的是“落霞与孤鹜齐飞”“秋水共长天一色”&#xff0c;而不是“哇&#xff0c;夕阳真好看”。在看到漫天飞舞的雪花时&#xff0c;能脱口而出“忽如一夜春风来&#xff0c;千树万树梨花开”&#xff0c;…

xilinx zynq+vitis实现命令行编译输出xsa以及bin文件

执行菜单命令【开始】—【所有程序】—【Xilinx Design Tools】—【Vivado2020.1】—【Vivado2020.1Tcl Shell】,弹出命令界面 或者cmd命令下输入call D:\soft_install\vivado2020.1\Vivado\2020.1\bin\vivado.bat -mode tcl 2.输入打开工程指令&#xff1a; open_project …

vue3配置router路由并实现页面跳转

1、安装vue-router 用vue3需要安装版本4.0以上的vue-router&#xff0c;安装命令&#xff1a; npm install vue-routernext --savevue2尽量安装4.0以下版本&#xff0c;安装命令&#xff1a; npm i vue-router3.1.3在package.json中可以查看vue-router版本号&#xff1a; 2、…

2399929 - 业务伙伴同步时出现错误 F2163

症状 无法保存供应商角色中业务伙伴的更改。在事务代码 MDS_PPO2 中从 BP 到供应商同步期间出现错误“尚未创建供应商 **&#xff08;错误消息&#xff1a;F2 163&#xff09;”。 环境 SAP ERP Central Component 6.0 SAP ERP 6.0 SAP ERP 的 SAP 增强包SAP ERP&#xff08…

LinkedBlockingQueue原理

1. 基本的入队出队 public class LinkedBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {static class Node<E> {E item;/*** 下列三种情况之一* - 真正的后继节点* - 自己, 发生在出队时* - null, 表…