$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
>>> 算术右移
运算符:
有符号 无符号变量:
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 结构中,父进程会被阻塞直到所有的子进程结束。
// 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
// fork…join_any结构目标地址为d的发包进程将会在三个接口中的其中一个发包进程结束后立即运行
fork
send_packet( interface_a);
send_packet( interface_b);
send_packet( interface_c);
join_any
send_packet( interface_d ) ;
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]。
verilog编程中当多个always出现时,always块都是同时执行的,即在同一时间,两个always都在运行。其实就相当于两个电路,同时上电。
所以不能给一个信号在两个always中赋值。