$display:

作用是控制台输出信息

$display("Start simulation")        //显示字符串
$display("data_play = %h hex", 100)     //显示data_play的16进制数(或者其他进制)
$display("Simulation time is %t", $time)     //显示仿真的时间

常数 变量:

SV 中的 logic 数据类型和 Verilog 中的 reg 类型是一样的,可以互换使用,更多的是兼容 wire 类型。

// 常数
16'habcd  // 16位二进制位宽,---- ---- ---- ----,代表的数是十六进制abcd
// 变量
logic a;  // 1bit
logic [3:0]b;  // 4bit
logic [31:0][31:0]c;  // 32x32bit

// logic 不分wire reg 
logic [31:0] a, b, c;
assign c = a & b;

数组声明 初始化 '{ }

//一维数组
int a[0:15];   //16个整数 【0】...【15】
int b[16];     //16个整数 【0】...【15】    只给出数组宽度就是大端模式
int c[15:0];   //16个整数, a和b是一样的,c与其相反

//多维数组
int a[0:7][0:3];
int a[8][4];
a[7][3] = 1;
//  '{ }
int a[4] = '{0,1,2,3};         //对4个元素分别赋值0-3。 a[0]=0; a[1]=1; a[2]=2; a[3]=3;
int a[4:0] = '{0,1,2,3};       //a[3]=0; a[2]=1; a[1]=2; a[0]=3;
int b[4];
b = '{0,1,2,3};                //先声明,后赋值
b[0:2] = '{0,1,2};             //部分赋值
int c[5] = '{5{8}};            //5个值全赋值为8
int d[5] = '{6,7,default:1};   //{6,7,1,1,1}

动态数组

int dyn[];            //声明动态数组
dyn = new[5];         //分配5个元素,此时动态数组的宽度为5
dyn = new[20](dyn);   //重新分配20个元素,并将原dyn的值赋给新的dyn
dyn = new[100];       //分配100个元素,原先的值已丢弃(释放)

int dyn[] = '{0,1,2,3};          //声明动态数组,宽度为4,并赋值如下:dyn[0]=0;dyn[1]=1;dyn[2]=2;dyn[3]=3;

队列{ }

// 队列的初始化不需要 '{ },而是 { }
int q[$];
int qq[$] = {1,2,3,3,2,1};

队列函数 push_front、push_back、pop_front、pop_back

// push_front、push_back、pop_front、pop_back这四个函数只实用于队列,不能用于其他数组
int q2[$] = {1,2};
int q1[$] = {7,8,9};
q1.insert(1, q2);          //q1为{7,1,2,8,9}。在q1的第1位上插入q2

算术左移-逻辑左移、算术右移、逻辑右移:

算术左移和逻辑左移一样都是右边补0,
逻辑右移很简单,只要将二进制数整体右移,左边补0即可,
算术右移符号位要一起移动,并且在左边补上符号位,也就是如果符号位是1就补1符号位是0就补0。

<<    左移 (逻辑左移、算术左移)
>>    逻辑右移  // 整体右移,左边补0
>>>   算术右移

运算符:



systemverilog task语法_sed

systemverilog task语法_systemverilog_02


有符号 无符号变量:



bit              // 1位两态无符号整数
byte             // 8位两态有符号整数
shortint         // 8位两态有符号整数
int              // 16位两态有符号整数
longint          // 32位两态有符号整数
// 对四态状态值的检查
if ($isunknown(data) )
$display(“@%0t : 4-state value detected“,$time);

模块声明 例化:

// 模块声明
module adder (
    input logic [3:0] a, b,
    output logic [3:0] c
);
    assign c = a + b;
endmodul

// 模块例化
logic [3:0] b, c;
adder adder_inst0(.a(4'b0010), .*);   // .*表示之后的所有端口同名例化:相当于  adder adder_inst0(.a(4'b0010), .b(b), .c(c));

always_comb

always_comb内部每条语句都是阻塞赋值语句。不能出现电路语句(assign)
always_comb有以下性质:
内部覆盖性
对外原子性

always_comb begin
    a = 1'b1;
    b = a;
    a =1'b0;
    c = a;
end
// 电路输入:1'b1;1'b0;  // 右边(1'b1;1'b0; a)除去左边(a,b,c)
// 电路输出:(a,b,c)//左边
// 运行结果 a=1'b0, b=1'b1, c=1'b0,
assign a = b;
always_comb begin
    b = 1'b1;
    c = a; 
    b = 1'b0;
end

