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

🎉欢迎来到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.mfbz.cn/a/29074.html

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

相关文章

解决elementUI弹出框关闭后再打开el-select下拉框无法选中的问题

文章目录 一、问题描述&#xff1a;二、问题解决 一、问题描述&#xff1a; 使用的前端UI框架为elementUI。 el-select组件在一个弹框中&#xff0c;打开该弹框&#xff0c;el-select可以正常选中&#xff0c;但是保存弹框中的表单信息关闭弹框后&#xff0c;再打开弹框&…

Spark大数据处理讲课笔记4.1 Spark SQL概述、数据帧与数据集

零、本讲学习目标 了解Spark SQL的基本概念掌握DataFrame的基本概念掌握Dataset的基本概念会基于DataFrame执行SQL查询 在很多情况下&#xff0c;开发工程师并不了解Scala语言&#xff0c;也不了解Spark常用API&#xff0c;但又非常想要使用Spark框架提供的强大的数据分析能力…

物联网Lora模块从入门到精通(二) LED灯泡闪烁与呼吸灯

目录 一、前言 二、实践与代码 1.电亮LED1 2.熄灭LED1 3.翻转LED电平 4.LED1与LED2交替闪烁 5.LED1呼吸灯 三、程序代码 一、前言 本篇内容属于新大陆物联网Lora模块开发&#xff0c;使用给定的Lora基础例程&#xff0c;并在其基础上开发完成&#xff0c;并可为其他版本的Lo…

亚马逊云科技Serverless构建的实时数仓解决方案,助力猎豹降低30%成本

也许你也听过这样一句话&#xff1a;“21世纪什么最贵&#xff1f;人才&#xff01;”当数字经济全面席卷而来&#xff0c;这个问题的答案不可置否地变为了“数据”。通过数据分析获取近乎实时的洞察&#xff0c;以驱动业务的全流程&#xff0c;是企业数字化转型的必经之路。借…

Linux操作系统学习——启动

概要 Linux操作系统内核是服务端学习的根基&#xff0c;也是提高编程能力、源码阅读能力和进阶知识学习能力的重要部分&#xff0c;本文开始将记录Linux操作系统中的各个部分源码学习历程。 1. 理解代码的组织结构 以Linux源码举例&#xff0c;首先你得知道操作系统分为哪几个部…

【SpringCloud入门】-- 初识Eureka注册中心

目录 1.SpringCloudEureka简介 2.什么是CAP原则&#xff1f; 3.注册中心的概念&#xff1f; 4.SpringCloud其他注册中心 5.搭建Eureka注册中心 6.eureka的配置(主要是server&#xff0c;instance&#xff0c;client) 7.eureka集群概念 8.eureka集群搭建 1.SpringCloudE…

基于R语言的物种气候生态位动态量化与分布特征模拟

目录 专题一 引言 专题二 数据获取与处理方法 专题三 组合物种分布模型&#xff08;Ensemble Species Distribution Model&#xff09;的原理与使用 专题四 拓展研究 专题五 结果分析与论文写作 专题六 案例分析 专题七 总结和展望 利用R语言进行物种气候生态位动态量化…

【蓝桥杯算法题】用java遍写税收计算

【蓝桥杯算法题】用java遍写税收计算 题目&#xff1a;劳务报酬税收计算&#xff1a;输入 m &#xff0c;输出税后收入。如果 m <800&#xff0c;不扣税&#xff0c; 如果800< m <4000&#xff0e;则 m 减去800后的金额扣除20&#xff05;所得税。如果 m >4000&…

【计算机网络详解】——运输层(学习笔记)

&#x1f4d6; 前言&#xff1a;两台主机的通信&#xff0c;实际上两台主机中的应用进程进行通信&#xff0c;而在一台计算机中&#xff0c;用不同的端口号标识不同的应用进程。本节将介绍传输层的相关内容&#xff0c;包括端口号的分配方法、端口号的复用与分用、以及传输层的…

编译tolua——3、以pbc为例子,添加第三方库

目录 1、编译工具和环境说明 2、基础编译tolua 3、以pbc为例子&#xff0c;添加第三方库 4、更新luaJit 大家好&#xff0c;我是阿赵。 之前分享过怎样正常编译基础版本的tolua。这次用添加pbc为例&#xff0c;看看怎样往tolua里面添加其他的第三方库。知道了方法之后&#xf…

