调试用系统任务和常用编译预处理语句
用于调试和差错的系统任务,以及编写模块时的预处理语句
系统任务$monitor
提供了监控和输出参数列表中表达式或变量值的功能。参数列表中输出控制格式字符串和输出表列的规则和 d i s p l a y 一 样 。 当 启 动 一 个 带 有 一 个 或 多 个 参 数 的 display一样。当启动一个带有一个或多个参数的 display一样。当启动一个带有一个或多个参数的monitor任务时,仿真器会建立一个处理机制,当参数列表中的变量或表达式值发生变化,整个参数列表中变量或表达式的值将输出显示。同一时刻多个参数值变化,该时刻只输出显示一次。但参数可以实$time系统函数,这样参数列表中变量或表达式的值同时发生变化的时刻可以通过标明同一时刻的多行输出显示。
$monitor($time, ,"rxd=%b txd=%b",rxd,txd); // , ,代表空参数,输出时为空格。
$monitoron 和 $monitoroff 打开关闭监控标志来控制监控任务$monitor的启动和停止,来控制任务发生。调用$monitoron启动$monitor 时,不管参数列表值是否发生变化,总是立刻输出显示当前时刻参数列表中的值,用于初始化。默认仿真开始就开启了。多模块调试下,许多模块都调用了$monitor ,任何时刻只能有一个起作用,需要搭配$monitoron $monitoroff使用,将需要监控的模块打开,监视完毕关闭。
$time时间度量系统函数
- 系统函数$time,返回一个64位的整数表示当前仿真时刻。时刻以模块仿真时间尺度为基准
`timescale 10ns/1ns
module test; // 想再16ns时设置set为0,32ns设置set为1
reg set;
parameter p=1.6;
initial
begin
$monitor($time,,"set=",set);
#p set=0; // 延迟p个时间单位,一个时间尺度10ns,总是输出时间尺度的倍数,所以时16ns
#p set=1;
end
endmodule
输出结果为: $time显示时刻受时间尺度比例影响,总是输出整数
0 set=x
2 set=0 所以再1.6和3.2取整后输出2和3
3 set=1
- $realtime系统函数,作用和上一样,只是返回一个实数型。
`timescale 10ns/1ns
module test;
reg set;
parameter p=1.55;
initial
begin
$monitor($realtime,,"set=",set);
#p set=0;
#p set=1;
end
endmodule
输出结果为:
0 set=x
1.6 set=0 将仿真时刻经过尺度变化后输出,不用取整。1.6个10ns,就是16ns了
3.2 set=1
$finish
$finish/ $finish(n) 退出仿真器,返回主操作系统,结束仿真。可以带参数,输出不同特征信息,默认参数为1. 0不输出任何信息,1输出当前仿真时刻和位置,2输出仿真时刻,位置和再仿真过程中所用的memory内存及CPU时间统计
$stop
暂停,仿真环境下给出一个交互式命令提示符,可以带参数 $stop(n) 0-2 越大输出信息月多
$readmemb $readmemh
从文件中读取数据到存储器。可以在仿真的任何时刻执行使用
可以读取的数据文件只能包含:空白位置(空格,换行,tab, form-feeds) ,注释,二进制(readmemb),十六进制(readmemh)的数字。数字不能包含位宽说明和格式说明。
地址指的是对存储器(memory)建模的数组的寻址指针。当数据文件被读取,每个被读取的数字都被存放到地址连续的存储器单元中,地址范围由系统任务声明语句中的起始地址和终止地址说明,每个数据的存放地址在数据文件中说明。数据文件中地址格式为 @十六进制数。允许大小写,字符@和数字之间无空白位置。系统任务遇到一个地址说明时,系统任务将该地址后面的数据存放到存储器对应地址单元中:
module test;
reg [7:0] memory [0:7]; //声明有8个8位的存储单元
integer i;
initial
begin
// 读取存储器文件 init.dat 到存储器中的给定地址
$readmemb("init.dat", memory);
//显示初始化后的存储器内容
for(i=0; i < 8; i = i + 1)
$display("Memory [%d] = %b", i, memory[i]);
end
endmodule
/*文件init.dat包含初始化数据。用@<地址>在数据文件中指定地址。地址以十六进制数说明。数据用空格符分隔。
数据可以包含x或者z。未初始化的位置缺省值为x。 名为init.dat的样本文件内容如下所示。*/
@002
11111111 01010101
00000000 10101010
@006
1111zzzz 00001111
当仿真测试模块时,将得到下面的输出:
Memory [0] = xxxxxxxx
Memory [1] = xxxxxxxx
Memory [2] = 11111111
Memory [3] = 01010101 地址连续
Memory [4] = 00000000
Memory [5] = 10101010
Memory [6] = 1111zzzz
Memory [7] = 00001111
- 如果数据文件中没有进行地址声明,默认起始地址为存储器定义语句中的起始地址,文件的数据连续存放到存储器中,直到单元存满或数据存完
- 系统任务说明语句说明了起始地址,结束地址,就不考虑存储器定义时的起始地址终止地址,由说明语句决定
- 数据文件中的地址必须在声明范围内,否则报错
- 数据文件的数据个数和系统任务的范围暗示的数据个数不同也会报错
//下面举例说明:
//先定义一个有256个地址的字节存贮器 mem:
reg[7:0] mem[1:256];
下面给出的系统任务以各自不同的方式装载数据到存贮器mem中。
initial $readmemh("mem.data",mem); // 从地址1开始
initial $readmemh("mem.data",mem,16); // 16开始
initial $readmemh("mem.data",mem,128,1); // 从128到1,系统会检测数据文件中是否有128个数据,没有报错
$random
产生一个随机数,返回32位带符号整型数。
reg[23:0] rand;
rand = $random %60 // 范围在 -59-59间的随机数(-b+1) 到 (b-1)
rand = {$random} %60 // 0-59
用于产生随机脉冲序列或宽度随机的脉冲序列,用于电路测试。
/*下面例子中的Verilog HDL模块可以产生宽度随机的随机脉冲序列的测试信号源,在电路模块的设计仿真时非常有用。
同学们可以根据测试的需要,模仿例4,灵活使用$random系统函数编制出与实际情况类似的随机脉冲序列。*/
`timescale 1ns/1ns
module random_pulse( dout );
output [9:0] dout;
reg [9:0] dout;
integer delay1,delay2,k;
initial
begin
#10 dout=0;
for (k=0; k< 100; k=k+1)
begin
delay1 = 20 * ( {$random} % 6);
// delay1 在0到100ns间变化 20*(0,5)
delay2 = 20 * ( 1 + {$random} % 3);
// delay2 在20到60ns间变化
#delay1 dout = 1 << ({$random} %10);
//dout的0--9位中随机出现1,并出现的时间在0-100ns间变化
#delay2 dout = 0;
//脉冲的宽度在在20到60ns间变化
end
end
endmodule
编译预处理
以键盘左上角` 开头
宏定义`define
用一个名字代表一个字符串,编译预处理时,会将所有signal 替换成string。用于将一个i简单的名字替代一个长字符串,或用一个有含义的名替代没有含义的数字或符号。
`define WORDSIZE 8
module
reg[1:`WORDSIZE] data; //这相当于定义 reg[1:8] data;
- 宏名可以用大写字母代替,也可以用小写,建议大写
- `define 可以出现咋模块定义里,定义外,有效范围是定义命令到源文件结束
- 引用时加个` 符号
- 宏定义不是语句,后面不用加分号,加了就表示分号也是字符的一部分,可能会造成语法错误
- 宏名和内容要在一行,包含的注释不会包含进去
- 宏定义可以引用已定义的宏名
module test;
reg a, b, c;
wire out;
`define aa a + b
`define cc c + `aa
assign out = `cc;
endmodule
//这样经过宏展开以后,assign语句为 assign out = c + a + b;
文件包含处理 `include
将一个源文件的内容全部包含(复制)进来。
(1)文件aaa.v
module aaa(a,b,out);
input a, b;
output out;
wire out;
assign out = a ^ b;
endmodule
(2)文件 bbb.v
`include "aaa.v"
module bbb(c,d,e,out);
input c,d,e;
output out;
wire out_a;
wire out;
aaa aaa(.a(c),.b(d),.out(out_a)); // 模块aaa实际上作为模块bbb的子模块来调用
assign out = e & out_a;
endmodule
- 一个include只能指定一个文件,包含多个就用多个`include ,不能并列。但多个include命令可以写在一行,中间用空格隔开
- 可以包含相对路径,绝对路径
- 文件1包含文件2,文件2需要文件3,可以在文件一中包含文件2,3,3在2前
- 文件的包含可以嵌套
- Verilog编译器支持多模块编译,只要把需要包含的文件放到一个项目下,建立存放编译结果的库,就不用声明包含了,用模块名就能把所有有关的模块练习在一起。
时间尺度 `timescale
说明该命令后模块的时间单位和时间精度。可以在同一个设计中包含采用不同时间单位的模块。``timescale 时间单位/时间精度`,时间单位用来定义模块中仿真时间和延迟时间的基准单位,时间精度声明仿真时间的精确程度,用来对延迟时间值进行取整操作,一个程序中多个 timesclea命令,采用最小的时间精确值决定仿真的时间单位。另外时间精度要小于时间单位值。时间的数值必须整数有效的就有1,100,10,单位s, ms, us, ns, ps, fs。
`timescale 1ns/1ps; // 模块中所有时间值都表示为1ns的整数倍,模块的延迟时间可以表达为3位小数的实型,时间精度位1ps
`timescale 1us/100ns // 时间值均为10us的整数倍,延迟时间最小分辨率位十分之一微妙,即延迟时间可以带一位小数
`timescale 10ns/1ns // 小数点后可以有一位
module test;
reg set;
parameter d=1.55; // d的延迟时间实际是16ns(1.6×10ns),根据时间精度d的值从1.55取整为1.6
initial
begin
#d set=0;
#d set=1;
end
endmodule
多个模块用到的时间单位不同,需要用到:
- timescale声明本模块的时间单位和时间精度
- $printtimescale 输出显示一个模块的时间单位和精度
- $time,$realtime 及%t格式声明来输出显示EDA工具记录的时间信息。
条件编译命令 `ifdef `else `endif
`ifdef `ifndef `elsif `endif 对源程序中部分内容满足条件时才进行编译。通常选择一个模块的不同代表部分,选择不同的时许或结构信息,或对不同的EDA工具选择不同的激励时可以采用条件编译。这些指令可以出现在设计中的任何地方。可以有条件的编译语句,模块,语句块,声明和其他编译指令。条件编译标志可以用 `define 语句设置。
`ifdef TEST //若设置TEST标志,则编译test 模块
module test;
initial
$display("Module %m compiled");
endmodule
`else // 在缺省情况下,则编译stimulus 模块
module stimulus;
initial
$display("Module %m compiled");
endmodule
`endif // `ifdef 语句的结束
module top;
bus_master b1(); //无条件地调用模块
`ifdef ADD_B2
bus_master b2(); //若定义了ADD_B2文本宏标志,则有条件地调用b2
`ifdef ADD_B3
bus_master b3(); //若定义 ADD_B3文本宏标志,则有条件地调用b3
`else
bus_master b4(); //在缺省情况下,则有条件地调用b4
`endif
`ifndef IGNORE_B5
bus_master b5(); //若没有定义IGNORE_B5文本宏标志,则有条件地调用b5
endmodule
条件执行
允许设计者在运行时控制语句执行的流程。所有语句都被编译,但有条件的执行。$test$plusargs
//条件执行
module test;
reg a, b, c;
initial
begin
a = 1'b1; b = 1'b0; c = 1'b1;
if ($test$plusargs ("DISPLAY_VAR"))
$display("Display = %b ", {a,b,c} ); //只有当标志设置时才能显示
else
$display ("No Display"); //其他情况下不显示
end
endmodule
可以使用$value$plusargs 进一步控制条件执行,用于测试调用选项的参数值,如果没有找到匹配的调用选择,返回0,找到了返回非0.
//用系统任务 $value$pluargs 的条件执行
module test ;
reg [8 * 128 – 1 : 0 ] test_string ;
integer clk_period ;
…
…
initial
begin
if ($value$pluargs(“ test name = %s ”, test_string))
$readmemh (test_string, vectors ) ; // 读取测试向量
else
// 否则显示错误信息
$display (“ Test name option not specified”) ;
if ($value$pluargs(“ clk_t = %d ”, clk_period))
forever # (clk_period/2) clk = ~clk ; //设置时钟
else
//否则显示错误信息
$display (“ Clock period option name not specified”) ;
end
//例如要启动上述选项,需要使用带 +testname=test1.vec +clk_t = 10
// Test name = “test1.vec” 和 clk_period = 10 的命令行来启动仿真器
endmodule
一些练习题
- 层次电路,模块间的调用:已有的全加器模块,一个顶层模块调用这个,连接线为W1-5。
module FullAdder(A,B,Cin,Sum,Cout);
input A,B,Cin;
ouput Sum,Cout;
endmodule
module Top ... // 命名端口连接
FullAdder FA(.Sum(W1),
.Cout(W2),
.Cin(W3),
.A(W4),
.B(W5));
endmodule
- 没有输入输出的测试模块
module TestFixture
reg A,B,SEL;
wire F;
MUX2M(SEL,A,B,F);
initial // 两个initial块并行
begin
SEL=0; A=0; B=0;
#10 A=1;
#10 SEL=1;
#10 B=1;
end
initial
$monitor(SEL,A,B,,F); // 检测变量变化并输出
endmodule
- 事件A分别在10,20,30发生,而B一直保持X状态,问在50时Count的值是多少。
reg [7:0] Count;
initial
Count=0;
always
begin // always 块内顺序执行
@(A) Count=Count+1;
@(B) Count=Count+1;
end
Count=1;
(这是因为当A第一次发生时,Count的值由0变为1,然后事件控制 @(B) 阻挡了进程。)
- 一些可综合的模块
1.always @(posedge Clock)
begin
A<=B;
if(C)
A<=1'b0; // 非阻塞赋值方式,块结束后才能完成这次赋值操作
end
带同步复位端的触发器
2.always @( A or B)
case(A)
1'b0: F=B;
1'b1: G=B;
endcase
不能综合,没有default,状态描述不完整比如当A等于0时,G的状态不确定
3.always @( posedge A or posedge B )
if(A)
C<=1'b0;
else
C<=D;
带异步复位端的触发器
4.always @(posedge Clk or negedge Rst)
if(Rst)
A<=1'b0;
else
A<=B;
不能综合(产生了异步逻辑,是有两个时钟嘛)
- 四位移位寄存器
- case语句
SEL:OP
000:1
001:3 casex(SEL)
010:1 3'b( ): OP=3;
011:3 3'b( ): OP=1;
100:0 3'b( ): OP=0;
101:3 endcase
110:0
111:3
标准答案:
casex(SEL)
3'bXX1: OP=3;
3'b0X0: OP=1;
3'b1X0: OP=0;
endcase
- 位拼接{1,0} 等同于 64’H0000000010000000 位拼接一般要指明位数,否则默认是32位二进制。
- 模块间调用时,引用其他模块定义的参数(parameter)要加上其他模块名
module M
'include "defs.v"
..
if(OP == <defs.Reset>) // defs.v文件中定义的 parameter Reset=8'b10101111;
Bus=0;
endmodule
- 改变模块间的参数
Module Pipe(IP,OP)
parameter Option=1;
parameter Depth=1;
...
endmodule
Pipe #(1,8) P1(IP1,OP1); // 改变对应的两个参数值
module Pipe (IP ,OP);
parameter Option =1;
parameter Depth = 1;
…………
endmodule
module
Pipe P1(IP1 ,OP1);
defparam P1.Depth=16; // 一个模块改变另一个模块参数时,要用这个
endmodule
- monitor语句的用法,观测变量值
Module Test
Top T();
initial
$monitor(Test.T.B1.C.Count)
endmodule
module Top;
Block B1();
Block B2();
endmodule
module Block;
Counter C();
endmodule
module Counter;
reg [3:0] Count;
....
endmodule