Verilog-B站教程-Notes
FPGA 设计流程
组合逻辑与时序逻辑
组合逻辑
组合逻辑是指没有时钟信号触发的逻辑电路,其输出只取决于输入信号。组合逻辑只能实现纯粹的逻辑功能,例如加法器、比较器和选择器等,没有记忆功能。组合逻辑的输出仅仅取决于当前的输入状态,与之前的输入状态和时钟信号无关。
时序逻辑
时序逻辑是指由时钟信号触发的逻辑电路,其输出不仅仅取决于输入信号,还取决于之前的状态。时序逻辑通常用于实现计数器、存储器和状态机等具有记忆功能的电路。时序逻辑的输出状态不仅仅取决于当前的输入状态,还受到先前的输入状态和时钟信号的影响。
时序逻辑与组合逻辑的对比
时序逻辑和组合逻辑的主要区别在于它们的实现方式和功能。时序逻辑通过使用时钟信号来同步电路的输入和输出,可以实现记忆功能。组合逻辑没有时钟信号,只能实现纯粹的逻辑功能,没有记忆功能。此外,时序逻辑的设计更加复杂,需要考虑时序的影响,而组合逻辑的设计更加简单,只需要考虑逻辑关系。在实际应用中,时序逻辑和组合逻辑通常会结合使用,以实现更为复杂的电路功能。
时序逻辑更胜任大规模的逻辑电路.
也就是说, 时序逻辑中, 多了时钟信号和寄存器.
如这里的时序逻辑图, 当与非门产生结果时, 其会传入寄存器的输入端, 但只有当时钟信号为上升沿时, 结果才会从寄存器的输出端传出.
一个实现异或门的例子
1 |
|
在Verilog中,当在模块中声明输入和输出端口时,如果没有明确指定端口类型,则其默认类型为wire类型 (如这里的 a
, b
).
组合逻辑和时序逻辑中的赋值
使用assign关键字来创建组合逻辑的赋值语句:
如:
1 |
|
使用非阻塞赋值 (<=) 和 阻塞赋值 (=) 来创建时序逻辑的赋值语句:
如:
1 |
|
这里的 posedge
是一个关键字,表示时钟信号的上升沿.
同样, 表示下降沿的关键字为 negedge
.
区分组合逻辑和时序逻辑
在Verilog中,可以使用 always
关键字来表示时序逻辑,而组合逻辑则可以在模块的顶层代码中使用组合逻辑赋值语句来实现。下面是一些区分时序逻辑和组合逻辑的方法:
always
关键字常用于表示时序逻辑,用于在时钟边沿触发某些行为, 如:
1 |
|
(这部分代码的意思为, 当时钟信号处于上升沿时, 进入 always
块, 如果设置了 reset
, 那么执行 q<=0
; 不然执行 q<=d
)
之外的应该就是组合逻辑, 且用 assign
来赋值.
二选一多路器实现
1 |
|
也就是说, 如果 sel == 1
, 那么输出的值等于 a
端的输入, 如果 sel == 0
, 那么输出的值等于 b
端的输入.
仿真和测试
在 vivado 中, 设计时, 创建 design 文件, 仿真时, 创建 simulation 文件.
关于:
1 |
|
的作用, 其影响 #1
这类延时操作, 如这里就是延时 1ns
, 因为精度为 1ps
, 因此也可以写成 #1.001
.
测试文件一般不需要端口.
测试一个模块, 也就是将一个模块例化. 例化和面向对象创建实例类似, 用模块名当作类型, 再加上一个例化名称.
注意, 例化时, 类型需匹配 (也就是和模块定义时相同, 但实际操作时发现好像不需要匹配).
如:
1 |
|
有时, vivado 默认只会跑 1000ns
, 可以让其继续跑
也可以手动停止, 用 $stop
布局布线 (run implementation) 之后, 就可以时序仿真 (Time Simulation).
性能分析时, 就需要借助时序仿真.
组合逻辑38译码器实现与相关语法基础
38 译码器, 就是将 3 位的输入信号, 转换为 8 位的输出信号. 其作用就是, 用 3 个输入信号, 来决定 8 个输出端口中哪一个来输出.
图像为:
其输入和输出为:
也就是说, 用 3 个输入信号, 来决定 8 个输出端口中哪一个来输出.
在设计时, 并不需要设置 timescale, 这个在测试仿真时设置就行.
设计代码如:
1 |
|
数字之间可以用 _
(下划线) 来连接.
注意 case ({a,b,c})
中的 {a,b,c}
的含义. {}
是拼接符号, a,b,c
都是 1 位的信号, 这样连接后, {a,b,c}
就表示一个 3 位的信号.
注意, 以 always
块描述信号赋值时, 被赋值对象必须定义为 reg
类型.
敏感信号指引起输出变化的信号.
使用 initial 块的原因
对于需要初始化的寄存器或变量,使用initial语句块可以保证它们的值在仿真开始时被正确地初始化。而使用赋值语句可能会导致在仿真开始时出现未定义的值
关于 wire 类型
一般将要观察的信号定义为 wire
.
在 Verilog 中,wire 类型只能被用于连接两个逻辑门或者模块之间的信号,不能被赋值。wire 类型的值是由其他逻辑门或者模块的输出产生的,因此不能通过赋值语句来改变其值。
注意:
1 |
|
这段代码中, 并没有给 wire
类型端口 out
赋值, 而是用 assign
语句将一个表达式赋值给一个 wire 类型的信号, 也就是说, 没有赋予标量值.
在 Verilog 中,wire 类型表示一个连续的、无限制的信号。与之相对的是 reg 类型,它表示一个有限的、可以存储数据的信号。由于 wire 类型的信号只用于连接不同的模块或组合逻辑电路中,所以它们通常是由其他信号计算得出,而不能被显式地赋值。
当我们使用 assign 语句时,实际上是在将一个逻辑表达式连接到一个 wire 类型的信号上。这种连接的方式不同于对 reg 类型的信号进行赋值,因为 reg 类型的信号具有存储功能,可以被显式地赋值。
因此,我们可以将 assign 语句看作是在描述一种逻辑关系,而不是直接对 wire 类型的信号进行赋值。虽然 wire 类型的信号不能被显式地赋值,但是通过使用 assign 语句,我们可以将多个信号连接起来,从而实现组合逻辑电路。
关于 assign 语句
assign
语句只能用于给 wire
类型的信号进行赋值,不能用于给 reg
类型的信号进行赋值。在 Verilog 中,reg
类型的信号需要在 always
块中被赋值。
时序逻辑计数器设计与相关语法
二选一多路其和三八译码器本质上都是组合逻辑电路. 其输出和时间没有关联.
时序逻辑不仅和输入的信号相关, 其同时与时钟信号相关.
D 触发器 D-flip-flops
D 触发器
D 触发器是一种时序电路元件,用于在时钟信号的边沿上存储数据。它有一个数据输入端(D)和一个时钟输入端(CLK),以及一个输出端(Q)。
D 触发器的工作原理是,当时钟信号的上升沿或下降沿到达时,它会将数据输入端的值存储到输出端中,并在时钟信号保持稳定时保持输出端的值不变。因此,D 触发器是一种同步存储器,它只在时钟信号的边沿上响应并存储数据。
(时钟信号保持稳定的意思是, 经过上升沿/下降沿之后的一段高电平/低电平, 毕竟是方波)
图像如:
存储功能, 也就是指 D 端口的输入会保存在 Q 端口.
Verilog 实现如:
1 |
|
计数器
假设为 4 位计数器. (4 位是指输入端口 D 和输出端口 Q 都是 4 位的)
这里的 $\oplus$ 表示的是一个加法器.
其一端一直输入 1, 另一端输入输出的结果, 也就有:
1 |
|
的效果.
时钟一般是比较稳定的, 可以设置.
利用计数器, 使 LED 灯每 500 ms 闪烁
假设时钟频率为 $50MHz$, 则周期为 $20ns$.
若要每 500ms 闪烁, 则需要经过 $500ms/20ns$ 个周期, 即 25000000 个周期.
也就是让计数器来记录 25000000 个周期.
代码如下:
(这里的复位接口的作用是让 counter 复位)
1 |
|
代码解释:
当 clk
信号的上升沿或者 reset
信号的下降沿时, 执行 always 块.
<=
指非阻塞赋值.
上述代码中的 always
块也可以分开写 (推荐):
1 |
|
test bench 代码为:
1 |
|
基础语法与应用讲解1-位操作
设计让 8 个 LED 灯以每个 0.5s 的速率循环闪烁.
1 |
|
test bench 的代码为:
1 |
|
参数化设计
这里用到 parameter
关键字, 其和 C 语言中的 #define
类似. 用于定义一个常量.
使用参数化设计实现模块的重用
给模块定义一个参数, 然后在实例化模块时通过修改参数来达到不同的效果.
修改参数用 defparam
.
如:
1 |
|
或者直接将参数修改写入例化中:
1 |
|