二进制
常量
格式:二进制位宽+'+进制符号(b:2;h:16;d:10)+数据
1'b1
1'b0
16'habcd
4'd10
变量
logic a;//一位二进制
logic [3:0]b;//4位二进制
logic [31:0][31:0]c;//32*32位二进制
组合/位绑定
{a, 1'b1}//高位是a,低位是常数二进制1的两位向量
{a, b}//五位二进制,最高位a,低四位b
{16{1'b1}}//16位全1
{a, 16{b}}//
运算符
基本运算符
缩位运算符
运算符不能视为向量,例如(a+b)[3:0]的写法是错误的
赋值语句
变量 = 表达式
- 等号左边的变量,可以是单个变量,也可以是 {a,b], 但不能有常量或运算符
- 等号右边的表达式,可以是单个变量或常量,也可以是 {a + b,1’b1],要注意位数
电路语句1——assign
现有电路图再有电路语句,电路代码包括以下几个部分:
- 二进制信号声明
- 数字元件
- 电路连接
assign语句是一种电路语句,可以描述一部分电路,这些电路的的作用是运算符的功能
assign语句的基本形式为: assign 赋值语句;
logic [31:0]a,b,c;
assign c = a&b;
适用于简单组合电路;已有电路图 (仅包含基本元件)或逻辑表达式的组合电路
电路语句2——元件例化语句
需求:
- 层次化设计、功能区分
- 代码复用
- 黑盒
1.模块声明
module adder(
//输入和输出声明
input logic [3:0]a,b,
output logic [3:0]c
);
//电路代码
assign c = a +b;
endmodule
2.元件例化
- 输入:可以接变量、常量、表达式、未绑定
- 输出:只能接变量,
logic [3:0] b,c;
//模块名 元件名(接口对应:.+端口名(信号名))
adder adder_inst0(.a(4'b0010), .b, .*);//assign c = 4'b0010 + b
//等效于adder adder_inst(.a(4'b0010), .b(b), .*);
//.b与.b(b)等效的条件:信号名和端口名一致
//也等效于adder adder_inst(.a(4'b0010), .*);
//.*即未用的其他同位宽变量,如果位宽不同将报错
adder adder_inst1(.a[0](1'b0));//错误写法,端口名是一个整体,必须要整个对应
电路语句3——always_comb
always_comb用于描述复杂电路
always_comb begin
a = 1'b1;
b = a;
a = 1'b0;
c = a;
end
always_comb内部每条语句都是赋值语句。不能出现电路语句
always_comb内部描述电路行为。
always_comb有以下性质:
- 内部覆盖性:后面的赋值覆盖前面的赋值
- 对外原子性:
assign a = b;
always_comb begin
b = 1'b1;
c = a;// c = b;
b = 1'b0;
end
//结果:a=b=c=0(c=a时)
//结果:a=b=0,c=1(c=b时)
- 阻塞赋值
always_comb内部还支持if、case等控制语句:
always_comb内部的控制语句:case
case语句常用于描述选择器和译码器
unique case
有unique,表明其中有一项符合要求,允许并行比较
always_comb begin
unique case (a[3:0])
4'd1:begin
end
4'd0:begin
end
default:begin
end
endcase
end
如果没有default,遇到不匹配时将什么都不做
priority 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()//.....
end
always_comb内部的控制语句:if和for
if和for是always_comb 中的常用语法。
if
if和else用于条件判断
always_comb begin
if(a[3])begin
b = 1'b1;
end else if(a[2])begin
b = 1'b0;
end else begin
end
end
和case要有default类似,在always_comb 中,if也应有else。
for
for在always_comb中,会被解释为循环展开。
for相关的语句:break,continue
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
for和if即可表示行为,也可生成电路
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
assign a[i] = b[i] & (c[i] == d[i] | e[i]);//编译器认为i是常数,a[i,i+3]合法
always_comb begin
end
end
电路语句4——always_ff
always_ff 用于描述触发器
- always_ff全部使用<=的非阻塞赋值(并行)
- always_comb全部使用=的阻塞赋值
always_ff @(posedge clk, negedge resetn) begin
if(~resetn)begin
q <= 0';
end else if(en) begin
q <= d;
end
end
always_ff 里可以描述很复杂的逻辑,但那样写不直观。写代码时,也应该参考状态方程
logic [3:0] a,a_nxt;
always_ff @(posedge clk) begin
if(~resetn)begin
a < '0;
end else if(en) begin
/*
unique case(a)
4'd3: a <= 4'd2;
default: a <= 4'd3;
endcase
*/
a <= a_nxt;
{b,c} <= {b_nxt,c_nxt};
// b <= a;//b赋的是上升沿时刻a的值,可能与a_nxt不同
end
end
always_comb begin
a_nxt = a;
b = a_nxt;//b=a
unique case(a)
4'd3: begin
a_nxt = 4'd2;
end
default: begin
end
endcase
end
高级语法1——typedef
1. typedef
数字电路中,万物皆为二进制。
类型同一为logic,符合这一规律。但这对程序员,可能不太友好。
- 需要管理变量的位数
- 同一位数的信号,可能意义完全不同
对此,引入自定义类型语法 typedef
基本格式为: typedef 已有类型 新类型;
typedef logic[31:0] word_t;
typedef logic[5:0] entry_t;
typedef entry_t[31:0] 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;//logic [5:0] = entry_t
assign table1[0][1] = '0;
用途举例
typedef logic[3:0] code_t;
typedef logic[15:0] info_t;
typedef logic[31:0] paddr_t; // 物理地址
typedef logic[31:0] vaddr_t; // 虚拟地址
2. struct
结构体struct可以描述一组相关的数据。
以译码器为例,按以前的写法,可能需要这样写:
logic [3:0] alufunc;
logic mem_read;
logic mem_write;
logic reg_write;
logic [6:0] control;
assign control[3:0] = {alufunc, mem_read, mem_write, reg_write};
结构体类型相关的语法如下:
// type definition
typedef struct packed{
logic [3:0] alufunc;
logic mem_read;
logic mem_write;
logic reg_write;
}control_t;//使用typedef定义的结构体,视作一个结构体类型,需要进一步定义才有结构体变量实体
// variable declaration
control_t control;
logic reg_write;
assign reg_write = control[0];//用control.reg_write更直观
// using struct without typedef
struct packed{
logic [3:0] alufunc;
logic mem_read;
logic mem_write;
logic reg_write;
}control_without_typedef;//不使用typedef定义的结构体,视作一个结构体变量实体,与上面的control是同一个类型
struct语法有很多好处,用途也很广,例如可以使用struct结构来优化流水线寄存器的编写
typedef struct packed{
//...
}pipeline_decode_t;
pipeline_decode_t p,p_nxt;
always_ff @(posedge clk)begin
p <= p_nxt;
end
3. enum
枚举的语法形式为:
typedef enum <datatype>{
IDEN_1, IDEN_2//0,1,...
} typename;
举个instance
typedef enum logic [3:0]{
ALU_ADD, ALU_AND, ALU_SUB
}alufunc_t;
alufunc_t alufunc;
enum logic [3:0]{
ALU_ADD, ALU_AND, ALU_SUB
}alufunc_without_typedef;
enum语法常用于编码 (包括状态机的编码)
enum类型的变量,在Vivado仿真里会显示枚举项。
枚举项被视为常量,各枚举类型的枚举项名字不能冲突
enum类型的变量,赋值时只能用枚举项
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 <= '0//state是变量时候可以这么写
state <= state_t'(0);
end else begin
state = state_nxt;
end
end
Union
联合类型的语法:
typedef union packed{
struct packed{
logic zero;
logic [31:0]aluout;
} alu;
struct packed{
logic zero;
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]
对union类型的变量进行赋值时,要注意多驱动
‘0是位全0,'1是位全1
高级语法2——parameter
引入元件例化的语法,有很多好处。
然而,已有的模块设计语法,缺乏flexibility。
适用同一int和long l ong 的加法器,需要写两个。
为了使模块代码具有更高的复用性,引入参数 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
logic [31:0] a,b,c;
adder #(.N(32), .element_t(logic[63:0])) adder_inst1(.a, .b, .c);
logic [15:0] d,e,f;
adder adder_inst2(.a(d), .b(e), .c(f));
end module
module top #(
parameter logic SIM = 1'b0;
)(input logic clk,resetn);
module sim();
top #(.SIM(1'b1)) top_inst(.clk,.resetn);
// -DDEBUG
endmodule
parameter也可用于全局常量声明。作为一句语句,它以分号结尾。
parameter logic[5:0] OP_ADDI = 6'b101010;
always_comb begin
unique case(op)
OP_ADDI: begin//6'b101010: begin
end
endcase
end
高级语法3——预编译命令
C语言中的部分预编译命令:
#include <stdio.h>
#ifndef __SHARE_H
#define __SHARE_H
#endif
#define N 1000000 + 3
// int a[N];
有了预编译命令,就可以用利用头文件,提升代码易读性
sv中的预编译命令,和c语言基本一致,用 `(反引号) 开头:
`include "mips.svh"// sv头文件后缀为svh
// vivado把同一project的所有文件视为同一目录下,故include时无需加目录
`ifndef __SHARE_SVH
`define _SHARE_SVH
`endif
`define LINES 0x10
logic a[`LINES-1:0]; // 使用宏时,也需要以`开头
预编译命令可以达到以下效果:
veri
- 配置一些参数。类似于 parameter语句。
- 根据不同的参数,生成不同的电路。不同于mux。
- generate if语句的粒度为电路语句。而 `ifdef 之类的预编译命令可以是任意粒度的。
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
- 使用头文件。类似于package 语句。
- 使用宏来封装一些功能。部分情况下可用function语句代替
高级语法4——interface
电路图清晰地标明了元件的每位输入是从哪个元件的输出得到的,而元件例化语句无法做到这一点:
- 元件例化语句不标出信号是作为输入还是输出
- 信号相关模块的例化代码可能隔了很远
同时,模块接口部分的代码,在语法上也有可改进的地方: - 不同模块可能复用一部分接口
- 添加一个接口,需要修改多处代码
interface语法可以解决这一些问题,其语法形式与模块类似,具体为:
interface interface_name(input logic d, output logic e);/*inputs and outputs*/
// signa1s
logic c;
// modports
modport modport_namel(input c,d, output e);
modport modport_name2(output c,f);
endinterface
// module
module module_name(
interface_name.modport_namel variable_name// input logic c,d
);
// use `variable_name` like a struct
logic d;
assign d = variable_name.c;
assign variable_name.e = d;
endmodule
module top();
// instantiate an interface involves declaring the singals inside it
interface_name intf_inst(.d(), .e());
module_name instance_name(.variable_name(intf_inst.modport_namel)):
endmoduTe
把interface的声明放到头文件里,可以大幅减少源代码量
modport就是一组数据,包括输入和输出
Verilator暂不支持interface语法。