基础知识
1 模块(Module)
Verilog中的module可以看成一个具有输入输出端口的黑盒子,该黑盒子有输入和输出接口(信号),通过把输入在盒子中执行某些操作来实现某项功能。(类似于C语言中的函数)
图1 模块示意图
1.1 模块描述
图1 所示的顶层模块(top_module)结构用Verilog语言可描述为:
🔹 模块以module 开始,endmodule结束
🔹 top_module 为模块名
🔹 input : 为输入端口
🔹 output: 为输出端口
🔹 所有代码必须处于module模块中!
同理,图1 所示的次级模块(mod_a)结构用Verilog语言可描述为:
注意事项:每个模应单独块处于一个.v文件中,模块名即为文件名(规范代码!)
1.2 模块输入输出信号
🔹 输出:output
🔹 输入:input
模块的输入输出端口都可看出模块的信号,若不写信号类型则默认为wire类型信号!
除了wire型信号,还有reg型信号,具体详见1.4节!
1.3 模块实例化
如图1所示,top_module的两个输入端口连接到次级模块(mod_a)的输入端口,那如何在top_module模块模块中使用mod_a模块的功能呢?这就需要通过模块实例化,可以把top_module看成C语言中的主函数,次级模块mod_a看成普通函数,这样就可以在主函数中调用其他函数来完成相应的功能!
在top_module中实例化mod_a的方式为:
模块实例化语法:模块名 实例名(定义连接port的信号);
🔹 按mod_a定义的端口顺序实例化: mod_a instance1 (a, b, out);
🔹 按mod_a端口名实例化: mod_a instance2 (.in1(a), .in2(b), .out(out)); (推荐此种写法)
2 逻辑块(always、generate)
2.1 always逻辑块
always块可构建 组合逻辑块 和 时序逻辑块,复杂的逻辑操作都需要处于该逻辑块中,如if、case、for等
(1) 组合逻辑块
🔹 always逻辑块中任意信号变化时立即触发,执行begin - end之间的语句
🔹 begin - end用于将多条语句组成一个代码块,只有一条语句时可省略
(1) 时序逻辑电路
🔹 clk 信号的上升沿触发
🔹 posedge: 上升沿
🔹 negedge: 下降沿
2.2 generate逻辑块
generate主要结合for循环使用,主要用途有:
🔹 对向量中的多个位进行重复操作
🔹 对同一个模块进行多次重复实例化(主要用途)
(1) 操作向量
(2) 模块重复多次实例化
🔹 注意:模块多次实例化时必须写每个begin_end结构的名称(gen_mod_a)
🔹 仿真器会通过gen_mod_a来标识生成结构: gen_mod_a[0],gen_mod_a[1]....
2.3 initial块
initial块可以理解为一个初始化块,在initial的起始位置的语句在0时刻即开始执行,之后如果遇到延时,则延时之后执行接下来的语句。
初始块是不可综合的,因此不能将其转化为带有数字元素的硬件原理图。因此初始块除了在仿真中使用外,并没有太大的作用。
如:在仿真文件中初始化各种参数:
注意:
🔹 initial 块在电路中不可综合,故一般不出现在RTL代码中
🔹 initial 一般只在仿真文件中使用
若需要在RTL代码中初始化参数,需要用always块,用initial块会导致错误!
如下所示,在RTL代码中初始化存储器的方式为:
3 赋值方式
Verilog 中赋值方式有三种:连续赋值、阻塞赋值、非阻塞赋值
3.1 连续赋值(assign)
🔹 该语句表示把x和y两个信号进行连接,真实的物理连接!
🔹 不能在always块中使用
3.2 阻塞赋值(=)
🔹 组合always块中用阻塞式赋值
🔹 执行顺序:按照begin_end语句块中的顺序依次执行,上述输出结果为:out1 = a ,out2 = b
3.3 非阻塞赋值(<=)
🔹 时序always块中用非阻塞赋值
🔹 执行顺序:begin_end中所有语句并行执行,上述输出结果为:out1 = a ,out2 = a
基础语法
1.1 标识符
(1) 用途:标识符用于定义常数、变量、信号、端口、参数名、模块名等。
(2) 组成:字母、数字、$、下划线任意组合而成
(3) 注意事项:
🔹 区分大小写(Verilog 和 verilog是不同的)
🔹 第一个字符只能是字母或下划线(123demo 是非法标识符)
1.2 逻辑值与逻辑运算
1.2.1 逻辑值
Verilog中有4中逻辑值:0、1、x、z
🔹 0: 低电平
🔹 1:高电平
🔹 x: 表示状态未知
🔹 z:表示高阻状态
注意:这里的z、x是不区分大小写的(X、Z也可)
1.2.2 逻辑运算
(1) 逻辑运算符:&&(与)、==(相等)、||(或)、!=(不等)
🔹 如 m&&n : 判断m和n是否全为真(非0即为真),真则输出1'b1,否则输出1'b0 (4’b1010&4’b0101 = 1’b1)
🔹 最后输出结果只有1bit
(2) 按位运算符:&、|、~、^、~&、~^、~|
🔹 如 m&n : 是把m的每一位与n的每一位按位做与运算 (4’b1010&4’b0101 = 4’b0000)
🔹 输出结果与m/n的bit数相同
(3) 归约运算符: &、|、~、^、&、~^、~|
🔹 只有一个参量参与运算时( &为一元运算符),表示规约与,即向量内部进行与运算
🔹 即(&4’b0101 = 0&1&0&1 = 1'b0 )
🔹 最后输出结果只有1bit
1.3 常量的表示方法
与C语言类似,常量主要有:整数型、实数型和字符串型三种
1.3.1 用十进制整数表示整型常量
(1) 正数:直接写 10 表示位宽为32bit的十进制整数(系统默认)
(2) 负数:-10需要用二进制补码表示,多了一位符号位(1 1010)
(3) 用科学计数法表示:12.345e3 表示 12345
1.3.2 用基数法表示整数型常量
(1) 二进制(b): 8'b1000_1100
(2) 十六进制(h): 8'h8c
(3) 八进制(o): 8'o214
(4) 十进制(d): 8'140
注意事项:
🔹 当表示二进制时,最好每4位写一个下划线以增强可读性:如8'b1000_1100 与8'b10001100 是一样的
🔹 基数表示法中遇到x时:十六进制表示4个x,八进制中表示3个x
🔹 当位宽大于二进制位数时左边自动补0,小于二进制数时2从左边截断!
1.3.3 字符串(用双引号)
(1) 每个字符由1个8位的ASCII码值表示,即需要1byte存储空间
(2) 如:“Hello world” 字符串由11个ASCII符号构成,需要11byte存储空间
1.4 注释方式
Verilog中注释主要有行注释(//)和块注释(/* .... */)两种,表示方法与C语言一致!
1.5 变量(wire、reg)
Verilog中的变量主要有两种:wire和reg
1.5.1 wire
(1) 线网型(wire): 表示电路间的物理连接,wire定义的变量也可看成信号端口
(2) 当两个wire信号被连续赋值时,在逻辑块中会被映射成真实的物理连线,此时这两个信号端口的变化是同步的!
1.5.2 reg
(1) 寄存器型(reg): 表示一个抽象的数据存储单元
(2) reg 具有对某一时间点状态进行保持的功能
1.5.3 用法与注意事项
(1) 在always、initial语句中被赋值的变量(赋值号左边的变量)都是reg型变量
(2) 在assign语句中被赋值的变量,为wire型变量
1.6 向量(vector)与 参数(常量)
1.6.1 parameter 参数(常量)
(1) 参数是一种常量,通常出现在module内部,常被用于定义状态、数据位宽等
(2) 只作用于声明的那个文件,且可以被灵活改变!
(3) 局部参数localparam,只在本模块中使用
(4) 参数的名称一般为大写,以区分其他变量
1.6.2 向量(vector)
vector(向量),是一组信号的集合,可视为位宽超过1bit 的 wire 信号。
(1) 定义方式:
🔹 [upper:lower] 定义位宽,如 [7:0] 表示位宽为8 bit ,即upper=7,lower=0
🔹 vector_name可以一次写多个向量
1.6.3 向量片选
🔹 a[3:0] 取向量a的0~4位数据
🔹 b[n] 取向量b的第n位数据
🔹 c[-1:-2] 取向量c的最低2位数据
🔹 c[0:3] 取向量c的最高4位数据
多路选择器应用:实现一个 256 选 1 选择器,sel 信号作为选择信号,当 sel = 0 时选择 in[3:0],sel = 1 时选择 in[7:4],以此类推。
🔹 片选信号sel输入为n位二进制数,当参与运算、充当索引时会自动转换成十进制数
🔹 该题所选取的信号片段为: in[sel*4+3: sel*4] ,但这不符合Verilog的片选语法规则故应写成:
in[sel*4 +: 4] 表示索引从sel*4开始的高4bit信号
in[sel*4+3 -: 4] 表示索引从sel*4+3开始的低4bit信号
🔹 或是直接选出需要的每一位,再用{ }拼接成新向量:
{in[sel*4+3], in[sel*4+2], in[sel*4+1], in[sel*4+0]}
1.7 三元表达式
(1) 与C语言相同,Verilog也有三元表达式:
当条件为真,表达式值为if_true ,否则表达式值为if_false。
(2) 应用
1.8 分支语句(if-else、case)
1.8.1 if-else语句
(1) 最常用的形式:(优势:输出的所有可能都写到,不存在未知电平输出!)
(2) 不建议使用if-else嵌套,会存在优先级问题,导致逻辑混乱,
(3) 所有if-else语句都应写成(1)的形式!
(4) 根据条件表达式依次比较,存在优先级!
1.8.2 case 语句
(1) 书写形式:
比较<控制表达式>与<分支语句n>的取值相等则执行对应语句,否则执行default后语句!
(2) 执行完某一分支语句后立即跳出case语句结构,终止case语句执行。
(3) <分支语句n>的取值必须互不相同!
(4) 以encase结束case语句块
(5) 各分支语句间不存在优先级!
(6) 具体应用: 用case语句搭建多路选择器,(以9选1多路选择器为例)
1.9 for循环语句
(1) 书写形式:
🔹 执行<循环语句>n次
🔹 for_name为每一次循环的名称
2 关系运算符(>、<、>=、<=)
🔹 运算结果为真返回 1
🔹 运算结果为假返回 0
🔹 若某个操作数值不定(x),则返回值为 x
2.1 拼接运算符({ , })
2.1.1 拼接
用一对花括号加逗号组成“{ , }”拼接运算符,逗号隔开的数据按顺序拼接成新数据!
2.1.2 通过拼接实现移位
在左边拼接实现右移,右边拼接实现左移!
2.1.3 连接符中重复多次的操作
语法: {重复次数{vector}}
2.2 移位运算符
移位运算符用于将左边操作数左移或右移指定的位数!移位后空闲位用0填充。
🔹 左移运算符:<<
如:4‘b1101 << 3 结果为:4‘b1000
🔹 右移算法符: >>
如:4‘b1101 >> 3 结果为:4‘b0001
🔹 移位运算符其他用途:左移一位可以看成是乘以 2,右移一位可以看成是除以 2。
🔹 移位运算符代替乘除法可以节省资源!
3 二进制全加器
🔹 a、b为输入 1bit 数据
🔹 cin为上一个加法器进位输入
🔹 cout为本加法器的进位输出
🔹 sum = a+b
代码实现:
4 16进制全加器
16进制全加器如上图所示,它可由上节中16个二进制全加器组合而成。
用Verilog实现16进制全加器代码为:
5 模块中的参数传递
5.1 定义可传递参数的模块
5.2 带参数模块的实例化