Verilog-B站教程-Notes

B 站教程1

B 站教程2

FPGA 设计流程

组合逻辑与时序逻辑

组合逻辑
组合逻辑是指没有时钟信号触发的逻辑电路,其输出只取决于输入信号。组合逻辑只能实现纯粹的逻辑功能,例如加法器、比较器和选择器等,没有记忆功能。组合逻辑的输出仅仅取决于当前的输入状态,与之前的输入状态和时钟信号无关。

时序逻辑
时序逻辑是指由时钟信号触发的逻辑电路,其输出不仅仅取决于输入信号,还取决于之前的状态。时序逻辑通常用于实现计数器、存储器和状态机等具有记忆功能的电路。时序逻辑的输出状态不仅仅取决于当前的输入状态,还受到先前的输入状态和时钟信号的影响。

时序逻辑与组合逻辑的对比

时序逻辑和组合逻辑的主要区别在于它们的实现方式和功能。时序逻辑通过使用时钟信号来同步电路的输入和输出,可以实现记忆功能。组合逻辑没有时钟信号,只能实现纯粹的逻辑功能,没有记忆功能。此外,时序逻辑的设计更加复杂,需要考虑时序的影响,而组合逻辑的设计更加简单,只需要考虑逻辑关系。在实际应用中,时序逻辑和组合逻辑通常会结合使用,以实现更为复杂的电路功能。

时序逻辑更胜任大规模的逻辑电路.

也就是说, 时序逻辑中, 多了时钟信号和寄存器.

如这里的时序逻辑图, 当与非门产生结果时, 其会传入寄存器的输入端, 但只有当时钟信号为上升沿时, 结果才会从寄存器的输出端传出.

一个实现异或门的例子

1
2
3
4
5
6
7
8
9
10
11
`timescale 1ns/1ps

module vlg_design1(
input a,b,
output reg z
);

always @(a or b) begin
z = z ^ b;
end
endmodule

在Verilog中,当在模块中声明输入和输出端口时,如果没有明确指定端口类型,则其默认类型为wire类型 (如这里的 a, b).

组合逻辑和时序逻辑中的赋值

使用assign关键字来创建组合逻辑的赋值语句:

如:

1
assign out = (in1 & in2) | (~in3);

使用非阻塞赋值 (<=) 和 阻塞赋值 (=) 来创建时序逻辑的赋值语句:

如:

1
2
3
4
always @(posedge clk) begin
q <= d;
q = d;
end

这里的 posedge 是一个关键字,表示时钟信号的上升沿.

同样, 表示下降沿的关键字为 negedge.

区分组合逻辑和时序逻辑

在Verilog中,可以使用 always 关键字来表示时序逻辑,而组合逻辑则可以在模块的顶层代码中使用组合逻辑赋值语句来实现。下面是一些区分时序逻辑和组合逻辑的方法:

always 关键字常用于表示时序逻辑,用于在时钟边沿触发某些行为, 如:

1
2
3
4
5
6
7
always @(posedge clk) begin
if (reset) begin
q <= 0;
end else begin
q <= d;
end
end

(这部分代码的意思为, 当时钟信号处于上升沿时, 进入 always 块, 如果设置了 reset, 那么执行 q<=0; 不然执行 q<=d)

之外的应该就是组合逻辑, 且用 assign 来赋值.

二选一多路器实现

1
2
3
4
5
6
module mux2(
input a, b, sel,
output out
);
assign out = (sel == 1) ? a : b;
endmodule

也就是说, 如果 sel == 1, 那么输出的值等于 a 端的输入, 如果 sel == 0, 那么输出的值等于 b 端的输入.

仿真和测试

在 vivado 中, 设计时, 创建 design 文件, 仿真时, 创建 simulation 文件.

关于:

1
`timescale 1ns/1ps

的作用, 其影响 #1 这类延时操作, 如这里就是延时 1ns, 因为精度为 1ps, 因此也可以写成 #1.001.

测试文件一般不需要端口.

测试一个模块, 也就是将一个模块例化. 例化和面向对象创建实例类似, 用模块名当作类型, 再加上一个例化名称.

注意, 例化时, 类型需匹配 (也就是和模块定义时相同, 但实际操作时发现好像不需要匹配).

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
module mux_2_test();
reg a, b, sel;
wire out;

mux_2 mux_2_test1(
.a (a),
.b (b),
.sel(sel),
.out(out),
);

initial begin
a = 0, b = 0, sel = 0;
#200;
a = 0, b = 0, sel = 1;
#200;
a = 0, b = 1, sel = 0;
#200;
a = 0, b = 1, sel = 1;
#200;
a = 1, b = 0, sel = 0;
#200;
a = 1, b = 0, sel = 1;
#200;
a = 1, b = 1, sel = 0;
#200;
a = 1, b = 1, sel = 1;
#200;
end
endmodule

有时, vivado 默认只会跑 1000ns, 可以让其继续跑

也可以手动停止, 用 $stop

布局布线 (run implementation) 之后, 就可以时序仿真 (Time Simulation).

性能分析时, 就需要借助时序仿真.

组合逻辑38译码器实现与相关语法基础

