跑仿真
VCS 用于编译和放在,Verdi 用于查看波形。
编辑 test.v 和 tb_test.v 文件
编译、仿真
module load vcs vcs-R-full64 -sverilog -l vcs_comile.log -kdb -lca tb_test.v test.v
查看波形
module load verdi verdi
用 Verdi 软件打开波形文件(fsdb 文件)
基本语法
每个模块的内容都是嵌在 module 和 endmodule 两个语句之间;
每个模块实现特定的功能,模块可以进行层次嵌套;
除了 endmodule 语句外,每个语句和数据定义的最后必须有分号;
用 /**/ 和 // 进行注释。
模块
每个 Verilog 程序包括四个部分:端口定义、I/O说明、内部信号声明、功能定义。
内部信号声明
reg [1:0] R变量1, R变量2...; wire [3:0] W变量1, W变量2...;
功能定义
用“assign”声明语句
用实例元件
and and_inst( q, a, b );
像调库元件一样,输入元件的名字和相连的引脚即可,表示在设计中用到一个跟与门(and)一样的名为 and_inst 的与门,其输入端为 a, b,输出为 q。要求每个实例元件的名字必须唯一,并避免与其他调用与门的实例混淆。
用“always”块
"always" 块中的语句顺序执行,多个 "always" 之间是同时执行的。
always @(posedge clk or posedge clr) begin if(clr) q<=0; else if(en) q<=d; end end
整数
二进制整数(b 或 B)
十进制整数(d 或 D)
十六进制整数(h 或 H)
八进制整数(o 或 O)
8'b10101100 // 位宽为 8 的二进制表示,'b 表示二进制
8'ha2 // 位宽为 8 的十六进制,'h 表示十六进制
x和z值
'x' 表示不定值,'z' 表示高阻值,z 的另一种表达方式是写作 '?'
4'b10x0 // 位宽为4的二进制数从低位数起第二位为不定值
4'b101z // 位宽为4的二进制数从低位数起第一位为高阻值
12'dz // 位宽为12的十进制数其值为高阻值(第一种表达方式)
12'd? // 位宽为12的十进制数其值为高阻值(第二种表达方式)
8'h4x // 位宽为8的十六进制数其低四位值为不定值
负数
在位宽表达式前加一个减号即可:
-8'd5 // 这个表达式代表5的补数(用八位二进制数表示)
参数(Parameter)类型
parameter msb=7; // 定义参数 msb 为常量 7
parameter e=25, f=29; // 定义两个常量参数
parameter average_dely = d+f; // 用常数表达式赋值
参数型常数经常用于定义延迟时间和变量宽度。
在模块或实例引用时可通过参数传递改变在被引用模 块或实例中已定义的参数,如:
module Decode(A, F); parameter Width=1, Polarity=1; ...... endmodule module Top; wire[3:0] A4; wire[4:0] A5; wire[15:0] F16; wire[31:0] F32; Decode #(4,0) D1(A4,F16); // 实例 D1 中 width = 4, Polarity = 0 Decode #(5) D2(A5,F32); // 实例 D2 中 width = 5, Polarity = 1 Endmodule
wire和reg
wire 型数据常用来表示用于以 assign 关键字指定的组合逻辑信号。Verilog 程序模块中输入输出信号类型缺省时自动定义为 wire 型。wire 型信号可以用作任何方程式的输入,也可以用作 "assign" 语句或实例元件的输出。
wire [7:0] a, b; // 定义了两个八位的 wire 型数据。共有 2 条总线,每条总线内有 8 条线路
reg 表示寄存器。reg 型数据常用来表示用于"always"模块内的指定信号,常代表触发器。通常,在设计中要由"always"块通过使用行为描述语句来表达逻辑关系。"always"块内被赋值的每一个信号都必须定义成 reg 型。
reg [4:0] rega; // 定义了一个四位的名为 rega 的reg型数据
reg 型只表示被定义的信号将用在"always"块内,并不是说 reg 型信号一定是寄存器或触发器的输出。虽然 reg 型信号常常是寄存器或触发器的输出,但并不一定总是这样。
设计代码仅用 reg 和 wire
设计代码:由本模块产生且是用 always 产生的信号,用 reg 类型
测试代码:用 initial 产生的信号(一般是对测试模块的输入),用 reg 类型
其他都要 wire
reg 类型的信号,不一定产生寄存器
运算符
算术运算符(+,-,*,/)
赋值运算符(=,<=)
时序逻辑用 "<="
组合逻辑用 "="
<=(非阻塞赋值方式,如 b<=a;)
① 块结束后才完成赋值操作;
② b 的值不是立刻就改变的。
=(阻塞赋值方式,如 b=a;)
① 赋值语句执行完后,块才结束;
② b的值在赋值语句执行完后立刻就改变的。
关系运算符(>,<,>=,<=)
逻辑运算符(&&,||,!)
【为避免歧义,逻辑运算符两边必须为 1 bit 信号】
条件运算符(?:)
r = s ? t : u
位运算符(~,|,^,&,^~)
~
:取反(按位取反)&
:按位与|
:按位或^
:按位异或^~
:按位同或移位运算符(<<,>>)
位拼接运算符({ })
用这个运算符可以把两个或多个信号的某些位拼接起来进行运算操作:
{信号 1 的某几位, 信号 2 的某几位, ..., ..., 信号 n 的某几位}
例如:
{4{w}} // 这等同于 {w,w,w,w}
{b,{3{a,b}}} // 这等同于 {b,a,b,a,b,a,b}
等式运算符
==
:等于!=
:不等于===
:等于!===
:不等于"==" 和 "!=" 又称为逻辑等式运算符。其结果由两个操作数的值决定。由于操作数中某些位可能是不定值 x 和高阻值 z,结果可能为不定值 x。而 "===" 和 "!==" 运算符则不同,它在对操作数进行比较时对某些位的不定值 x 和高阻值 z 也进行比较,两个操作数必需完全一致,其结果才是 1,否则为 0。"===" 和 "!==" 运算符常用于 case 表达式的判别。
并行块
块内语句是同时执行的,即程序流程控制一进入到该并行块,块内语句则开始同时并行地执行。
块内每条语句的延迟时间是相对于程序流程控制进入到块内时的仿真时间的。
当按时间时序排序在最后的语句执行完后或一个 disable 语句执行时,程序流程控制跳出该程序块。
// 用并行块产生一个时序波形
fork
#50 r='h35; // '#'表示延时
#100 r='hE2;
#150 r='h00;
#200 r='hF;
#250 -> end_wave; // 触发事件 end_wave
join
产生的波形是和下面这个顺序块相同的:
parameter d=50; // 声明 d 是一个参数
reg [7:0] r; // 声明 r 是一个 8 位的寄存器变量
begin
#d r='h35;
#d r='hE2;
#d r='h00;
#d r='hF;
#d -> end_wave;
end
if
if(a>b)
begin
out1<=int1;
out2<=int2;
end
else
begin
out1<=int2;
out2<=int1;
end
使用条件语句不当在设计中会生成原本没想到有的锁存器:
另一种偶然生成锁存器是在使用 case 语句时缺少 default 项的情况下发生的:
为了避免偶然生成锁存器的错误。如果用到 if 语句,最好写上 else 项。如果用 case 语句, 最好写上 default 项。遵循上面两条原则,就可以避免发生这种错误,使设计者更加明确设计目标, 同时也增强了 Verilog 程序的可读性。
while
下面例子用 while 循环语句对 rega 这个八位二进制数中值为 1 的位进行计数:
begin: countls // 给这个 begin 块命名为 countls
reg[7:0] tempreg;
count=0;
tempreg=rega;
while(tempreg)
begin
if(tempreg[0]) count=count1;
tempreg = tempreg>>1;
end
end
for
下面例子用 while 循环语句对 rega 这个八位二进制数中值为 1 的位进行计数:
begin:init_men
reg[7:0] tempi;
for(tempi=0;tempi<memsize;tempi=tempi+1)
memory[tempi]=0;
end
结构说明语句
initial 和 always 说明语句在仿真的一开始即开始执行。initial 语句只执行一次。相反,always 语句则是不断地重复执行,直到仿真过程结束。
inital
例 1:
用 inital 语句在仿真开始时对各变量进行初始化。
initial
begin
areg=0; // 初始化寄存器 areg
for(index=0; index<size; index=index+1)
memory[index]=0;
end
例 2:
用 initial 语句来生成激励波形作为电路的 测试仿真信号。一个模块中可以有多个 initial 块,它们都是并行运行的。initial 块常用于测试文件 和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境。
initial
begin
inputs='b00000; // 初始时刻为 0
#10 inputs='b011001;
#10 inputs='b011011;
#10 inputs='b011000;
#10 inputs='b001000;
end
always
一个模块中的多个 always 块是并行运行的。
always 语句在仿真过程中是不断重复执行的,只有和一定的时序控制结合在一起才有用。下面这个例子中,每当 areg 信号的上升沿出现时把 tick 信号反相,并且把 counter 增加 1。
reg[7:0] counter:
reg tick;
always @(posedge areg)
begin
tick=~tick;
counter=counter+1;
end
always 的时间控制可以是沿触发也可以是电平触发的,可以单个信号也可以多个信号,中间需要用关键字 or 连接,如:
always @(posedge clock or posedge reset) // 由两个沿触发的 always 块
begin
...
end
always @(a or b or c) // 由多个电平触发的 always 块
begin
...
end
门级结构描述
门类型
and 与门
nand 与非门
nor 或非门
or 或门
xor 异或门
xnor 异或非门
buf 缓冲器
not 非门
......
// 描述了一个名为 nd1 的与非门(nand),输入为 data、clock 和 clear,输出为 a,
// 输出与输入的延时为 10 个单位时间
nand #10 nd1(a, data, clock, clear);
用门级结构描述 D 触发器
module flop(data, clock, clear, q, qb);
input data, clock, clear;
output q, qb;
nand #10 nd1(a, data, clock, clear),
nd2(b, ndata, clock),
nd4(d, c, b, clear),
nd5(e, c, nclock),
nd6(f, d, nclock),
nd8(qb, q, f, clear);
nand #9 nd3(c, a, d),
nd7(q, e, qb);
not #10 iv1(ndata, data),
iv2(nclock, clock);
endmodule
在这个 Verilog HDL 结构描述的模块中,flop 定义了模块名,设计上层模块时可以用这个名(flop)调用这个模块;module、input、output、endmodule 等都是关键字;nand 表示与非门;#10 表示 10 个单 位时间的延时;nd1、nd2、......、nd8、iv1、iv2 分别为下中的各个基本部件。
用已有模块来组成其他模块
如果已经编制了一个模块,如上面的 flop,可以在另外的模块中引用这个模块,引用的方法与门类型的实例引用非常类似。
module hardreg(d, clk, clrb, q);
input clk, clrb;
input[3:0] d;
output[3:0] q;
flop f1(.data(d[0]), .clock(clk), .clear(clrb), .q(q0),),
f2(.data[d1], .clock(clk), .clear(clrb), .q(q[1]),),
f3(.data[d2], .clock(clk), .clear(clrb), .q(q[2]),),
f4(.data[d3], .clock(clk), .clear(clrb), .q(q[3]),);
endmodule
在上面这个结构描述的模块中,hardreg 定义了模块名;f1、f2、f3、f4 分别为下图中的各个基本部件,而其后面括号中的参数分别为下图中各基本部件的输入输出信号。请注意当 f1 到 f4 实例引用已编模块 flop 时,由于不需要 flop 端口中的 qb 口,故在引用时把它省去,但逗号仍需要留着。
电路设计的3种结构
组合逻辑
always@(*)begin 语句 end
时序逻辑
同步复位的时序电路
always@(posedge clk) begin if(rst_n==1'b0)begin 语句 else begin 语句 end end
异步复位的时序电路
always@(posedge clk or negedge rst_n) begin if(rst_n==1'b0)begin 语句 else begin 语句 end end
电路设计要点
一个 always 只产生一个信号(方便分析调试、便于改代码)
一个信号只能在一个 always 中产生
条件判断只使用 if else 和 case 即可完成设计
含义 posedge 或 negedge 的,一定是 D 触发器,是时序电路
设计时,想立即有结果,用组合逻辑;想延时一拍有结果,用时序逻辑
模块例化
一个模块由其他子模块组成,例化可以理解为模块的调用。
练习
例1
module mul_module(
mul_a ,
mul_b ,
clk ,
rst_n ,
mul_result
);
parameter A_W = 4;
parameter B_W = 3;
parameter C_W = A_W + B_W;
input[A_W-1:0] mul_a;
input[B_W-1:0] mul_b;
input clk;
input rst_n;
output[C_W-1:0] mul_result;
reg [C_W-1:0] mul_result;
reg [C_W-1:0] mul_result_tmp;
always@(*)begin
mul_result_tmp = mul_a * mul_b;
end
always@(posedge clk or negedge rst_n) begin
if(rst_n==1'b0)begin
mul_result <= 0;
end
else begin
mul_result <= mul_result_tmp;
end
end
endmodule
例2
module mul2port(
din_a ,
din_b ,
din_c ,
din_d ,
sel_a ,
sel_b ,
clk ,
rst_n ,
result_a,
result_b
);
input[2:0] din_a;
input[1:0] din_b;
input[3:0] din_c;
input[3:0] din_d;
input sel_a;
input sel_b;
output[6:0] result_a;
output[5:0] result_b;
reg [6:0] result_a;
reg [5:0] result_b;
reg [3:0] sel_dout;
wire [6:0] result_a_tmp;
wire [5:0] result_b_tmp;
reg sel;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
result_b <=0;
else begin
result_b <= result_b_tmp;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
result_a <= 0;
else begin
result_a <= result_a_tmp
end
end
mul_module#(.A_W(3),.B_W(4)) mul_4_3(
.mul_a (din_a),
.mul_b (sel_dout),
.clk (clk),
.rst_n (rst_n),
.mul_result (result_a_tmp)
);
mul_module#(.A_W(2),.B_W(4)) mul_4_2(
.mul_a (din_b),
.mul_b (sel_dout),
.clk (clk),
.rst_n (rst_n),
.mul_result (result_b_tmp)
);
always@(*)begin
if(sel==0)
sel_dout = sel_a;
else
sel_dout = sel_b;
end
always@(posedge cld or negedge rst_n)begin
if(rst_n==1'b0)begin
sel <= 0;
end
else begin
sel <= sel_a & sel_b;
end
end
endmodule
评论区