// 先 always_comb 得: c = a, b = 1'b0; 再assign得:a = b = 1'b0; c = a = 1'b0
// 于是 a=1'b0, b=1'b0, c=1'b0,
assign a = b;
always_comb begin
    b = 1'b1;
    c = b; 
    b = 1'b0;
end

// 先 always_comb 得: c = b = 1'b1; b = 1'b0 再assign得:a = b = 1'b0;
// 于是 a=1'b0, b=1'b0, c=1'b1,

always_comb case

unique case

case块内没有优先级,并行比较

// unique case
always_comb begin
    b = 1'b0;
    unique case (a[3:0])
        4'd1: begin b = 1'b1; end
        4'd0: begin b = 1'b0; end
        default: begin end
    endcase
end

priority case

case块内有优先级,串行比较 (依次比较)

always_comb begin
    priority case (1'b1)
        a[3]: begin  end
        a[2]: begin  end
        default: begin end
    endcase
end

// 相当于:
/*
always_comb begin
    if(a[3]) begin       
    end else if(a[2]) begin      
    end else if() begin  end 
end
*/

always_comb if 和 for

if
if和else用于条件判断

always_comb begin
    b = 1'b0;
    if (a[3]) begin b = 1'b1; end 
    else if (a[2]) begin b = 1'b0; end 
    else begin   end
end

for
for在always_comb中,会被解释为循环展开。

logic [15:0]a;
logic [3:0] b;

always_comb begin
    b = '0;
    for (int i = 15; i >= 0; i--) begin
        if (a[i]) begin
            b = i[3:0];
            break;
        end
    end
end
always_comb begin
    for (int i = 0 ; i < 16; i ++) begin
        if (i >= n) break;
    end
end
always_comb begin
    for (int i = 0; i < 16; i++) begin
        a[i] = b[i] & (c[i] == d[i] | e[i]); // 编译器不认为i是常数,a[i:i+3]非法
    end
end

for (genvar i = 0; i < 16; i++) begin  // genvar assign
    assign a[i] = b[i] & (c[i] == d[i] | e[i]); // 编译器认为i是常数,a[i:i+3]合法
    always_comb begin
        
    end
end

always_ff

always_ff用于描述触发器。

always_ff @(posedge clk) begin
    if (~resetn) begin
        q <= '0;  // 触发器非阻塞赋值
    end else if (en) begin
        q <= d;
    end
end
logic [3:0] a, a_nxt;
// always_ff只写触发器部分 非阻塞赋值
always_ff @(posedge clk) begin
    if (~resetn) begin
        a <= '0;
    end else if (en) begin
        {a, b} <= {a_nxt, b_nxt};
    end
end

always_comb begin
    a_nxt = a;
    // 不属于触发器的逻辑,写在always_comb  阻塞赋值
    unique case(a)
        4'd3: begin
            a_nxt = 4'd2;
        end
        default: begin
            
        end
    endcase
end

typedef

自定义类型

// typedef 已有类型 新类型;
typedef logic[31:0] word_t;  // 新类型为word_t, 32位的logic组成一个word_t
typedef logic[5:0] entry_t;   // 新类型为entry_t, 6位的logic组成一个entry_t
typedef entry_t[31:0] table_t;  // 新类型为table_t, 32个的entry_t组成一个table_t

word_t a, b;
assign b = {a[15:0], a[31:16]};

table_t table1; // logic [31:0][5:0]
assign table1[1] = '0;
assign table1[0][1] = '0;

结构体 struct

// type definition  打包
typedef struct packed {
    logic [3:0] alufunc;  // control_t[4]
    logic mem_read;
    logic mem_write;
    logic regwrite;
    logic [3:0] reg_addr;  // control_t[0]
} control_t;  // control_t是结构体的名字

// variable declaration 
control_t control;  // 结构体声名

// 解包
logic regwrite;  
assign regwrite = control.regwrite;  // 也可以assign regwrite = control[0] 索引 control.regwrite

// using structs without typedef 也可以的定义结构体写法
struct packed {
    logic [3:0] alufunc;
    logic mem_read;
    logic mem_write;
    logic regwrite;
} control_without_typedef;
typedef struct packed {
    .....
} pipeline_decode_t;

pipeline_decode_t p, p_nxt;
always_ff @(posedge clk) begin
    p <= p_nxt;
end

enum 枚举

enum语法常用于编码(包括状态机的编码)。enum类型的变量,在Vivado仿真里会显示枚举项。枚举项被视为常量,各枚举类型的枚举项名字不能冲突。enum类型的变量,赋值时只能用枚举项。

语法

typedef enum <datatype> { // enum体内常为 常量
    IDEN_1, IDEN_2
} typename;
// 定义1
typedef enum logic [3:0] {  // [3:0]可以多,不可以少
    ALU_ADD, ALU_AND, ALU_SUB
} alufunc_t;

// 使用
alufunc_t alufunc;   // 变量声名

// 定义2
enum logic [3:0] {
    ALU_ADD, ALU_AND, ALU_SUB
} alufunc_without_typedef;
// 定义
typedef enum logic [1:0] {
    STATE_0, STATE_1, STATE_2
} state_t;

// 使用
state_t  state, state_nxt;   // 变量声名

always_ff @(posedge clk) begin
    if (~resetn) begin
        state <= state_t'(0);  // state <= '0 错误,需要强制类型转换为state_t型
    end else begin
        state <= state_nxt;
    end
end
强制类型转换 新类型'(旧类型具体变量)

union

联合类型 Union内的成员公用同一存储空间。所以对其中一个成员赋值,其他成员也会相应变化,只是数据类型不同而已

typedef union packed {
    struct packed {
        logic zero;
        logic [31:0] aluout;
    } alu;
    struct packed {
        logic branch_taken;
        logic [31:0] pcbranch;
    } branch;
    struct packed {
        logic [31:0] addr;
        logic mem_read;
    } memory;
} result_t;

result_t res;

logic [31:0] addr, aluout;
assign addr = res.memory.addr; // assign addr = res[32:1]
assign aluout = res.alu.aluout; // assign aluout = res[31:0]

assign res.alu.aluout = '1;

parameter

适用同一算法 int(32位) 和 long long(64位) 的加法器,需要写两个。为了使模块代码具有更高的复用性,引入参数parameter

// 加法器
module adder #(  // #() 括号内的参数例化后可以改变 
	parameter int N = 16,
    parameter logic [31:0] W = 32'd100000,
    parameter type element_t = logic[31:0]  // 定义类型
)(
    input logic[N-1:0] a, b,
    output logic[N-1:0] c
);
    assign c = a + b;
