文章目录
2、程序块(Program Block)和时序区域(Timing Region)
一、数据类型
1、内建数据类型
(1)逻辑类型
a、四值逻辑
可以表示0、1、X、Z四种状态,默认初始值为x
wire,reg,logic,integer,time
b、二值逻辑
只可以表示0和1两种状态,默认初始值为0。
bit:可自定义位宽
byte:8位宽,取值范围:-128~127
shortin:16位宽,取值范围:
~
int:32位宽,取值范围:
~
longint:64位宽,取值范围:
~
(2)符号类型
a、有符号类型
byte、shortint、int、longint、integer
b、无符号类型
wire、reg、logic、bit
(3)类型转换
a、隐式转换
b、显示转换
静态转换
动态转换
注意:
1 四值逻辑中的x赋值给二值逻辑的时候,就是0
2 赋值只能在声明的时候直接赋值,或者在过程块里面赋值
3 变量的赋值必须在声明之后
二、随机化
1、修饰符
(1)rand
表示每次随机化类时,这些变量都会赋一个值
(2)randc
表示周期随机性,即所有可能的值都赋过值后随机值才可能重复
2、随机约束种类
使用关键字constraint来添加约束语句块,指定随机变量的取值范围,或者各个变量之间的相互关系。约束块不是自上而下执行的程序性代码,而是声明性代码,是并行的,所以所有的约束表达式同时有效。
(1)布尔表达式 Boolean expressions
布尔表达式就是用最简单的不等式规定变量的取值范围或者相互之间的大小关系。约束块中只能包含表达式:<、<= 、== 、>= 、>。在一个表达式中最多只能使用一个关系操作符。
class order;
rand bit [7:0] low, num, high;
constraint c_boolean{
low<num;
num<high;
}
endclass
(2)权重分配 Weighted distributions
dist操作符允许产生权重分布,这样某些值得选取机会要比其他值更大。权重分配有两种表达方式,分别为:=和:/。:=表示值范围内的每一个值得权重是相同的,:/表示权重要均分到值范围内的每一个值。
class weighted
rand int src, dst;
constraint c_weighted{
src dist{0:=40, [1:3]:=60};
dst dist{0:/40, [1:3]:/60};
}
endclass
(3)范围表达式 Range expression
class range
rand int a;
int low, high;
constraint c_range_a{
a inside{[low:high]};
}
rand int b;
constraint c_range_b{
!(b inside{[low:high]});
}
endclass
(4)条件表达式 Conditional expressions
class conditional;
bit flag;
rand bit [31:0] a;
constraint c_conditional{
if(flag)
a inside {[40:80]};
else
a inside {[0:20],[100:120]};
}
endclass
3、相关函数
(1)randomize()函数
randomize()函数为类里所有的rand和randc类型的变量赋一个随机值,随机变量的值要符合约束。randomize()函数执行成功,返回值为1,执行失败,返回值为0。randomize()函数不能被重写。
module testbench;
class rand_class;
rand bit a;
randc bit b;
constraint c {...};
endclass
rand_class p;
initial begin
p = new();
if (p.randomize() == 0)
$display("Randomize fail!!!!");
...
end
endmodule
(2)pre_randomize()和post_randomize()函数
在调用randomize()函数时,会同时调用这两个函数,这三个函数执行顺序如下:
pre_randomize() -> randomize() -> post_randomize()
pre_randomize()和post_randmize()函数可以被用户重写。
三、接口
1、接口(Interface)
(1)接口定义
接口包含了连接、同步、甚至两个或者更多块之间的通信功能,它们连接了设计块和测试平台。最简单的接口仅仅是一组双向信号的组合。这些信号使用logic数据类型,可以使用过程语句驱动。过程赋值语句就是在initial和always语句块中的赋值语句。
//DUT_if.sv
interface DUT_if (input bit clk);
logic rst_n;
logic in_a;
logic out_b;
endinterface
(2)DUT模块使用接口
//DUT.sv
module DUT(DUT_if DUTif);
...
always @ (posedge DUTif.clk or negedge DUTif.rst_n) begin
if (!DUTif.rst_n)
DUTif.in_a <= 'b0;
else
DUTif.in_a <= 'b1;
end
endmodule
(3)激励文件使用接口
接口信号必须使用非阻塞赋值来驱动。
(4)测试平台顶层(top)文件
上述所有块都在top模块中例化和连接。
//top.sv
module top;
bit clk;
//使用接口时,先对接口进行实例化
DUT_if DUTif (clk);
DUT d (DUTif);
DUT_tb t (DUTif);
endmodule
2、modport
(1)带有modport的接口
在接口中可以使用modport结构能够将信号分组并指定方向。
//DUT_if.sv
interface DUT_if (input bit clk);
logic rst_n;
logic in_a;
logic out_b;
modport DUT_mod (input out_b, rst_n, clk,
output in_a);
modport DUT_tb_mod (input clk, in_a,
output rst_n, out_b);
endinterface
(2)其它模块中使用modport
需要将modport名(DUT_mod或DUT_tb_mod等)放在接口名即DUT_if的后面。其它部分跟前面的例子相同。
//DUT.sv
module DUT (DUT_if.DUT_mod DUTif);
...
endmodule
//DUT_tb.sv
module DUT_tb (DUT_if.DUT_tb_mod DUTif);
...
endmodule
四、激励时序
1、时钟块
在接口块中,可以使用时钟块来指定同步信号相对于时钟的时序。时钟块中的任何信号都将同步地去打动或采样,这就保证了测试平台在正确的时间点与信号交互。一个接口可以包含多个时钟块,因为每个块中都只有一个时钟表达式,所以每一个对应一个时钟域。典型的时钟表达式如:@(posedge clk) 或 @(clk)。
时钟块大都在测试平台中使用。一旦定义了时钟块,测试平台中,就可以用@xxxif.cb表达式等待时钟,而不需要描述确切的时钟信号和边沿。
2、程序块(Program Block)和时序区域(Timing Region)
(1)时间片
System Verilog引入一种新的时间片的划分方式。在一个时间片内首先执行的是Active region,在这个区域中运行设计时间,包括RTL、门级代码和时钟发生器。第二个区域是Observed region,执行断言。接下来就是执行测试平台的Reactive region。最后就是Postponed region,它将在时间片的最后,所有设计活动都结束后的只读时间段采样信号。
(2)程序块(Program)
测试代码应当包含在一个单个的程序块中。应当使用OOP通过对象而非模块来创建一个动态、分层的测试平台。应当总是将程序块声明为automatic类型。
仿真的结束:在Verilog中,仿真在调度时间存在的时候会继续执行,直到遇到$finish。System Verilog把任何一个程序块都是为含有一个测试。如果仅有一个程序块,那么当完成所有initial块中的最后一个语句时,仿真就结束了,因为编译器认为这就是测试的结尾。即使还有模块或者程序块的线程在运行,仿真也会结束。如果存在多个程序块,仿真在最后一个程序块结束时结束。当然,仍然可以用$finish来结束仿真。
时钟块的默认时序是在#lstep延时之后采样输入信号,在#0延时之后驱动输出信号。lstep延时规定了信号在前一个时间片的Postponed区域,在设计有任何新的动作之前被采样。这样就可以在时钟改变之前捕获输出值。
在System Verilog中,可以在program中使用initial块,但是不能使用always块。如果确实需要一个always块,可以使用“initial forever”。
3、接口的驱动和采样
测试平台需要驱动和采样设计的信号,这主要是通过带有时钟块的接口做到的。异步信号通过接口时没有任何延时,而时钟块中的信号将得到同步。
(1)接口信号采样
当从时钟块中读取一个信号的时候,是在时钟沿之前得到采样值,例如在Postponed区域。
(2)接口信号驱动
在时钟块中应当使用同步驱动,即“<=”操作符来驱动信号。如果测试平台在时钟的有效沿驱动同步接口信号,那么其值将会立即传递到设计中,这是因为时钟块的默认输出延时是#0。如果测试平台在时钟有效沿之后驱动输出,那么该值直到时钟的下一个有效沿才会被设计捕获。因此,驱动总是同步的。
五、面向对象编程
1、OOP术语
(1)类(class)
包含变量和子程序的基本构建块。
(2)对象(object)
类的一个实例。
(3)句柄(handle)
指向对象的指针。
(4)属性(property)
存储数据的变量。也可称作变量。
(5)方法(method)
任务或函数中操作变量的程序性代码。也可称作程序。
(6)原型(prototype)
程序的头。包括程序名、返回类型和参数列表。
2、类和对象
(1)定义类并创建新对象
package Package
//定义类
class Transaction;
//变量
bit ...;
//程序
function void ...;
...
endfunction
endclass
endpackage
module
import Package::*;
//声明一个句柄
Transaction tr;
//为一个Transaction对象分配空间
Tr = new();
...
endmodule
在声明句柄tr的时候,它被初始化为特殊值null。调用new()函数来创建Transaction对象。new函数为Transaction分配空间,将变量初始化为默认值,并返回保存对象的地址。对于任意一个类,System Verilog创建一个默认的new函数来分配并初始化对象。
注:
1、避免在声明一个句柄的时候调用构造函数,即new函数。
2、类应当在program或者module外的package中定义。
(2)构造函数
new函数也称为构造函数,可以通过自定义new函数将变量的默认值设成期望的数值。new函数不能有返回值。
(3)使用对象
使用“.”符号来引用变量和程序。
(4)对象的解除分配
System Verilog分辨对象不再被引用的办法就是记住指向它的句柄的数量,当最后一个句柄不再引用某个对象了,System Verilog就释放该对象的空间。
3、静态变量
在System Verilog中,可以在类中创建一个静态变量。该变量将被这个类的所有实例所共享。
可以通过句柄引用静态变量。也可以使用类名加上“::”(类作用域操作符)引用静态变量。
静态变量通常在声明时初始化。
4、类的方法
类的方法指在类的作用域内定义的内部task和function。类中的方法默认使用自动存储。
六、System Verilog实现流水灯
代码:
led.sv
module led (
input logic clk, // 时钟信号
input logic rst_n, // 复位信号(低有效)
output logic [7:0] led // 8位LED输出
);
logic [23:0] counter; // 24位计数器,用于产生慢时钟信号
// 计数器逻辑
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
counter <= 24'd0;
else
counter <= counter + 1;
end
// LED流水灯逻辑
always_ff @(posedge counter[23] or negedge rst_n) begin
if (!rst_n)
led <= 8'b0000_0001;
else
led <= {led[6:0], led[7]};
end
endmodule
testbench
module led_tb;
logic clk;
logic rst_n;
logic [7:0] led;
// 实例化待测试的流水灯模块
led uut (
.clk(clk),
.rst_n(rst_n),
.led(led)
);
// 时钟信号生成
initial begin
clk = 0;
forever #10 clk = ~clk; // 50MHz时钟周期为20ns
end
// 测试逻辑
initial begin
// 初始化信号
rst_n = 0;
#100;
rst_n = 1;
// 仿真运行一段时间后结束
#1000000;
$stop;
end
endmodule
结果: