Verilog HDL 简介与基本语法 2

FPGA学习 阶段一:Verilog HDL 简介与基本语法 2

1. 模块及其结构

Verilog的基本设计单元是“模块”block,一部分描述接口,另一部分描述逻辑功能,比如:

1
2
3
4
5
6
7
8
module block(a,b,c,d);
input a,b; //描述接口
output c,d;

assign c = a | b; //描述逻辑功能
assign d = a & b;

endmodule
以上是一个简单的模块,首先描述了四个接口a,b,c,d module 之后利用assign语句描述了接口之间的逻辑关系,即c等于a或b,d等于a且b module 这样一个完整的模块就定义好了 ### 1.1 功能定义 功能定义部分有三种方法,每个逻辑功能定义之间是并行的关系,即每个功能没有先后顺序,都是同时进行,但每个功能内部中是按照顺序执行的 1. assign语句 assign语句通常用来描述组合逻辑,即可以完整用逻辑门结构来表示的逻辑 2. always语句 always语句不但能描述组合逻辑,还可以用来描述时序逻辑 3. 例化实例元件 如:and #2 u1(q,a,b)定义了一个输入为a和b,输出为q的与门 ### 1.2 模块的调用 在模块调用时,信号通过模块端口在模块之间传递
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
module seg_led_static_top (
input sys_clk , // 系统时钟
input sys_rst_n, // 系统复位信号(低有效)

output [5:0] sel , // 数码管位选
output [7:0] seg_led // 数码管段选

);

//parameter define
parameter TIME_SHOW = 25'd25000_000; // 数码管变化的时间间隔0.5s

//wire define
wire add_flag; // 数码管变化的通知信号

//调用模块

//每隔0.5s产生一个时钟周期的脉冲信号
time_count #( //列出调用的模块名
.MAX_NUM(TIME_SHOW) //列出模块中的参数并重命名
) u_time_count(
.clk (sys_clk ), //将底层信号传到上层格式: .被调用模块信号名 (顶层信号名)
.rst_n (sys_rst_n),
.flag (add_flag )
);
//每当脉冲信号到达时,使数码管显示的数值加1
seg_led_static u_seg_led_static (
.clk (sys_clk ),
.rst_n (sys_rst_n),

.add_flag (add_flag ),
.sel (sel ),
.seg_led (seg_led )
);

//被调用模块
module time_count(
input clk , // 时钟信号
input rst_n , // 复位信号

output reg flag // 一个时钟周期的脉冲信号
);

//parameter define
parameter MAX_NUM = 25000_000; // 计数器最大计数值

//reg define
reg [24:0] cnt; // 时钟分频计数器
利用逻辑结构框图,上述模块之间的调用以及信号连接可以表示如下: 框图连线 ## 2. 结构语句 结构语句有initialalways ### 2.1 initial语句 initial语句在模块中只执行一次 它常用于test bench(测试文件)的编写,用来产生仿真测试信号(激励信号),或者用于对储存器变量赋初值:
1
2
3
4
5
6
7
8
9
10
11
12
13
initial
begin
sys_clk <=1'b0;
sys_rst_n <=1'b0;
touch_key <=1'b0; //全部拉低
#20 sys_rst_n <=1'b1; //20个单位时间(20ns)后拉高电平
#10 touch_key <=1'b1;
#30 touch_key <=1'b0;
#110 touch_key <=1'b1;
#30 touch_key <=1'b0;
end

always #10 sys_clk <= ~sys_clk //每10ns对sys_clk取反一次,也就是说产生周期为20ns,频率为50Mhz的时钟
上面的例子画出波形图如下: 8Ky5gs.png 可以看到,实际仿真的波形与我们设定的完全相符,起始状态全部为低电平,sys_clk以周期为20ns为单位不断进行电平反转,20ns后sys_rst_n被拉高,再经过10ns后touch_key被拉高.... ### 2.2 always语句 - always语句一直不停的执行,如上的sys_clk信号,并不是取反一次就结束了,而是不停地进行每10ns取反一次。 - always后紧跟着的是触发条件,触发条件可以是单个信号也可以是多个信号,多个信号中间用or连接,连接而组成的列表称为“敏感列表” - 通常使用的posedge是指上升沿触发,negedge是指下降沿触发 - #### 2.2.1 描述时序逻辑电路 时序逻辑电路中,任一时刻的输出不仅取决于当时的输入信号,而且还取决于电路原来的状态。或者说还与以前的输入有关,因此时序逻辑必须具备记忆功能
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
31
32
33
module flow_led(
input sys_clk , //系统时钟
input sys_rst_n, //系统复位,低电平有效

output reg [3:0] led //4个LED灯
);

