目录
1.引言
本篇内容是本人对于学习Verilog HDL语法过程中的总结,我的预期是将内容写的细一点,但作为一个初学者难免有所纰漏亦或是逻辑问题,所以会对内容进行长时间的修正、调整和补充。
首先,Verilog是什么?是一种硬件描述语言( HDL,Hardware Description Language)。
描述的是什么?描述的是数字电路或数字系统的模型。
个人理解就是将数字电路(或系统)的模型,转化为编程语言的形式,将其描述出来。
我们编的不只是程序,而是可以具象化将一块一块的电路构造好后,连接起来,共同实现所需功能。(我们编写的也可能会是我们的梦想)
在这之中,模型也分为很多层次:
描述级别 | 介绍 |
1.系统级(system-level) | 后续补充 |
2.算法级(algorithm-level) | 后续补充 |
3.RTL级(register transfer level) |
描述如何控制和处理各类数据在寄存器之间的流动。 |
4.门级(gate-level) |
描述逻辑门之间的连接 |
5.开关级(switch-level) | 描述三极管和储存节点之间的连接 |
以上1~3都属于行为描述,1~3中只有RTL与逻辑电路有明确的对应关系。
现在明确了 Verilog是可以描述出整个电路系统,用一个不太恰当的比喻,就像我们知道了用砖,水泥或者钢筋、混凝土可以搭出房子一样;而房子是一间一间,一层一层盖起来的,复杂电路系统则是基于模块,一个一个配合共同实现功能的,每一个模块又可以由一个个子模块构成。
因此,下一节对模块进行简要介绍。
2.模块(block)
我个人理解:模块是实现特定功能的基本单元;我承认这句话看起来像是一句废话,还是那个不太恰当的比喻,就好像盖房子,用砖和水泥给自己盖一个新家用于生活,那你屋子里至少需要有厨房,卧室,客厅,卫生间等等,这些特定功能的基本单元。
模块如何用Verilog定义呢?
//模块定义 模块名
module <module name>(
CLK,
RSTB,
……
DOUT);
input wire CLK; //时钟
input wire RSTB; //复位下降沿有效
……
output wire DOUT; //输出信号
//参数
parameter WIDTH=8;
//模块内容
………………
………………
//实例化 实例化命名
example example_inst(
.CLK(CLK),
.RSTB(RSTB),
.DOUT(DOUT)//
);
endmodule
本人目前接触到的模块有,加法器,选择器,触发器,译码器,FIFO,锁存器(这个慎用)等。
以下以一个选择器作为例子。实现功能:
模块有三个输入分别为 in_1,in_2,sel
一个输出out
输入sel为1 模块out = in_1输入的值;
输入sel为0 模块out = in_2。
RTL文件:
//模块开始 模块名
module mux2_1
( //输入输出列表(注意,除了列表结尾,每行后面都是逗号)
//类型 数据类型[位宽] 变量名,
input wire [0:0] in_1, //input signal 1
input wire in_2, //input signal 2
input wire sel , //select signal
output reg out //output
//这里out后面不加逗号 直接括号分号结束
);
/*定义模块方法2
module mux2_1(
in_1,
in_2,
out
);
input wire [0:0] in_1; //input signal 1
input wire in_2; //input signal 2
input wire sel; //select signal
output reg out;
*/
//模块内容
//always表示块,块内的内容一直运行,后面(*)表示触发条件,即块内的变量sel、out只要有改变,
//就会运行块内语句
always@(*)
if(sel == 1'b1)//这里一手简单的判断语句
out = in_1;
else
out = in_2;
//快乐的时间就是这么短暂,模块结束了
endmodule
Test_bench文件实现功能:
每隔10us就给RTL文件中的 in_1,in_2,sel,赋值一个二进制随机数0或者1;
可以通过quartus和modelsim联合仿真观察out输出有没有严格实现功能
//时间尺度 单位时间间隔/时间精度
`timescale 1us/1us
//模块开始 输入输出列表为空
module tb_mux2_1();
//定义寄存器型变量
reg in_1;
reg in_2;
reg sel;
//定义线型变量
wire out;
//初始化块,注意这个初始化初学阶段只能用于Test_bench之中跑仿真用,千万不能写在rtl中
initial
begin
in_1 <= 1'b0;
in_2 <= 1'b0;
sel <= 1'b0;
end
//always块没有触发条件就代表一直执行 #10表示延迟10*单位时间间隔的时间
// %2表示除以2的余数 {$random}%2表示产生随机数除以2求余数
// 限定产生的随机数<2 即0和1
always #10 in_1 <= {$random} % 2;
always #10 in_2 <= {$random} % 2;
always #10 sel <= {$random} % 2;
//
initial
begin
// 1e-9s == ns
$timeformat(-6,0,"us",6);
//$monitor(参数列表)用于监控和输出参数列表个参数的值
//写法类似于C语言中的printf。
$monitor("@time %t:in_1 = %b sel=%b out=%b",$time,in_1,in_2,sel,out);
end
//实例化,将rtl文件和test_bench文件之间的变量连接起来,
//连接起来后就可以通过test_bench文件给rtl文件中的输入赋值,来测试模块功能能否实现
mux2_1 mux2_1_inst
(
.in_1(in_1), //input signal 1
.in_2(in_2), //input signal 2
.sel(sel), //select signal
.out(out) //output
);
//模块结束
endmodule
以上代码是跟哔哩哔哩上野火FPGA 火哥学的,他的视频讲得很细,网址我附在结尾,如果以上代码或其他内容侵权请联系我,我删。
对了,鲁迅先生说:每个文件内最好只写一个模块,一种功能。
3.常量、数据类型、运算符
(1)常量
1)数字
表示形式
二进制整数:b或B
八进制整数:o或O
十进制整数:d或D
十六进制整数:h或H
直接举例:
位宽'进制+数字
//例如
8'b0001_0010;//8位二进制数 下划线是为了方便看,不影响实际值,等价于8'b00010010
//注意下划线只能放在数字部分的中间如16'b0001_0000_0010_0100
//不能放在进制和数字之间,这样是错误的,如8'b_00010000
8'h80;//8位十六进制数 转换为二进制为1000_0000;
2)x和z值
在数字电路中,x代表不定值,z代表高阻态值。
这个我还没用过,就不过多介绍,先放在这里,若果后续有接触到我会进行补充。
(2)数据类型
就我现在所学,目前常用的就是wire型,reg型以及parameter型。(如果后面我所学有涉及到其他的我会补充)
1)wire型
格式:
wire [位宽范围] 变量1,变量2……;
//例如
wire [3:0] a,b;//定义位宽为4的 两个wire型变量 a和b。
wire [0:0] c;//定义位宽为1的 一个wire型变量 c。
wire d;//定义位宽为1的 一个wire型变量 d。
- wire型数据大多用来表示用assign关键字指定的被赋值的信号;
- 在模块中,输入输出信号没有特别定义的话,默认为wire型;
- wire型信号可以用做任何方程式的输入,也可以用作assign语句或实例化元件的输出;
- 如果不指定位宽的话,默认为1。
2)reg型
格式1:
reg [位宽范围] 变量1,变量2……;
//例如
reg [4:1] a,b;//定义位宽为4的 两个reg型变量 a和b。
reg [0:0] c;//定义位宽为1的 一个reg型变量 c。
reg d;//定义位宽为1的 一个reg型变量 d。
reg [4:0] e;
always(posedge clk or negedge rst)begin
if(~rst)
e <= 4'b0;
else
e <= {1,2,3,4};
end
//e[0]=4,e[1]=3,e[2]=2,e[3]=1;
- reg型数据初始值默认为x,即不定值。
- reg型数据大多用来表示用always块内的被赋值的信号,常代表触发器;
- 在always块中,被赋值的每一个信号都必须为reg型;
- 如果不指定位宽的话,默认为1。
格式2:用于定义memory型的变量
reg [位宽-1:0] memory名 [深度-1:0];
例如:
parameter FIFO_WIDTH = 8;
parameter FIFO_DEPTH = 8;
reg [FIFO_WIDTH-1 : 0] FIFO [FIFO_DEPTH-1:0];
3)参数型
parameter 参数名=参数值;
//例如
parameter FIFO_DEPTH = 8;//定义FIFO_DEPTH为8
parameter FIFO_WIDTH = 8;
- parameter型数据常用于定义位宽,FIFO深度,以及延迟时间
(3)运算符
1)算术运算符
符号 | 使用示例 | 结果(十进制) | 描述 |
+ | a+b |