赞
踩
Verilog 的数据类型支持4值逻辑:0,1以及
在综合工具中,或者在实际实现的电路中,并没有x值,只存在0,1,z三种状态;
模拟工具中,x值多表示信号没被赋值。
数据常量:<位宽><‘进制><数字>,进制有二(b/B),八(o/O),十(d/D)和十六进制(h/H)
Data = 0; // 赋全0
Data = ’bz; // 赋全Z
Data=~0; // 赋全1
字符串型:每个字符占8位
reg[8*19:1] string_value;//19个字节宽
initial string_value=“Hello Verilog World”;
Verilog共有19种数据类型: wire\reg\parameter\integer\time\large\medium\scalared\small\tri\trio\tril\triand\trir\trireg\vectored\wand\wor等
线网(net)型:wire,tri,可以理解为实际电路中的导线、连线
综合工具支持net类型中wire、tri类型的综合。
其他的子类型前端逻辑设计很少使用,与基本逻辑单元工艺库有关。
寄存器型(变量型):reg,表示临时存储数据的变量,具有记忆特性;但不能就认为是实际电路中的寄存器
在过程语句块initial或always中被赋值的变量都必须定义为寄存器型。
reg:可定义的无符号整数变量,可以是标量(1位)或矢量,是RTL建模中最常用的寄存器类型.
integer :32位有符号整数变量,通常用作不会由硬件实现的数据处理。
real:双精度的带符号浮点变量,用法与integer相同。
time :64位无符号整数变量,用于仿真时间的保存与处理。
realtime:与real内容一致,但可以用作实数仿真时间的保 存
与处理
数组:由多个数据组成,每个数据位宽为n
Verilog-1995:只允许定义一维数组,数组声明仅限于reg,integer和time寄存器类数据类型,常用作存储器,为RAM和ROM行为级建模。如
reg [15:0] MEM [0:1023]
,1K × 16b的存储器Verilog-2001:所有寄存器型和线网型变量都可用于声明数组 (reg, integer, time, real, realtime;所有net类型);允许定义多维数组,但没有定义EDA工具如何存储数组元素。如reg [31:0] array2 [0:255][0:15]
Verilog定义的数组一次只能访问数组的一个元素,或者一个元素的1位或部分位。为数组元素赋值需要定义索引,索引可以是整数或表达式。
System Verilog:压缩数组和非压缩数组
常量(参数):parameter,localparam,
其他SV扩展的相关类型:SV扩展了原有的整数、reg等变量数据类型(logic、bit等);增加了用户自定义(typedef)、枚举(enum)、结构体(struct)等很多类C的数据类型
可综合的操作符如下表所示
运算符类型 | 操作数 | 符号 | 结果 |
---|---|---|---|
位运算符 拼接及复制操作符 | 单目 | ~ { } {{ }} | |
逻辑运算符 | 单目 | ! | 1 bit |
算术运算符 | 单目 | + - (表示正负) | |
算术运算符 | 双目 | * / + - | |
移位运算符 | 双目 | << >> | |
关系运算符 | 双目 | > < >= <= | 1 bit |
相等运算符 | 双目 | == != | 1 bit |
位运算符 | 双目 | &, ~&, ^, ^~, |, ~| | |
缩减运算符 | 单目 | &, ~&, ^, ^~, |, ~| | 1 bit |
逻辑运算符 | 双目 | &&, || | 1 bit |
条件运算符 | 三目 | ?: |
注意integer和reg类型在算术运算时的差别。integer是有符号数,而reg是无符号数
对操作数的所有位进行位操作
===:按位比较,包括x、z值的比较,每位都必须相等,结果只能为true或false。
!==: 按位比较,包括x、z值的比较,比较两个值是否不等,结果只能为true或false。
和=的区别:后者比较两个数是否完全相等,对x和z都要进行比较,两个操作数必须完全一致,结果才是1(比较结果不是0,就是1,无不定状态)
复制一个变量或在{ }中的值,{n{m}}将m重复n次。比如,{3{2’b10}} = 6’b101010
+:
:变量[起始地址 +: 数据位宽] <–等价于–> 变量[(起始地址+数据位宽-1) : 起始地址]data[0 +: 8] <--等价于--> data[7:0]
data[15 +: 2] <--等价于--> data[16:15]
data[7 -: 8] <--等价于--> data[7:0]
data[15 -: 2] <--等价于--> data[15:14]
module mult_acc(out, ina, inb, clk, reset_n); // 输入输出端口声明: Verilog-2001 input [DATA_WIDTH-1:0] ina, inb; input clk, reset_n; output reg [2*DATA_WIDTH-1:0] out; // 参数声明 parameter DATA_WIDTH = 8; /* 另外两种端口和参数声明的方式 1. Verilog-1995 module mult_acc(out, ina, inb, clk, reset_n); // 声明端口方向和位宽 input [DATA_WIDTH-1:0] ina, inb; input clk, reset_n; output [2*DATA_WIDTH-1:0] out; // 声明端口类型 reg [2*DATA_WIDTH-1:0] out; // ina和inb不再声明类型,默认为wire parameter DATA_WIDTH = 8; 2. Verilog-2001 module mult_acc #( parameter DATA_WIDTH = 8 ) ( input [DATA_WIDTH-1:0] ina, inb; input clk, reset_n; output reg [2*DATA_WIDTH-1:0] out; ); */ // 功能实现 wire [2*DATA_WIDTH-1:0] adder_out, mult_out; assign adder_out = out + mult_out; always @(posedge clk) begin if (~reset_n) begin out <= {(2*DATA_WIDTH-1){1'b0}}; end else begin out <= adder_out; end end // 模块实例化 multa #( .DATA_WIDTH(DATA_WIDTH) ) u1( .in_a(ina), .in_b(inb), .m_out(mult_out) ); endmodule
<assign> [strength] [#delay] <net_name> = <expressions>
assign sum= function_value(a,b)
assign {carry_out, sum_out} = ina+inb+carry_in
assign #10 a=10;
initial结构用于仿真,因此其介绍放在章节 testbench编写
中
过程结构中的时序控制有三类:简单延迟、使用wait表示的事件敏感控制和基于事件的时序控制,其中前两者只能用于仿真,因此其介绍放在章节 testbench编写
中。本小节只介绍基于事件的时序控制。
电平敏感:敏感列表内多个电平敏感信号用 or连接
always@(a or b or c) // Verilog-1995,常描述组合逻辑,需列出所有输入信号
always @(a, b, c) // Verilog-2001,可以使用','而不是or
always @(*) // Verilog-2001,可以使用@(*)来代替所有输入信号列表
沿敏感:用关键字posedge和negedge限定信号敏感边沿,敏感表中可以有多个信号,用关键字or连接
always@(posedge clk or negedge reset)//常描述时序逻辑
阻塞赋值(blocking) “= ”:连续的多条阻塞赋值操作是顺序完成的,一般用于组合逻辑
// b值拷贝到a然后回传 begin a=1;b=3; a = #5 b; b = #5 a; #10 $display(a, b); // 输出3,3 end // a和b值安全交换 begin a=1;b=3; fork a = #5 b; b = #5 a; #10 $display(a, b); // 输出3,1 join end
非阻塞赋值(non-blocking) “<= “:连续的非阻塞赋值是同时完成操作的,一般用于时序逻辑
if(expression)
true_statement;
[else
fault_statement;]
//表达式为逻辑0、x、z时被认为是假
case语句
always@(x or z)
case(alu_control)
2'b00: y=x+z;
2'b01: y=x-z;
2'b10: y=x*z;
default: y=0; // 避免产生隐含的锁存器
endcase
casez语句:认为表达式中的z值和?是无关值(即对应为无需匹配)
module prio_encoder_casez(input [3:0] r,output reg [2:0] y);
always @*
casez (r)
4'b1???: y = 3'b100;
4'b01??: y = 3'b011;
4'b001?: y = 3'b010;
4'b0001: y = 3'b001;
4'b0000: y = 3'b000; // 这里可以使用default
endcase
endmodule
casex语句:认为表达式中的z,x值和?为无关值
casex(encoder)
4'b1xxx: high_lvl = 3;
4'b01xx: high_lvl = 2;
4'b001x: high_lvl = 1;
4'b0001: high_lvl = 0;
default: high_lvl = 0;
endcase
一共四类循环语句,即repeat、forever、while和for,一般只能用于initial/always/task/function
repeat:执行固定次数的循环,一般用于仿真
// 示例一 integer count; initial begin count=0; repeat(128) begin $display(“Count=%d”, count); count= count+1; end end // 示例二:两个8位数的二进制数的乘法 module mult_repeat ( input [8:1] op0, input [8:1] op1, output reg [16:1] result ); reg [16:1] tempa; reg [8:1] tempb; always @* begin result = 0; tempa = op0; tempb = op1; repeat(8) begin if(tempb[1]) result = result + tempa; tempa = tempa << 1; tempb = tempb >> 1; end end endmodule
forever:无穷循环,常用来产生周期性的波形,作为仿真激励信号。一般用在initial过程语句中,要退出只能采用强制退出循环的方法,如disable语句或执行$finish
reg clk ;
initial begin
clk = 0 ;
forever begin
clk = ~clk;
#5;
end
end
while
// 两个8位数的二进制数的乘法
module mult_while (input [8:1] op0,input [8:1] op1,output reg [16:1] result);
interger i=1;
always @*
begin
result = 0;
while(i<=8)
if(op1[i])
result = result + (op0 << (i - 1));
i = i + 1;
end
endmodule
for
// 示例一:两个8位数的二进制数的乘法 module mult_for (input [8:1] op0, input [8:1] op1,output reg [16:1] result ); integer i; always @* begin result = 0; for(i=1;i<=8;i=i+1) if(op1[i]) result = result + (op0 << (i-1)); end endmodule // 示例二:行波进位加法器 module RippleCarryAdder #( WIDTH = 32 ) ( input [WIDTH-1:0] a, b, input cin, output reg [WIDTH-1:0] sum, output reg cout ); integer i; always @* begin sum = 0; cout = cin; for(i = 0; i < WIDTH; i = i + 1) begin sum[i] = ((a[i]) ^ b[i]) ^ cout; cout = (b[i]&cout) | (a[i]&cout) | (b[i]&a[i]); end end endmodule
没有仔细看,碰到的时候再说
task的可综合性:不同工具支持不一样,尽量只用于testbench
可综合
在Verilog-2001中新增了语句generate,通过generate循环,可以完成一个模块的多次例化
// 示例一 //1bit width buffer_1 module buffer_1( input wire in, output wire out ); assign out = ~in; endmodule //8bit width buffer module buffer_8( input wire[7:0] din, output wire[7:0] dout ); // Generate block genvar i; generate for(i=0; i<8; i=i+1) begin:BLOCK1 buffer_1 buffer_1_1(.in(din[i]), .out(dout[i])); end endgenerate endmodule
// 示例二 // 进位选择加法器,使用了上面给出的RippleCarryAdder模块 module CarrySelectAdder #( WIDTH = 32, BLK_WIDTH = 4 ) ( input [WIDTH-1:0] a, b, input cin, output reg [WIDTH-1:0] sum, output cout ); localparam BLK_NUM = WIDTH/BLK_WIDTH; wire [BLK_NUM - 1:0] BLK_Cin; wire [BLK_NUM - 1:0] BLK_Cout0, BLK_Cout1; reg [BLK_NUM - 1:0] BLK_Cout; wire [WIDTH-1:0] sum0, sum1; assign cout = BLK_Cout[BLK_NUM - 1]; assign BLK_Cin = {BLK_Cout[BLK_NUM-2:0], cin}; genvar i; generate for(i = 0; i < BLK_NUM; i = i + 1) begin RippleCarryAdder #(.WIDTH(BLK_WIDTH)) myadder0(.a(a[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .b(b[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .cin(1'b0), .sum(sum0[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .cout(BLK_Cout0[i])); RippleCarryAdder #(.WIDTH(BLK_WIDTH)) myadder1(.a(a[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .b(b[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .cin(1'b1), .sum(sum1[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH]), .cout(BLK_Cout1[i])); always@* begin case(BLK_Cin[i]) 1'b0: {sum[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH], BLK_Cout[i]} = {sum0[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH], BLK_Cout0[i]}; 1'b1: {sum[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH], BLK_Cout[i]} = {sum1[i*BLK_WIDTH+BLK_WIDTH-1:i*BLK_WIDTH], BLK_Cout1[i]}; endcase end end endgenerate endmodule
概念:通过基本的逻辑门基元互连来描述电路称为门级建模
优点:门级描述在实际电路和模型间提供了一种更紧密的点对点映射方法。
特点
Verilog提供一个由该语言定义的26个门级和开关级基元组成的集合
门级基元包括4类,有14个门类型
多输入门:一个标量输出和多个标量输入,常用的门类型关键字: and, nand, or, xor, nor, xnor;
实例化方法:多输入门 实例名(输出,输入1,…输入n);
多输出门:一个标量输入和一个或多个标量输出,buf, not
实例化方法:多输出门 实例名(输出1,输出2,…输出n,输入);
三态门:bufif0, bufif1, notif0, notif1
上拉门,下拉门:Pulldown, pullup
开关级基元
和C语言的含义一样
使用`define的意义:
使用示例
`define BYTE_SIZE 8
`define WORD_SIZE (BYTE_SIZE*2)
reg [`WORD_SIZE-1] data;
用来说明该命令后模块的仿真时间单位和时间精度
使用形式:`timescale 仿真时间单位/时间精度
时间单位
s | 秒(1s) |
---|---|
ms | 毫秒( 1 0 − 3 10^{-3} 10−3s) |
us | 微秒( 1 0 − 6 10^{-6} 10−6s) |
ns | 纳秒( 1 0 − 9 10^{-9} 10−9s) |
ps | 皮秒( 1 0 − 12 10^{-12} 10−12s) |
fs | 飞秒( 1 0 − 15 10^{-15} 10−15s) |
`timescale 1ns/1ps // 测试时间基本单位为1ns,精度为1ps module tb_mult_acc; reg clk, reset_n; reg [7:0] ina, inb; wire [15:0] out; // 系统任务和系统函数关键字前必须加上$ initial $monitor($time,"MAC=%h", out); // 时钟生成 initial clk = 0; always #5 clk = ~clk; // 由于测试时间基本单位为1ns,因此时钟为100MHz // 复位信号生成 initial begin reset_n = 1'b0; #20 reset_n = 1'b1; end // 输入激励 initial begin ina = 8'b0; inb = 8'b0; #20 ina = 8'd1; inb = 8'd2; #10 ina = 8'd2; inb = 8'd3; #10 ina = 8'd3; inb = 8'd4; #10 ina = 8'd4; inb = 8'd5; end // 退出仿真器,停止仿真 initial #55 $finish; mult_acc # ( .DATA_WIDTH(8) ) test1( .clk(clk), .reset_n(reset_n), .ina(ina), .inb(inb), .out(out) ); endmodule
可以使用#delay来设置延时
assign语句的延迟设置:用于控制任一操作数发生变化到左边被赋予新值之间的时间延迟。指定赋值延迟有三种方法:
普通赋值延迟
wire Out;
assign #10 Out = in1 & in2;
隐式赋值延迟
wire #10 Out = in1 & in2;
线网申明延迟
wire #10 Out;
assign Out = in1 & in2;
线网延迟设置同样用于门延迟
initial或always语句块:在testbench中使用简单延时(#延时)施加激励,不可综合
always @( sl or a or b)
if (! sl)
#10 out = a;
// 从a到out延时10个时间单位
else
#12 out = b;
//从b到out延时12个时间单位
//在简单延时中可以使用参数parameter
parameter cycle = 20;
initial clk = 0;
always #(cycle/2) clk = ~clk;
wait用于行为级代码中电平敏感的时序控制,不可综合,只用于testbench等行为级仿真
always @(a or b)
begin
wait (!enable) // 当enable为低电平时执行加法
out = a + b;
end
两种过程结构(initial/always)或其他行为语句(if、for、case)中如果存在两条或两条以上的语句需要用块语句进行封装,即begin-end和fork-join。
顺序块:语句置于关键字begin和end之间,块中的语句常以顺序方式执行
并行块:关键字fork和join之间的是并行块语句,块中的语句并行执行,常用于testbench描述
顺序块和并行块可以混合使用,可以自我嵌套或互相嵌套
initial
fork
#10 a = 1;
#15 b = 1;
begin
#20 c = 1;
#10 d = 1;
end
#25 e = 1;
join
有名块:定义了标识名称的顺序语句块或并行语句块, 即需在begin或fork后面加 :block_name
begin:Continue//该begin…end块定义为Continue
statement1;
statement2;
……
end
是Verilog中预先定义好的,用于控制和检测仿真模拟过程的任务或函数,以$开头关键字,只能用于仿真验证
输出参数列表中信号的当前值,$display输出时会自动换行,$write不会换行
语法:$display/write([“format_specifiers”,] <argument_ list>);
示例:
$display($time, "%b \t %h \t %d \t %o", sig1, sig2, sig3, sig4);
$display($time, "%b \t", sig1, "%h \t", sig2, "%d \t", sig3, "%o", sig4);
$display/$write支持二进制、八进制、十进制和十六进制的显示任务。缺省基数为十进制
$display (sig1, sig2, sig3, sig4); // $write
$displayb (sig1, sig2, sig3, sig4); // $writeb
$displayo (sig1, sig2, sig3, sig4); // $writeo
$displayh (sig1, sig2, sig3, sig4); // $writeh
format_specifiers中会用到的显示格式符
%h hex %o octal %d decimal %b binary
%c ASCII %s string %v strength %m module
%t time %% 输出% %0d 无前导0的十进制数
选通监视,提供一种显示数据的机制,在同一时间单元的所有赋值语句执行完毕后才执行
语法:$strobe([“format_specifiers”,] <argument_ list>);
$display显示的变量值是执行到该语句时变量的值,而$strobe显示的是执行该语句的仿真时刻的所有语句执行完后的结果
module tb; reg flag; reg [31: 0] data; initial begin $writeb("writeb", ,"%d", $time, ,"%h \t", data, , flag, "\n"); #15 flag = 1; data = 16; $displayh("displayh", ,$time, ,data, , flag); end initial begin #10 data = 20; // 注意下面两条语句的输出 $strobe("strobe", ,$time, , data); $display("display", ,$time, , data); data = 30; end endmodule /* 下面是输出 writeb 0 xxxxxxxx x display 10 20 strobe 10 30 displayh 000000000000000f 00000010 1 */
strobe/$strobeb/$strobeo/$strobeh
持续检测(变量列表中)一个或多个信号的变化,每当被监测的信号值发生变化,就将在当拍结束时显示该信号值。
语法:$monitor([“format_specifiers”,] <argument_ list>);
显示参数定义方式与$display同
$monitor/$monitorb/$monitoro/$monitorh
将$monitor写到initial块中就可以在整个仿真过程对指定的变量值进行监测。
与$display不同,在仿真过程中只能有一个$monitor语句起作用,后面的$monitor将覆盖前面的$monitor。只有新的$monitor的参数列表中的信号被监视,而前面的$monitor的参数则不被监视
但是vivado好像不是这样,前面的$monitor的参数貌似也会被监视
module tb; reg flag; reg [31: 0] data; initial fork #10 data = 5; #15 flag = 1; #15 data = 16; join initial begin $monitor("Monitor(data)",, "%d", data); #11; $monitor("Monitor(flag)",, "%d", flag); end endmodule /* 下面是vivado仿真器的输出 Monitor(data) x Monitor(data) 5 Monitor(flag) x Monitor(flag) 1 Monitor(data) 16 */
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
$monitoron和$monitoroff任务用来启动和关闭监控功能。仿真开始时,仿真器的默认状态是$monitoron,使用户可以在仿真时只监视特定时间段的信号。
$monitor和$strobe一样,显示参数列表中信号的稳定状态值,也就是在仿真时间前进之前显示赋值后信号的值。参数列表中信号值的任何变化将触发$monitor 。但$time, $stime, $realtime不能触发。
在任一显示任务中使用%m格式选项,可以显示任何级别的层次
当一个模块的多个实例同时执行同一段代码时, 使用%m选项可以区分哪个模块实例在进行输出
module tb; buff b0 (.buf_in(1'b0), .buf_out()); endmodule module buff ( input buf_in, output buf_out ); wire a; inv i0 (.in(buf_in), .out(a )); inv i1 (.in(a ), .out(buf_out)); initial $display("Inside hierarchy %m"); endmodule module inv ( input in, output out ); assign out = ~in; initial $display("Inside hierarchy %m",, out); endmodule /* 下面是输出 Inside hierarchy tb.b0.i0 1 // 实例i0的out值 Inside hierarchy tb.b0.i1 0 // 实例i1的out值 Inside hierarchy tb.b0 */
%m选项无需参数
用于写文件的系统任务(常用于保存验证的输出结果)
用法:
文件的打开和关闭: $fopen,$fclose
integer descriptor;//32位整数,标识一个打开的文件
descriptor =$fopen(“filename”);
/*
1. 系统任务$fopen打开指定文件,并返回一个32位整数给描述符descriptor(文件指针)
2. 32bit的descriptor中的一位对应一个通道;标准输出占用32bit多通道描述符的最低位,且始终是打开的;最高位是保留位。因此Verilog允许最多同时打开30个用户文件。
3. $fclose(descriptor); 一旦文件被关闭descriptor中的对应位被清0,不能再写入文件
*/
$fdisplay/$fwrite/$fmonitor/$fstrobe:第一个参数是文件描述符,其余为带有参数表的格式定义,与对应的显示任务相同
$readmemb,$readmemh:分别用于将文件中的二进制或十六进制数读到存储器(即寄存器数组中),用数据文件对存储器初始化
语法表达式:$readmemb/$readmemh(“filename”,memname[,start address][,finish address];
使用示例
reg[7:0] mem[1:256];
initial begin
$readmemh("mem.data",mem);
$readmemh("mem.data",mem,16);
$readmemh("mem.data",mem,128,1);
end
转换函数
数学函数
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。