当前位置: 首页 > news >正文

Verilog基础(三):过程

过程(Procedures)

- Always块 – 组合逻辑 (Always blocks – Combinational)

由于数字电路是由电线相连的逻辑门组成的,所以任何电路都可以表示为模块和赋值语句的某种组合.

然而,有时这不是描述电路最方便的方法.

两种always block是十分有用的:

  • 组合逻辑: always @(*)
  • 时序逻辑: always @(posedge clk)

always @(*)就相当于赋值语句–assign,因此选择哪一种语法仅仅取决与方便程度.

block内还有更丰富的语句集,比如if-else,case等等.但不能包含连续赋值即不可包含assign,因为他与always @(*)冲突.

以下语句是等价的

assign out1 = a & b | c ^ d;
always @(*) out2 = a & b | c ^ d;

  • Module Declaraction
module top_module(input a, input b,output wire out_assign,output reg out_alwaysblock
);
  • Solution
// synthesis verilog_input_version verilog_2001
module top_module(input a, input b,output wire out_assign,output reg out_alwaysblock
);assign out_assign = a&b;always @(*) out_alwaysblock = a&b;
endmodule
- Always块 – 时序逻辑 (Always blocks – Clocked)

verilog中有三种赋值方式:

  • 连续赋值: assign x = y; 不能在always-block内使用
  • 阻塞赋值: x = y;, 只能在always-block内使用
  • 非阻塞赋值: x <= y,只能在always-block内使用

请在组合逻辑中使用阻塞赋值,在时序逻辑中使用非阻塞赋值
否则将产生难以发现的错误

请实现如下电路:

  • Module Declaraction
module top_module(input clk,input a,input b,output wire out_assign,output reg out_always_comb,output reg out_always_ff   );
  • Solution
// synthesis verilog_input_version verilog_2001
module top_module(input clk,input a,input b,output wire out_assign,output reg out_always_comb,output reg out_always_ff   );
assign out_assign = a^b;always @(*) out_always_comb = a^b;always @(posedge clk) out_always_ff <= a^b;
endmodule
- If语句

if语句通常创建一个2对1的多路选择器,如果条件为真,则选择一个输入,如果条件为假,则选择另一个输入.

以下两种写法是等价的:

always @(*) beginif (condition) beginout = x;endelse beginout = y;end
endassign out = (condition) ? x : y;

建立一个在a和b之间选择的2对1多路选择器.如果sel_b1和sel_b2都为真,则选择b.否则,选择a.

执行相同的操作两次,一次使用assign语句,一次使用if语句.

  • Module Declaraction
module top_module(input a,input b,input sel_b1,input sel_b2,output wire out_assign,output reg out_always   ); 
  • Solution
// synthesis verilog_input_version verilog_2001
module top_module(input a,input b,input sel_b1,input sel_b2,output wire out_assign,output reg out_always   ); assign out_assign = (sel_b1&sel_b2)?b:a;always @(*) beginif(sel_b1&sel_b2) beginout_always = b;endelse beginout_always = a;endend
endmodule
- If语句引发的锁存(latches)

以下代码包含锁存的错误行为.

  • Module Declaraction
always @(*) beginif (cpu_overheated)shut_off_computer = 1;
endalways @(*) beginif (~arrived)keep_driving = ~gas_tank_empty;
end
  • Solution
// synthesis verilog_input_version verilog_2001
module top_module (input      cpu_overheated,output reg shut_off_computer,input      arrived,input      gas_tank_empty,output reg keep_driving  ); //always @(*) beginif (cpu_overheated) beginshut_off_computer = 1;endelse beginshut_off_computer = 0;endendalways @(*) beginif (~arrived&~gas_tank_empty) beginkeep_driving = ~gas_tank_empty&(~arrived);endelse beginkeep_driving = ~(gas_tank_empty|arrived);endendendmodule

If语句在硬件描述语言(如Verilog)中用于描述受条件控制的电路。然而,不当的If语句使用可能会引发锁存器(latches)的产生,这在FPGA或ASIC设计中通常是不希望看到的。以下是对If语句引发锁存器的详细分析:

  • 一、锁存器的基本概念

锁存器是一种在异步电路系统中对输入信号电平敏感的单元,用来存储信息。当锁存信号有效时,数据被锁存,输入信号不再起作用。锁存器也被称为透明锁存器,因为不锁存时输出对于输入是透明的。

  • 二、If语句引发锁存器的情况

    在Verilog中,If语句引发锁存器的情况主要包括以下几种:

    1. 组合逻辑中If语句缺少Else分支

      • 在组合逻辑电路中,如果If语句没有覆盖所有可能的条件,并且没有提供Else分支来指定其他条件下的输出,那么综合工具可能会推断出一个锁存器行为来保持上一个状态。
      • 例如,if (enable) reg <= data;enable为假时,reg的值将保持不变,这可能导致锁存器的产生。
    2. 敏感信号列表不完整

      • always块中使用非阻塞赋值(<=)时,如果没有显式的敏感信号列表或者敏感信号列表不完整,也可能导致锁存器的产生。
      • 这是因为综合器可能无法正确判断何时应更新信号,从而推断出锁存器行为来保持信号状态。
    3. 输出变量赋值给自己

      • 在If语句或组合逻辑中,如果输出变量被赋值给自己(即赋值表达式中包含输出变量自身),也可能导致锁存器的产生。
      • 这是因为输出变量需要具有存储功能来保持其上一个状态。
  • 三、避免If语句引发锁存器的策略

    为了避免If语句引发锁存器,可以采取以下策略:

    1. 确保If语句结构完整

      • 在组合逻辑中,确保If语句覆盖所有可能的条件,并提供Else分支来指定其他条件下的输出。
      • 这有助于确保输出在所有条件下都有一个已知的状态,从而避免锁存器的产生。
    2. 使用阻塞赋值明确表达组合逻辑

      • 在组合逻辑中,使用阻塞赋值(=)来明确表达逻辑关系,而不是使用非阻塞赋值。
      • 这有助于综合工具正确识别组合逻辑并避免推断出锁存器。
    3. 完善敏感信号列表

      • 在使用非阻塞赋值时,确保always块有完整的敏感信号列表。
      • 这有助于综合工具正确判断何时应更新信号并避免锁存器的产生。
    4. 避免输出变量赋值给自己

      • 在组合逻辑中,避免将输出变量赋值给自己。
      • 如果需要保持上一个状态,可以考虑使用触发器(Flip-Flop)而不是锁存器。
  • 四、锁存器的危害与替代方案

锁存器在FPGA或ASIC设计中可能带来以下危害:

  1. 不可预测的时序行为:锁存器的输出取决于输入信号的持续电平,而不是特定的时钟边沿,这使得时序分析和预测更加困难。
  2. 系统不稳定:由于锁存器的输出直接由输入决定,任何输入上的噪声或毛刺都会立即反映到输出上,可能导致系统不稳定或误操作。
  3. 资源利用率降低:FPGA内部的锁存器实现通常不如寄存器高效,可能降低资源利用率并增加功耗。

因此,在FPGA设计中,通常推荐使用寄存器(触发器)来代替锁存器,除非有特殊的应用场景要求锁存器的使用。寄存器在时钟边沿更新,提供了更可预测和稳定的行为,便于时序分析和设计验证。

综上所述,If语句在硬件描述语言中的使用需要谨慎,以避免引发不必要的锁存器。通过确保If语句结构完整、使用阻塞赋值明确表达组合逻辑、完善敏感信号列表以及避免输出变量赋值给自己等策略,可以有效避免锁存器的产生。


- Case语句

verilog中的case语句几乎等同于if elseif else的序列,该序列将一个表达式与其他表达式列表进行比较.它的语法和功能与C语言中的switch语句不同.

always @(*) begin     // This is a combinational circuitcase (in)1'b1: begin out = 1'b1;  // begin-end if >1 statementend1'b0: out = 1'b0;default: out = 1'bx;endcase
end
  • case语句以case开头,每个"case item"以冒号结尾,没有switch
  • 每个case项只能执行一条语句.这使得C中使用的“break”不必要.但这意味着如果需要多个语句,必须使用begin…end

