Verilog 各种语句语法
1 连续/持续赋值
1.1 基本语法
持续赋值只能对线网 net 型信号进行赋值,来模拟表达式左边信号,随右边表达式信号s变化,而不断刷新的动作。语法规则如下:
assign <net_name> = <expression>;
1.2 目标类型
- 标量线网(1位)
- 向量线网(多位)
- 线网向量的常数型一位选择
- 线网向量的常数型部分位选择
- 上述类型的任意的拼接运算结果
1.3 并行执行情况
比如下面是一个1位全加器代码,接受两个加数(通常称为 A 和 B)和一个来自低位的进位输入(Carry-in, Cin),输出输出本位和(Sum)和进位输出(Carry-out, Cout),用于传递给更高一位的加法器。
module FA_df(A, B, Cin, Sum, Cout);
input A, B, Cin;
output Sum, Cout;
assign Sum = A ^ B ^ Cin;
assign Cout = (A & B) | (B & Cin) | (Cin & A);
endmodule
其中这两个 assign 赋值语句是并行执行的,与其书写的顺序无关。只要信号 A 或者 B 或者 Cin 任何一个信号发生改变,和信号 Sum 与高位进位信号 Cout 的值就会立即相应的进行刷新,跟代码的前后顺序无关。
1.4 线与问题
“线与问题”(Wired-AND Problem) 指的是当多个驱动源试图驱动一个普通的 wire 网络,并且这些驱动源输出不同的值时,会导致线冲突(Wire Conflict),而不是实现预期的按位逻辑与(AND) 操作。
比如下面的代码,当源1为0,源2为1时,就会发生线冲突,因为两个驱动源的输出值不同,无法确定最终的输出结果,bus 的结果变成 x。
wire bus;
assign bus = source1; // source1 为 0
assign bus = source2; // source2 为 1
2 过程块与 always 块
2.1 过程块的分类
过程块有两种类型:
initial块:只能执行一次,用在测试模块中always块:循环执行,常用来描述组合或者时序逻辑电路
2.2 过程块的赋值对象要求
- 在过程块中的赋值称为过程赋值
- 在过程赋值语句中表达式左边的信号必须是寄存器(register)类型
- 在过程赋值语句等式右边可以是任何有效的表达式,数据类型也没有限制。
- 如果一个信号没有声明则缺省为 wire 类型。使用过程赋值语句给 wire 赋值会产生错误。
2.3 always 块
always 语句用于描述时序逻辑和组合逻辑,其语法为 always @(敏感信号),当检测到敏感信号发生变化时,便会执行 always 块中的语句。
比如下面的例子
reg [3: 0] sum;
always @ (a or b) begin // 检测 a 或者 b 的变化
sum = a + b;
// 与 wire [3:0] sum; assign sum = a + b; 类似
end
2.4 过程块的边沿触发
用关键字 posedge(上升沿)和 negedge(下降沿)限定信号敏感边沿,一般用于描述时序逻辑电路。敏感表中可以有多个信号,用关键字or连接。
比如下面时钟控制的加法器,只有时钟信号 clk 到达上升沿(posedge)时,块内描述的电路才刷新,将 a 和 b 进行加操作,然后赋给 out
module reg_adder(clk, a, b, out);
input clk;
input [2: 0] a, b;
output reg out;
always @ (posedge clk) begin
out <= a + b;
end
endmodule
3 阻塞赋值和非阻塞赋值
过程块内的赋值方式分为两类:
- 阻塞赋值(Blocking Assignment):使用
= - 非阻塞赋值(Non-Blocking Assignment):使用
<=
3.1 阻塞赋值
阻塞性过程赋值是顺序执行的,即写在前面的语句先执行,写在后面的语句后执行。
比如下面的程序,T1赋值首先发生,计算T1;接着执行第二条语句,T2被赋值;然后执行第三条语句,T3被赋值;最后才执行Cout赋值语句。
input A, B, Cin;
output Cout;
reg T1, T2, T3; //内部信号
reg Cout;
always @(A or B or Cin) begin
T1 = A & B;
T2 = B & Cin;
T3 = A & Cin;
Cout = T1 | T2 | T3;
end
3.2 非阻塞赋值
非阻塞性过程赋值是并发执行的,即多个语句可以同时执行,写在前面的语句与写在后面的语句同时执行,跟书写顺序没有关系。相当于先进行计算,再统一赋值。
描述组合逻辑使用阻塞式赋值;而描述时序逻辑使用非阻塞式赋值。
reg T1, T2, T3; //内部信号
reg Cout;
always @(posdege clk) begin
T1 <= A & B;
T2 <= B & Cin;
T3 <= A & Cin;
Cout = T1 | T2 | T3;
end
3.3 注意事项
- 综合时,网线数据类型的变量会被映射成硬件中的连线;寄存器数据类型的变量根据被赋值的具体情况,被映射成连线、触发器或者锁存器。
- 不建议对同一个赋值对象既使用阻塞赋值,又使用非阻塞赋值;在同一个过程块里面,既使用阻塞赋值,又使用非阻塞赋值。
五大原则:
- 时序电路建模时,用非阻塞赋值。
- 用 always 块写组合逻辑时,采用阻塞赋值。
- 在同一个 always 块中不要同时使用非阻塞赋值和阻塞赋值。
- 锁存器电路建模时,用非阻塞赋值。
- 在同一个always块中同时建立时序和组合逻辑电路时,用非阻塞赋值。
4 条件语句
Verilog里面的条件语句包括 if else 语句和 case 语句。
条件语句必须在 initial 和 always 语句所引导的过程块中使用。表达式为0或者x视为假;
4.1 if else语句
语法结构如下:
if (condition) begin
// if语句块
end else begin
// else语句块
end
if else支持嵌套,与C/C++类似。如果只有一句命令可以不使用begin end。
4.2 case语句
语法结构如下:
case (condition)
value1: //执行语句1
value2: //执行语句2
default: //执行语句3
endcase
- 一般加上
default,防止产生锁存器以及自锁现象; - 所有表达式位宽必须相同;
case可以换成casez和casex,casez用来处理不考虑高阻值z的比较过程,casex用来处理将高阻值z和不定值x都视为无关值,不考虑;case语句支持嵌套,与C/C++类似。如果只有一句命令可以不使用begin end。
5 循环语句
Verilog 里面有4中循环,分别为for循环、repeat循环、forever循环和while循环,最常用的为for循环。
for 循环
for循环与C/C++类似,综合电路使用最多的语法结构如下:
for (init; condition; update) begin
// for循环体
end
比如下面的代码,
input [31:0] addr;
output reg [3:0] line;
integer n; // 注意,integer为32位,
// 最好改为 reg [1:0] n;
always @ (posedge clk) begin
for(n = 3; n >= 0; n = n - 1) begin
if (addr == n)
line[n] <= 1;
else
line[n] <= 0;
end
end
forever循环
用于连续执行某个过程,直到仿真结束。需要注意的是,forever循环语句常用于产生周期性的波形,只能用来作为仿真测试信号,必须写在 initial块中。
语法结构如下:
forever begin
// forever循环体
end
6 函数和任务
任务和函数必须在module内定义和例化
6.1 函数
主要用于计算并返回一个值。它像数学函数一样,接受输入参数,经过处理,返回一个结果。
- 必须有一个返回值,且返回值类型必须与函数名一致。
- 在同一时间步内完成执行(类似组合逻辑),不能包含 #(延迟控制)或 wait 等时序控制语句,不能包含 initial 或 always 块。
- 可以在连续赋值语句(assign)、always 块的过程性语句中调用
- 不能驱动 wire 类型的变量(除非是函数内部声明的 wire),可以对 reg 类型的变量(如返回值本身或内部 reg)赋值。
函数的语法结构如下:
function [range] function_name;
input [width-1:0] input1, input2, ...; // 只能是 input
// [other declarations - reg, integer, etc.]
begin
// Function body - combinational logic only
function_name = <calculated_value>; // Must assign result to function_name
end
endfunction
例如:
module example_func;
// 定义一个求最大值的函数
function [7:0] max;
input [7:0] a, b;
begin
if (a > b) max = a; // 返回值赋给函数名
else max = b;
end
endfunction
reg [7:0] x, y, result;
initial begin
x = 8'h10;
y = 8'h25;
result = max(x, y); // 调用函数,结果赋值给 result
$display("Max of %h and %h is %h", x, y, result); // 输出: Max of 10 and 25 is 25
end
endmodule
6.2 任务
用于执行一系列操作,可以有多个输出,也可以不返回任何值。功能比函数更强大、更灵活。
- 任务不能有返回值,通过 output 或 inout 端口参数返回结果
- 允许包含 #(延迟控制)和 wait 等时序控制语句,允许包含 initial 或 always 块
- 不能在连续赋值语句(assign)中调用,只能在 initial 或 always 块等过程性语句中调用
- 可以驱动 wire 和 reg 类型的变量(通过 output 或 inout 端口)
语法结构如下:
task task_name;
input [width-1:0] in1, in2, ...;
output [width-1:0] out1, out2, ...;
inout [width-1:0] io1, ...;
// [other declarations - reg, integer, etc.]
begin
// Task body - can contain timing controls
out1 = <calculated_value>; // Assignments to output/inout ports
end
endtask
例如:
module example_task;
// 定义一个计算和与差的任务
task add_and_subtract;
input [7:0] a, b;
output [8:0] sum; // 9位防止溢出
output [8:0] diff; // 9位
begin
sum = a + b; // 计算和
diff = a - b; // 计算差
#10; // 任务内部可以有延迟
end
endtask
reg [7:0] x, y;
wire [8:0] s, d;
initial begin
x = 8'd15;
y = 8'd7;
add_and_subtract(x, y, s, d); // 调用任务,通过 output 端口获取结果
$display("Inputs: %d, %d", x, y);
$display("Sum: %d, Diff: %d", s, d); // 输出: Sum: 22, Diff: 8
end
endmodule