在复习硬件编程语言课程时,我梳理了一些之前容易忽略或混淆的Verilog语法细节,特此总结分享,希望能帮助大家查漏补缺。
一、task与function的区别
两者结构相似,但用途和规则有显著不同。
1. task(任务)
任务定义格式如下:
task <任务名>;
// 端口和类型说明
// 局部变量声明
begin
// 语句
end
endtask
示例:
task read_memory;
input [15:0] address;
output [31:0] data;
reg [3:0] counter;
reg [7:0] temp [1:4];
begin
for(counter = 1; counter <= 4; counter = counter + 1)
temp[counter] = mem[address+counter-1];
data = {temp[1],temp[2],temp[3],temp[4]};
end
endtask
// 调用示例
module demo_task;
reg [7:0] mem[127:0];
reg [15:0] a;
reg [31:0] b;
initial begin
a = 0;
read_memory(a,b); // 任务调用
#10;
a = 10;
read_memory(a,b);
end
endmodule
2. function(函数)
函数定义格式如下:
function [返回值位宽] <函数名>;
// 输入参量和类型说明
// 局部变量声明
begin
// 语句
end
endfunction
默认返回值类型为reg,也可指定为integer或real。
示例:
function [3:0] num_0;
input [7:0] x;
reg [3:0] count;
integer i;
begin
count = 0;
for(i = 0; i < 8; i = i + 1)
if(x[i] == 1'b0)
count <= count + 1;
else
count <= count;
num_0 = count; // 赋值给函数名以返回结果
end
endfunction
// 调用示例
module demo_function(
input [15:0] num, // 输入序列
output [15:0] a // 序列中0的个数
);
wire [15:0] a;
assign a = num_0(num); // 函数调用,作为表达式的一部分
endmodule
3. 核心区别总结
| 特性 |
function |
task |
| 调用关系 |
可调用函数,不可调用任务 |
可调用函数和任务 |
| 执行时刻 |
总是在仿真0时刻开始 |
可以在非0时刻开始 |
| 时序控制 |
不能包含延时、事件控制语句 |
可以包含 |
| 输入/输出 |
至少有一个输入,无output/inout |
可有可无 |
| 返回值 |
有一个返回值 |
无返回值 |
| 调用形式 |
不能作为独立语句,是表达式一部分 |
通过一条独立的语句调用 |
| 作用域 |
可出现在连续赋值和过程块中 |
只能出现在过程块中 |
| 中断 |
不允许disable |
可以disable |
理解这些区别对于编写正确的硬件描述语言模块至关重要。
二、显示类系统任务:$display,$write, $monitor,$strobe
这些系统任务都用于信号输出显示,但行为各有特点。
-
$display 与$write
$display:输出后自动换行。
$write:输出后不自动换行,适合在一行内组合多个输出。
-
$monitor 与$display
$monitor:通常在initial块中调用,具备持续监视功能。只要未使用$monitoroff,它就会监控所列信号,任何信号变化都会触发一次全部信号的格式化输出。同一仿真时刻多个信号变化,只打印一次。
$display:不具备监视功能,执行一次输出一次即结束。
-
$monitor 与$strobe
$strobe:在当前时间步的所有操作完成之后才执行输出,常用于查看该时刻的最终稳定值。它同样具有监控功能。$strobeb, $strobeh, $strobeo是其针对不同数制的变体。
示例分析:
initial begin
a = 0;
$display(a); // 输出此刻a的值:0
$strobe(a); // 将在时间步结束时输出a的最终值
a = 1; // a值立即变为1
end
这段代码的输出结果是:
0
1
因为$display立即输出a=0,而$strobe等到initial块内所有赋值完成(a=1)后才输出。
三、仿真控制:$finish 与$stop
这两个系统任务用于控制仿真流程。
$finish:终止并退出仿真。
$stop:暂停仿真,通常可在此刻交互式地查看信号状态。
它们都可以带一个可选的整数参数n:
$finish(n);
$stop(n);
| n值 |
含义 |
| 0 |
不输出任何信息 |
| 1 |
输出仿真时间和位置 |
| 2 |
输出仿真时间和位置,并附加内存和CPU时间统计 |
四、随机数生成:$random
$random用于生成随机数,常用于测试激励。
$random % x; // 产生范围在 (-x+1) 到 (x-1) 之间的随机整数
{$random} % x; // 产生范围在 0 到 (x-1) 之间的随机整数 (x > 0)
使用{$random}可以确保得到一个正数种子,从而生成非负随机数。
五、时间尺度:$time 与$realtime
这两个系统任务返回当前的仿真时间。
$time:返回一个64位整数时间值,按仿真时间单位计算。
$realtime:返回一个实型数时间值,精确反映时间尺度。
六、文件读取:$readmemh 与$readmemb
用于从外部文件读取数据并加载到存储器(寄存器数组)中,是搭建测试环境的重要手段。
$readmemh:读取十六进制格式的文件。
$readmemb:读取二进制格式的文件。
调用格式:
$readmemh(“数据文件.dat”, <存储器名>, <起始地址>, <结束地址>);
文件格式要求:
- 只能包含:空白符、注释行、二进制/十六进制数字。
- 数字中不能有位宽或格式说明(如
8‘hFF是不允许的,应直接写FF)。
- 可以使用
@<十六进制地址>在文件中指定后续数据的加载地址。
示例:
假设有数据文件data.dat,内容为:
@0010 // 从地址0x10开始加载
AA BB
@0020 // 从地址0x20开始加载
CC DD
通过$readmemh(“data.dat”, mem);读取后,存储器mem的0x10和0x11地址将分别存入AA和BB,0x20和0x21地址将分别存入CC和DD。

熟练掌握这些系统任务能极大提升数字电路仿真与测试的效率。Verilog中用于仿真的语法非常丰富,本文仅对部分易混淆点进行了总结。