【FPGA零基础学习之旅#8】阻塞赋值与非阻塞赋值讲解

news2024/12/23 23:18:15

🎉欢迎来到FPGA专栏~阻塞赋值与非阻塞赋值


  • ☆* o(≧▽≦)o *☆~我是小夏与酒🍹
  • 博客主页:小夏与酒的博客
  • 🎈该系列文章专栏:FPGA学习之旅
  • 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
  • 📜 欢迎大家关注! ❤️
    FPGQ2

CSDN

🎉 目录-阻塞赋值与非阻塞赋值

  • 一、基础知识讲解
  • 二、阻塞赋值讲解
    • 代码编写
  • 三、非阻塞赋值讲解
    • 2.1 代码编写
    • 2.2 非阻塞赋值仿真
    • 2.3 延时解决
  • 四、阻塞赋值与非阻塞赋值分析与比较
    • 4.1 赋值语句
    • 4.2 分析与比较
  • 五、六个原则

遇见未来

一、基础知识讲解

阻塞赋值,操作符为 “ = ”,“阻塞”是指在进程语句(initialalways)中,当前的赋值语句会阻断其后语句的正常执行,也就是说后面的语句必须等到当前的赋值语句执行完毕才能执行。而且阻塞赋值可以看成是一步完成的,即:计算等号右边的值并同时赋给左边变量。

非阻塞赋值,操作符为 “ <= ”,“非阻塞”是指在进程语句(initialalways)中,当前的赋值语句不会阻断其后语句的正常执行。

二、阻塞赋值讲解

代码编写

阻塞赋值: 设计三个输入a、b和c,一个输出out:注意计算过程,使用分步计算,先计算a+b的值,然后再对这个值加c

BlockAndNonblock.v:

//--------<阻塞赋值与非阻塞赋值讲解示例代码>----------
module BlockAndNonblock(
	input 				Clk,
	input 				Rst_n,
	input 				a,
	input 				b,
	input 				c,
	output reg	[1:0] out//out在always块中进行赋值,需要定义为reg类型;
);

//--------<计算过程>----------
//out = a + b + c;
//a,b,c三者求和的最大值为3,3为一个两位宽的数,因此需要定义out的位宽为2;
//对上式计算进行分解,并定义中间变量d;
//d = a + b,out = d + c;
	
	reg [1:0]d;//d = a + b;
	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			out = 2'b0;
		else begin
			d = a + b;
			out = d + c;
		end
	end

endmodule

上述代码的RTL视图:

RTL1
对于上述RTL视图:ab通过加法器(ADDER)进行相加,得到的结果再和c相加,最后将值通过寄存器输出。

对于Verilog HDL的并行执行来说,如果我们交换always块中的计算顺序,RTL视图应该保持不变才对,即:

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		out = 2'b0;
	else begin
		out = d + c;
		d = a + b;
	end
end

对应的RTL视图为:

RTL2
📜观察上述两个RTL视图,当交换计算顺序之后,出现了两级寄存器,导致代码的实现电路是不一样的。

三、非阻塞赋值讲解

2.1 代码编写

对上述编写的计算过程使用非阻塞赋值方式实现,即:

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		out <= 2'b0;
	else begin
		d <= a + b;
		out <= d + c;
	end
end

BlockAndNonblock.v:

//--------<阻塞赋值与非阻塞赋值讲解示例代码>----------
module BlockAndNonblock(
	input 				Clk,
	input 				Rst_n,
	input 				a,
	input 				b,
	input 				c,
	output reg	[1:0] out//out在always块中进行赋值,需要定义为reg类型;
);

//--------<计算过程>----------
//out = a + b + c;
//a,b,c三者求和的最大值为3,3为一个两位宽的数,因此需要定义out的位宽为2;
//对上式计算进行分解,并定义中间变量d;
//d = a + b,out = d + c;
	
	reg [1:0]d;//d = a + b;
	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			out <= 2'b0;
		else begin
			d <= a + b;
			out <= d + c;
		end
	end

endmodule

对应的RTL视图为:

RTL4

同时,我们改变计算顺序,再观察一下:

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		out <= 2'b0;
	else begin
		out <= d + c;
		d <= a + b;
	end
end

对应的RTL视图为:

