System Verilog 学习笔记(一)

目录

一、数据类型 

1.Verilog 基本数据类型 :

2. System Verilog 新添加的数据类型

3. 四值逻辑数据分类

4. 二值逻辑数据类型

5. 有符号类型和无符号类型

6. 仿真行为

7.其他类型

二、自定义类型

1.概述     

2. 枚举类型

1)定义枚举型

2)自定义枚举类型

一、数据类型 

1.Verilog 基本数据类型 :

    都为四值逻辑,即可表示0、1、X 和 Z 值。

 1)变量类型(variables): reg, integer 和 time 等变量,用来存储组合逻辑或时序逻辑的值。

        a. 变量类型保存在initial、always、task 和 function 内赋的值。

        b. 变量只能用过程赋值来赋值。

        c. 类型是integer 或 time 的变量一般像有相同数量位数的 reg 一样运转。

        d. 表达式中integer(32位)的值为有符号数,而 time 或 reg 的值为无符号数。

        e. reg 描述逻辑,integer 描述循环变量和计算, real 在系统模型中使用, time 和 realtime 保存测试装置的仿真时间。

 2)线网类型(nets):wire, wor, wand 等用来连接硬件模块,以及提供特殊的线网逻辑解决方案。

        a.线网用于建模结构化描述中的连接线路和总线线网的值,由线网的驱动器driver决定。

        b.驱动器:门,模块的实例或连续赋值的输出。

2. System Verilog 新添加的数据类型

1) SV添加了许多新的数据类型。

2) SV将硬件信号区分为“类型”和“数据类型”。

3) 类型即表示该信号为变量类型(variables)或者线网类型(wire)。

4) 线网类型赋值只能使用连续赋值语句(assign), 变量类型赋值可以使用连续赋值或过程赋值(initial/always)。例如logic[3:0] a,该变量默认类型为var(变量),可以使用连续赋值或过程赋值。

5) 数据类型则表示该数据是四值逻辑(logic) 还是二值逻辑(bit)。

        SV中的变量名称首先看类型是变量类型还是线网类型,再看数据类型是四值逻辑还是二值逻辑,最后看是有符号还是无符号数值。

        在testbench中大量使用logic类型变量,很少使用wire;当多余一个驱动源的时候,或者设计模块端口是双向的时候,使用wire类型。

module tb;
    wire w1;
    logic r1;  //(var)
    assign w1 = 1'b1;
    assign r1 = 1'b0;

    wire logic w2;
    var logic r2;
    assign w2 = 1'b1;
    assign r2 = 1'b0;
    initial begin
        #1ns;
        $display("w1 = %b, r1 = %b, w1, r1");
        $display("w2 = %b, r2 = %b, w2, r2");
    end
endmodule
    

3. 四值逻辑数据分类

1. 四值逻辑类型: integer 、reg、logic、net-type(如wire、tri。

1) SV中可以直接使用logic(数据类型)来达到通用的存储硬件数据的目的:

logic reset; //a bit wide 4-state variable
logic [63:0] data; // a 64 bit variable
logic [0:7] array [0:225]; //an array of 8-bit variables

2) logic虽然只是表示数据类型,而在声明时它会被默认为表示变量类型,用户也可以显示声明其类型:

var logic[63:0] addr; // a 64-bit wide variable
wire logic[63:0] data; // a 64-bit wide net

4. 二值逻辑数据类型

用来对在RTL更高抽象级的模型建模,例如系统级或者事物级模型,

1) 二值逻辑类型:byte(8b)、shortint(16b)、int(32b)、longint(64b)、bit(1b)。

2)RTL级需要表示硬件逻辑,X值用来捕捉设计错误,例如寄存器微初始化,Z值用来表示未连接或者三态的设计逻辑,因此不能使用二值逻辑,而在SV验证里不需要表示低层次的硬件逻辑,因此大多会用到二值逻辑。

3) bit类型默认也为变量类型。

5. 有符号类型和无符号类型

1) 无符号类型:bit、logic、reg、net-type。