38 译码器, 就是将 3 位的输入信号, 转换为 8 位的输出信号. 其作用就是, 用 3 个输入信号, 来决定 8 个输出端口中哪一个来输出.

图像为:

其输入和输出为:

也就是说, 用 3 个输入信号, 来决定 8 个输出端口中哪一个来输出.

在设计时, 并不需要设置 timescale, 这个在测试仿真时设置就行.

设计代码如:

1
2
3
4
5
6
7
8
9
10
11
module decoder_3_8(
input a,b,c,
output reg [7:0] out
);
// 此时也可以写为
// always @(a,b,c)
always @(*)
case ({a, b, c})
3'b000: out =

endmodule

数字之间可以用 _ (下划线) 来连接.

注意 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
2
3
module mux_2( input a,b,sel, output out );
assign out = ( sel == 1 ) ? a : b;
endmodule

这段代码中, 并没有给 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
2
3
4
5
6
7
8
9
10
11
module d_ff (
input clk,
input d,
output reg q
);

always @(posedge clk) begin
q <= d;
end

endmodule

计数器

假设为 4 位计数器. (4 位是指输入端口 D 和输出端口 Q 都是 4 位的)

这里的 $\oplus$ 表示的是一个加法器.

其一端一直输入 1, 另一端输入输出的结果, 也就有:

1
a = a + 1

的效果.

时钟一般是比较稳定的, 可以设置.

利用计数器, 使 LED 灯每 500 ms 闪烁

假设时钟频率为 $50MHz$, 则周期为 $20ns$.

若要每 500ms 闪烁, 则需要经过 $500ms/20ns$ 个周期, 即 25000000 个周期.

也就是让计数器来记录 25000000 个周期.

代码如下:
(这里的复位接口的作用是让 counter 复位)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module(
input clk, reset,
output led
)
reg[24:0] counter;
reg led;

always @ (posedge clk or negedge reset)
if (!Reset_n) begin
counter <= 0;
led <= 0;
end
else if (counter == 25000000) begin
led <= !led;
counter <= 0;
end
else
counter <= counter + 1'd1;
endmodule

代码解释:
clk 信号的上升沿或者 reset 信号的下降沿时, 执行 always 块.

<= 指非阻塞赋值.

上述代码中的 always 块也可以分开写 (推荐):

1
2
3
4
5
6
7
8
9
10
11
12
13
always @ (posedge clk or negedge reset) 
if (!reset)
counter <= 0;
else if (counter == 25000000 - 1)
counter <= 0;
else
counter <= counter + 1'd1;

always @ (posedge clk or negedge reset)
if (!reset)
led <= 0;
else if (counter == 25000000 - 1)
led <= !led;

test bench 代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
`timescale 1ns/1ns

module led_flash_tb ()

reg clk;
reg reset;
wire led;

led_flash flash(
.clk(clk);
.reset(reset);
.led(led);
);

initial clk = 1;
always #10 clk = !clk;

initial begin
reset = 0
# 200
reset = 1
# 200000000
end

endmodule

基础语法与应用讲解1-位操作

设计让 8 个 LED 灯以每个 0.5s 的速率循环闪烁.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module led_run(
input clk, reset,
output[7:0] led
)

reg[24:0] counter;
reg led;

always @ (posedge clk or negedge reset)
if (!reset)
counter <= 0;
else if (counter == 25000000-1)
counter <= 0;
else
counter <= counter + 1'b1;

always @ (posedge clk or negedge reset)
if (!reset)
led <= 8'b0000_0001;
else if (counter == 25000000-1) begin
if (led == 8'b1000_0000)
led <= 8'b0000_0001;
else
led <= led << 1;
end
endmodule

test bench 的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
`timescale 1ns/1ns

module led_run_tb ()
reg clk;
reg reset;
wire[7:0] led;

led_run led_run(
.clk(clk),
.reset(reset),
.led(led)
)

initial clk = 1;

always #10 clk = !clk;

initial begin
reset = 0;
# 201
reset = 1;
# 400000000
end


endmodule

参数化设计

这里用到 parameter 关键字, 其和 C 语言中的 #define 类似. 用于定义一个常量.

使用参数化设计实现模块的重用

给模块定义一个参数, 然后在实例化模块时通过修改参数来达到不同的效果.

修改参数用 defparam.

如:

1
2
3
4
5
6
7
8
9
defparam     u_ram_4x4.MASK = 7 ;
ram_4x4 u_ram_4x4
(
.CLK (clk),
.A (a[4-1:0]),
.D (d),
.EN (en),
.WR (wr), //1 for write and 0 for read
.Q (q) );

或者直接将参数修改写入例化中:

1
2
3
4
5
6
7
8
9
ram_4x4    u_ram_4x4
(
.MASK (7),
.CLK (clk),
.A (a[4-1:0]),
.D (d),
.EN (en),
.WR (wr), //1 for write and 0 for read
.Q (q) );

从计数器到可控线性序列机


Verilog-B站教程-Notes
http://example.com/2023/03/23/Verilog-B站教程-Notes/
作者
Jie
发布于
2023年3月23日
许可协议