RTL3
📜由于该部分是使用非阻塞赋值的方式实现计算,所以当计算顺序改变之后,RTL视图并不会出现改变

2.2 非阻塞赋值仿真

对非阻塞赋值方式进行仿真:

BlockAndNonblock_tb.v:

`timescale 1ns/1ns
`define clock_period 20

module BlockAndNonblock_tb;

	reg Clk;
	reg Rst_n;
	reg a,b,c;
	
	wire [1:0]out;

	BlockAndNonblock BlockAndNonblock0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.a(a),
		.b(b),
		.c(c),
		.out(out)
	);

	initial Clk = 1;
	always#(`clock_period/2) Clk = ~Clk;
	
	initial begin 
		Rst_n = 0;
		a = 0;b = 0;c = 0;
		#(`clock_period*200+1);
		Rst_n = 1;
		
		a = 0;b = 0;c = 0;
		#(`clock_period*200);
		a = 0;b = 0;c = 1;
		#(`clock_period*200);
		a = 0;b = 1;c = 0;
		#(`clock_period*200);
		a = 0;b = 1;c = 1;
		#(`clock_period*200);
		a = 1;b = 0;c = 0;
		#(`clock_period*200);
		a = 1;b = 0;c = 1;
		#(`clock_period*200);
		a = 1;b = 1;c = 0;
		#(`clock_period*200);
		a = 1;b = 1;c = 1;
		#(`clock_period*200);
		#(`clock_period*200);
		$stop;
	end

endmodule

设置好仿真脚本之后进行仿真,需要注意好细节部分的波形图:
仿真1
之后观察后仿真:
仿真2
在后仿真中添加子模块之后,会产生很多的信号,这些信号都是由布局布线生成的中间信号。放大波形之后,仔细观察各数据的变化:
仿真4
从上图的波形仿真中可以看出,非阻塞赋值并不会受到上一语句的影响,即:

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)
		out <= 2'b0;
	else begin
		d <= a + b;
		out <= d + c;
	end
end

其中d <= a + b;并不会阻塞out <= d + c;语句的执行。

因此,在仿真一开始的阶段里(系统一直处于复位状态时),out的值都是未知态,原因是在d被计算出来之前,d处于未知态:
仿真5

out的值只与上一时刻的计算结果有关。

2.3 延时解决

为了解决out值延迟的情况,可将计算过程改写为:

out <= a + b + c;

即:

//--------<阻塞赋值与非阻塞赋值讲解示例代码>----------
module BlockAndNonblock(
	input 				Clk,
	input 				Rst_n,
	input 				a,
	input 				b,
	input 				c,
	output reg	[1:0] out//out在always块中进行赋值,需要定义为reg类型;
);

//--------<计算过程>----------
	
	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)
			out <= 2'b0;
		else begin
			out <= a + b + c;
		end
	end

endmodule

对应的RTL视图:

RTL5
📜从上述RTL视图中可以看出:a + b + c执行的是组合逻辑运算。

四、阻塞赋值与非阻塞赋值分析与比较

对于阻塞赋值与非阻塞赋值的区别,本部分只做简单讲解,不会太深入内部原理。

4.1 赋值语句

在always中使用的赋值语句有两种:阻塞赋值和非阻塞赋值。赋值语句的左端都必须是reg类型,这是语法的强制要求,所以always结构中所有语句的左端变量都必须是reg型,这是与assign语句不同的,请格外留意。

4.2 分析与比较

🔸先看阻塞赋值:

  • 顺序块中,一条阻塞赋值语句执行结束后,才能继续执行下一条阻塞赋值语句。
  • 语句执行结束后,左侧值会立刻改变,前面语句赋值的结果可以被后面的语句使用。

BlockAndNonblockPlus.v:

module BlockAndNonblockPlus(
	input Clk,
	input Rst_n,
	output reg [1:0]a,
	output reg [1:0]b,
	output reg [1:0]c
);

	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)begin
			a = 2'd1;
			b = 2'd2;
			c = 2'd3;
		end
		else begin
			a = 2'd0;
			b = a;
			c = b;
		end
	end

endmodule

测试激励文件:

`timescale 1ns/1ns
`define clock_period 20

