跳转至

System Verilog HDL

顺风而呼,声非加疾也,而闻者彰。 ——《劝学》

System Verilog(以下简称SV)是一种非常主流的硬件描述语言,作为Verilog的重要拓展,SV支持Verilog的全部语法,并在其基础上进行严格化、抽象化,能够更好适应大规模的硬件开发。在本门课程中,我们会用到SV的几个基础用法,以更好地为大规模、创新性数字电路设计服务。

你是否已经对Verilog足够熟悉?

本页内容为System Vrilog专属特性,需要你在充分阅读并掌握普通班Verilog教程后进行阅读。

1 数据类型

logic:自动推断四态变量类型

SV提供给我们一个新增的logic类型,这个类型的变量既可以是wire类型,也可以是reg类型,其具体类型是在编译时由编译器自动推断所得(类似于C++中的auto),这为硬件开发带来了诸多便利。

logic变量和Verilog中所有变量相同,都有0,1,X,Z四种状态。我们应该在声明时尽可能将所有变量都声明为logic。

  • logic作为线网型变量:
    logic [31:0] in;
    logic [31:0] out;
    assign out = in;
    
    此处out变量不在always中且组合赋值,因此其被推断为线网型变量。
  • logic作为寄存器型变量:
    logic [31:0] add_reg;
    always @(posedge clk) begin
        add_reg <= add_reg + 1;
    end
    
    此处add_reg变量被时序赋值,因此其被推断为寄存器型变量。
使用logic隐藏蹩脚的always中赋值问题

Verilog和SV中都有这样的规定:只有reg型变量在always中被赋值。但在always@(*)这样的组合电路描述中,即便一个变量根本不是寄存器,它也要被声明为寄存器型变量——这显然难以让人理解。如果使用logic类型,那么虽然在机器看来这个变量依然被推断为reg,但在我们看来,这个变量理解为wire型也完全是合理的,这会更符合人们的认知。

例如:不使用logic来描述一个mux的逻辑可以表述成:

module mux(
    input       din1,
    input       din2,
    input       sel,
    output reg  dout
);
always @(*) begin
    case(sel)
    1'b0: dout = din1;
    1'b1: dout = din2;
end
endmodule
但使用logic就可以让变量意义更加容易被理解:
module mux(
    input  logic  din1,
    input  logic  din2,
    input  logic  sel,
    output logic  dout
);
always @(*) begin
    case(sel)
    1'b0: dout = din1;
    1'b1: dout = din2;
end
endmodule

bit:自动推断二态变量类型

bit类型的变量与logic类似,都由编译器自动推断其具体类型,但其只有0和1两种状态,这可能会引起仿真综合不匹配的问题:二态变量在开始模拟时每个位的值都是0,而综合实现时每个位可能是0也可能是1。 因此我们并不推荐大家使用这种类型,仅作为了解即可。

enum:枚举类型

enum类型与C++中的枚举类型基本相同,这种枚举类型适合在有限状态机中进行使用,以简化代码量。值得注意的是,在使用enum时最好能够规定其类型和宽度,否则编译器会默认其为32位宽的变量:

enum logic [1:0] {IDLE, LOOKUP, MISS, REFILL} state, next_state;
enum logic [1:0] {S1=2'b11, S2=2'b00, S3=2'b10} crt, nxt;

2 数组操作

向量声明

在Verilog中,我们只能用以下方法来声明一个32位的寄存器:

reg [31:0] register;
如果要按照字节粒度去访问它,在书写代码时会造成很多困扰。例如如果你想要访问第三个字节,你需要:
assign temp = register[23:16];
但在SV中,我们可以按照指定粒度来声明,并方便地对一个粒度内全部位进行赋值和读取:

logic [3:0][7:0] register;
assign temp = register[2];
assign register[2] = 8'hff;
assign register[1][1] = 1'b0;

C语言风格的数组声明

如果我们要声明一个长度为128,有32为数据组成的数组,在SV中我们可以有两种方法,其中下面的方法是比较简便的且接近C语言的:

logic [31:0] register1 [0:127];
logic [31:0] register2 [128];

数组整体赋值

SV支持多行同位宽数组的整体赋值:

logic [31:0] big_vec [256];
logic [31:0] small_vec [128];
assign small_vec = big_vec[128:255];

数组聚合赋值

SV同样支持将多个分离变量合并赋值给一个多行数组:

logic [7:0] a, b, c;
logic [7:0] vec [0:3];
assign vec = {8'hff, a, b, c};

3 always衍生物

always_comb

always_combalways@(*)的完全替代,显式地说明了块内描述了一个组合电路,这很有利于初学者读懂代码:

always_comb begin
    case(alu_op) 
    `ADD:                   result = sr1 + sr2;
    `SUB:                   result = sr1 - sr2;
    default:                result = 0;
end

always_ff@(posedge clk)

always_ff@(posedge clk)always@(posedge clk)的完全替代。虽然代码多出了几个字符,但这样书写可以很清晰地告诉自己下面的内容正在对一个寄存器进行时序赋值,降低了代码书写时错误的可能性:

always_ff @(posedge clk) begin
    if(!rstn) begin
        dout <= 0;
    end
    else begin
        dout <= din;
    end
end

参考

本部分内容参考了以下资料: