赞
踩
FPGA学习笔记(二)Verilog语法初步学习( 语法篇1)
FPGA数字电子技术复习笔记(一)verilog语法规则补充(语法篇2)
FPGA学习笔记(七)verilog的深入学习之任务与函数(语法篇3)
作为HDL语言,有两种基本的用途:系统仿真和设计实现。所有的HDL描述都可用于仿真,但并非所有的HDL描述都可综合。
参考:FPGA学习笔记—Verilog HDL 可综合语句和不可综合语句汇总
衔接之前的:FPGA学习笔记(二)Verilog初步入门
不在字符串中的空格(\b)、TAB(\t)、换行符(\n)、换页符被忽略,所以写程序时可以跨越多行书写。
起名字可以用英文字母、下划线(这两个能开头)、数字、$组成的标识符
+/- 位宽 基数符号 数值
分别对应正负、常量对应二进制数的宽度、定义后面数值的表示形式、数值左边为最高位,右边最低位。
+号一般省略
例子:3‘d2
代表数值范围:0~2的三次方即 0 ~ 8。此时数值为2(十进制)。
字符串是“ ”中间的字符序列,不允许多行书写,在表达式和赋值语句中,要转换成无符号整数,用8位的ASCII表示
例如:“ab”等价为 16’h5758,一个字符是8位,所以n个字符就定义为n*8位
这里使用串口助手要注意:
参考:ASCII码对照表
除了我们常见的reg类型,还有其他三种
integer counter;
initial
counter=-1;
类似c语言,如果把实数类型(real)传递给integer时,只能保留整部部分,实数的小数部分会被舍弃。
实数型常量的表达方式也可以用科学计数法表示
例:
23.51e2–>2351.0
3.6E-1 —> 0.36
time型变量通常用来存储仿真时间
例:
time current_time;
initial
current_time=$time; //利用系统函数获取当前仿真时间
缩位运算符是单目运算符有:&、~&、|、 ~|、^、 ~ ^或者 ^ ~
分别是与、与非、或、或非、异或、同或
主要计算过程:第一步先将操作数的第一位与第二位进行或与非运算,第二步将运算结果与第三位进行或与非运算,依次类推,直至最后一位。
例:
A=4b‘1010
~^A=1
过程:(~1)^0=0,
(~0)^1=0,
(~0)^0=1,
调用方法:
A1是名字,可以省略。括号第一个参数必须是输出变量。
例子:
例子:bufif1 B1(out,in,ctrl);
!!!!!
主要是三种:利用基础门级电路、assign(连续赋值语句,也叫数据流描述方法)、initial/always(过程块语句结构,也叫行为描述方法)
例如:
第一种:
module mux2to1(D0, D1, S, Y );
input D0, D1, S; //定义输入信号
output Y; //定义输出信号
wire Snot, A, B ; //定义内部节点信号数据类型
//下面对电路的逻辑功能进行描述
not U1(Snot, Sl);
and U2(A, D0, Snot);
and U3(B, D1, S);
or U4(Y, A, B);
endmodule
第二种:assign只能这种数据表达形式,不想always中,有语句可以用
module mux2to1_dataflow(D0, D1, S, Y );
input D0, D1, S;
output Y;
wire Y ;
//下面是逻辑功能描述
assign Y = (~S & D0) | (S & D1); //表达式左边Y必须是wire型
endmodule
第三种:相当与把电路化出了最简单的状态,更高效了
module mux2to1_bh(D0, D1, S, Y );
input D0, D1, S;
output Y;
reg Y ;
//逻辑功能描述
always @(S or D0 or D1) //任何输入信号的变化都会进入,括号里面的也叫敏感变量,也可以用*代替
if (S == 1) Y = D1; //也可以写成 if (S) Y = D1;
else Y = D0; //注意表达式左边的Y必须是reg型
endmodule
不过现在都用别的方法定义模块了
例子:
module top_seg_led
(
//global clock
input sys_clk , // 全局时钟信号
input sys_rst_n, // 复位信号(低有效)
output led, //1个LED灯
//seg_led interface
output [5:0] seg_sel , // 数码管位选信号
output [7:0] seg_led , // 数码管段选信号
input key //按键信号
);
这里面的有位宽定义的一般不能去掉前面的output/input。如下:
module top_seg_led
(
//global clock
input sys_clk , // 全局时钟信号
sys_rst_n, // 复位信号(低有效)
key //按键信号
output led, //1个LED灯
//seg_led interface
output [5:0] seg_sel , // 数码管位选信号
output [7:0] seg_led , // 数码管段选信号
);
2022.10.18更新
嵌套case语句:
case(A)
1'b1:case(B)
2'b0:begin
x1=1'b1;
x2=1'b0;
end
2'b01:begin
x3=1'b1;
x4=1'b0;
end
default:.........
endcase
default:.....
endcase
一个case语句不需要加begin end,像1‘b1里的。但是在具体的分支里面,多个语句还是要有begin end
casex/casez:
casez将比较双方表达式(分支表达式和case_expr控制表达式)中出现的z的位当作不用关心的位处理,在分支表达式中所有为z的位也可以用?代替。
casex则认为表达式中所有的z和x都是无关项
casex(I)
8'b1xxx_xxxx: //只要I的最高位i等于1就执行,相当if(I[7])
y=3'd7;
8'b01xx_xxxx:
y=3'd6;
8'b001x_xxxx:
y=3'd5;
8'b0001_xxxx:
y=3'd4;
8'b0000_1xxx:
y=3'd3;
...............
8'b0000_0001:
y=3'd0;
default:.......
2022.10.22更新
在 Verilog 中允许声明reg,integer,time,real,realtime及其向量类型的数组,对数组的维数没有限制。线网数组也可用于连接实例的端口,数组中的每个元素都可以作为一个标量或向量。
integer count [0: 7] ; //由8个计数变量组成的数组
reg bool[31:0] ; // 由32个1位的布尔( boolean)寄存器变量组成的数组
reg [4:0] port_id[0:7]; //由8个端口标识变量组成的数组,端口变量的位宽为5
integer matrix [4:0][0:255] ; //二维的整数型数组
reg [63:0] array_4d [15:0][7:0][7:0][255:0] ;//四维64位寄存器型数组
wire [7:0] w_array2 [5:0] ;//声明8位向量的数组
下面有一些赋值例子:
matrix[1] [0] = 33559;//把数组中第1行第0列的整数型单元(32位)置为33559
array_4d[0] [0][0][0][15:0] = 0;//把四维数组中索引号为[0][0][0][0]的寄存器型单元的0~15位都置为0
matrix = 0 ; //非法,企图写整个数组
matrix [1] = 0;//非法,企图写数组的整个第2行,即从matrix[1] [0]直到matrix[1][255]
reg [17:0] ADD_SUM_DI[0:251];//初始化
ADD_SUM_DI[0]=18'd1; //赋值当然只能在always进行
ADD_SUM_DI[1]=18'd2;
ADD_SUM_DI[2]=18'd3;
...
就是字符串中的%要写成%%才行
2022.10.23更新
在while语句中可以使用各种操作符,并且可以用这些操作符构成任意的逻辑表达式。如果循环中有多条语句,则须使用begin和end。
while((i < 16) && continue ) //用操作符的多个条件构成while表达式
begin
if (flag[i])
begin
$display ("Encountered a TRUE bit at element number %d",i);
continue = 'FALSE;
end
i = i + 1;
end
初始条件和完成自加操作的过程赋值语句都包括在 for循环中。
for ( count=0; count < 128; count = count + 1)
repeat循环的功能是执行固定次数的循环,它不能根据一个逻辑表达式来确定循环是否继续进行。repeat循环的次数必须是一个常量、一个变量或者一个信号。如果循环重复次数是变量或者信号,循环次数是循环开始执行时变量或者信号的值,而不是循环执行期间的值。
//说明:从О增加到127计数并显示integer count;
initial
begin
count = 0 ;
repeat (128)
begin
$display ( "Count = %d",count) ;
count = count + 1 ;
end
end
关键字forever用来表示永久循环。直到遇到系统任务$finish为止。如果需要从forever循环中退出,可以使用disable 语句。
通常情况下forever循环是和时序控制结构结合使用的。如果没有时序控制结构,那么仿真器将无限次地执行这条语句 ,并且仿真时间不再向前推进,使得其余部分的代码无法执行。
//例1:时钟发生器
//用forever循环,不用always块
reg clock;
initial
begin
clock = 1'b0 ;
forever #10 clock = ~clock; //时钟周期为20个单位时间
end
//例2:在每个时钟正跳变沿处使两个寄存器的值一致reg clock;
reg x, y;
initial
forever @(posedge clock) x = y;
即begin end语句
一条一条语句顺序执行的,这里指的是阻塞赋值的情况下
在写仿真的时候,如果语句包括了延时控制,那么延时下一条语句要在前面这条延时处理完才能执行
//说明2:带延迟的顺序块reg x, y;
reg [1:0] Z, w;
initial
begin
x= l'b0; //在仿真时刻o完成
#5 y = l'b1;//在仿真时刻5完成
#10 z = {x,y}; //在仿真时刻15完成
#20 w = {y,x}; //在仿真时刻35完成
end
关键字fork和join声明,语句是并行执行的,但是要注意如果两条语句在同一时刻对同一变量进行操作,可能引起隐含的竞争,因为目前的仿真器无法正确的处理竞争,所以要避免这一写法。
例子:应用在仿真里面
//例1:带延迟的并行块reg x,y;
reg [1:0] z, w;
initial
fork
x= 1 'b0; //在仿真时刻О完成
#5 y = 1'b1; //在仿真时刻5完成
#10 z = {x, y}; //在仿真时刻10完成
#20 w = {y, x}; //在仿真时刻20完成
join
顺序块和并行块可以互相嵌套
和上面看两个并不是并列关系,但是同等重要。
命名块主要指给前面两种块起个名字,而且命名后的块里面的变量可以通过名字引用访问,也可以被禁用,停止执行。
//命名块
module top;
initial
begin: block1 //名字为block1的顺序命名块
integer i; //整型变量i是block1命名块的静态本地变量
//可以通过层次名top.block1.i被其他模块访问
…
end
initial
fork : block2 //名字为block2的并行命名块
reg i; //寄存器变量i是block2命名块的静态本地变量
//可以通过层次名top.block2.i被其他模块访问
...
join
具体语法就是在之前的额块语句begin/fork后面加一个:,之后后面再跟上起的名字
Verilog通过关键字disable提供了一种终止命名块执行的方法。使用disable则可以禁用设计中的任意一个命名块。
//在(向量)标志寄存器的各个位中从低有效位开始查找第一个值为1的位1/从向量标志寄存器的低有效位开始查找第一个值为1的位
reg [15:0] flag;
integer i; //用于计数的整数
initial
begin
flag = 16'b 0010_0000_0000_0000;
i = 0;
begin: block1 // while循环声明中的主模块是命名块block1
while(i < 16)
begin
if(flag[i])
begin
$display( "Encountered a TRUE bit at element number %d",i) ;
disable block1; //在标志寄存器中找到了值为真的位,禁用block1end
i = i + 1;end
end
end
end
当对向量中的多个位进行重复操作时,或者当进行多个模块的实例引用的重复操作时,或者在根据参数的定义来确定程序中是否应该包括某段Verilog 代码的时候使用生成块
例如:对两个N位总线变量进行按位异或
//本模块生成两条N位总线变量的按位异或
module bitwise_xor (out, i0,i1);//参数声明语句。参数可以重新定义
parameter N = 32; //默认的总线位宽为32位
//端口声明语句
output [N-1:o] out;
input [N-1:0]i0,il;
//声明一个临时循环变量,该变量只用于生成块的循环计算。verilog仿真时该变量在设计中并不存在
genvar j;
//用一个单循环生成按位异或的异或门( xor)
generate for (j=0; j<N; j=j+1)begin: xor_loop
xor gl (out [j], i0[j], i1[j]);
end //在生成块内部结束循环
endgenerate //结束生成块
//另外一种编写形式
//异或门可以用always块来替代
// reg [N-1:0] out;
//generate for (j=0; j<N; j=j+1) begin: bit
//always (i0[j] or i1[j]) out [j] = i0[j] ^ i1[j];
// end
//endgenerate
endmodule
在仿真开始之前,仿真器会对生成块中的代码进行确立(展平),将生成块转换为展开的代码,然后对展开的代码进行仿真。
关键词genvar用于声明生成变量,生成变量只能用在生成块中;在确立后的仿真代码中,生成变量是不存在的;
生成变量的值只能由循环生成语句来改变;
循环生成语句可以嵌套使用。但是使用同一个生成变量作为索引的循环生成语句不能相互嵌套;
xor_loop是赋予循环生成语句的名字,目的在于通过它对循环生成语句中的变量进行层次化引用。因此,循环生成语句中各个异或门的相对层次名分别为: xor_loop[0].g1,xor_loop[1].g1,…,xor_loop[31].g1。
利用if else来选择执行的模块
//有条件地调用(实例引用)不同类型的乘法器
//根据参数ao_width和al_width的值,在调用时引用相对应的乘法器实例
generate
if (a0_width <8)||(a1_width < 8)
cla_multiplier # (ao_width,a1_width) mo (product,a0,ai);
else
tree_multiplier #(ao_width, 'a1_width)mo · (product,a0,al);
endgenerate //生成块的结束
//根据总线的位宽、调用(实例引用)相应的加法器
//参数卫在调用(实例引用)时可以重新定义
//调用(实例引用)不同位宽的加法器是根据不同的N来决定的generate
case (N)
//当N=1或2时分别选用位宽为1位或2位的加法器
l: adder_1bit adder1(co,sum,a0,al, ci) ; // 1位的加法器
2: adder_2bit adder2(co,sum,a0, a1,ci) ; //2位的加法器
//默认的情况下选用位宽为N位的超前进位加法器
default: adder_cla #(N) adder3(co,sum,a0,a1,ci);
endcase
endgenerate //生成块的结束
2022.11.19更新
变量[起始地址 +: 数据位宽] ------》 变量[(起始地址+数据位宽-1):起始地址]
变量[结束地址 -: 数据位宽] -------》 变量[结束地址:(结束地址-数据位宽+1)]
uart_bytes_data_reg <= {uart_sing_data,uart_bytes_data_reg[(BYTES*8-1)-:(BYTES-1)*8]};
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。