1.基础知识
所有的verilog代码都是以module(模块)的方式存在,一个简单的逻辑可以由一个module组成,复杂的逻辑可以包含多个modules,每个module有独立的功能,
并可通过输入、输出端口被其它module调用。通过module的方式可以将一些比较独立、可以复用的功能进行模块化。verilog语法有很多,而且分为可综合(
综合后可以生成对应的硬件电路)的语法和不可综合(综合后不可以生成对应的硬件电路)的语法,可综合的代码是非常少的,大多数代码是不可以综合的,
但是可以再仿真用于验证逻辑的正确性,十分方便。
2.常量与变量
常量是verilog中不变的数值,verilog中的常量有3种类型
(1)整数型
(2)实数型
(3)字符串型
变量类型:
(1)线网型:表示电路间的物理连线
(2)寄存器型:verilog中一个抽象的数据存储单元
线网型和寄存器类型具体又包含很多种变量,线网型变量最常用的变量就是wire,而寄存器最常用的变量是reg,wire可以看成直接的连接,
在可综合的逻辑中会被映射成一根真实的物理连线,而reg具有对某个时间点状态进行保存的功能,如果在可综合的时序逻辑中表达,会被
映射成一个真实的物理寄存器,而在verilog仿真器中,寄存器类型的变量通常要占用一个仿真内存空间。因此在设计逻辑的时候要明确定义
信号是wire还是reg属性,凡是在always或initial语句中被赋值的变量(赋值号左边的变量),不论是表达的是组合逻辑电路还是时序逻辑,都一定
是reg变量,凡是在assign语句中被赋值的变量,一定是wire型变量
基数表达法的基本格式:
[换算为二进制后位宽的总长度][’][数值进制符号][与数值进制符号对应的数值],其中[位宽的总长度]可有可无。当[换算为二进制后位宽的总长度]比[与数值进制符号对应的数值]
的实际位数多,则自动在[与数值进制符号对应的数值]的左边补足0,如果位数少,则自动截断[与数值进制符号对应的数值]左边超出的位数
h:十六进制
d:十进制
b:二进制
//1.测试verilog的基本变量
`timescale 1ns/1ps
module tb_led();
parameter TIME=8'h50; //整数型
parameter NAME="ycl"; //字符串型
parameter WEIGHT=12.3; //实数型
reg [15:0] a; //定义一个16位reg类型变量a
reg [15:0] b;
reg [15:0] c;
reg d; //定义1位reg类型变量(不写位宽,默认为1位)
reg [7:0] e; //定义一个8位reg类型变量e
initial begin
a = 16'd10;
b = 16'h20;
d = 1'b1;
e = 8'b1111_000;
c = a + b;
$display ("Hello FPGA!!");
$display ("a = %d,b = %x,c = %x,d = %b,e = %b,TIME = %x,NAME = %s,WEIGHT = %f",a,b,c,d,e,TIME,NAME,WEIGHT);
end
endmodule
3. timescale
`timescale 1ns/1ps
时间单位和时间精度由值1、10、100以及单位s、ms、us、ns、ps和fs组成
时间单位:定义仿真过程中所有与时间单位相关的单位。仿真中使用"#数字"表示延时相应时间单位的时间,例如#10表示延时10个单位的时间,即10ns
时间精度,精度一旦超过了精度,就会四舍五入:决定时间相关量的精度及仿真显示的最小刻度
#10 :延时10 * 1ns = 10ns
#10.001 :延时 10 * 1ns + 1ps
#10.0027 :延时 10 * 1ns + 3ps
#10.0023 :延时 10 * 1ns + 2ps
//2.测试timescale作用
`timescale 1ns/1ps
module tb_led();
reg a;
reg b;
reg c;
reg d;
initial begin
a = 1;
#10; //延时10ns
a = 0;
end
initial begin
b = 1;
#10.005; //延时10ns + 5ps
b = 0;
end
initial begin
c = 1;
#10.0036; //延时10ns + 4ps
c = 0;
end
initial begin
d = 1;
#10.0034; //延时10ns + 3ps
d = 0;
end
endmodule
参考链接:
https://blog.csdn.net/yanchuan23/article/details/122920088
4.initial、always语句块
initial语句只执行一次,一个模块中可以有多个initial块,它们都是并行的,initial语句最常见用于测试文件里面初始化语句,用来产生测试环境和设置信号记录。这个初始化过程不需要任何仿真时间,即在0ns时间内,便可以完成存储器的初始化工作
always语句在仿真过程中不断运行着直到仿真结束,一个模块中可以有多个always块,他们都是并行执行的,语句由于不断活动的特性,只有和一定的时序控制逻辑结合在一起才有用
//相当于初始化变量
initial 语句格式:
initial begin //在begin-end串行语句块中,一条非阻塞过程语句的执行不会阻塞下一条语句的执行,也就是在本条非阻塞型过程赋值语句对应的赋值操作执行完之前,下一条语句也可以开始执行
语句1;
语句2;
语句3;
end
//相当于while循环
always <时序控制>
语句
//3.测试initial 和 always关键字
`timescale 1ns/1ps
module tb_led();
reg a;
reg b;
reg c;
reg d;
reg e;
//在一个module模块中可以有多个initial和always语句,相互都是独立的,并行进行的
/*
在begin-end串行语句块中,一条非阻塞过程语句的执行不会阻塞下一条语句的执行,也就是说在本条非阻塞型过程赋值语句对应的赋值
操作执行完之前,下一条语句也可以开始执行
*/
initial begin //有多个语句时,用begin end 括起来,相当于{}
a = 1;
b = 0;
#10; //延时10ns后,赋值1给b,前10ns 一直为0
b = 1;
end
always begin
c = 0;
#10;
c = 1;
#10;
end
initial d = 1;
always #10 d = $random % 2; //10ns产生一个随机数
initial e = 0;
always #10 e = ~e; //10ns翻转一次,产生一个时钟
endmodule
参考链接:
https://blog.csdn.net/woshiyuzhoushizhe/article/details/95448348
5.非阻塞赋值与阻塞赋值
赋值语句的赋值方式有两种,分别为"<="(非阻塞赋值)和"="(阻塞赋值)
1.以赋值操作符"<="来标识的赋值操作称为非阻塞过程赋值,具有如下特点:
a.在begin-end串行语句块中,一条非阻塞过程语句的执行不会阻塞下一条语句的执行,也就是说在本条非阻塞过程赋值语句对应的赋值操作执行完之前,
一下条语句也可以开始执行
b.仿真过程在遇到非阻塞过程赋值语句后首先计算其右端赋值表达式的值,然后等到仿真时间结束时再将该计算结果赋值变量,也就是说,这种情况的赋值
操作是在同一仿真时刻上的其他操作结束后才得以执行
2.以 赋 值 操 作 符 “ = ” 来 标 识 的 赋 值 操 作 称 为 “ 阻 塞 型 过 程 赋 值,具有如下特点:
a.在 begin-end 串行语句块中的各条阻塞型过程赋值语句将以它们在顺序块后排列次序依次得到执行
b.阻塞型过程赋值语句的执行过程是:首先计算右端赋值表达式的值,然后立即将计算结果赋值给“=”左端的被赋值变量
阻塞型过程赋值语句的这两个特点表明:仿真进程在遇到阻塞型过程赋值语句时将计算表达式的值并立即将其结果赋给等式左边的被赋值变量;在串行语句块中,下一条语句
的执行会被本条阻塞型过程赋值语句所阻塞,只有在当前这条阻塞型过程赋值语句所对应的赋值操作执行完后下一条语句才能开始执行
// 4.测试非阻塞赋值和阻塞赋值
`timescale 1ns/1ps
module tb_led();
reg clk;
reg a;
reg b;
reg c;
reg d;
reg e;
reg f;
reg g;
reg h;
reg i;
reg x;
reg z;
initial clk = 0;
initial begin //以下赋值会并行进行,因为都为非阻塞赋值
a <= 0;
b <= 1;
c <= 0;
d <= 1;
end
always #10 clk = ~clk; //产生一个20ns的时钟
always@(posedge clk) begin
a <= b; //非阻塞赋值,两条会同时进行
b <= a;
end
always@(posedge clk) begin
c = d; //阻塞赋值,只有执行完该语句后,才能往下继续执行
d = c;
end
initial begin
e <= 1;
f <= 0;
end
always@(posedge clk) begin //下面的语句开执行的条件是:在clk的上升沿
e <= f; //先执行,e 为 0
#10; //延时10ns后执行以下赋值语句
f <= e; //f 为 0;
end
initial g <= 1;
always@(negedge clk) begin //在clk下降沿开始执行下面的语句
g = ~g;
end
initial begin
h <= 0;
i <= 1;
x <= 0;
z <= 1;
end
always@(posedge clk) begin
h <= i;
i <= h;
#40
x <= z;
z <= x;
#15
h <= 0;
i <= 1;
end
endmodule
参考链接:
https://blog.csdn.net/sxy121835/article/details/124235500
6.赋值语句
赋值语句的赋值方式有两种,分别为"<="(非阻塞赋值)和"="(阻塞赋值)
1.以赋值操作符"<="来标识的赋值操作称为非阻塞过程赋值,具有如下特点:
a.在begin-end串行语句块中,一条非阻塞过程语句的执行不会阻塞下一条语句的执行,也就是说在本条非阻塞过程赋值语句对应的赋值操作执行完之前,
一下条语句也可以开始执行
b.仿真过程在遇到非阻塞过程赋值语句后首先计算其右端赋值表达式的值,然后等到仿真时间结束时再将该计算结果赋值变量,也就是说,这种情况的赋值
操作是在同一仿真时刻上的其他操作结束后才得以执行
2.以 赋 值 操 作 符 “ = ” 来 标 识 的 赋 值 操 作 称 为 “ 阻 塞 型 过 程 赋 值,具有如下特点:
a.在 begin-end 串行语句块中的各条阻塞型过程赋值语句将以它们在顺序块后排列次序依次得到执行
b.阻塞型过程赋值语句的执行过程是:首先计算右端赋值表达式的值,然后立即将计算结果赋值给“=”左端的被赋值变量
阻塞型过程赋值语句的这两个特点表明:仿真进程在遇到阻塞型过程赋值语句时将计算表达式的值并立即将其结果赋给等式左边的被赋值变量;在串行语句块中,下一条语句
的执行会被本条阻塞型过程赋值语句所阻塞,只有在当前这条阻塞型过程赋值语句所对应的赋值操作执行完后下一条语句才能开始执行
3.assign和always语句
assign语句是连续赋值语句,一般是将一个变量的值不间断地赋值给另外一个变量,两个变量之间就类似于被导线连接到了一起,习惯上当作连线
使用,assign语句地基本格式是:
assign a = b (逻辑运算符) c …;
assign语句地功能属于组合逻辑地范畴,应用范围可以概括为以下几点:
(1)持续赋值
(2)连线
(3)对wire型变量地赋值,wire是线网,相当于实际地连接线,如果要用assign直接连接,就用wire变量,wire型变量随时发生变化
(4)多条assign连续赋值语句之间相互独立,并行执行
always语句是条件循环语句,执行机制是通过对一个称为敏感变量表地事件驱动来实现地,always语句的基本格式是:
always @(敏感事件)begin
程序语句
end
always是"一直、总是"的意思,@后面跟着事件,整个always的意思是:当敏感事件的条件满足时,就执行一次"程序语句"。
always @(a or b or d)begin
if(sel == 0)
c = a + b;
else
c = a + d;
end
这段程序的意思是:当信号 a 或者信号 b 或者信号 d 发生变化时,就执行一次下面语句。在执行该段语句时,首先判断信号 sel 是否为 0,如果为 0,则执行第 3 行代码。 如果 sel 不为 0,则执行第 5 行代码。需要强调的是, a、 b、 c 任意一个发生变化一次, 2 行至 5 行也只执行一次,不会执行第二次。
此处需要注意,仅仅 sel 这个信号发生变化是不会执行第 2 行到 5 行代码的, 通常这并不符合设计者的想法。例如,一般设计者的想法是: 当 sel 为 0 时 c 的结果是 a+b;当 sel 不为 0 时 c 的结果是 a+d。但如果触发条件没有发生改变, 虽然 sel 由 0 变 1, 但此时 c 的结果仍是 a+b
当敏感信号非常多时很容易会把敏感信号遗漏,为避免这种情况可以用*代替,这个*是指程序语句中所有的条件信号,即a、b、d、sel(不包括c)
always @(*)begin
if(sel == 0)
c = a + b;
else
c = a + d;
end
敏感列表是**“ posedge clk”,其中 posedge 表示上升沿**。也就是说, 当 clk 由 0 变成1 的瞬间执行一次程序代码,即第 2 至 5 行, 其他时刻 c 的值保持不变。要特别强调的是: 如果 clk没有由 0 变成 1,那么即使 a、 b、 d、 sel 发生变化, c 的值也是不变的
always @(posedge clk)begin
if(sel == 0)
c <= a + b;
else
c <= a + d;
end
敏感列表是“ posedge clk or negedge rst_n”,也就是说,当 clk 由 0 变成 1 的瞬间,或者 rst_n 由 1 变化 0 的瞬间,执行一次程序代码,即第 2 至 8 行, 其他时刻 c 的值保持不变。这种信号边沿触发,即信号上升沿或者下降沿才变化的 always, 被称为“时序逻辑”, 此时信号 clk 是时钟。注意: 识别信号是不是时钟不是看名称,而是看这个信号放在哪里,只有放在敏感列表并且是边沿触发的才是时钟。而信号 rst_n 是复位信号, 同样也不是看名字来判断,而是放在敏感列表中且同样边沿触发,更关键的是“程序语句”首先判断了 rst_n 的值, 这表示 rst_n 优先级最高,一般都是用于复位
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)
c <= 0;
else if(sel == 0)
c <= a + b;
else
c <= a + d;
end
/*
5.测试assign
*/
module tb_led();
reg in;
wire out;
reg[1:0] o;
reg[31:0] count;
initial in <= 0;
always #10 in = ~in;
assign out = (in == 1) ? 1 : 0;
//assign out <= (in == 1) ? 0 : 1; //这种赋值"<="是错误的,使用assign时要使用=进行赋值,而不能使用<=进行赋值
initial o = 0;
initial count = 0;
always@(posedge in) begin
if(in == 1)
o <= 3;
count <= count + 1;
// else
// o <= 2;
end
endmodule
参考链接:
https://blog.csdn.net/Royalic/article/details/121196365
https://blog.csdn.net/Lily_9/article/details/83993254
7.verilog中系统函数
/*
6.verilog中系统函数
*/
module tb_led();
reg[15:0] a;
initial $display ("Hello FPGA!"); //display用于显示输出
initial a <= 0;
initial $monitor("a = %d",a); //初始化一次,monitor持续监测变量
always begin
a = a + 1;
$display ("time = %d,random = %d",$time(),$random()); //time为时间函数,返回64位当前仿真时间,random用于产生随机函数,返回随机数
#10;
//$stop; //暂停仿真,在窗口输入命令可以让程序继续执行
//$finish; //结束仿真
end
endmodule