浅谈Verilog代码的执行顺序

news2025/1/17 5:58:00

一、组合逻辑和时序逻辑

         数字电路可以分成两大类,一类叫组合逻辑电路,另一类叫做时序逻辑电路。

        组合逻辑电路:由门电路组成,其某一时刻的输出状态只与该时刻的输入状态有关,而与电路原来的状态无关,并没有记忆功能。

        时序逻辑电路:由锁存器、触发器和寄存器等单元组成,其某一时刻的输出状态不仅与该时刻的输入状态有关,而且与电路原来的状态有关,具有记忆功能。

        而组合逻辑电路和时序逻辑在FPGA中并行执行这是毋庸置疑的,唯一不同的就是组合逻辑只要信号发生改变就随便改变,时序逻辑则需要随着时钟的上升沿或下降沿的到来而改变。

assign  result1 = a & b;

always @(*) begin
	if (~reset_n) begin
		result2 = 0;
	end
	else begin
		result2 = a & b;
	end
end

always @(posedge clk or negedge reset_n) begin
	if (~reset_n) begin
		result3 <= 0;
	end
	else begin
		result3 <= a & b;
	end
end

在第一个clk时reset_n低电平有效,因此result2和result3被置为0,而result1则因为assign语句被置为1;

在第一个clk结束后reset_n变为高电平,因此result2被置为1,result3则因为没有检测到clk上升沿仍被置为0;

在第二个clk结束后a从1变为0,此时组合逻辑的result1和result2随之变化,而时序逻辑result3则在第三个上升沿clk时被置为0。

二、阻塞赋值和非阻塞赋值

        阻塞赋值(=):在赋值时,先计算等号右手部分的值,再赋值给左边变量,直到该语句赋值完成,后面的语句才能执行,会阻塞后面的语句。

        非阻塞赋值(<=):执行赋值语句右边,然后将begin-end之间的所有赋值语句同时赋值到赋值语句的左边,但是左边的变量的值不会立即更新,直到always块所有语句执行完,才将左边变量的值更新。

always @(posedge clk or negedge reset_n) begin
	if (~reset_n) begin
			b <= 0;
			c <= 0;
		end
	else begin
		b <= a;
		c <= b;
	end
end 

always @(*) begin
	e = d;
	f = e;
end 

        可以简单这么理解:阻塞赋值时顺序执行,前一句执行完后才会执行后一句;非阻塞赋值时并行执行。

        这样也就很好解释了为什么什么是打拍。如下图仿真所示

非阻塞赋值:

在第一个clk时, reset_n为低电平有效,因此b、c被置为0;

在第二个clk时,执行赋值语句,因为是非阻塞赋值,将b的值置为a、将c的值置为b(注意此时b不等2,b的值仍为0),赋值结束后b=2、c=0

在第三个clk时,执行赋值语句,此时b=2,将c的值置为b,因此赋值结束后c=2

阻塞赋值:

先执行e=d,执行完毕后e=3;

再执行f=e,执行完毕后f=3。

         通过RTL图也可以理解,非阻塞赋值被综合成了两个D触发器,因此当一个clk到来时寄存器的值通过Q输出到c中,即b原来的0值,而a的值通过寄存器的D存进b里,;第二个clk到来时才会将b后来更新的值送到c寄存器。而阻塞赋值则被综合成了连线哈哈^_^

 针对阻塞赋值和非阻塞赋值的使用:

1、时序逻辑,使用非阻塞赋值;

2、锁存器建模,使用非阻塞赋值;

3、组合逻辑,使用阻塞赋值;

4、当在同一个always块里面既为组合逻辑又为时序逻辑时,使用非阻塞赋值;

5、组合逻辑输出时,为消除毛刺会在输出端加一个触发器,即使用非阻塞赋值。

三、组合逻辑和时序逻辑中的if语句

3.1 if else语句

       if-else具有优先级,只有if或者当前一级else if的条件不满足才会进行后面的判断,否则就直接执行当前条件下的语句

