目录
1简介
HDL (hardware description language)
是硬件描述语言,用于描述数字电路的结构和功能
verilog在进行编译下载之后,会生成电路,且其是并行运行的
c语言是软件编程语言,是存储器中的一组指令,在这个过程中,单片机需要译码,执行,这个过程是自上而下运行的
这个过程是FPGA同单片机/CPU的区别,由于这个特征,fpga的处理速度会快很多(同一环境)
GPIO(通用输入输出 general purpose input /output)是通用的数字输入输出接口,可以通过程序控制来读取或控制外部设备
2逻辑值
1代表高电平 表示接至电路激励vcc
0代表低电平 表示接地
X代表未知 既可以是0也可以是1
Z表示高阻态,表示没有激励,或者是悬空状态
3标识符
标识符可以是数字 字母 符号和下划线
标识符的第一个字符必须是字母或者下划线
标识符是区分大小写的
4类型
寄存器数据 线网数据 参数数据
寄存器数据 存储数据 关键字是reg 是register
默认初始值为不定值x
reg[31:0] delay_cnt; #31;0表示位宽 表示32位寄存器 高位在前 低位在后
reg类型的数据只能在always和initial中赋值
即,在其他情况下不给寄存器赋初值
注;在赋值情况always和initial中,若没赋值,其默认初始值为不定值x
若过程语句描述的是时序逻辑 即always语句中带有时钟信号,则该寄存器变量为对应的触发器
若过程语句描述的是组合逻辑 即always语句不带有时钟信号,则该寄存器变量为硬件连线
线网类型
表示结构实体之间的物理连线(门)
门的运算和距离:
与门(&)在电路中表示为串联 全1信号才为1;或门(|)在电路中表示为并联,有1则为1
包括wire类型和tri类型 最常用的是wire类型,此类型描述门与门或者是模块与模块之间的关系
特殊的门
异或门 ~(A^B):相同输出为0 不同输出为1
同或门!(A^B):相同输出为1 不同输出为0
线网类型不存储值,若没有驱动元件连接,其变量默认为高阻 即悬空(值为z)
参数类型
一个标准的参数定义是有以下四个属性的:type类型,range位宽/区间,name名字和value数值。实际操作中,除了名字和数值其他两个是可以省略的,比如下面这些参数定义方法都是可以编译的:
参数是一个常量 在verilog HDL中用paramenter定义常量,我们可以一次定义多个参数,参数与参数之间需要用逗号隔开,每个参数定义的右边必须是一个常数表达式。
参数的右边是一个可计算出来的确定值
此处编写过程中,用多行parameter来表明参数,主要目的是为了提高代码的美观程度
在后续的模块调用时,可以用参数传递的方式来改变已被定义的参数,参考
从几个简单例子聊聊Verilog的参数化设计(parameter、localparam和`define)_verilog define-CSDN博客
中的
module text(
input clk //输入时钟
);
parameter num = 100;
wire a,b,c;
assign a = num
assign b = num
assign c = num
//其中asssign是赋值语句 ,后面的内容可以是信号名,也可以是单个信号:如
//assign <net_expression> = [drive_strength] [delay] <expression of different signals or constant value>
注1 wire和reg之间的关系
wire
和reg
都是用来声明信号(或线)的关键字,但在使用上有一些区别。
-
wire
用于连接连续赋值逻辑(如组合逻辑)和模块之间的信号传输。它通常用于声明需要连接到组合逻辑电路输出端口、模块输入端口或模块之间的中间信号。wire
类型的信号在赋值时会立即更新,不需要时序控制。wire
信号的赋值可以是连续赋值方式(如assign语句)或者通过模块端口连接。 -
reg
用于声明时序逻辑(如寄存器)的信号。它通常用于声明需要存储数据的信号,如状态机的状态寄存器、计数器等。reg
类型的信号需要在时钟边沿或触发器控制下进行更新。reg
信号的赋值通常使用always
块或者时钟触发器描述,通过非阻塞赋值(<=
)实现时序逻辑的存储操作。
需要注意的是,reg
并不仅仅表示真正的寄存器(Flip-Flop),它也可以表示存储器元素或存储器阵列(如RAM、ROM等)。在时序逻辑中,reg
通常用于存储状态、计数值等。
总结起来,wire
用于连续赋值和连接信号,reg
用于时序逻辑和存储信号。根据不同的信号类型和使用场景,选择合适的关键字可以更好地描述和实现设计。
注2 assign的定义
assign定义中并不能出现一次assign中定义两个值如
assign a,b =c
而是分开写
assign a = c;
assign b = c;
不能再moudle(xx)内容中出现(xx)中没有的参数,比如:
moudle top_module(
input a,
input b,
output out);
在运行的内容中 不能人为设置一个c,如c=a+b;
注3 定义数值后的位宽设置
b为二进制,o(octor)为八进制,h(hex)为十六进制
表明位宽,如问:是否可以表达这样两个声明:4'b11和16'h1111
在Verilog中,数值的表示应该明确指定其宽度和基数,但在您提供的两个例子中都存在一些格式问题。
对于 `4'b11`,其表达的意图是定义一个4位宽的二进制数,但是它没有正确地指定所有的四位。正确的表示应该是前面填充零以达到指定的位宽,如下:
4'b0011 // 正确表示一个4位宽的二进制数
对于 `16h'1111`,这里面的基数前缀和数字之间有一个错误。在Verilog中,十六进制的前缀应为 `'h`,而不是 `h'`。并且,如果你想表示一个16位的十六进制数,你应该确保提供足够的数字来满足位宽要求。正确的表示为:
```verilog
16'h1111 // 正确表示一个16位宽的十六进制数
如果你的意图是将这个十六进制数扩展到16位宽度,那么还需要在前面填充零:
16'h1111 // 当前只是表示了四位十六进制数1111
16'h0011 // 如果想要在前面补零达到16位宽度
所以,请确保你的数值宽度和基数前缀的使用是正确的。在实际应用中,位宽应该匹配变量的声明,而数值应该提供足够的数字位来符合位宽要求。
运算逻辑
1算术运算符
注 取模等于取余数
2关系运算符
逻辑运算符同c保持一致 其中
等于关系为==
不等于关系为!=
3逻辑运算符
4条件运算符
其中 以result = (a>=b)?a;b为例 若a不小于b 则result的结果为a 否则为b
类比if else 是一个相当简洁的语句
5位运算符
其中:若前后位数不同 如a为2 b为4
则对a进行补0
如a= 10 b=1001
a与b表示为 0010 &1001 = 1011
与&
或|
6移位运算符
7拼接运算符
注意:这里的拼接是指:若a是8位[7:0]b是4位[3;0]其结果为[11;0]
8运算优先级
常用关键字
注:a|b 为a或b 其为a+b
a&b 为a与b 其为ab
模块结构
verilog中最基本的单元为模块(block)
模块由两个部分组成
1
描述输入输出接口 input a,b ;
描述输出接口 output c,d:
注:不加声明,默认类型为wire类型,若是其他类型 如是寄存器类型,就要在前面声明:reg后缀位宽
位宽
在Verilog中,位宽是人为定义的,而不是默认的。Verilog语言允许设计者明确指定信号、寄存器、端口等数据对象的位宽。
当定义一个数据对象时,需要使用方括号([ ])来指定位宽。例如,wire [7:0] data;
定义了一个8位宽的无符号信号data
。在这个例子中,位宽被设置为7到0,表示数据由7位到0位。
可以根据设计需求和数据对象的属性来选择适当的位宽。在进行算术运算或数据传输时,数据对象的位宽应该匹配或兼容,以确保正确的结果或数据传输。如果位宽不匹配,可能会导致数据截断、溢出或不准确的计算结果。
需要注意的是,如果没有明确指定位宽,Verilog编译器会根据上下文和操作数的默认规则来推断位宽。这种情况下,编译器可能会使用默认的位宽规则,但这不是一种可靠的做法。为了确保代码的可读性、可维护性和正确性,建议在设计中明确指定位宽。
设置位宽的目的是为了定义信号、寄存器、端口等数据对象的大小。位宽指的是数据对象可以表示的二进制位数,它决定了数据对象可以存储的取值范围和能够表示的精度。
设置正确的位宽非常重要,因为它直接影响到设计的功能和性能。以下是一些设置位宽的目的:
-
数据范围:位宽定义了一个信号或寄存器可以表示的最大值和最小值。通过设置适当的位宽,可以确保存储的数据在没有溢出或截断的情况下能够正确表示。
-
精度和分辨率:位宽也决定了数据对象的精度和分辨率。较大的位宽可以提供更高的精度和更小的分辨率,而较小的位宽则提供较低的精度和较大的分辨率。
-
存储和计算效率:过大的位宽会占用更多的存储空间,并且需要更多的计算资源来处理。因此,设置适当的位宽可以在满足设计需求的同时,尽量减少资源的使用,提高存储和计算的效率。
-
与其他模块的接口兼容性:在设计中,不同模块之间进行数据传输时,需要保证接口的兼容性。设置正确的位宽可以确保数据在模块之间正确传递,并防止数据丢失或截断。
需要注意的是,位宽的选择应该根据具体的设计需求和目标来确定。合理设置位宽可以充分利用硬件资源,提高设计的效率和性能。
多个模块的并行结构:
可以在一段程序中定义和实例化多个模块。每个模块都是独立的功能单元,可以在同一个文件中定义或者在不同的文件中定义。
例如,下面是一个Verilog代码示例,其中定义了三个模块moduleA、moduleB和moduleC:
module moduleA(input wire a, output wire b);
// 模块A的功能实现
endmodule
module moduleB(input wire x, output wire y);
// 模块B的功能实现
endmodule
module moduleC(input wire p, input wire q, output wire r);
// 模块C的功能实现
wire intermediate_wire;
moduleA u1(.a(p), .b(intermediate_wire));
moduleB u2(.x(q), .y(intermediate_wire));
// 其他逻辑操作或信号连接
assign r = intermediate_wire;
endmodule
// 顶层模块,实例化moduleC
module top_module(input wire p, input wire q, output wire r);
moduleC u3(.p(p), .q(q), .r(r));
endmodule
模块的调用
调用模块类似于c语言中的函数和py中的调库
如:顶层模块 moudleXX_TOP中可以包含之前说明好的模块
结构语句
1 always
always 用来描述时序逻辑
比如在计数器对系统时钟计数中
可以有
always@(posedge a 上升沿 or negedge b下降沿)begin
计数内容
end
其中:begin(\\)end充当了大括号的作用
其中 由(or)链接起来的内容称之为敏感列表,其
用于确定逻辑电路中哪些信号会导致对应的逻辑块重新计算的一种机制。敏感列表用于提供逻辑块的输入信号,并在这些输入信号发生变化时触发相关逻辑块的重新计算,以确保电路的正确性。
设置敏感列表的步骤如下:
-
确定需要被监测的信号:首先,你需要确定逻辑电路中哪些信号是需要被敏感列表所监测的。这些信号通常是与逻辑块的输入相关联的信号。
-
在逻辑块中添加敏感列表声明:在设计FPGA逻辑时,你需要在对应的逻辑块中添加敏感列表声明。这可以通过使用特定的语法或者工具来完成,在HDL(硬件描述语言)中,你可以使用敏感列表的语法来声明哪些信号是敏感的。
-
更新逻辑块的计算逻辑:一旦你确定了需要被监测的信号并在逻辑块中进行了敏感列表声明,接下来你需要更新逻辑块的计算逻辑。当敏感列表中的信号发生变化时,对应的逻辑块将会重新计算,以确保逻辑在新的输入信号下的正确性。
通过设置敏感列表,你可以确保FPGA逻辑在需要的时候进行重新计算,从而保证电路的正确性和稳定性。请注意,具体的设置方法可能会因使用的HDL、设计工具和FPGA平台而有所不同。
阻塞赋值和非阻塞赋值
即:若逻辑电路只与输入的电平变化有关 :取决于输入的顺序 采取阻塞赋值 从上往下运行赋值
若逻辑电路只与时序逻辑有关:其中包括时钟,同步信号或者需要并行实现的任务。采用非阻塞赋值
在一个always中不同时出现两种赋值方式,也不对a进行多次always赋值。