Verilog简介
Verilog是一种硬件描述语言(HDL),可以转化成现实中的电路结构,可以实现并行执行,不同于用于变成单片机的C语言,单片机只是执行程序,并不会改变硬件,只能实现串行执行。这也使得FPGA获得了更快的执行速度、更高的上手门槛和更高昂的价格。
目前有三种主流的硬件描述语言,Verilog,VHDL,SystemVerilog,其中SystemVerilog集两者之大成,支持了更多的功能,一般用于项目中,学习的时候可以先学习Verilog和VHDL,Verilog语法规则更像C语言,更加容易上手,而VHDL更像Ada,语法严谨。在正点原子的教程中,先学习Verilog。但是,我们需要熟悉并行执行的思想,摆脱C语言串行执行的特性,这样才能做到得心应手。
Verilog可以用于描述电路的结构、功能和行为,并通过EDA工具转化为实际电路,最后再根据FPGA的结构和资源映射到使用的FPGA中,生成比特流文件,进行烧录。
Verilog语法
基础知识
逻辑状态
逻辑状态 | 表示状态 |
0 | 低电平,一般为GND |
1 | 高电平,一般为VCC |
X | 未知状态,如同时连接高低 |
Z | 高阻态,可以被上拉或下拉 |
数据表示
再Verilog中,数据的表示需要考虑位数和进制,表示方法如下
4'b1011 // 四位二进制1011
4'd10 // 四位十进制1010
其中第一个数是位宽(转换到二进制的位数),后面的字母表示进制如二进制b
,十进制d
, 十六进制h
,最后面就是数据了,如果前面不加限定,Verilog语言默认为32'd
。
基础语法
标识符
标识符就相当于变量名,总体的命名规则和C语言类似,这里就不过多赘述了。
可以采用下划线分隔的方法如cpu_clk
等。
变量
Verilog的变量有三种类型,寄存器,线网和参数。
名称 | 关键字 | 使用方法 | 特点 |
寄存器 |
|
| 只能再always和initial赋值,初值为 |
线网 |
|
| 表征物理连接,值受到驱动它的门电路、assign语句等的影响。默认为 |
参数 |
|
| 常量,如果程序多处用到该常量,可以统一常量。赋值时必须使用常量赋值。 |
运算符
大部分和C语言一致,下面只列出不同
/
在Verilog中,除法只保留整数。~
按位取反&
按位与|
按位或^
按位异或&&
逻辑与||
逻辑或- 位运算时,左移位宽增加,右移位宽不变(舍弃末尾)
{a,b}
拼接,将ab拼接为一个
这里需要注意这些运算的优先级
程序框架
模块
模块是Verilog和设计的最基本单元。模块有一部分描述接口,一部分描述功能。例如
module block(a, b, c, d); // define the interfaces of the module
// set the mode of the interfaces
input a, b;
output c, d;
// description of the function of the module
assign c = a | b;
assign d = a & b;
endmodule // any module must include
模块分为可综合和不可综合两种,可综合意味着通过模块可以生成电路,而上一课的仿真模块则是不可综合的。
module flow led(
input sys_clk, // System Clock
input sys_rst_n, // Reset Signal, Low
output reg[3:0] led );
// reg define
reg [23:0]counter;
// counter will count and
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'bl;
else
counter < 24'd0;
end
//通过移位寄存器控制I0口的高低电平,从而改变1ED的显示状态
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
一般描述功能会用到三种方法
- assign 描述组合逻辑
- always 描述时序或组合逻辑
- 例化实例元件
注意,这三种方法描述的是并行执行的,但是在单个always中的代码是串行的
模块调用
`timescale 1ns/1ns // set the unit and accuracy by `timescale unit/accuracy
module TB_LED ();
reg key;
wire LED;
initial begin //run at the intitialize
// generate an alternate changing signal
key <= 1'b1;
#200
end
// connnect the test signal to the module
LED u_led(
.Key (key),
.LED (LED)
);
endmodule
上一节课的仿真程序其实就是模块例化,第12行例化了一个LED模块。并把顶层模块的key和LED信号传入到例化模块,除了输入,还可以传递输出和参数
module seg_led_sstatic_top ();
time_count #(
.MAX_NUM (TIME_SHOW) // parameter
) u_time_count (
.clk (sys_clk), // input
.flag (add_flag) // output
);
endmodule
Initial & Always
这两个东西有点像arduino里面的setup和loop,一个初始化执行,一个重复执行。Initial一般用在仿真文件里,它可以在初始化的时候产生测试信号。
initial begin
button <= 1'b0;
#100
button <= 1'b1;
end
always在比那些的时候必须给一个时钟延迟,也就是执行的周期
always #10
sys_clk <= ~sys_clk; // generate a clock
这是通过认为设定周期来实现功能。而always语句还能够通过上升沿和下降沿触发。
always @(posedge sys_clk or negedge sys_rst_n) begin
// your code
end
always @(sys_clk or sys_rst_n) begin
// your code
end
always @( * ) begin
// your code
end
这种边沿触发就可以实现时序电路,而由or连接的触发调节列表被称为敏感列表。
下面一种就是电平触发的, 前面不加posedge, * 表示对所有输入变量敏感。这种电平触发可以用于构建组合逻辑。
高级语法
赋值语句
在Verilog中,分为阻塞赋值和非阻塞赋值
- 阻塞赋值
前面的赋值语句会阻塞后面的,也就是说前面还没有执行完,后面就不可以执行。
- 非阻塞赋值
相当于把多个赋值语句并行执行,先把右边的值算出,然后统一赋给左边
//阻塞赋值
a = b;
//非阻塞赋值
a <= b;
简单来说,阻塞赋值用于组合逻辑,非阻塞逻辑用于时序逻辑,因为时序逻辑涉及到电路的执行顺序
注意:
- 在同一个always中不要混用两种赋值方法
- 不能再多个always中赋值同一个变量
条件语句
if-else
在verilog中没有大括号,需要使用begin-end引导。
if-else
语句必须放在由always或initial引导的程序块中。
如果条件为1则为真 0,x,z状态均为假。
其他语法与c语言相同。
case
case语句有些不同。
case (num)
4'h0 : led <= 8'b0011_1010;
4'h1 : led <= 8'b0101_1100;
...
default : led <= 8'b1111_1111;
endcase
直接去掉了switch,使用case引导,下面直接列举数据。
还有两种变体
casez
不考虑条件中的高阻值,也就是只要1, 0, x相同就进入条件casex
不考虑条件中的高阻值和未知数x。
状态机
状态机应该是在数电中学过的,就是用来描述时序电路功能的有限状态机,它可以表示电路的状态之间的转换关系,比如转换的条件和转换过程中的输出,下面以电子密码锁来举例
每一个圆圈代表电路的一个状态,箭头表示状态之间的转换。这种可以在多个状态之间转化的时序电路被称为有限状态机。其基本结构图如下
下面这种状态机被称为Mealy电路,它的输出由输入和当前状态决定,输入和上一状态经过组合逻辑F产生新的状态并在clk边沿是进入寄存器,最后通过组合逻辑G输出
如果输出与输入无关,也就是去掉上面的蓝线,或者说输入改变状态,然后状态经由组合逻辑输出,输入不直接影响输出。这种状态机被称为Moore状态机。
Verilog状态机设计
状态机的设计分为以下四个步骤
- 状态空间定义
- 状态跳转
- 下一状态判断
- 各个状态的动作
这四个步骤分别对应上图中的圆圈、箭头、跳转条件(箭头旁的文字)和最后的解锁动作。
parameter STATEA = 4'b0001;
parameter STATEB = 4'b0010;
parameter STATEC = 4'b0100;
parameter STATED = 4'b1000;
reg[3:0] current_state;
reg[3:0] next_state;
这段代码完成了状态空间的定义,首先通过独热编码对四个状态进行编码,接下来定义了两个状态寄存器,分别存储当前状态和下一状态。
always @(posedge clk or negedge rst_n)
if(!rst_n)
current_state <= STATEA;
else
current_state <= next_state;
end
下一状态判断就是一个组合逻辑,输入此状态和其他输入,输出下一状态
always @(current_state or input_signals) begin
case (current_state)
STATEA : begin
if(signal1)
next_state = STATEB;
else
next_state = STATEC;
end
STATEB : begin
if(signal2)
next_state = STATEC;
else
next_state = STATEB;
end
default : next_state = current_state;
endcase
end
这段代码就可以通过当前状态和输入产生下一状态。
最后就是根据当前状态产生输出
assign out = (current_state == STATEA) ? behavior1 : behavior2;
always @(current_state) begin
if(current_state == STATEA)
out = out1;
else
out = out2;
end
有两种方式可以实现,一种是简单逻辑用assign加三目运算。复杂逻辑使用always加条件语句。
这四个步骤是并行执行的,并不需要规定顺序
三段式状态机
这种状态机在输出上加一个寄存器。这个寄存器主要实现以下几种功能。
- 对输出的竞争冒险现象进行滤波。
- 方便时序计算与约束(目前了解即可,后面会学)
- 对齐输出信号,方便总线传输(在clk来的时候输出)
总结
通过本节学习,熟悉Verilog基本语法,掌握以下知识点。
- Verilog的基础语法
- Verilog程序框架
- Verilog状态机的设计