endmodule


module top #(  
    parameter logic SIM = 1'b0
)(input logic clk, resetn
 );
    
    logic [31:0] a, b, c;
    //  例化 32位加法器  adder #(需要修改的参数) adder_inst1()
    // 修改parameter int N为32,修改parameter type element_t为logic [63:0]
    adder #(.N(32), .element_t(logic [63:0])) adder_inst1(.a, .b, .c);
    
    //  例化 默认加法器(16位) 
    logic [15:0] d, e, f;
    adder adder_inst2(.a(d), .b(e), .c(f));
endmodule

// 例化top
module sim();
    top #(.SIM(1'b1)) top_inst (.clk, .resetn);  // -DDEBUG
endmodule

预编译命令

预编译命令,利用头文件,提升代码易读性。
C语言的预编译命令

#include <stdio.h>

#ifndef __SHARE_H    // if not define ..., define..., endif
#define __SHARE_H

#endif

#define N 1000000 + 3  // 宏

int a[N];

sv中的预编译命令,用`(反引号)开头:

`include "mips.svh" // sv头文件后缀为svh
					// vivado把同一project的所有文件视为同一目录下,故include时无需加目录
`ifndef __SHARE_SVH
`define __SHARE_SVH

`endif

`define LINES 0x10

logic a[`LINES-1:0]; // 使用宏时,也需要以`开头
assign a = b + c
    #ifdef D_INSIDE
    + d
    #endif
;
// 相当于
generate if (D_INSIDE) begin
    assign a = b + c + d;
end else begin
	assign a = b + c;    
end

抽象接口 interface

interface interface_name(input logic d, input logic e);

	logic c;  // signals

	modport modport_name1(input c d, output e);
	modport modport_name2(output c);

endinterface

// module
module module_name(interface_name.modport_name1 variable_name);  //接口名.端口名 变量名 input logic c d,output e 

	logic d;
	assign d = variable_name.c;

	assign variable_name.e = d;

endmodule

// 例化module和interface接口
module top();
	interface_name intf_inst(.d(), .e());

	module_name instace_name(.variable_name(intf_inst.modport_name1));

	interface_name intf_inst2(.d(), .e());

endmodule
// RTL 举例

// 接口
interface ticket_if(input logic clk,rst_n,[5:0]m_in,output logic ticket_out,[5:0]m_out);
    
    logic [5:0]sum;

    task change(input logic [5:0]in_data, output logic [5:0]out_data );           
		out_data = in_data - 6;
    endtask //automatic
    
	// 端口
    modport ticket_ports(input clk, rst_n, m_in, output ticket_out, m_out,sum,
            import task change(input logic [5:0]in_data, output logic [5:0]out_data ));
            
endinterface 

// module调用接口
module ticket(ticket_if.ticket_ports ports); // 调用ticket_if接口的ticket_ports端口 变量命名为ports(ports包括了端口中的所有input output信号)

	enum logic[1:0] {s0,s1,s2} state_c,state_n;  // s0,s1,s2都是logic[1:0]类型,定义了两个枚举体 state_c,state_n

	always_ff @(posedge ports.clk or negedge ports.rst_n) // 连接接口中的信号,端口中信号调用ports.
     
    if(!ports.rst_n)
        state_c <= s0;
    else
        state_c <= state_n;
        
always_comb       
    case(state_c)
        s0:begin
            ports.sum = ports.m_in;
            ports.ticket_out = 0;
            if(ports.sum>=6)
                state_n <= s2;
            else
                state_n <= s1;
        end
        s1:begin
            ports.sum = ports.sum + ports.m_in;
            if(ports.sum>=6)
                state_n <= s2;
            else
                state_n <= state_c;
        end
        s2:begin
            ports.change(ports.sum, ports.m_out);  // 接口中task调用
            //ports.m_out = ports.sum - 6;
            ports.ticket_out = 1;
            state_n <= s0;
        end
        default:state_n <= s0;
    endcase

endmodule

// ------testbench----
module tb_ticket;

	timeunit 1ns;
	timeprecision 100ps;
	
	
	logic clk,rst_n;
	logic [5:0]m_in;
	logic ticket_out;
	logic [5:0]m_out;
	
	initial
	  begin
	      clk = 0;
	      rst_n = '1;
	      #5 rst_n = '0;
	      #5 rst_n = '1;
	  end
	  
	initial
	  begin
	      #10 m_in=2;
	      #10 m_in=3;
	      #10 m_in=4;
	      #10 m_in=5;
	      #10 m_in=6;
	      #10 m_in=7;
	      #10 m_in=8;
	  end
	
	always #5 clk = ~clk;
	
	//ticket_if ports(.*);
	ticket_if ports(clk, rst_n, m_in, ticket_out, m_out);  // 接口
	ticket u_ticket(ports.ticket_ports);  // module调用接口
endmodule

fork…join并发结构

fork…join内的每个语句块称为子进程,执行这段fork…join代码
的称为父进程。

fork…join

fork…join 结构中,父进程会被阻塞直到所有的子进程结束。

systemverilog task语法_sed_03

// fork…join结构目标地址为接口d的发包进程只有在其他三个目标地址为a/b/c的发包进程结束后才可以运行
fork
	send_packet( interface_a);
	send_packet( interface_b);
	send_packet( interface_c);
join
	send_packet( interface_d ) ;

fork…join_any

systemverilog task语法_systemverilog_04

// fork…join_any结构目标地址为d的发包进程将会在三个接口中的其中一个发包进程结束后立即运行
fork
	send_packet( interface_a);
	send_packet( interface_b);
	send_packet( interface_c);
join_any
	send_packet( interface_d ) ;

systemverilog task语法_systemverilog_05

fork…join_none

// fork·join_none结构,所有四个发包进程将同时运行目标地址为d的发包进程将会在三个接口中的其中一个发包进程结束后立即运行
fork
	send_packet( interface_a);
	send_packet( interface_b);
	send_packet( interface_c);
join_none
	send_packet( interface_d ) ;

fork … begin … end … join

在定义一个fork…join块的时候,将整个子进程封装在一个begin…end块中会将整个块作为单个进程执行,其中每条语句顺序地执行。

fork
	begin
		statement1;// 一个带有2条语句的子进程
		statement2 ;
	end
join

顺序语句begin……end中的执行

always@(negedge clk) begin
	for(p=1;p<6;p++)
		q[p+1] <= q[p];
end

q[p]的值是在什么时候赋给q[p+1]的?

首先非阻塞赋值<=是在这个begin 。。。end模块结束的时候一起完成赋值的 并不是下一个下降沿才执行;(阻塞赋值是立即执行的,如果有多个赋值,就会阻碍下一条赋值语句) 。

其次for语句在这里 其实是一个时钟只执行一次 就是说第一个下降沿 q[2]<=q[1],第二个下降沿q[3]<=q[2]。

systemverilog task语法_赋值_06


verilog编程中当多个always出现时,always块都是同时执行的,即在同一时间,两个always都在运行。其实就相当于两个电路,同时上电。

所以不能给一个信号在两个always中赋值。