设计特性
相对于Verilog面向可综合设计的优化:
- 添加接口(interface)从而将通信和协议检查进一步封装。
- 添加类似C语言的数据类型,例如int,byte添加用户自定义类型,枚举类型,结构体类型。
- 添加类型转换($cast(T,S)或者 ‘( ) ),添加包(package)从而使得多个设计之间可以共享公共类型和方法。
- 添加方便的赋值操作符和运算操作符,例如++、+=、===,添加priority 和unique case 语句。
- 添加**always_comb、always_latch 和 always_f **等过程语句块。
过程语句块的新特性
always语句块被细分为了:always_comb、always_latch、always_ff
组合逻辑语句块always_comb
always_comb可以**自动嵌入敏感列表,而且对其中调用内部函数会自动展开并添加进去(@*并不会展开内部调用的函数,因此可能敏感列表不全,这样的后果是影响仿真,但不会影响综合)**。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20always @* begin // <= Infers @(data)
al=data<<1; //甚至可以不给敏感列表,只不过不会执行
b1=decode();
...
end
always_comb begin // <= Infers @(data, sel,c,d,e)
a2=data<<1;
b2=decode();
...
end //两者在综合电路时功能一样,但仿真时由于上面的不会受sel、b、c等信号的影响
//导致仿真结果不相同
function decode; //function with no inputs
begin
case(sel)
2'b01: decode=d | e;
2'b10: decode=d & e;
default: decode=c;
endcase
end endfunctionalways_comb可以禁止共享变量,即赋值左侧的变量无法被另一个过程块所赋值。
软件工具会检查该过程块,如果其所表示的不是组合逻辑,那么会发出警告。
1
2
3always @(a,en) if(en) y=a; //Verilog、由于没有else变成锁存逻辑
=>
always_comb if(en) y=a; //systemverilog 会发出警报always_comb在仿真0时刻会自动触发一次,无论在0时刻是否有敏感信号列表中的信号发生变化。
锁存逻辑语句块always_latch
always_latch表示锁存逻辑,且自动插入敏感列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22module register_reader
(input clk,ready,resetN,
output logic[4:0] read_pointer);
logic enable; //internal enable signal for the counte
logic overflow; //internal counter overflow flag
always_latch begin //latch the ready input
if(!resetN)
enable <= 0;
else if(ready)
enable <= 1;
else if(overflow)
enable<=0;
end
always @(posedge clk,negedge resetN) begin //5-bit counter
if(!resetN)
{overflow,read_pointer} <= 0;
else if(enable)
{overflow,read_pointer} <= read_pointer + 1;
end
endmodulealways_latch
if(enable) g<=d;
EDA工具会检查always_latch过程块是否真正实现了锁存逻辑。
时序逻辑语句块always_ff
always_ff用来表示时序逻辑
1
2
3
4
5alwaya_ff @(posedge clock, negedge resetN)
if(!renetN)
q <= 0;
else
q <= d;敏感列表必须指明posedge 或者 negedge(综合要求),从而使得EDA工具实现同步或者异步的复位逻辑。
EDA工具也会验明always_ff过程块语句是否实现了时序逻辑。
赋值操作符
Verilog没有简单的方法可以对向量填充1,SV可以通过** ‘0,’1,’z 和 ‘x**来分别填充0,1,z和x,代码会根据向量的宽度自动填充,提高了代码的便捷性和复用性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/***********要求赋值64个1**********/
# Verilog:
parameter N=64;
reg[N-1:0] data_bus;
data_bus=64'hFFFFFFFFFFFFFFF;//set all bits of data bus to 1
# SystemVerilog:
vecs[i]='1; //'hffffffffffffffff
vecs[i]=64'b0-1; //'hffffffffffffffff
vecs[i]={64{1'b1}}; //'hffffffffffffffff
vecs[i]=-64'b0; //'hffffffffffffffff
vecs[i]='b1; //'h0000000000000001
vecs[i]=0-1; //'hffffffffffffffff #32-bit隐式转换成64-bit
vecs[i]={64{1}}; //'h0000000100000001 #64个32-bit的1,前面被截掉
vecs[i]=~0; //'hffffffffffffffffSV在比较数据时,可以通过**==?**来进行通配比较
1
2logic [7:0] opcode;
if (opcode==?8'b11011???) // 比较时不考虑低三位SV添加了inside操作符,用来检查数值是否在一系列值的集合中
1
2
3
4logic [2:0]a;
if (a inside {3'b001,3'b010,3'b100}) //检查a是否在{}中
//等同于
logic [2:0] a; if((a==3'b001) || (a==3'b010) || (a==3'b100))
增强的case语句
仿真和综合可以会将case语句做不同的翻译,Verilog定义case语句在执行时按照优先级,而综合编译器则会优化case语句中多余的逻辑。
SV提供了unique和priority的声明,结合case,casex和casez来进一步实现case对应的硬件电路,保持仿真与综合的一致性
unique case语句
- unique case要求每次case选择必须只能满足一条case选项。
- unique case不能有重叠的选项,即多个满足条件的选项。
- unique case可以并行执行,并且case选项必须完备。
priority case语句
- priority case则表示必须至少有一个case选项满足要求,如果有多个case选项满足时,第一个满足的分支将会被执行。
- priority case的逻辑同 if..else 的逻辑一致。
- unique 和 priority 的声明也可以结合 if..else 条件语句使用。
- unique 和 priority 使得仿真行为同设计者希望实现的综合电路保持一致。
接口
接口的概述
- SV在Verilog语言基础上扩展了接口(interface)。
- 接口提供了一种新型的对抽象级建模的方式,可以简化建模和验证大型复杂的设计。
- Verilog是通过模块之间进行端口连接来完成模块间通信的。对于大型的设计通过端口进行连接将会让硬件集成变得非常乏味和容易出错。
接口的优势
- 一个设计发生了变化,不会影响太多的端口声明和连接
- interface允许多个信号被整合到一起用来表示一个单一的抽象端口。
- 多个模块因此可以使用同一个interface,继而避免分散的多个端口信号连接。
- 避免许多不必要的重复定义,特别是对于常用总线端口
接口的内容
- 接口不单单可以包含变量或者线网,它还可以封装模块之间通信的协议。
- 此外接口中还可以嵌入与协议有关的断言检查、功能覆盖率收集等模块。
- 接口不同于模块(module)的地方在于,接口不允许包含设计层次(接口无法例化module)但是接口可以例化接口。接口中可以进步声明modport来约束不同模块连接时的信号方向。
接口的声明和例化
- 接口的定义、例化方式同模块定义以及例化方式类似。
- 接口也可以有端口,例如外部接入的时钟或者复位信号。
- 接口内部可以声明所有的变量或者线网类型。
- 模块的端口如果声明方向(input、output、inout),则例化的时候可以不连接(悬空),若声明interface,则例化的时候必须连接接口实例,或另一个接口端口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28interface main_bus (input logic clock,resetN,test_mode);
wire [15:0] data;
wire [15:0] address;
logic [7:0] slave_instruction;
logic slave_request;
logic bus_grant;
logic bus_request;
logic slave_ready;
logic data_ready;
logic mem_read;
logic mem_write;
endinterface
module top (input logic clock, resetN, test_mode);
logic [15:0] program address, jump address;
logic [7:0] instruction, next_instruction;
main_bus bus //例化一个接口
( .c1ock(clock),
·resetN(resetN),
.test_mode(test_mode)
);
assign bus.address=mem_select? slave_address:'z;
assign bus.data=bus.slave_ready? slave_data:'z;
/*************索引接口中的单个信号***********/
<port name>.<internal interface signal name>
always @(posedge bus.clock, negedge bus.resetN)
采样和数据驱动
竞争问题
- 在仿真行为中,为了尽量避免时序电路中时钟和驱动信号的时序竞争问题,我们需要给出尽量明确的驱动时序和采样时序。
- 默认情况下,时钟对于组合电路的驱动会添加一个无限最小时间(delta-cycle)的延迟,而该延迟无法用绝对时间单位衡量,它要比最小时间单位精度还小。
- 在一个时间片(time-slot)中可以发生很多事情,例如在仿真器中敲入命令”run 0”,即是让仿真器运行一个delta-cycle的时间。
- 由于各种可能性,clk与被采样数据之间如果只存在若干个(0..N)delta-cycle的延迟,因此采样数据中的竞争问题会成为潜在困扰仿真采样准确性的问题。
避免采样的竞争问题
- 在驱动时,添加相应的人为延迟,模拟真实的延迟行为,同时加大clk与变量之间的延迟,以此提高DUT使用信号时的准确度和TB采样信号时的可靠性。
- 对于一些采样时依然存在delta-cycle延迟的信号,我们还可以依靠在采样事件前的某段时刻中进行采样,来模拟建立时间的采样要求,确保采样的可靠性。
时钟块的使用
- clocking块可以定义在interface、module、program中
- clocking中列举的信号应该由interface或者其它声明clocking的模块定义的
- clocking在声明完名字之后,应该伴随着定义默认的采样事件,即”default input/output event”
- clocking默认采样事件前的1step对输入进行采样,在采样事件后的**#0**对输出进行驱动
- 也可以在定义信号方向时,用新的采样事件可以对默认事件做覆盖
- 接口中可以多个添加时钟块,且同一信号在不同时钟块中方向可以不同
- 时钟块采样和时钟直接采样,被测值的变化均在上升沿,仅仅是采样点不同
- 输入数据要求在设定的时间之前就要满足数值的建立
1
2
3
4
5
6
7clocking bus @(posedge clock1); //定义时钟块上升沿驱动、采样
default input #10ns //默认上升沿之前10ns采样
output #2ns; //默认上升沿之后2ns驱动
input data,ready,enable; //对三个输入信号采样(前10ns)
output negedge ack; //clock1下降沿驱动ack(后2ns)
input #l step_addr; //clock1上升沿前1时间片采样
endclocking
接口规定方向
- 接口中的变量或者线网信号,对于连接到该接口的不同模块则可能具备这不同的连接方向。
- 引入了modport来作为module port的缩写,表示不同的模块看到同一组信号时的视角(连接方向)。
- 在接口中声明modport,需要指明modport中各个信号的方向,不需要声明宽度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18interface chip_bus (input logic clock,restN);
modport master(...);
modport slave (...);
endinterface
module primary (interface pins); //一般接口例化
...
endmodule
module secondary (chip_bus pins); //指定接口例化
...
endmodule
module chip(input logic clock, resetN);
chip bus bus (clock, resetN); //例化接口
primary il (bus. master); //例化模块1 具体到哪一根线减少意外驱动
secondary i2 (bus. slave); //例化模块2
endmodule
接口总结
- 接口对于设计复用非常有利,如果要添加新的信号,只需要在接口中声明,而不必在模块中声明。
- 接口减少了模块之间错误连接的可能性。
- 由于接口将有关信号都集合在一起,因此在使用这些信号时需要多添加一个层次(接口实例名)。
- 接口往往会将有关的信号集合在一起,这意味着对于拥有多组不相关信号的设计而言,它可能需要有多个接口实例才能完成与其它模块的连接。