module BlockAndNonblockPlus_tb;
	
	reg Clk;
	reg Rst_n;
	
	wire [1:0]a;
	wire [1:0]b;
	wire [1:0]c;
	
	BlockAndNonblockPlus BlockAndNonblockPlus0(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.a(a),
		.b(b),
		.c(c)
	);
	
	initial Clk = 1;
	always#(`clock_period/2) Clk = ~Clk;
	
	initial begin
		Rst_n = 0;
		#(`clock_period*10);
		Rst_n = 1;
		#(`clock_period*10);
		$stop;
	end
	
endmodule

直接看波形仿真结果:
仿真6
可见,当复位信号变为高电平时,a、b、c的值都变为了0。分析下列语句:

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)begin
		a = 2'd1;
		b = 2'd2;
		c = 2'd3;
	end
	else begin
		a = 2'd0;
		b = a;
		c = b;
	end
end

代码中使用的是阻塞赋值语句,从波形图中可以看到,在复位的时候( Rst_n=0),a=1,b=2,c=3;而结束复位之后(Rst_n=1),当 Clk 的上升沿到来时,a=0,b=0,c=0。这是因为阻塞赋值是在当前语句执行完成之后,才会执行后面的赋值语句,因此首先执行的是 a=0,赋值完成后将 a 的值赋值给 b,由于此时 a 的值已经为 0,所以 b=a=0,最后执行的是将 b 的值赋值给 c,而 b 的值已经赋值为 0,所以 c 的值同样等于 0。

🔸接下来看非阻塞赋值:

  • 同一时间点,前面语句的赋值不能立刻被后面的语句使用。
  • 所有的赋值是在右侧运算完毕时统一完成的。

BlockAndNonblockPlus.v:

module BlockAndNonblockPlus(
	input Clk,
	input Rst_n,
	output reg [1:0]a,
	output reg [1:0]b,
	output reg [1:0]c
);

	always@(posedge Clk or negedge Rst_n)begin
		if(!Rst_n)begin
			a <= 2'd1;
			b <= 2'd2;
			c <= 2'd3;
		end
		else begin
			a <= 2'd0;
			b <= a;
			c <= b;
		end
	end

endmodule

测试激励文件保持不变,观察波形图:
仿真7
分析以下代码与仿真结果:

always@(posedge Clk or negedge Rst_n)begin
	if(!Rst_n)begin
		a <= 2'd1;
		b <= 2'd2;
		c <= 2'd3;
	end
	else begin
		a <= 2'd0;
		b <= a;
		c <= b;
	end
end

代码中使用的是非阻塞赋值语句,从波形图中可以看到,在复位的时候(Rst_n=0),a=1,b=2,c=3;而结束复位之后(Rst_n=1),当 Clk 的上升沿到来时,a=0,b=1,c=2。这是因为非阻塞赋值在计算赋值等号右边的表达式或变量和更新赋值等号左边的表达式或变量期间,允许其它的非阻塞赋值语句同时计算赋值等号右边的表达式或变量和更新赋值等号左边的表达式或变量

因此,当复位结束之后的第一个时钟上升沿到来时:a = 0,b = 1,c = 2;第二个时钟上升沿到来时:a = 0,b = 0,c = 1;第三个时钟上升沿到来时:a = 0,b = 0,c = 0

🔸观察上述代码生成的RTL视图:
阻塞赋值:
RTL6
非阻塞赋值:
RTL7

  • 在描述组合逻辑的 always 块中用阻塞赋值,则综合成组合逻辑的电路结构。
  • 在描述时序逻辑的 always 块中用非阻塞赋值,则综合成时序逻辑的电路结构。

五、六个原则

对于阻塞赋值与非阻塞赋值,在 《小梅哥FPGA设计思想与验证方法》 中提及了六个原则,可解决在综合后仿真中出现的绝大多数的冒险竞争问题

  1. 时序电路建模时,用非阻塞赋值;
  2. 锁存器电路建模时,用非阻塞赋值;
  3. always块建立组合逻辑模型时,用阻塞赋值;
  4. 在同一个 always块中建立时序和组合逻辑电路时,用非阻塞赋值;
  5. 在同一个 always块中不要既用非阻塞赋值又用阻塞赋值;
  6. 不要在一个以上的 always块中为同一个变量赋值。

csdn

