verilog语法简介
代码示例
Verilog语言的一个特点为模块化,一般一个.v文件为一个模块。
1 | // flowing_light.v |
1 | // flowing_light_tb.v |
模块端口设置
模块化的特点是将系统抽象成输入和输出的模型。端口定义了输入和输出。
1 | module flowing_light( |
上述代码的括号内部分定义了该模块的输入和输出。端口的设置格式为 <类型>[端口位宽]<端口名称>。各个端口的定义语句之间由逗号隔开,括号后面加分号。
Verilog语句块
always语句块
相当于 while ,@() 内是循环条件。
1 | always @(posedge clock) begin |
这段代码中 posedge 指的是时钟上升沿;如果想要时钟下降沿,可以使用 negedge;如果对于一个时钟,既不加 posedge 也不加 negedge,则上升下降沿都能触发。比如说这段代码:
1 | always @(OpCode) begin |
当 OpCode 发生变化的时候就能触发。
代码中的 begin 和 end 相当于C语言中的大括号。
1 | always #(PERIOD*2) clock=!clock; |
则表示每经过 PERIOD*2 的时间,clok 翻转一次。时间的单位也在.v文件中写出,如
1 |
则表示时间的单位为1ns,分辨率为1ps。
initial语句块
initial语句在程序的最开始执行,且仅执行一次。在执行initial时,always不会执行。
1 | initial begin |
如这里的initial语句的例子,就表示先设置 a,b,ci 的值,然后过100个时钟周期再设置 a,b 的值,再过100个时钟周期再设置……
case和casex语句块
case 语法大致如下,和C语言中的switch类似:
1 | case (OpCode) |
casex 与 case 的区别在于,case 语句是精确匹配输入信号值的,如果输入信号与 case 语句中的任何一个值不匹配,则不会执行任何语句。而 casex 语句则是按位通配符匹配输入信号值的。它允许使用 x(表示未知值)、z(表示高阻值)和 *(表示通配符)等特殊字符,从而可以匹配更多的输入信号模式。
感觉ChatGPT说的地方好像有点问题,待会再看看语法书。
Verilog数据类型
寄存器(register)类型
1 | reg [23:0] cnt_reg; |
就是寄存器类型,表示抽象的数据存储单元。除了 reg,还可以使用 integer,real 进行声明。上方的例子定义了24位的 cnt_reg 和8位的 light_reg。寄存器类型可以初始化。
线网(net)类型
如
1 | wire [7:0] led; |
定义了一个8位的 wire 类型。在电路图中wire作用相当于导线。除了 wire,还可以使用 tri,wand 来定义线网类型。
参数数据类型
参数类型就相当于常量常量,如
1 | parameter PERIOD = 10; |
就定义了一个值为10的常量 PERIOD。
数字格式
如 24'h000001,第一个数字表示数字在二进制下的位数,' 用于分隔,h 表示后面的数字用16进制表示(b表示二进制,d表示十进制)。同时为方便阅读,后面的数字之间可以加下划线而不影响结果,如该数字还可以写成 24'h00_00_01
模块间引用
1 | module flowing_light_tb( |
这里展示了 flowing_light_tb 模块引用 flowing_light 模块的方式。比如 .clock(clock) 中的 .clock 表示要使用 flowing_light 中 clock 的端口;而括号中 clock 则表示将 flowing_light_tb 中的寄存器变量 clock 接上。
同一个模块可以被多次引用,但是每次实例化的名字要不同
1 | adder_1bit a1(.a(a[0]),.b(b[0]),.ci(ci),.s(s[0]),.co(ct[0])), |
如这段代码就将adder_1bit用了4次,每次实例化的给的名称分别为 a1,a2,a3,a4。(这里类似于cpp中创建了一个类,然后在另一个类中使用这个类)
