由于本实验的硬件设计部分使用 Verilog HDL 编写, 考虑到很多同学之前并没有接触过 Verilog 或者是使用的 VHDL 语言进行设计, 故本章主要目的是帮助同学们熟悉 Verilog HDL 硬件描述语言以及两个常用的模块: 使能信号和总线译码器, 从而更好的完成本节课的实验.
DL (hardware description language), 指出了我们学习 Verilog 的关键: 硬件描述. 要时刻牢记, Verilog 是硬件描述语言, 不要用执行软件的思路来理解学习 Verilog 代码.
常量类型
整数
- 二进制整数 : b 或 B 表示
- 十进制整数 : d 或 D 表示
- 十六进制整数 : h 或 H 表示
- 八进制整数 : o 或 O 表示
整数的表述方法一般有以下几种:
1.<位宽> <进制> <数字>方法,
例如想要表示整数 22, 位宽定义为 8, 则用二进制表示为 8'b00010110或8'b0001_0110; 用十进制表示为 8'd22; 用 16 进制表示为 8'h16; 用 8进制表示为 8'o26.
2.<进制> <数字> 方法, 一般来说, 这样表示默认位宽为32位.
仍然拿整数 22 举例, 进制为 16 进制, 则可以表示为 'h16. 与 32'h16等价.
3.<数字> 方法. 一般来说, 这样定义整数时, 默认进制为10, 默认位宽为32位.
- 参数
定义参数时, 需要使用关键字 parameter 声明, 且只能赋值一次. 用法为 parameter 常量 = 整数.
例如定义整个模块中数据位宽为 32, 则 parameter data_width = 10'd32;
变量类型
- net型
wire: 表示一般的连接线
tri: 表示电路连接是三态类型的, 使用时与 wire 等价, 可以增强可读性
- variable型
reg: 默认位宽为 1 位的变量, 默认是无符号数
integer: 默认位宽为 32 位的变量, 通常用于描述整数参数.
变量定义的方法为 <变量类型> <位宽> <变量名称>.
例如定义一个 reg 变量, 位宽 2, 命名为 a, 则为 reg [1:0] a.
对于一个模块来说,输入端口可以由 reg / net 驱动, 但是输入端口本身只能是 net; 输出端口则可以是 reg / net 类型, 但是输出端口只能驱动 net 型. 下面举一个例子
+------------+
a-------------------> A Y>--------> y
| |
| |
b-------------------> B |
+------------+
对于上图这个模块来说, a, b 是外部输入给模块的, 所以 a, b 可以为 reg / net 型; 而对于模块内部定义的输入端口 A, B 来说, 必须是 wire 类型. 同样的, 模块内部定义的 Y 输出端口来说, 可以为 reg / net 型; 但是它只能驱动 net 型.
运算符
- 算数运算符 : +、-、* 、/、%
算术运算符中, 进行乘法/除法运算时, 结果要略去小数部分,只取整数部分; 进行取模运算时, 可以使用 % 求余数, 结果的符号位采用模运算符中第一个操作数的符号.
例如, -10 % 3 结果为 -1, 11 % -3 结果为 2. 另外, 在进行算数运算时, 某一个操作数有不确定的值 X, 则整个运算的结果也为不确定的 X.
- 按位运算符 : &、|、~ 、^(异或) 、~^(同或)
按位运算符要求对两个操作数的相应位数逐位进行计算,
例如 0101 & 1001 = 0001, 1100 & 1001 = 1000.
- 缩位运算符 : &、|、~ 、^ 、~^、 ~&
缩位运算符作用于一个多位的变量, 作用是将多位变量的每一位进行逻辑运算.
如一个四位的变量 b, c = &b的含义为 c = (((b[0] & b[1]) & b[2]) & b[3]).
- 逻辑运算符 : &&、||、!
逻辑运算符只区分真假, 而不管什么数值. 例如, 逻辑运算中, 输入 4'ha1 和 4'h01 是没有区别的, 都表示逻辑真; 而 0 为逻辑假. 所以一般来说, 逻辑运算的结果要么为真, 要么为假. 特例是如果有一个输入为未知 X, 那么结果也是 X.
- 关系运算符 : <=、=>、<、>
相等运算符 : == 、! =、!==(不全等)、 ==\= (全等)
相等运算符中的 !== 和 ==\= 常用于 case 表达式的判别, 如果操作数中存在 Z 或者 X, 那么操作数必须完全相同结果才为 0/1, 否则为 1/0.
- 移位运算符 : <<(左移)、>>(右移)
使用移位运算符时, a >> n 中 a 代表要进行右移的操作数, n 代表右移的位数; 左移同理. 移位产生的空位用 0 来填补.
例如 a 是一个 5 位的寄存器, 若 a = 4'b1001 << 1, 则 a 的结果为 5'b10010, 但如果 a 是一个 4 位的寄存器, 仍然对 a 做上述操作, 则 a 的结果为 4'b0010.
- 拼接运算符 : { }
拼接运算符可以将变量任意组合后输出给另一个变量. 用法: {信号 1 的某几位, 信号2 的某几位, …… , 信号 n 的某几位}
例如: {a, b[3:0], w, 3'b101} 等同于 {a, b[3], b[2], b[1], b[0], w ,1b'1, 1'b0, 1'b1}, {4{w}} 等同于 {w,w,w,w}, 需要注意 4 这个位置必须是常量表达式.
- 条件运算符 : ? :
条件运算符中的 ? 实现的是多路复用器, 用法为 assign wire变量 = 条件 ? 表达式 1: 表达式 2; 例如下面这个式子: assign out = sel ? in1 : in0; 描述了当 sel 为 1 的时候, out = in1, sel 为 0 的时候, out = in0.
和 C 语言一模一样?
是的, 你很敏锐, 在功能上运算符是和 c 语言类似的, 但是需要注意的是, Verilog 是硬件描述语言, 这其中每种运算符实际上都与电路对应. 如 + 运算符对应加法器, & 运算符对应与门, 等等.
表达式和操作数
表达式由操作符和操作数构成,其目的是根据操作符的意义得到一个计算结果。表达式可以在出现数值的任何地方使用。
操作数可以是任意的数据类型,只是某些特定的语法结构要求使用特定类型的操作数。操作数可以为常数,整数,实数,线网,寄存器,时间,位选,域选,存储器及函数调用等。
常用语句
- if-esle 语句
- case 语句
- always 语句
- assign 语句
这里着重介绍 always 语句和 assign 语句.
always 语句几乎等同于 VHDL 中的 process 语句, 区别在于在 always 语句中描述上升沿时, 可以直接使用 always (posedge clk) 描述.
assign 语句用于给 wire 型变量赋值, 使用方法为 assign wire型变量 = 常数.
例如给位宽为 4 的 data 变量赋值 10, 则为assign data = 4'd10;
请不要整活
也许你还看到了其他的一些语句, 诸如 for, function ,while 之类, 不要在设计过程中使用它们, 这会导致无法综合, 这些语句只是用来仿真的.
赋值
- 阻塞赋值
- 非阻塞赋值
阻塞赋值为顺序赋值, 下一条语句执行前, 当前语句一定会执行完成, 阻塞赋值使用 = 作为赋值符;
非阻塞赋值为并行赋值, 下一条语句和当前语句的执行时同时进行的, 非阻塞赋值使用 <= 作为赋值符.
赋值和电路的关系
描述组合逻辑电路时, 需要使用阻塞赋值, 描述时序逻辑电路时, 需要使用非阻塞赋值. 当使用 assign 语句时, 一定要使用阻塞赋值, 当使用 always 语句时, 阻塞赋值和非阻塞赋值均可使用.
question
使用条件运算符时, 可以使用非阻塞赋值吗?
END
文章来源:
推荐内容:
- 【课程实验 LAB0】FPGA简介
- 【课程实验 LAB0】为什么是 FPGA?
- 【课程实验 LAB0】设计方法
- 【课程实验 LAB0】平台介绍
- 【课程实验 LAB0】软硬件关系及软件Modelsim, Vivado, Keil的介绍
- 【课程实验 LAB1】“施法”让CPU动起来:搭建 Keil 环境
- 【课程实验 LAB1】“施法”让CPU动起来:运行吧,第一个汇编程序
- 【课程实验 LAB1】实现你的首个SoC:函数调用
- 【课程实验 LAB2】实现你的首个SoC:硬件部分说明
- 【课程实验 LAB2】实现你的首个SoC:搭建Keil工程和Modelsim 仿真
- 【课程实验 LAB2】实现你的首个SoC:下载比特流到 FPGA 和使用 Keil 调试
- 【课程实验 LAB3】流水灯的几种点法:数据存储器
- 【课程实验 LAB3】流水灯的几种点法:GPIO外设实现流水灯
- 【课程实验 LAB3】流水灯的几种点法:硬件流水灯
- 【课程实验 LAB4】如何召唤"沉睡的软件":使用矩阵键盘4个按键控制流水灯模式
- 【课程实验 LAB4】如何召唤"沉睡的软件":使用矩阵键盘控制数码管显示
更多内容请关注微处理器系统结构与嵌入式系统设计专栏