always @(posedge clk or negedge reset_n) begin
	if (~reset_n) begin
		result <= 0;
	end
	else  if(a==0) begin
		result <= 0;
	end else if(a==1) begin
		result <= 1;
	end else begin
		result <= 2;
	end
end

         还需要注意的是if-else不能执行多个条件,如果所有的condition1、condition2、condition3都为真,程序会首先判断condition1为真,执行statement1,然后就会退出执行

always @(posedge clk or negedge reset_n) begin
	if (condition1) begin
        state1;
	end
	else  if(condition2) begin
		state2;
	end else if(condition3) begin
		state3;
	end else begin
		state2;
	end
end

3.2 多if语句

         首先要再次明确一点的就是FPGA是并行执行的,在FPGA里是没有顺序执行这个概念的,但按照Verilog语法规定always里面的代码是顺序的,即按照代码顺序推理逻辑并综合。经常看到有文章说多个always块并行执行、always块内顺序,这个说法并不完全对的。

        而当always块中有多个if语句时,程序是并行执行的,可以看到在组合逻辑中当三个sel信号有效时,三个result分别同时被a、b、c赋值,从RTL图中也能看出代码被编译器综合成为三个独立的二选一多路选择器,说明三个if语句确实是并行执行的。

always @(*) begin
	result1 = 0;   //避免产生latch
	result2 = 0;
	result3 = 0;
	if (sel1)
		result1 = a;
	if (sel2)
		result2 = b;
	if (sel3)
		result3 = c;
end

        而在时序逻辑中生成的RTL图,因为需要用到时钟,因此在二选一多路选择器后多了一个D触发器,但仍然是并行运行了三个if语句。

always @(posedge clk) begin
	result1 <= 0;
	result2 <= 0;
	result3 <= 0;
	if (sel1)
		result1 <= a;
	if (sel2)
		result2 <= b;
	if (sel3)
		result3 <= c;
end

         但是有一种情况需要注意,一些文章说多if语句也具有优先级,这种说法是错误的。因为在他们的代码中错误的写法形成了多重驱动。比如以下多if写法,如果多个if语句相互独立,那么被赋值的变量只能出现在一个if语句里,否则就会产生多重驱动。但是这样写仍然会被编译器综合成功,因为编译器按代码顺序推理逻辑,result=c最后被推理综合,因此优先级最高,最后综合成顺序执行的硬件电路。

always @(*) begin
	result = 0;
	if (sel1)
		result = a;
	if (sel2)
		result = b;
	if (sel3)
		result = c;
end

        从仿真的结果也能看出,当三个sel同时有效时,result被优先级最高的c赋值。但这样的写法时错误的,应尽量避免。

3.3 if语句和赋值语句

        if语句和赋值语句单独拎开其实没什么好说的,但是如果放一块就需要注意一些细节地方。如下面代码,always块中的count计数在每个CLK的上升沿加一,有两个if语句的判断,这里再次提醒FPGA是并行执行的,并且赋值语句和if语句没有优先级关系,因此加一和判断是同时执行的。