🧸结尾


  • ❤️ 感谢您的支持和鼓励! 😊🙏
  • 📜您可能感兴趣的内容:
  • 【stm32开发】stm32+oled最小系统板资料(原理图、PCB、示例代码)【六一】
  • 【FPGA零基础学习之旅#7】BCD计数器设计
  • 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
  • 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制
    遇见未来

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

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

相关文章

Redis从入门到精通【高阶篇】之底层数据结构跳表(SkipList)

文章目录 0.前言1.跳表(SkipList)基本详解2. 源码解析3.总结4.思考题5. Redis从入门到精通系列文章 0.前言 上个篇章回顾&#xff0c;我们上个章节我们学习了《Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解》&#xff0c;我们从源码层了解整数集由一个头部和…

Day08 Python数据结构(数据容器)详解

文章目录 第五章 Python数据容器5.1. 容器类型介绍5.2. 数据容器运算符5.2.1. 成员运算符5.2.2. 身份运算符 5.3. 字符串str5.3.1. 字符串的定义5.3.2. 运算符的相关操作5.3.3. 索引和切片5.3.3.1. 索引5.3.3.2. 切片 5.3.4. 字符串遍历5.3.5. 字符串的相关操作5.3.5.1. 获取的…

git安装以及git小乌龟使用

一、下载git 打开git官网地址&#xff1a;https://git-scm.com/进行下载 下载完安装&#xff0c;一直next就好&#xff0c;如果愿意就可以改下安装路径&#xff0c;改在d盘。 具体可以参考&#xff1a;git安装教程 二、安装完下载小乌龟以及语言包 下载地址&#xff1a;小乌龟…

【OpenMMLab AI实战营二期笔记】第六天 目标检测和MMDetection

1.什么是目标检测&#xff1f; 目标检测 vs 图像分类 目标检测的应用 &#xff08;1&#xff09;人脸识别 &#xff08;2&#xff09;智慧城市 &#xff08;3&#xff09;自动驾驶 &#xff08;4&#xff09;下游视觉任务&#xff1a;场景文字识别、人体姿态估计 目标检测技术…

微服务 springcloud 08.zuul框架,API网关,整合ribbon和hystrix框架

01.zuul是作为springcloud中的一个网关框架来使用的。 zuul API 网关&#xff0c;为微服务应用提供统一的对外访问接口。 zuul 还提供过滤器&#xff0c;对所有微服务提供统一的请求校验。 在项目中的位置&#xff1a; 02.新建 sp11-zuul 项目 03.pom.xml 需要添加 sp01-com…

【雕爷学编程】Arduino动手做(111)---震动提醒模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

纺织工厂运营神技,不会你就OUT了!

在纺织工业中&#xff0c;保持适宜的生产环境是至关重要的。温湿度、能耗、设备运行状态以及空气质量等因素对纺织品的质量和生产效率都有着重要影响。 为了实现对生产环境的全面监控和管理&#xff0c;纺织企业越来越多地采用动环监控系统。 客户案例 湖州市某纺织工厂监控项目…

USART2实现Motbus485通信

本文用的单片机是原子的战舰V4 1. 先来驱动一下usart2 USART驱动配置一般步骤: STEP1&#xff1a;使能相关时钟&#xff0c;这块板子usart2用到了A2、A3分别为TX脚、RX脚&#xff0c;D7的作用是发送接收模式控制。下面开启GPIO与USART2时钟&#xff1a; RCC_APB1PeriphClockCm…

SpringBoot中打印 sql 语句

系列文章目录 文章目录 系列文章目录前言一、在配置文件中 application.yml 配置即可二、#Log4g打印SqL语句三、配置Logback总结 前言 在SpringBoot中&#xff0c;我们可以使用日志框架来打印SQL语句&#xff0c;常用的日志框架有Logback和Log4j2。下面以Logback为例&#xff…

35:考虑virtual函数以外的其他选择

假设你正在写一个视频游戏软件&#xff0c;你打算为游戏内的人物设计一个继承体系&#xff0c;剧中人物被伤害或因其他因素而降低健康状态的情况并不罕见。你因此决定提供一个成员函数healthValue&#xff0c;它会返回一个整数&#xff0c;表示人物的健康程度。 由于不同的人物…

Linux x86_64平台同时编译x86_64和arm64两个架构的Qt应用程序出现XRes库无法找到

一 背景 在ubuntu x86_64平台上需要同时编译x86_64和arm64两个架构的Qt应用程序。在实践过程中&#xff0c;发现XRes库只能安装在其中一个平台。 二 根因 安装amd64版本的XRes库会删除arm64版本的库&#xff0c;反之亦然。 在安装amd64版本时&#xff0c;会删除arm64版本&a…

内部员工有没有在线帮助文档可以使用呢

当今企业中&#xff0c;内部员工的工作内容变得越来越复杂&#xff0c;需要不断学习新的知识和技能。在这种情况下&#xff0c;企业需要给员工提供一种便捷的在线帮助文档&#xff0c;使员工能够更加高效地完成工作任务。本文将介绍企业内部员工在线帮助文档的重要性以及如何建…

区块链如何助力价值互联网?这些专家有话说

6月13日&#xff0c;由开放原子开源基金会主办&#xff0c;XuperCore开源项目承办的2023开放原子全球开源峰会区块链分论坛在北京举办。来自区块链领域内的专家、行业代表汇聚一堂&#xff0c;分别以主题演讲、圆桌论坛等形式&#xff0c;阐述了全球区块链行业的新理念、新发展…

Opencv仿射函数getAffineTransform底层实现原理

推导 三角形ABC仿射成为三角形DEF的变换矩阵M 猜测矩阵M [ [a1,b1,c1], [a2,b2,c2] ] 仿射变换的数学联系 对于A点和D点 AX*a1AY*b1c1DX AX*a2AY*b2c1DY 对于B点和E点 BX*a1BY*b1c1EX BX*a2BY*b2c2EY 对于C点和F点 CX*a1CY*b1c1FX CX*a2CY*b2c2FY 求解 对以上数…

seqkit 两种拆分方法的比较

seqkit拆分fastq&#xff0c;fasta等文件有两种方式&#xff1a;split和split2&#xff0c; 二者的逻辑并不一样。 split 是将原文件拆分&#xff0c;写满第一个文件&#xff0c;再写第二个文件 split2是将原文件的内容&#xff0c;挨个写到各个拆分文件里面去&#xff1f; 比…

拓尔微技术干货 | get 5个知识点,了解细分驱动技术

本期我们对步进电机驱动原理、五线的步进电机和四线的步进电机、2-2相励磁和1-2相励磁、步进电机驱动的优化方案、拓尔微TMI8150B细分驱动技术的原理进行详细的介绍&#xff0c;共分为5个知识点&#xff0c;全是技术干货&#xff0c;赶紧get下来~ ✔get 1&#xff1a;步进电机…

datax安装部署使用 windows

Datax在win10中的安装_windows安装datax_JMzz的博客-CSDN博客 DataX/userGuid.md at master alibaba/DataX GitHub 环境准备&#xff1a; 1.JDK(1.8以上&#xff0c;推荐1.8) 2.①Python(推荐Python2.7.X) ②Python(Python3.X.X的可以下载下面的安装包替换) python3.0需…

Docker安装——CentOS7.6(详细版)

ps:docker官网 在 CentOS 上安装 Docker 引擎 |官方文档 &#xff08;&#xff09; 一、确定版本&#xff08;必须是7以上版本&#xff09; cat /etc/redhat-release 二、卸载旧版本&#xff08;或者之前装过&#xff0c;没有安装过就不用管了&#xff09; &#xff08;root用…

OpenCV 项目开发实战--实现填充图像中的孔( Python / C++ ) 代码实现

文末附分别基于C++和python实现的相关测试代码下载链接 图 1. 左:原始的图像。中心:阈值和倒置。右:孔被填充。 在本教程中,我们将学习如何填充二值图像中的孔洞。考虑图 1 中左侧的图像。假设我们想要找到一个将硬币与背景分开的二进制掩码,如右图所示。在本教程中,包含…

2.0C++继承

C继承概述 C 中的继承是指一个类可以从另一个类中继承属性和方法&#xff0c;这个被继承的类称为基类或父类&#xff0c;继承它的类称为派生类或子类。 C三种继承 1、公有继承 public 在公有继承中&#xff0c;基类的公有成员和保护成员都可以被派生类访问&#xff0c;而基…