//reg define
reg [23:0] counter;

//计数器对系统时钟计数,计时0.2秒
always @(posedge sys_clk or negedge sys_rst_n)
begin
if (!sys_rst_n)
counter <= 24'd0;
else if (counter < 24'd1000_0000)
counter <= counter + 1'b1;
else
counter <= 24'd0;
end

//通过移位寄存器控制IO口的高低电平,从而改变LED的显示状态
always @(posedge sys_clk or negedge sys_rst_n)
begin
if (!sys_rst_n)
led <= 4'b0001;
else if(counter == 24'd1000_0000)
led[3:0] <= {led[2:0],led[3]};
else
led <= led;
end

endmodule
上面的例子很好理解: 如果遇到sys_clk上升沿或sys_rst_n下降沿则进入always语句,然后进行always中的条件判断等等...综合为逻辑框图,可用下图来表示: 8KgW4O.png #### 2.2.2 组合逻辑电路 组合逻辑电路中,任意时刻的输出仅仅取决于该时刻的输入,与电路原来的状态无关 比如,我们可以用always表示一组逻辑门电路:
1
2
3
4
5
always @(a or b or c or d or e or f or g or h or p or m)
begin
out1 = a ? (b + c) : (d + e);
out2 = f ? (g + h) : (p + m);
end
这个逻辑是: 判断a是否为1?若a为1,则out1 = b + c;反之out1 = d + e; 判断f是否为1?若f为1,则out1 = g + h;反之out1 = p + m;

如果组合逻辑快语句的输入变量很多,那么编写敏感列表会很繁琐且容易出错,这时候可以用*来表示对后面语块中所有输入变量的变化都是敏感的

1
2
3
4
5
always @( * )
begin
out1 = a ? (b + c) : (d + e);
out2 = f ? (g + h) : (p + m);
end
### 2.3 赋值语句 在描述组合逻辑的 always 块中用阻塞赋值 = ,综合成组合逻辑的电路结构; 这种电路结构只与输入电平的变化有关系。 在描述时序逻辑的 always 块中用非阻塞赋值 <=,综合成时序逻辑的电路结构; 这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化。

2.3.1 阻塞赋值

阻塞赋值和其他常用语言中赋值的效果相同,即计算RHS(Right Hand Side)并更新LHS(Left Hand Side),也就是说,在同一个always中,后面的赋值语句是在前一句赋值结束之后才开始赋值的,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
a = 1;
b = 2;
c = 3;
end
else
begin
a = 0;
b = a;
c = b;
end
end
显然,执行完always后a,b,c都为0,这是因为阻塞时赋值先计算了a = 0,然后再处理了b = a,以此类推,b和c的值都和a相同了 8lTTvq.png 那么有没有办法让b和c赋第一个begin中的值呢? #### 2.3.2 非阻塞赋值 非阻塞赋值可以看做两个步骤: - 赋值开始的时候计算RHS - 赋值结束的时候更新LHS 通俗来讲,所谓非阻塞是指在计算一个非阻塞赋值的RHS以及更新LHS期间,允许其他非阻塞赋值语句同时计算RHS和更新LHS,也就是说,不同非阻塞赋值语句之间是同时进行的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
a <= 1;
b <= 2;
c <= 3;
end
else
begin
a <= 0;
b <= a;
c <= b;
end
end
执行以后,b=1,c=2,a=0,可见非阻塞赋值确实是并行的 8lTHK0.png 注意: 在同一个always块中不要既用非阻塞赋值又用阻塞赋值 不允许在多个always块中对同一个变量进行赋值!

作者

Hank.Gan

发布于

2020-03-14

更新于

2021-08-10

许可协议

评论