代码 | 备注 |
---|---|
reg/wire | 设计中所有的信号类型定义,只有 reg 和 wire 两种(一般来说,用always设计代码都是 reg,其它的都是用 wire) |
parameter | 设计代码中所有的位宽、长度、状态机命名等,建议都用参数表示,阅读方便并且容易修改(参数化定义,和C语言里面的宏定义#define类似) |
assign/always | 程序块主要部分,至简设计法对always使用有严格规范,语法格式见下方,详情请关注后续有关组合逻辑和时序逻辑的博客 |
if else/case | always里面的语句,使用if else 和 case 两种方法用来做选择判断,可以完成全部设计 |
算术运算符(+,-,*,/,%) | 可以直接综合出相对应的电路。但除法和求余运算的电路面积一般比较大,不建议直接使用除法和求余 |
赋值运算符(=,<=) | 时序逻辑用"<=“,组合逻辑用”=";其他情况不存在 |
关系运算符(==,!=,>,<,>=,<=) | |
逻辑运算符(&&,!,II) | |
位运算符(~,^,&,I) | |
移位运算符(<<,>>) | |
拼接运算符({ }) |
信号类型详解:Verilog HDL信号类型(reg&&wire)
程序语句详解:Verilog HDL程序语句(assign&&always)
数字表示详解:Verilog HDL数字表示(进制/X态/Y态)
按位逻辑运算符:Verilog HDL按位逻辑运算符及逻辑运算符
//组合逻辑格式如下:
always@(*) begin
//代码语句
end
//或者可以直接使用assign
//时序逻辑格式如下:
always@(posedge clk or negedgerst_n) begin
if (rst_n==1'b0)begin
//代码语句
end
else begin
//代码语句
end
end
/*
时序逻辑中,敏感列表一定是clk的上升沿和复位的下降沿、最开始
必须判断复位。
*/
模块(module)是 Verilog 的基本描述单位,是用于描述某个设计的功能或结构,是与其他模块通信的外部端口。
模块在概念上可以等同于一个器件,如通用器件(与门、三态门等)或通用宏单元(计数器、ALU、CPU等)。因此一个模块可以在另一个模块中调用,一个电路设计可由多个模块组合而成。在进行大型数字电路的设计时,可以将其分割成大小不一的小模块,每个小模块实现特定的功能,最后通过由顶层模块调用子模块的方式来实现整体功能。(可类比于C语言,main()函数为顶层模块,自己写的函数就相当于一个个的功能模块)
模块有五个主要部分:端口定义、参数定义(选)、I/O说明、内部信号声明、功能定义。
模块总是以关键词module开始,以关键词endmodule结尾,模块的一般语法结构如下所示:
//端口定义:
module module_name(
clk, // 端口1,时钟
rst_n, // 端口2,复位
dout // 其他信号,如dout
);
//参数定义:(可选,不必须)
parameter DATA_W = 8;
// I/O说明:
input clk; // 输入信号定义
input rst_n; // 输入信号定义
output [DATA_W-1:0] dout; // 输出信号定义
//信号说明:运用reg/wire定义
reg [DATA_W-1:0] dout; //信号类型
reg signal1; //信号类型
//...........以下为描述功能部分.............
//功能定义:
//1.组合逻辑写法:
always@(*) begin
//代码语句
end
//2.时序逻辑写法:
always@(posedge clk or negedgerst_n) begin
if (rst_n==1'b0)begin
//代码语句
end
else begin
//代码语句
end
end
//结束
endmodule
上述代码中的module_name类似于C语言中的函数名,大家可以随意取名。我们假如起名为MCU,那么就可以把这个模块想象成一块MCU。那么这块MCU有着许多的管脚。其中有的管脚叫做clk,也就是时钟管脚;有的叫做rst_n,也就是复位管脚,如下图所示:
写法:parameter D_W = 8;
此项多用于后续大型设计中有着许多的管脚,管脚的信号位宽不一样,一个个修改的话过于麻烦,类似C语言宏定义。
在I/O说明中,模块的端口可以是输入端口、输出端口或者双向端口,说明格式如下:
输入端口:
input [信号位宽-1:0] 端口名1;
input [信号位宽-1:0] 端口名2;
…;
输入端口:
output [信号位宽-1:0] 端口名1;
output [信号位宽-1:0] 端口名2;
…;
双向端口:
inout [信号位宽-1:0] 端口名1;
inout [信号位宽-1:0] 端口名2;
…;
信号位宽:为什么要写成信号位宽-1的形式呢,比如信号位宽为8,那么信号位为0-7。假如以刚才的MCU举例的话,我们输入一个信号位宽为8,那么就代表着需要MCU的8个管脚来接收这个信号。如下图:
信号类型一共有两种,一个是reg(寄存器型),另一个是wire(线型)。写法见上方模块结构内部。
一个模块能够在另外一个模块中被引用,这样就建立了描述的层次。模块实例化语句形式如下:
module_nameinstance_name(port_associations);
//举例如下:
module and(
C,
A,
B
);
input A,B;
output C;
//其余部分省略
endmodule
//在下面的“and_2”模块中对上一模块“and”进行例化,有两种方式:
module and_2(xxxxxxxx);
.......
//方式一:实例化时采用位置关联,T3对应输出端口C,A对应A,B对应B
and A1 (T3,A,B);
//方式二:实例化时采用名字关联,例如.C是and器件的端口,其与信号T3相连
and(
.C(T3),
.A(A),
.B(B)
);
在实例化中可能有些管脚没有用到,可在映射中采用空白处理。
//例:
DFF d1(
.Q(QS),
.Qbar(),
.Data(D),
.Preset(),
.Clock(CK)
);
//输入管脚悬空端口的输入为高阻态
说明:
1.建议在例化的端口映射中采用名字关联,这样当被调用的模块管脚改变时不易出错。
2.模块例化类似于C语言中的实参和形参。举个例子,我们先定义一个模块为USB,模块内部含有三个管脚,在一个单片机的扩展板上面有三个USB接口,我们假设单片机扩展板为模块MCU,那么我们在使用MCU时,可以把三个USB模块命名为USB1,USB2,USB3,在使用时需要在MCU模块中例化三个USB。
3.还是以USB模块为例,假如例化时有.C©,那么.C()代表原来module USB时里面的C管脚,括号里面的C代表连接的其他部位名字为C的管脚。
Tip:如果您在阅读的过程中发现任何不妥之处欢迎前来指正!