2)有符号类型:byte、shortint、int、longint、integer。

3)可以在有符号类型后加unsigned来表示无符号类型,例如:byte有符号类型,表示数值范围[-128,127]。 byte unsigned 表示无符号类型,等同于bit [7:0], 表示数值范围是[0,255]。

4)在构建验证环境总线功能模型(BFM)时,无需关注硬件底层逻辑,可使用而二值逻辑实现。

5.)SV在于C语言交互时,可使用二值逻辑来使得两种语言的边界数据传输更简单。

6. 仿真行为

1)四值逻辑变量例如reg、logic或者integer等,在仿真开始时初值为X。

2)二值逻辑变量例如bit等,在仿真开始时的初值为0。

3)如果四值逻辑与二值逻辑的数据类型之间发生的默认转换,那么Z和X值将转换为0。

4)二值逻辑也可以用来实现可综合电路,只是二值逻辑没有X、Z值,因此可能会出现仿真行为同综合电路结果不一致的情况。

7.其他类型

1)SV添加void类型来表示空类型,经常用在函数定义时表示不会返回数值,同C的void。

2)SV添加shortreal表示32位单精度浮点类型,同C的float,而verilog的real类型表示双精度浮点类型,同C的double。

二、自定义类型

1.概述     

1)通过typedef来创建用户自定义类型。

2)通过enum来创建枚举类型。

3)通过struct来创建结构体类型。

  SV提供了特性使得用户可以构建更高抽象层的数据类型。

typedef int unsigned unit;
...
unit a, b;  // two variables of type unit.

2. 枚举类型

1)枚举类型(enum)提供方法来描述抽象变量的合法值范围,其每一个值都需要提供一个用户自定义的名字。

        例如枚举类型RGB可以拥有red、green和blue的三个数值:

enum {red, green, blue} RGB;

RGB默认初始值位 red。