本地项目托管到 Gitee

本地项目托管到 Gitee 1、创建远程仓库2、Git Bash Here第一步&#xff1a;初始化本地仓库 git init第二步&#xff1a;建立链接git remote add origin xxx第三步&#xff1a;将远程仓库中的文件推送至本地仓库中git pull --rebase origin master第四步&#xff1a;将本地项目放…

【利用AI让知识体系化】简要了解网络七层协议

文章目录 一、前言引言目的和意义 二、网络七层协议简介OSI参考模型七层协议分层结构和各层协议简介 三、物理层 - Layer 1物理层概述物理层的作用物理层标准和协议 四、数据链路层 - Layer 2数据链路层概述常见的协议 五、网络层 - Layer 3网络层概述网络层的作用IP地址的作用…

cool-admin框架后端使用-node版本,线上宝塔部署

版本6.x 宝塔新建一个文件夹和创建好数据库&#xff0c;记录账号和密码&#xff0c;自行创建&#xff0c;不做说明 特别注意&#xff0c;如果用宝塔node管理那里运行&#xff0c;如果按照到有pm2的&#xff0c;要先卸载&#xff0c;不可以共存&#xff0c;会有冲突 cool-vue前端…

爬虫数据采集需要什么样的代理ip以及遇到的反爬措施

随着互联网的快速发展&#xff0c;数据已经成为许多行业中的重要资源。网络爬虫作为一种数据采集工具&#xff0c;在许多领域中得到了广泛应用。但是现在很多网站都有非常多的限制&#xff0c;所以在爬取数据的时候&#xff0c;还需要借助代理ip来助力&#xff0c;才能更好的完…

谁说不可兼得,用scrcpy实现手机免流播放bilibili投屏电脑

目前的手机大额流量卡都是支持设备免流的&#xff0c;但是如何将这个流量用在其他设备&#xff0c;就相当麻烦。于是我查找了些相关Android投屏技术资料&#xff0c;发现了一个简单的USB投屏工具——scrcpy。 安装说明 Office&#xff1a;https://github.com/Genymobile/scrcp…

深度学习之卷积神经网络(CNN)

大家好&#xff0c;我是带我去滑雪&#xff01; 卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种基于深度学习的前馈神经网络&#xff0c;主要用于图像和视频识别、分类、分割和标注等计算机视觉任务。它主要由卷积层、池化层、全连接…

手机技巧:安卓微信 8.0.38 内测版本功能一览

2023年6月14号安卓版本的微信8.0.38又开始内测了&#xff0c;今天就赶紧下载体验一下&#xff0c;下面就来给大家一一介绍&#xff0c;本次安卓微信内测版本功能更新&#xff0c;感兴趣的朋友可以文末下载体验一下&#xff01; 首先看一下官方的更新内容&#xff1a; 本次更新…

网络安全实验室|网络信息安全攻防学习平台(脚本关1-6)

传送门&#xff1a; http://hackinglab.cn/ 1. key又又不见了 点击此处开启抓包&#xff0c;send ti repeater 模块 yougotit_script_now2. 快速口算 脚本来源&#xff1a; https://blog.csdn.net/hzxtjx/article/details/125692349 import requests,re# 自动处理cookies&a…

Java+Swing+mysql仿QQ聊天工具

JavaSwingmysql仿QQ聊天工具 一、系统介绍二、功能展示1.用户登陆2.好友列表3.好友聊天4.服务器日志 三、系统实现四、其它1.其他系统实现2.获取源码 一、系统介绍 系统主要功能&#xff1a;用户登陆、好友列表、好友聊天、服务器日志 二、功能展示 1.用户登陆 2.好友列表 3…

Ubuntu远程装载硬盘Mount and unmount disk

小虎本来以为Ubuntu&#xff08;Linux系统&#xff09;不能够远程装载硬盘&#xff0c;每次重开机都要线下重新装载硬盘。但是苦于工作站不在身边&#xff0c;跑来跑去太麻烦&#xff0c;所以想远程装载一下。 解决方法 遍历搜索所有硬盘。找到对应名字。 lsblk创建一个文件…