always @(posedge clk or negedge reset_n) begin
	if (~reset_n) begin
		count <= 0;
		result1 <= 0;
		result2 <= 0;
	end
	else begin
		count <= count + 1'b1;
		if (count == 8'd0) begin
			result1 <= 8'd66;
		end
		if (count == 8'd5) begin
			result2 <= 8'd88;
		end
	end
end

在第一个clk时,reset_n低电平有效,因此count、result1、result2都被置为0;

在第二个clk时,count=count+1=0+1、同时if(count==8'd0)有效(此时count仍为0),赋值和if语句判断并行执行

直到第六个clk,count=count+1=5+1、同时if(count==8'd5)有效(此时count仍为5),赋值和if语句判断并行执行

        从RTL图中也能看出,编译器综合出了三个没有优先级关系的的D触发器,如果这方便还不理解的话可以去看FPGA时序分析与时序约束(一)-CSDN博客中建立时间和保持时间的概念去加深理解。

3.4 case语句

        再浅说一个case语句,case语句条件选项可以有多个,而且这些条件选项不要求互斥。虽然这些条件选项是并发比较的,但执行效果是谁在前且条件为真谁被执行

always@(*) begin
	case(sel)
		2'd0 : result = a;
		2'd1 : result = b;
		2'd1 : result = c;
		2'd2 : result = d;
		default : result = 0; //在组合电路中,如果所有分支没有列出,且不使用default,则会生成latch
	endcase
end

        如仿真所示

当sel=0时,程序执行2'd0 : result = a = ;

当sel=3时,程序执行default : result = 0;

当sel=1时,由于2'd1 : result = b比2'd1 : result = c在程序中的位置更靠前,因此执行2'd1 : result = b = 2。

四、 时序逻辑中的清零及保持

         在时序逻辑电路中,如果没有对信号进行赋值更改,那么信号就会一直保持上一个周期的值,因此尽量将每个信号在每个分支都写出赋值。

always @(posedge clk or negedge reset_n) begin
	if (~reset_n) begin
		start 	<= 1'd0;
		finish	<= 1'd0;
		count 	<= 8'd0;
	end
	else if (start_en) begin
		start	<= 1'd1;
	end else if (start) begin      //start一直为1因此一直计数
		count	<= count + 1'd1;
	end else if (count==10) begin
		start 	<= 1'd0;
		finish	<= 1'd1;
		count 	<= 8'd0;
	end else begin
		start 	<= 1'd0;
		finish	<= 1'd0;
		count 	<= 8'd0;
	end
end

        如以下代码。

当start_en高电平时,将start置为1;

在下一个clk到来时start为高电平进入计数,但是在这个分支仅对count进行了自增赋值 ,那么就会根据if else的优先级,always块会一直执行计数代码,忽略下面count==10的判断,而该分支没有对start进行处理,则start一直保持上一个周期的高电平

        因此针对这段代码可以将count==10的判断优先级提高,先进行清零的判断,再进行计数。

always @(posedge clk or negedge reset_n) begin
	if (~reset_n) begin
		start 	<= 1'd0;
		finish	<= 1'd0;
		count 	<= 8'd0;
	end
	else if (start_en) begin
		start	<= 1'd1;
	end else if (count==10) begin
		start 	<= 1'd0;
		finish	<= 1'd1;
		count 	<= 8'd0;
	end else if (start) begin
		count	<= count + 1'd1;
	end else begin
		start 	<= 1'd0;
		finish	<= 1'd0;
		count 	<= 8'd0;
	end
end

五、组合逻辑中的latch

        锁存器(Latch)是一种电平触发的存储单元,数据存储的动作取决于输入时钟(或者使能)信号的电平值,即当其使能端端口有效时将输入传递给输出、当其使能端口无效时输出保持不变。

5.1 为什么会产生latch

        在FPGA中构建组合逻辑电路,但此时就可能由于代码的不规范导致编译器综合出了带有锁存器的组合逻辑电路,产生的原因就是verilog代码中存在保持不变的情况,而组合逻辑时没有记忆功能的,为了记录当前的状态,就会引入锁存器来实现保持不变。

        那么反映在verilog代码中,如果没有为每个状态都提供完整的赋值,系统将使用上一个状态的值,这可能导致电路中的某些元素在某些条件下保持其前一个状态,从而形成Latch;又或者在条件语句中没有覆盖所有可能的输入条件,那么系统将使用未覆盖条件下的上一个状态,从而引入Latch。

always @(*)
begin
    if (condition1)
        // 处理condition1
    // 缺少对condition2的处理
end

5.2 为什么要避免latch

        信号由于经由不同路径传输达到某一汇合点的时间有先有后的现象,就称之为竞争;由于竞争现象所引起的电路输出发生瞬间错误的现象,就称之为冒险。有竞争不一定有冒险,但出现了冒险就一定存在竞争。发生冒险时往往会出现一些不正确的尖峰信号,这些尖峰信号就是“毛刺”。

        而latch的输出与使能信号有关, 当Latch的使能信号有效时,使得输出完全输入,输入状态可能多次变化,容易产生毛刺,增加了下一级电路的不确定性。如果毛刺刚好被采样到,那么电路的逻辑就有可能出现错误。

        其次在FPGA中只有查找表LUT和触发器FF,没有锁存器LATCH资源,因此需要用LUT去模拟LATCH,导致占用更多的逻辑资源;并且锁存器不能异步复位,上电后处于不定态;锁存器没有时钟信号参与信号传递,使得静态时序分析变得更加复杂

5.3 如何避免产生latch

5.3.1 使用完整的if-else语句

        在组合逻辑中,不完整的if-else语句会产生latch。如下代码所示,当condition1为真时,执行q=d;但condition1不为真,系统会默认else分支下的q保持不变,导致产生latch。

always @(*)begin
	if (condition1) begin
		q = d;
	end
end

        如图所示:

在最开始,当d=0、condition=0时, q处于未知态(因为没有赋初值);

当d=1、condition=1时,执行q=d,此时q=1;

当d=0、condition=0时,由于代码中没有condition=0的分支,系统不对q进行赋值,保持q=1,此时就产生了latch。

        从仿真结果可知这样的latch有两种避免方法:1.补全if-else语句;2.对输出赋初值

//方法1
always @(*)begin
	if (condition1) begin
		q = d;
	end else
        q = 0;
end

//方法2
always @(*)begin
    q = 0;
	if (condition1) begin
		q = d;
	end
end

        此外当有多个信号时,需要将每个信号在每个分支都写出赋值,否则也会因if-else结构不完整导致产生latch。

//因if-else不完整产生latch
always @(*)begin
	if (condition1) begin
		q1 = d1;
	end else
        q2 = d2;
end

//将每个信号在每个分支都赋值
always @(*)begin
    //q1 = 0; q2 = 0;   //方法1
	if (condition1) begin
		q1 = d1;
        q2 = 0;
	end else            //方法2
        q1 = 0;
        q2 = d2
end

5.3.2 使用完整的case语句

        case语句产生的原因同if-else语句相同,如下所示,还有一种2'b11的情况没有写出

always @(*)begin
	case(sel)
		2'b00 : q = 4'b0001;
		2'b01 : q = 4'b0010;
		2'b10 : q = 4'b0100;
	endcase
end

        因此case语句同样有两种方法避免latch:1.将case语句选项补全;2.用default关键字来代替其他选项结果。

always @(*)begin
	case(sel)
		2'b00 : q = 4'b0001;
		2'b01 : q = 4'b0010;
		2'b10 : q = 4'b0100;
        2'b11 : q = 4'b1000;
	endcase
end

always @(*)begin
	case(sel)
		2'b00 : q = 4'b0001;
		2'b01 : q = 4'b0010;
		2'b10 : q = 4'b0100;
        default : q = 4'b1000;
	endcase
end

5.3.3 输出变量不能赋值给自己

        在组合逻辑中,如果一个信号的赋值源头包含该信号本身,或者判断条件中有其信号本身的逻辑,则也会产生 Latch。因为此时信号具有存储功能,但是没有时钟驱动。这种产生latch的方式在 if 语句、case 语句、三元表达式中都可能出现,所以要避免这种写法。

always @(*)begin
	case(sel)
		2'b00 : q = q;				//产生latch
		2'b01 : q = 4'b0010;
		2'b10 : q = 4'b0100;
		default : q = 4'b1000;
	endcase
end

always @(*) begin
	if (q & sel)  q = 1'b1 ;    //产生latch
	else q = 1'b0 ;
 end

assign    q =  (sel && q) ? 1'b0 : 1'b1 ;  //产生latch

5.3.4 使用完整的敏感信号列表

        有时使用always@(a,b,c,d),但敏感列表没有列全,那么还是会保存之前的输出结果,产生latch。因此需要把敏感信号补全或者直接用 always@(*)。

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

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

相关文章

基于Java SSM框架实现健康管理系统项目【项目源码】

基于java的SSM框架实现健康管理系统演示 JSP技术 JSP是一种跨平台的网页技术&#xff0c;最终实现网页的动态效果&#xff0c;与ASP技术类似&#xff0c;都是在HTML中混合一些程序的相关代码&#xff0c;运用语言引擎来执行代码&#xff0c;JSP能够实现与管理员的交互&#xf…

AI模型私人订制

使用AI可以把你的脸换成明星的脸&#xff0c;可以用于直播、录播。 AI换脸1 也可以把视频中明星的脸换成你的脸 AI换脸2 之所以能够替换成功&#xff0c;是因为我们有一个AI人物模型&#xff0c;AI驱动这个模型就可以在录制视频的时候替换指定人物的脸。AI模型从哪里来&…

c++写入数据到文件中

假设你想编写一个C程序&#xff1a;当你在调试控制台输入一些数据时&#xff0c;系统会自动存入到指定的文件中&#xff0c;该如何操作呢&#xff1f; 具体操作代码如下&#xff1a; #include<iostream> #include<string> #include<fstream> using namespa…

Elasticsearch:在不停机的情况下优化 Elasticsearch Reindex

实现零停机、高效率和成功迁移更新的指南。更多阅读&#xff1a;Elasticsearch&#xff1a;如何轻松安全地对实时 Elasticsearch 索引 reindex 你的数据。 在使用 Elasticsearch 的时候&#xff0c;总会有需要修改索引映射的时候&#xff0c;遇到这种情况&#xff0c;我们只能做…

作业--day39

定义一个Person类&#xff0c;私有成员int age&#xff0c;string &name&#xff0c;定义一个Stu类&#xff0c;包含私有成员double *score&#xff0c;写出两个类的构造函数、析构函数、拷贝构造和拷贝赋值函数&#xff0c;完成对Person的运算符重载(算术运算符、条件运算…

十八、任务通知

1、前言 (1)所谓“任务通知”&#xff0c;可以反过来读"通知任务"。我们使用队列、信号量、事件组等等方法时&#xff0c;并不知道对方是谁。使用任务通知时&#xff0c;可以明确指定&#xff1a;通知哪个任务。 (2)使用队列、信号量、事件组时&#xff0c;我们都需…

C++初阶——权限与继承

目录 一、C权限方面的问题 1.访问权限 2.继承机制 二、Cconst引用 const引用有以下几个特点 临时对象引用 常量引用成员变量 二、c引用空间相关问题 三.auto 一、C权限方面的问题 【C入门】访问权限管控和继承机制详解_权限继承功能-CSDN博客文章浏览阅读840次。(2)但…

C#中string.ToUpper()和string.ToLower()的用法

目录 一、关于ToUpper()和ToLower() 1.ToUpper() 2.ToLower() 3.小结 二、实例 三、生成效果 一、关于ToUpper()和ToLower() 1.ToUpper() 使用字符串对象的ToUpper方法可以将字符串中的字母全部转换为大写。 string P_str_book "mingribook".ToUpper()…

简单Diff算法

简单Diff算法 渲染器的核心 Diff算法 解决的问题 比较新旧虚拟节点的子节点&#xff0c;实现最小化更新。 虚拟节点key属性的作用 就像虚拟节点的“身份证号”&#xff0c;在更新时&#xff0c;渲染器会通过key属性找到可复用的节点&#xff0c;然后尽可能地通过DOM移动操…

Spring Boot Admin健康检查引起的Spring Boot服务假死

问题现象 最近在spring boot项目中引入了 spring-boot-starter-actuator 后&#xff0c;测试环境开始出现服务假死的现象&#xff0c; 且这个问题十分怪异&#xff0c;只在多个微服务中的简称A的这个服务中出现&#xff0c;其他服务都没有出现这个问题&#xff0c; 之所以说…

Proxmox Backup Server(PBS)从2.X升级到PBS3

作者&#xff1a;田逸&#xff08;formyz&#xff09; 2023年11月31日&#xff0c;Proxmox 官方正式发布Proxmox Backup Server 3.1版本。现在我负责管理的Proxmox Backup Server&#xff08;以下简称PBS&#xff09;版本号为2.3&#xff0c;打算将部分PBS升级到PBS 3.1&#x…

微信小程序自定义步骤条效果

微信小程序自定义一个步骤条组件&#xff0c;自定义文字在下面&#xff0c;已完成和未完成和当前进度都不一样的样式&#xff0c;可点击上一步和下一步切换流程状态&#xff0c;效果如下。 这是视频效果&#xff1a; 前端实现步骤条效果 下面我们一步步实现编码&#xff0c;自定…

【拆盲盒02】算法题

题目 : 假设顺序表L中的元素按从小到大的次序排列&#xff0c;编写算法删除顺序表中“多余”的数据元素&#xff0c;即操作之后的顺序表中所有元素的值都不相同&#xff0c;要求时间尽可能的少&#xff1b;并对顺序表A&#xff08;1&#xff0c;1&#xff0c;2&#xff0c;2&am…

【js】js解析Token:

一、效果&#xff1a; 二、实现&#xff1a; export function getTokenObject(token) {//通过split()方法将token转为字符串数组,数组中的第二个字符进行解析return token ? JSON.parse(decodeURIComponent(escape(window.atob(token.split(".")[1].replace(/-/g &…

在 Spring 中操作 Redis

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信您对博主首页也很感兴趣o (ˉ▽ˉ&#xff1b;) &#x1f4dc;redis和缓存及相关问题和解决办法 什么是缓存预热、缓存穿透、缓存雪崩、缓存击穿 目录 1、引入依赖 2、对 Redis 的配置文件进行书写 3、S…

2022年中职组“网络安全”赛项湖南省B-3——私钥泄漏

B-3&#xff1a;应用服务漏洞扫描与利用 任务环境说明&#xff1a;需要环境有问题可以加q 服务器场景&#xff1a;Server15服务器场景操作系统&#xff1a;未知&#xff08;关闭链接&#xff09; 使用命令nmap探测目标靶机的服务版本信息&#xff0c;将需要使用的参数作为FLA…

UIToolKit使用心得

起因 因为那个uitoolkit自己写了一套graphView&#xff0c;所以想着来用用但是用完之后发现也不过如此 怎么构建自己的组件 我在继承Node之后想修改node的样式该怎么办呢是这样的。先用pick点击默认的node节点元素- 在pick默认创建的node节点之后&#xff0c;可以把它的uxml…

【产品设计】信息建设三驾马车:PLM系统拆解

本篇文章将介绍PLM的基础信息、发展及模块功能等内容&#xff0c;让大家对PLM有一个全面、完整地了解&#xff0c;方便在后期的工作中能快速地使用其解决方案&#xff0c;希望本篇文章能对你有所帮助。 PLM系统主要实现产品模块业务&#xff0c;既包含产品的创意设计、样品打样…

阶段十-分布式-docker虚拟化容器

第一章 Docker简介 1.2 节 Docker理念 Docker是基于Go语言实现的云开源项目&#xff1b;通过对应用组件的封装、分发、部署、运行等生命周期的管理&#xff0c;使用户的APP&#xff08;可以是一个WEB应用或数据库应用等等&#xff09;及其运行环境能够做到“一次封装&#xf…

《PCI Express体系结构导读》随记 —— 第I篇 第1章 PCI总线的基本知识(18)

接前一篇文章&#xff1a;《PCI Express体系结构导读》随记 —— 第I篇 第1章 PCI总线的基本知识&#xff08;17&#xff09; 1.4 PCI总线的中断机制 1.4.2 中断信号与PCI总线的连接关系 在PCI总线中&#xff0c;INTx信号属于边带信号。所谓边带信号是指这些信号在PCI总线环境…