2)Verilog语言自身不提供枚举类型,采用parameter常量来表示可取的值范围。或者使用`define来定义各个合法值对应的宏名称。

Verilog 代码:

define FETCH 3'h0  
define WRITE 3'h1
define ADD	 3'h2	
define SUB	 3'h3	
define MULT	 3'h4	
define DIV	 3'h5	
define SHIFT 3'h6
define NOP	 3'h7	

module controller (output reg	read, write,	
                   input  wire [2:0] instruction,	
                   input  wire	clock,resetN);	

parameter WAITE = 0,  //合法值
          LOAD  = 1, 
          STORE = 2;

reg [1:0] State, NextState;   //没有枚举类型,定义两个状态
always @(posedge clock,negedge resetN)
    if(!resetN) State <= WAITE;    //使用参数进行赋值
    else	    State <= NextState;	
always @(State)    begin
    case(State)
      WAITE:	NextState = LOAD;	
      LOAD:	    NextState = STORE;	
      STORE:	NextState = WAITE:	
    endcase 
end
always @(State, instruction)begin
    read  = 0; 
    write = 0;
    if(State == LOAD && instruction == `FETCH)  //宏定义用来描述instruction
        read = 1;
    else if (State == STORE && instruction == `WRITE)
        write = 1; 
end
endmodule

SV代码:

package chip_types;
    typedef enum {FETCH, WRITE,ADD, SUB,   //宏定义用enum替代
                  MULT,DIV,SHIFT,NOP} instr_t;  //缺省类型别名代称到instr_t
endpackage

import chip_types::*;  // 用import将instr_t从package导出
module controller (output logic	read, write,	
                   input  instr_t, instruction,	
                   input  wire	clock, resetN);	

enum {WAITE, LOAD, STORE} State,NextState;

always_ff @(posedge clock, negedge resetN)
    if(!resetN) State <= WAITE;
    else	    State <= NextState;	

always_comb begin
    case(State)
      WAITE:NextState = LOAD;
      LOAD:	NextState = STORE;	
      STORE:NextState = WAITE; 
    endcase 
end

always_comb begin
    read  = 0;
    write = 0;
    if (State == LOAD && instruction == `FETCH)
        read = 1;
    else if(State == STORE && instruction == `WRITE)
        write = 1; 
    end
endmodule

1)定义枚举型

a. 枚举型默认为 int,即32位的二值逻辑数据类型。

b. 为了能够更准确的描述硬件,SV允许指明枚举类型的数据类型,例如:

enum bit {TRUE, FALSE} Boolean;  //默认值TRUE=0,FALSE=1
enum logic [1:0] {WAITE, LOAD, READY} state; //默认值WAITE=00,LOAD=01,READY=10

c. 如果一个枚举类型被赋值,所赋的值必须符合其数据类型。

enum logic [2:0] {WAITE = 3'b001, 
                  LOAD  = 3'b010, 
                  READY = 3'b100}  state;

d. 如果枚举类型是四值逻辑数据类型,那么对枚举值赋为X或者Z也是合法的。

enum logic {ON = 1'b1, OFF = 1'bz}  out;

2)自定义枚举类型

a. 枚举类型也可以声明为自定义类型,这就使得可以用同一个枚举类型来生命多个变量或者线网。

typedef enum {WAITE, LOAD, READY} state_t;
state_t    state, next_state;

b. 如果枚举类型没有伴随typedef, 那么该枚举类型指的则是一个匿名枚举类型(anonymous enumerated type)。

enum {WAITE, LOAD, READY} state_t;  //state_t变成一个变量
state_t    state, next_state;  //此句话赋值错误。

c. Verilog与SV的数据类型转换是宽松的。

3)枚举类型赋值

  枚举类型赋值时相对严格,赋值操作符“=”两端尽量为相同的枚举类型。

  枚举类型可以转化为int, int不可以隐性的转化为枚举类型。

typedef enum {WAITE, LOAD, READY} states_t;    //声明一个类型
states_t state, next_state;         //声明俩变量
int foo;                            //int变量
state = next_state;  //(legal)  枚举类型之间赋值    
foo = state + 1;     //(legal)   state 枚举转化为int  枚举可以赋值给int
state = foo + 1;     //(illegal)   int不可直接赋给枚举类型
state = state + 1;   //(illegal) 
state++;             //(illegal)   ++暗示为int
next_state += state; //(illegal)   +=暗示为int

3. 结构体类型

1)定义结构体类型

       设计或者验证的数据经常会有逻辑相关的数据信号组,例如一个总线协议的所有控制信号,或者在一个状态控制器中用到的所有信号。

       SV添加了同C一样的结构体struct,结构体的成员可以是任何变量类型,包括自定义类型和其他常亮类型。

struct {
    int a, b;  //32 bit variables
    opcode_t opcode;  //user-defined type
    logic [23:0]  address;  //24-bit variables
    bit   error;   //1 bit 2 state var.
    }    Instruction_Word;

说明:用 (struct{} 为匿名的结构体类型)声明了一个变量 Instruction_Word。

        类型要由变量来声明。

      正是由于结构体是变量的集合,因此结构体类型的变量也可以索引到其内部的变量,索引方式同C语言一样:

.
Instruction_Word.address = 32'hF000001E;

      结构体类型默认也是变量类型,用户也可以显示声明其为var或者wire类型。

2)自定义结构体类型

  结构体类型也可以伴随着typedef来实现自定义结构类型。

typedef struct{    // structure definition
    logic [31:0] a, b;
    logic [7:0]  opcode;
    logic [23:0] address;
}   instruction_word_t;   //instruction_word_t为类型
instruction_word_t  IW;   //structure allocation  自定义类型声明了一个IW变量

3)结构体类型赋值

结构体变量可以通过索引其各个成员做依次的成员赋值:

always @(posedge clock, negedge resetN)
    if(!resetN)   begin
        IW.a = 100;
        IW.b = 5;
        IW.opcode = 8'hFF;
        IW.address = 0;
    end
    else begin
    ...
    end

也可以通过分号'和花括号{}来实现整体赋初值:

IW = '{100, 3, 8’hFF, 0};

IW = '{address : 0, opcode : 8’hFF,  a:00, b : 5};

4. 字符串类型

1)概述

a. SV引入string类型用来容纳可变长度的字符串。

b. 字符串类型变量的存储单元为byte类型。

c. 字符串类型变量长度为N时,其字符成员索引值为从0到N-1.

d. 不同于C函数,字符串结尾没有“空字符”即null字符“\0”。

e. 字符串的内存是动态分配的,用户无需担心内存空间管理。

2) 常见使用方法

typedef logic [15:0]   r_t;  //16b 四值逻辑无符号类型
r_t    r;
integer  i = 1;
string   b = "";    //等同于string b;
string   a = {"Hi", b}; 
r = r_t'(a);   //OK 将H、i对应的数据分别赋给r的两个字节
b = string'(r);  //整型赋给字符串
b = "Hi";
b = {5{"Hi"}};
a = {i{"Hi"}};   //i是变量
a = {i{b}};
a = {a, b};
a = {"Hi", b};
b = {"H", ""};
a[0] = "h";   

字符串赋给整型或把整型赋给字符串,都应该用一个显示的类型转化 dst = T'(src)。

3) 字符串内建方法

str.len(): 返回字符串的长度。

str.putc(i,c): 将第i个字符替换为字符c,等同于str[i] = c。

str.getc(i): 返回第i个字符。

str.substr(i,j): 将从第i个字符到第j个字符的字符串返回。

ste.{atoi(),atohex(),atooct,atobin}: 将字符串转变为十进制、十六进制、八进制或二进制数据。

str = "123";

int i = str.atoi();   //assigns 123 to i

三、设计特性

1.面向可综合设计的主要优化

a. 添加interface从而将通信和协议检查进一步封装。

b.添加类似C语言的数据类型,例如int、byte。

c. 添加用户自定义类型、枚举类型、结构体类型。

d. 添加类型转换($cast(T,S)或者'())。

e.添加包(package)从而使得多个设计之间可以共享公共类型和方法。

f. 添加方便的赋值操作符和运算操作符,例如++、+=、===。

g. 添加priority和unique case语句。

h. 添加always_comb、 always_latch和always_ff 等过程语句块。

2. 过程语句块新特性

a. SV添加了新的面向硬件的过程语句块,使得该语句块可以更让清楚地表达设计者的意图。

b. always 语句块被细分为:

组合逻辑语句块 always_comb

锁存逻辑语句块 always_latch

时序逻辑语句块 always_ff

1) 过程语句块 always_comb

a. 可以自动嵌入敏感列表。

b. 可以禁止共享变量,即赋值左侧的变量无法被另一个过程快所赋值。

c. 软件工具会检查该过程块,如果其所表示的不是组合逻辑,就会发出警告。、

always @ (a, en) if(en) y = a;  //Verilog 无else锁存逻辑而非组合逻辑
=>
always_comb if(en) y = a;  //System Verilog 非组合逻辑发出警告

d. always_comb 在仿真0时刻会自动触发一次,无论在0时刻是否有敏感信号列表中的信号发生变化。

Verilog @* 的敏感列表声明方式不同于always_comb:
a. @* 不要求可综合的建模要求,但always_comb则会限制其他过程块对同一变量进行赋值。

b. @* 的敏感列表可能不完全,例如如果一个过程块调用一个函数,那么@*则只会将该函数的形式参数自动声明到敏感列表,而不会将该函数展开。

c. always_comb则将被调用函数中可能参与运算的其他信号也声明到敏感列表中。

2) 过程语句块 always_latch

a. 表示锁存逻辑,且自动插入敏感列表。

always_latch
    if(enable) q <= d;

b. EDA工具会检查always_latch过程块是否真正实现了锁存逻辑。

3) 过程语句块 always_ff

a. 用来表示时序逻辑

always_ff @ (posedge clk, negedge resetN)
if (!resetN) q <= 0;
else q <= d;

b. 敏感列表必须指明posedge或者negedge(综合要求),从而使得EDA工具实现同步或异步的复位逻辑。

c. EDA工具也会验明always_ff过程块语句是否实现了时序逻辑。

4)赋值操作符

System Verilog 学习笔记(一)_第1张图片

a. SV可以通过'0, '1, 'z, 'x来分别填充0,1,z, x,代码会根据向量的位宽自动填充。

b. SV在比较数据时,可以通过==?进行通配比较。“==?”中X可以匹配0,1,x,z任意一个数。

System Verilog 学习笔记(一)_第2张图片

 c. SV添加了inside操作符,用来检查数值是否在一系列值的集合中。

logic [2:0] a;
if((a == 3'b001) || (a == 3'b010) || (a == 3'b100)) 
=>
if (a inside {3'b001, 3'b010, 3'b100}) 

5)增强的case语句

    仿真和综合可以将case语句做不同的翻译。Verilog定义case语句在执行时按照优先级,而综合编译器则会优化case语句中多余的逻辑。

     为保持仿真与综合的一致性,SV提供了unique和priority的声明,结合case, casez和casex来进一步实现case对应的硬件电路。

* unique case

a. unique case 要求每次case选择必须只能满足一条case选项。

b. unique case不能有重叠的选项,即多个满足条件的选项。

logic [2:0] request;
always_comb     //编译会出错,因为case的三个选项会有重叠的的部分
    unique casez (request)
        3'b1??: slave1_grant = 1;
        3'b?1?: slave2_grant = 1;
        3'b??1: slave3_grant = 1;
    endcase

c. unique case 可以并行执行,并且case选项必须完备。

logic [2:0] opcode;
always_comb     
    unique casez (opcode)
        3'b000: y = a + b;
        3'b001: y = a - b;
        3'b010: y = a * b;
        3'b100: y = a / b;
    endcase
* priority case

a. 表示必须至少有一个case选项满足要求。

b. 如果有多个case选项满足时,第一个满足的分支将会被执行。

c. priority case 的逻辑同if..,else的逻辑一致。

d. unique和priority的声明也可以结合if...else 条件语句使用。

e. unique和priority使得仿真行为同设计者希望实现的综合电路保持一致。

四、接口

1.概述

  SV在Verilog上扩展了接口(interface),添加了新的抽象端口类型interface。

  接口提供了一种新型的对抽象及建模的方式。

  接口的使用可以简化建模和验证大型复杂的而设计。

  interface允许多个信号被整合到一起用来表示一个单一的抽象端口。

  多个模块因此可以使用同一个interface,继而避免分散的多个端口信号连接。

interface  main_bus;
    wire [15:0] data;
    wire [15:0] address;
    logic [7:0] slave_instruction;
    logic       slave_request;
    logic       bus_request;
    logic       slave_ready;
    logic       data_ready;
    logic       mem_read;
    logic       mem_write;
endinterface

module top (input  wire clock,resetN,test_mode);

wire [15:0] data, address, program_address, jump_address;
wire [7:0]  slave_instruction, next_instruction;
wire [3:0]  slave_instruction;
wire        slave_request, slave_ready;
wire        bus_request, bus_grant;
wire        mem_read, mem_write;
wire        data_ready;

processor proc1(
     //main bus ports
    .data(data),
    .address(address),
    .slave_instruction(slave_instruction),
    .slave_request(slave_request),
    .bus_request(bus_request),
    .slave_ready(slave_ready),
    .data_ready(data_ready),
    .mem_read(mem_read),
    .mem_write(mem_write),
     //other ports
     .jump_address(jump_address),
     .instruction(instruction),
     .clock(clock),
     .resetN(resetN),
    .test_mode(test_mode)    
); 
interface  main_bus;
    wire [15:0] data;
    wire [15:0] address;
    logic [7:0] slave_instruction;
    logic       slave_request;
    logic       bus_request;
    logic       slave_ready;
    logic       data_ready;
    logic       mem_read;
    logic       mem_write;
endinterface
/************Top-level Netlist********/
module top (input  logic clock,resetN,test_mode);
    logic [15:0]   program_address, jump_address;
    logic  [7:0]   instruction, next_instruction;

    main_bus bus();  //instance of an interface
                     //interface name is bus

processor proc1(
     //main bus ports
    .bus(bus),    //interface connection
     //other ports
     .jump_address(jump_address),
     .instruction(instruction),
     .clock(clock),
     .resetN(resetN),
    .test_mode(test_mode)    
); 

2. 接口内容

        接口可以包含变量或者线网,也可以封装模块之间的通信协议。

        接口中可以嵌入与协议有关的断言检查、功能覆盖率收集等模块。

        接口与模块的区别在于,接口不允许包含设计层次,即接口无法例化module,但接口可以例化接口。

        接口中可以进一步声明modpart来约束不同模块连接时的信号方向。

3. 接口的声明

        接口定义同模块定义类似。

        接口也可以有端口,例如外部接入的时钟或者复位信号。

        接口内部可以声明所有的变量或者线网类型。

interface  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_request;
    logic       slave_ready;
    logic       data_ready;
    logic       mem_read;
    logic       mem_write;
endinterface
/************Top-level Netlist********/
module top (input  logic clock, resetN, test_mode);
    logic [15:0]   program_address, jump_address;
    logic  [7:0]   instruction, next_instruction;

    main_bus bus(           //instance of an interface
        .clock(clock),
        .resetN(rersetN),
        .test_mode(test_mode)
    ); 

4. 接口的例化

        接口例化方式和模块例化方式一致。

        模块的端口如果声明为input、output或者inout,那么在例化时可以不连接。

        模块的端口如果声明为interface,那么在例化时则必须连接到一个接口实例,或者另外一个接口端口。

        如果一个模块拥有一个接口类型端口,那么要索引该接口中的信号,需要通过一下方式进行:

.
always @(posedge bus.clock, negedge bus.resetN)

5. modport

        接口中的变量或者线网信号,对于连接到该接口的不同模块则可能具备不同的连接方向。

        接口引入了modport,表示不同的模块看到同一组信号的视角。(连接方向)

        在接口声明modport,需要指明modport中各个信号的方向。

interface  chip_bus (input  logic clock, resetN);
logic  interrupt_request, grant, ready;
logic [31:0]   address;
logic [63:0]   data;

modport  master(input  interrupt_request,
                input  address,
                output grant, ready,
                inout  data,
                input  clock, resetN );
modport  slave (output  interrupt_request,
                output  address,
                input grant, ready,
                inout  data,
                input  clock, resetN );
endinterface

        当一个模块在例化时,可以选择连接到interface端口中具体的某一个modport。

        这种方式可以降低方向连接错误的可能,进而避免信号多驱动的情况。

        design中使用modport居多。

interface  chip_bus(input  logic clock, resetN);
    modport master(...);
    modport slave (...);
endinterface

module  primary  (interface  pins);  //generic interface port
... 
endmodule


module  secondary (chip_bus pins)  //specific interface port
...
endmodule

module  chip  (input logic  clock, resetN);

    chip_bus  bus(clock, resetN);  //例化interface
    primary  i1  (bus.master);
    secondary i2 (bus.slave);
endmodule

6. 验证中的应用

        利用接口,也可以将测试平台同DUT连接在一起。

System Verilog 学习笔记(一)_第3张图片

module top;
    bit clk;
    always #50  clk = ~ clk;
    
    arb_if  arbif(clk);    //arbiter interface
    arb_with_port  a1 (.grant  (arbif.grant),  //.port (ifc.signal) arbiter design
                       .request(arbif.request),
                       .rst(arbif.rst),
                       .clk(arbif.clk));
    test_with_ifc  t1(arbif);  //From Sample 4-6
endmodule : top

 补充:testbench包含:DUT例化,interface例化,验证环境TB例化。

你可能感兴趣的:(System,Verilog学习,学习,fpga开发,笔记)