-
always_comp过程块表示建立组合逻辑模型。always_comp后面不需要指定敏感表,会被自动推断出来。推断的敏感表包含了所有被过程块读取并在块外赋值的信号。SystemVerilog的敏感表还包括了过程块中调用函数的所有信号。always_comp和always的另一个不同之处在于,在所有initial和always过程块启动后,always_comp块会在仿真的零时刻自动触发,不管推断出的敏感表中的信号是否发生了变化,这样的自动求值都会发生。相对于always,必须等到敏感表中至少有一个信号有变化才会触发语句块。
-
在大型设计中常遇到的一个问题是,组合过程块的代码量非常庞大,可能会显得笨重或者引起时序难以收敛。解决办法是把大的过程块拆分成若干个小的过程块。但是这种划分会导致许多信号要在几个过程块中来回传送,从而使代码难以理解。另一种办法是把所有组合逻辑都写在一个过程块中,但使用函数把它分割为更小的子块。由于函数也会综合成组合逻辑,因此这是描述大型组合逻辑的一种有效的代码结构。
-
使用函数构造大块组合逻辑时,Verilog的@*可能不会推断出完整的敏感表。always@*推断出的敏感列表只针对于那些被always块直接读取的信号,而对过程块调用的函数中读取的信号不能推断为敏感表。因此每个函数调用必须列出所有作为函数输入的信号,并且每个函数的定义也都必须列出这些信号作为形式输入变量。SystemVerilog中的always_comp过程块则消除了Verilog中always@*的这些缺陷。always_comp过程块对块内读取的信号和块内调用的函数读取的信号都敏感。这样编写函数时就不需要形式参数了。
// 敏感表中只包含data
always @ * begin
a1 = data << 1;
b1 = decode();
end
// 敏感表中包含data,sel,d,e,c
always_comp begin
a2 = data << 1;
b2 = decode();
end
// 不带输入的函数
function decode;
begin
case(sel)
2'b01: decode = d | e;
2'b10: decode = d & e;
default: decode = c;
endcase
end
endfunction
-
锁存逻辑always_latch和组合逻辑差不多。唯一区别是:在锁存逻辑中,过程块的输出变量不需要对所有可能的输入条件响应。条件中缺失的部分会产生latch。always_ff表示时序逻辑。
-
Verilog中函数名本身就是一个与该函数类型相同的变量,函数的返回值通过对函数名的赋值产生。当执行到函数的末尾就会退出,最后赋给函数名的值就是整个函数的返回值。函数必须有返回值,当函数被调用时,调用代码必须获得返回值。
// Verilog的函数,函数的类型默认是线网类型
// 因为函数本身就是为了实现一段组合逻辑
function [31:0] add_and_inc(input [31:0] a,b);
begin
add_and_inc = a + b + 1;
end
endfunction
- SystemVerilog为了保持与Verilog的兼容,既可以使用return,也可以通过对函数名赋值来指定函数返回值。如果执行到return语句就会返回一个值,并且函数终止执行。如果函数执行到最后也没遇到return语句,那么赋给函数名变量的值就是函数的返回值。即使使用了return语句,函数名仍然是一个变量,可以在执行return语句之前当做临时内存使用。函数可以显示地声明为void数据类型,表示函数没有返回值。函数可以有output和inout,这样一来,虽然空函数没有返回值,仍然可以传递数据。
function int add_and_inc(input int a,b);
add_and_inc = a + b;
return ++add_and_inc;
endfunction
// 空函数
typedef struct {
logic valid;
logic [7:0] check;
logic [63:0] data;
} packet_t;
function void fill_packet(
input logic [63:0] data_in;
output packet_t data_out
);
data_out.data = data_in;
for(int i = 0; i <= 7; i++)
data_out.check[i] = ~data_in[(8*i)+:8];
data_out.valid = 1;
endfunction
- Verilog和SystemVerilog的函数都有一些限制:函数不能包含任何类型的延迟或事件控制,也不能使用非阻塞语句,只能使用组合逻辑。Verilog传递数值只能使用顺序传递的方式,而SystemVerilog传递数值可以既可以使用顺序传递也可以使用形式参数来传递。SystemVerilog允许任务和函数为每个形式参数定义一个可选的缺省值。
// 定义函数
function int divide(input int numerator,denominator);
//
endfunction
// Verilog使用顺序参数传递数值
always@(posedge clk) begin
result <= divide(b,a);
end
// SystemVerilog使用形式参数传递数值
always@(posedge clk) begin
result <= divide(.numerator(b),.denominator(a));
end
// 形式参数可以指定缺省值
function int incrementer (int count = 0, step = 1);
//
endfunction
- Verilog当调用任务和函数时,系统会将输入值复制到任务和函数中,于是这些值变成任务或函数的局部数值。当任务或函数执行到末尾返回时,系统再将所有的输出复制到任务或函数调用程序。SystemVerilog对自动任务和函数进行了扩展,可以使用引用而不是复制的方法传递数值。为了通过引用传递数值,形式参数用关键字ref取代方向关键字input,output或inout进行声明。
module chip(...);
typedef struct {
logic valid;
logic [7:0] check;
logic [63:0] data;
} packet_t;
packet_t data_packet;
bit [7:0] raw_data [0:7];
always@(posedge clk)
if(data_ready)
fill_packet(.data_in(raw_data),.data_out(data_packet));
function automatic void fill_packet(
ref logic [7:0] data_in [0:7];
ref packet_t data_out
);
// ...
endfunction
endmodule
- 为了增强可读性,可以在任务和函数的末尾指定名称。
endtask: <task_name>
endfunction: <function_name>