如果有大量选项的情况,case语句比if语句更方便.因此,在本练习中,创建一个6对1的多路选择器.当sel介于0和5之间时,选择相应的数据输入,否则,输出0.数据输入和输出均为4位宽.小心锁存.

  • Module Declaraction
module top_module ( input [2:0] sel, input [3:0] data0,input [3:0] data1,input [3:0] data2,input [3:0] data3,input [3:0] data4,input [3:0] data5,output reg [3:0] out   );
  • Solution
// synthesis verilog_input_version verilog_2001
module top_module ( input [2:0] sel, input [3:0] data0,input [3:0] data1,input [3:0] data2,input [3:0] data3,input [3:0] data4,input [3:0] data5,output reg [3:0] out   );//always@(*) begin  // This is a combinational circuitcase(sel)3'b0: beginout = data0;end3'b001: beginout = data1;end3'b010: beginout = data2;end3'b011: beginout = data3;end3'b100: beginout = data4;end3'b101: beginout = data5;enddefault: beginout[3:0] = 0;endendcaseendendmodule
- 简单编码器1

priority encoder是一种组合电路,当输入一个vector时,输出第一个’1’出现的位置.例如:输入8’b10010000,输出3’d4,因为[4]是第一个高位.
构建一个4位encoder,若全是低位则输出0.

  • Module Declaraction
module top_module (input [3:0] in,output reg [1:0] pos  );
  • Solution
// synthesis verilog_input_version verilog_2001
module top_module (input [3:0] in,output reg [1:0] pos  );always @(*) beginif (in[0] == 1'b1) beginpos = 2'd0;endelse beginif(in[1] == 1'b1) beginpos = 2'd1;endelse beginif(in[2] == 1'b1) beginpos = 2'd2;endelse beginif(in[3] == 1'b1) beginpos = 2'd3;endelse beginpos = 0;endendendendend
endmodule
- 简单编码器2

假如现在输入是8位,那么就会有256种情况,我们可以使用casez来将item减少到9种.
例如:

always @(*) begincasez (in[3:0])4'bzzz1: out = 0;   // in[3:1] can be anything4'bzz1z: out = 1;4'bz1zz: out = 2;4'b1zzz: out = 3;default: out = 0;endcase
end
  • Module Declaraction
module top_module (input [7:0] in,output reg [2:0] pos  );
  • Solution
// synthesis verilog_input_version verilog_2001
module top_module (input [7:0] in,output reg [2:0] pos  );
always @(*) begincasez (in[7:0])8'bzzzzzzz1: beginpos = 3'd0;end8'bzzzzzz1z: beginpos = 3'd1;end8'bzzzzz1zz: beginpos = 3'd2;end8'bzzzz1zzz: beginpos = 3'd3;end8'bzzz1zzzz: beginpos = 3'd4;end8'bzz1zzzzz: beginpos = 3'd5;end8'bz1zzzzzz: beginpos = 3'd6;end8'b1zzzzzzz: beginpos = 3'd7;enddefault: beginpos = 0;endendcase
end
endmodule
- 避免锁存

假设您正在构建一个电路来处理游戏中PS/2键盘的扫描代码.

接收到的最后两个字节的扫描代码,您需要判断是否已按下键盘上的一个箭头键.这涉及到一个相当简单的映射,它可以使用一个case语句(或者如果elseif)实现,有四个case.

Scancode [15:0]Arrow key
16’he06bleft arrow
16’he072down arrow
16’he074right arrow
16’he075up arrow
Anythingelse none

为了避免创建锁存,必须在所有可能的条件下为所有输出分配一个值

  • Module Declaraction
module top_module (input [15:0] scancode,output reg left,output reg down,output reg right,output reg up  ); 
  • Solution
// synthesis verilog_input_version verilog_2001
module top_module (input [15:0] scancode,output reg left,output reg down,output reg right,output reg up  ); always @(*) beginleft = 0;down = 0;left = 0;right = 0;case (scancode)16'he06b: beginleft = 1;end16'he072: begindown = 1;end16'he074: beginright = 1;end16'he075: beginup = 1;enddefault: beginup = 0;down = 0;left = 0;right = 0;endendcaseend
endmodule

更多语法特点

verilog也有像C一样的三目算符:

- 三目算符

verilog也有像C一样的三目算符:

condition ? true : false;

给定四个无符号数,求其最小值.

  • Module Declaraction
module top_module (input [7:0] a, b, c, d,output [7:0] min);
  • Solution
module top_module (input [7:0] a, b, c, d,output [7:0] min);//// assign intermediate_result1 = compare? true: false;wire [7:0]min1,min2;assign min1=(a<b?a:b);assign min2 = (min1<c?min1:c);assign min = (min2<d?min2:d);
endmodule
- 优化运算1

奇偶校验经常被用来作为一种简单的方法检测错误.

创建一个电路,该电路将为一个8位字节计算一个奇偶校验位.

即计算输入8个位的异或

  • Module Declaraction
module top_module (input [7:0] in,output parity); 
  • Solution
module top_module (input [7:0] in,output parity); assign parity = ^in[7:0];
endmodule
- 优化运算2

建立如下电路:

  • out_and: 对输入数据求与

  • out_or: 对输入数据求或

  • out_xor:对输入数据求异或

  • Module Declaraction

module top_module( input [99:0] in,output out_and,output out_or,output out_xor 
);
  • Solution
module top_module( input [99:0] in,output out_and,output out_or,output out_xor 
);assign out_and = &in[99:0];assign out_or = |in[99:0];assign out_xor = ^in[99:0];
endmodule
- 循环 – 组合逻辑:实现Vector反转

反转vector顺序

  • Module Declaraction
module top_module( input [99:0] in,output [99:0] out
);
  • Solution
module top_module( input [99:0] in,output [99:0] out
);integer i;always @(*) beginfor(i=0;i<=99;i=i+1)out[7'd99-i] <= in[i];end
endmodule
- 循环 – 组合逻辑:实现255位计数器

计算vector中1的个数

  • Module Declaraction
module top_module( input [254:0] in,output [7:0] out );
  • Solution
module top_module( input [254:0] in,output [7:0] out );integer i;reg [7:0]count;always @(*) begincount=0;for(i=0;i<=254;i=i+1) beginif(in[i] == 1) begincount = count + 7'b1;endendendassign out = count;
endmodule
- 循环:实现100位加法器

通过实例化100个全加器构建一个100位加法器.

  • Module Declaraction
module top_module( input [99:0] a, b,input cin,output [99:0] cout,output [99:0] sum );
  • Solution
module top_module( input [99:0] a, b,input cin,output [99:0] cout,output [99:0] sum );reg [100:0]cin1;generategenvar i;for(i=0;i<100;i=i+1) begin:addsif(i==0) begin        add ins(a[i],b[i],cin,sum[i],cout[i]);            assign cin1[i+1]=cout[i];end            else beginadd ins(a[i],b[i],cin1[i],sum[i],cout[i]);assign cin1[i+1]=cout[i];            endendendgenerate
endmodulemodule add(input a, input b, input cin, output sum, output cout);assign {cout,sum}=a+b+cin;
endmodule
- 循环:实现100位BCD加法器

在Verilog中实现一个100位的BCD(Binary-Coded Decimal)加法器相对复杂,因为BCD编码的每个数字占用4位二进制数,所以100位的BCD数实际上表示的是25位的十进制数(100位 / 4位/十进制数字 = 25个十进制数字)。

BCD加法的一个关键问题是进位处理。在普通的二进制加法中,进位是逐位传递的,但在BCD加法中,每四位(一个BCD数字)之间可能需要额外的调整来处理从低位到高位的进位,以确保结果仍然是有效的BCD数。这通常涉及到将非BCD的中间结果转换为BCD格式。

以下是一个简化的Verilog代码示例,用于实现两个4位BCD数的加法,并调整结果以确保它是有效的BCD数。请注意,这个示例并没有直接扩展到100位,但它提供了一个基础,你可以在这个基础上构建更复杂的100位BCD加法器。

module bcd_adder_4bit(input [3:0] a, // 4-bit BCD inputinput [3:0] b, // 4-bit BCD inputinput cin,     // Carry-inoutput [3:0] sum, // 4-bit BCD outputoutput cout    // Carry-out
);wire [4:0] temp_sum; // 5-bit temporary sum to handle carrywire [3:0] adjusted_sum; // 4-bit adjusted BCD sumwire temp_cout; // Temporary carry-out// Perform binary addition of the BCD digits and the carry-inassign temp_sum = a + b + cin;assign temp_cout = (temp_sum[4] == 1'b1); // Carry-out if the sum is 5-bits wide// BCD adjustment: if the result is greater than or equal to 10 (0xA in BCD), adjust it// to be in the range 0-9 by subtracting 6 (0x6 in BCD) and setting the carry-out for the next BCD digitassign adjusted_sum = (temp_sum >= 4'd10) ? (temp_sum - 4'd6) : temp_sum[3:0];// Carry-out for the next BCD digit is set if the original sum was 10 or more, or if there was a carry-in and the sum was exactly 9wire carry_adjustment = (temp_sum == 4'd10) | (cin && temp_sum == 4'd9);assign cout = temp_cout | carry_adjustment;// Output the adjusted sumassign sum = adjusted_sum;endmodule

要构建一个100位的BCD加法器,你需要将上述4位BCD加法器模块级联25次(因为100位/4位/数字=25个数字)。每个模块的输出cout将作为下一个模块的cin。同时,你需要一个额外的机制来处理最高位可能产生的进位(如果两个加数的和超过最大的25位BCD数)。

请注意,这个示例代码没有考虑所有可能的边界情况和优化。在实际应用中,你可能需要更复杂的逻辑来处理溢出、零检测、符号扩展等问题。此外,对于大规模设计,使用高级综合工具或手动优化可能有助于提高性能和资源利用率。

http://www.lryc.cn/news/531559.html

相关文章:

  • 生成式AI安全最佳实践 - 抵御OWASP Top 10攻击 (上)
  • .Net WebAPI -[HttpPut(“{fileServiceId:int}“)]
  • [EAI-027] RDT-1B,目前最大的用于机器人双臂操作的机器人基础模型
  • C基础寒假练习(7)
  • Ajax:重塑Web交互体验的人性化探索
  • 【DeepSeek背后的技术】系列二:大模型知识蒸馏(Knowledge Distillation)
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.14 内存映射:处理超大型数组的终极方案
  • 【C++】STL——vector的使用
  • springboot/ssm互联网智慧医院体检平台web健康体检管理系统Java代码编写
  • 介绍一下Mybatis的Executor执行器
  • Wide Deep 模型:记忆能力与泛化能力
  • Hot100之矩阵
  • Python语言的安全开发
  • 蓝桥杯刷题DAY3:Horner 法则 前缀和+差分数组 贪心
  • java项目验证码登录
  • 手写MVVM框架-环境搭建
  • 2025年2月2日(网络编程 tcp)
  • 【Docker项目实战】使用Docker部署MinIO对象存储(详细教程)
  • 使用ollama本地部署Deepseek r1
  • Unity飞行代码 超仿真 保姆级教程
  • DeepSeek蒸馏模型:轻量化AI的演进与突破
  • 使用 sunshine+moonlight 配置串流服务无法使用特殊键
  • 5.角色基础移动
  • 单细胞-第四节 多样本数据分析,下游画图
  • Linux的循环,bash的循环
  • 【DeepSeek开发】Python实现股票数据可视化
  • 华为小米vivo向上,苹果荣耀OPPO向下
  • 毕业设计:基于深度学习的高压线周边障碍物自动识别与监测系统
  • el-table表格点击单元格实现编辑
  • 数据结构:时间复杂度