赞
踩
最常用的 2 种数据类型就是线网(wire)与寄存器(reg)
如果没有驱动元件连接到 wire 型变量,缺省值一般为 “Z”
表示存储单元,它会保持数据原有的值,直到被改写
寄存器不需要驱动源,也不一定需要时钟信号。在仿真时,寄存器的值可在任意时刻通过赋值操作进行改写
整数(integer)
声明时不用指明位宽,位宽和编译器有关,一般为32 bit。
实数(real)
可用十进制或科学计数法来表示。实数声明不能带有范围,默认值为 0。
时间(time)
对仿真时间进行保存。其宽度一般为 64 bit,通过调用系统函数 $time 获取当前仿真时间。
参数用来表示常量,用关键字 parameter 声明,只能赋值一次。
但是,通过实例化的方式,可以更改参数在模块中的值。
字符串保存在 reg 中,每个字符占一个字节(8bit)
字符串不能多行书写,即字符串中不能包含回车符。
如果寄存器变量的宽度大于字符串的大小,则使用 0 来填充左边的空余位;如果寄存器变量的宽度小于字符串大小,则会截去字符串左边多余的数据。例如,为存储字符串 “run.runoob.com”, 需要 14*8bit 的存储单元:
表达式由操作符和操作数构成
可以为常数,整数,实数,线网,寄存器,时间,位选,域选,存储器及函数调用等
算术、关系、等价、逻辑、按位、归约、移位、拼接、条件操作符
乘(*)、除(/)、加(+)、减(-)、求幂(**)、取模(%)
关系操作符有大于(>),小于(<),大于等于(>=),小于等于(<=)。
关系操作符的正常结果有 2 种,真(1)或假(0)。
等价操作符包括逻辑相等(),逻辑不等(!=),全等(=),非全等(!==)。
等价操作符的正常结果有 2 种:为真(1)或假(0)。
&&(逻辑与), ||(逻辑或),!(逻辑非)。
逻辑操作符的计算结果是一个 1bit 的值,0 表示假,1 表示真,x 表示不确定
取反(),与(&),或(|),异或(^),同或(^)。
按位操作符对 2 个操作数的每 1bit 数据进行按位操作。
如果 2 个操作数位宽不相等,则用 0 向左扩展补充较短的操作数。
归约与(&),归约与非(&),归约或(|),归约或非(|),归约异或(),归约同或(~)。
归约操作符只有一个操作数,它对这个向量操作数逐位进行操作,最终产生一个 1bit 结果。
A = 4'b1010 ;
&A ; //结果为 1 & 0 & 1 & 0 = 1'b0,可用来判断变量A是否全1
~|A ; //结果为 ~(1 | 0 | 1 | 0) = 1'b0, 可用来判断变量A是否为全0
^A ; //结果为 1 ^ 0 ^ 1 ^ 0 = 1'b0
左移(<<),右移(>>),算术左移(<<<),算术右移(>>>)
算术左移和逻辑左移时,右边低位会补 0。
逻辑右移时,左边高位会补 0;而算术右移时,左边高位会补充符号位,以保证数据缩小后值的正确性。
拼接符操作数必须指定位宽,常数的话也需要指定位宽。
condition_expression ? true_expression : false_expression
用 `timescale 编译指令将时间单位与实际时间相关联。
该指令用于定义时延、仿真的单位和精度
`timescale time_unit / time_precision // 时间单位/时间精度
为隐式的线网变量指定为线网类型,即将没有被声明的连线定义为线网类型
使得缺省连线类型为线网类型
用于将模块标记为单元模块
用于将模块标记为单元模块
连续赋值语句是 Verilog 数据流建模的基本语句,用于对 wire 型变量进行赋值
assign 为关键词,任何已经声明 wire 变量的连续赋值语句都是以 assign 开头
wire Cout, A, B ;
assign Cout = A & B ; //实现计算A与B的功能
或在 wire 型变量声明的时候同时对其赋值。
wire A, B ;
wire Cout = A & B ;
寄存器的时延也是可以控制的,这部分在时序控制里加以说明
连续赋值时延一般可分为普通赋值时延、隐式时延、声明时延
//普通时延,A&B计算结果延时10个时间单位赋值给Z
wire Z, A, B;
assign #10 Z = A & B ;
//隐式时延,声明一个wire型变量时对其进行包含一定时延的连续赋值。
wire A, B;
wire #10 Z = A & B;
//声明时延,声明一个wire型变量是指定一个时延。因此对该变量所有的连续赋值都会被推迟到指定的时间。
//除非门级建模中,一般不推荐使用此类方法建模。
wire A, B;
wire #10 Z ;
assign Z = A & B;
信号脉冲宽度小于时延时,对输出没有影响。
仿真时,时延一定要合理设置,防止某些信号不能进行有效的延迟。
对一个有延迟的与门逻辑进行时延仿真。
关键词:initial, always
一个模块中可以包含多个 initial 和 always 语句,但 2 种语句不能嵌套使用。
这些语句在模块间并行执行,与其在模块的前后顺序没有关系。
顺序执行,非阻塞赋值除外
每个 initial 语句或 always 语句都会产生一个独立的控制流,执行时间都是从 0 时刻开始。
initial 语句从 0 时刻开始执行,只执行一次,多个 initial 块之间是相互独立的。
多用于初始化、信号检测等
从 0 时刻开始执行其中的行为语句;执行完最后一条语句后,再次执行语句块中的第一条语句,如此循环反复
多用于仿真时钟的产生,信号行为的检测
关键词:阻塞赋值,非阻塞赋值,并行
过程性赋值是在 initial 或 always 语句块里的赋值,赋值对象是寄存器、整数、实数等类型。
这些变量在被赋值后,其值将保持不变,直到重新被赋予新值。
连续性赋值总是处于激活状态,任何操作数的改变都会影响表达式的结果;
过程赋值只有在语句执行时,才会起作用。
阻塞赋值属于顺序执行,即下一条语句执行前,当前语句一定会执行完毕。
前面的仿真中,initial 里面的赋值语句都是用的阻塞赋值。
非阻塞赋值属于并行执行语句,即下一条语句的执行和当前语句的执行是同时进行的,它不会阻塞位于同一个语句块中后面语句的执行。
不要在一个过程结构中混合使用阻塞赋值与非阻塞赋值。两种赋值方式混用时,时序不容易控制,很容易得到意外的结果。
在设计电路时,always 时序逻辑块中多用非阻塞赋值,always 组合逻辑块中多用阻塞赋值;在仿真电路时,initial 块中一般多用阻塞赋值。
//2个always 块中语句并行执行,赋值操作右端操作数使用的是上一个时钟周期的旧值,此时a<=b与b<=a就可以相互不干扰的执行,达到交换寄存器值的目的。
always @(posedge clk) begin
a <= b ;
end
always @(posedge clk) begin
b <= a;
end
等效于——
always @(posedge clk) begin
temp = a ;
a = b ;
b = temp ;
end
关键词:时延控制,事件触发,边沿触发,电平触发
Verilog 提供了 2 大类时序控制方法:时延控制和事件控制。
事件控制主要分为边沿触发事件控制与电平敏感事件控制。
当延时语句的赋值符号右端是常量时,2 种时延控制都能达到相同的延时赋值效果。
当延时语句的赋值符号右端是变量时,2 种时延控制可能会产生不同的延时赋值效果。
reg value_test ;
reg value_general ;
#10 value_general = value_test ;
#10 ;
value_ single = value_test ;
先将计算结果保存,然后等待一定的时间后赋值给目标信号。
内嵌时延控制加在赋值号之后。
reg value_test ;
reg value_embed ;
value_embed = #10 value_test ;
条件是信号的值发生特定的变化
关键字 posedge 指信号发生边沿正向跳变,negedge 指信号发生负向边沿跳变,未指明跳变方向时,则 2 种情况的边沿变化都会触发相关事件
//信号clk只要发生变化,就执行q<=d,双边沿D触发器模型
always @(clk) q <= d ;
//在信号clk上升沿时刻,执行q<=d,正边沿D触发器模型
always @(posedge clk) q <= d ;
//在信号clk下降沿时刻,执行q<=d,负边沿D触发器模型
always @(negedge clk) q <= d ;
//立刻计算d的值,并在clk上升沿时刻赋值给q,不推荐这种写法
q = @(posedge clk) d ;
用户可以声明 event(事件)类型的变量,并触发该变量来识别该事件是否发生。
触发信号用 -> 表示
event start_receiving ;
always @( posedge clk_samp) begin
-> start_receiving ; //采样时钟上升沿作为时间触发时刻
end
always @(start_receiving) begin
data_buf = {data_if[0], data_if[1]} ; //触发时刻,对多维数据整合
end
多个信号或事件中任意一个发生变化都能够触发语句的执行
用关键字 or 连接多个事件或信号。这些事件或信号组成的列表称为"敏感列表"。
or 也可以用逗号 , 来代替。
always @(posedge clk or negedge rstn) begin
//always @(posedge clk , negedge rstn) begin
更为简洁的写法是 @* 或 @(*),表示对语句块中的所有输入变量的变化都是敏感的
//两种写法等价
always @(*) begin
//always @(a, b, c, d, e, f, g, h, i, j, k, l, m) begin
后面语句的执行需要等待某个条件为真
initial begin
wait (start_enable) ; //等待 start 信号
forever begin
//start信号使能后,在clk_samp上升沿,对数据进行整合
@(posedge clk_samp) ;
data_buf = {data_if[0], data_if[1]} ;
end
end
关键词:顺序块,并行块,嵌套块,命名块,disable
一条条执行的,非阻塞赋值除外
每条语句的时延总是与其前面语句执行的时间相关
并行执行的,即便是阻塞形式的赋值
每条语句的时延都是与块语句开始执行的时间相关
而非阻塞赋值,也能达到和并行块同等的赋值效果。
顺序块和并行块可以嵌套使用
命名的块中可以声明局部变量,通过层次名引用的方法对变量进行访问。
module test;
initial begin: runoob //命名模块名字为runoob,分号不能少
integer i ; //此变量可以通过test.runoob.i 被其他模块使用
disable 可以终止命名块的执行,可以用来从循环中退出、处理错误等
if (i_d >= 50) begin //累加5次停止累加
disable runoob_d3.clk_gen ; //stop 外部block: clk_gen
disable runoob_d2 ; //stop 当前block: runoob_d2
end
disable 在 always 或 forever 块中使用时只能退出当前回合,下一次语句还是会在 always 或 forever 中执行
关键词:if,选择器
加入 begin 与 and 关键字是一个很好的习惯
关键词:case,选择器
case(case_expr)
condition1 : true_statement1 ;
condition2 : true_statement2 ;
……
default : default_statement ;
endcase
case 语句中的条件选项表单式不必都是常量,也可以是 x 值或 z 值,但一般不建议在 case 语句中使用 x 或 z 作为比较值。
casex、 casez 语句是 case 语句的变形,用来表示条件选项中的无关项。
casex 用 “x” 来表示无关值,casez 用问号 “?” 来表示无关值。
always @(*)
casez(sel)
4'b???1: sout_t = p0 ;
4'b??1?: sout_t = p1 ;
4'b?1??: sout_t = p2 ;
4'b1???: sout_t = p3 ;
default: sout_t = 2'b0 ;
endcase
关键词:while, for, repeat, forever
while (condition) begin
…
end
初始条件和自加操作等过程都已经包含在 for 循环中,所以 for 循环写法比 while 更为紧凑
不是所有的情况下都能使用 for 循环来代替 while 循环。
i = i + 1 不能像 C 语言那样写成 i++ 的形式,i = i -1 也不能写成 i – 的形式
for (i=0; i<=10; i=i+1) begin
#10 ;
counter2 = counter2 + 1'b1 ;
end
repeat 循环的次数必须是一个常量、变量或信号
repeat (loop_times) begin
…
end
forever 语句表示永久循环,不包含任何条件表达式,一旦执行便无限的执行下去,系统函数 $finish
可退出 forever。
相当于 while(1)
forever 循环是和时序控制结构配合使用的
关键词:deassign,force,release
过程连续赋值是过程赋值的一种。这种赋值语句能够替换其他所有 wire 或 reg 的赋值,改写了 wire 或 reg 型变量的当前值。
过程连续赋值的表达式能被连续地驱动到 wire 或 reg 型变量中,即过程连续赋值发生作用时,右端表达式中任意操作数的变化都会引起过程连续赋值语句的重新执行。
always @(posedge clk or negedge rstn) begin
if(!rstn) begin //Q = 0 after reset effective
Q <= 1'b0 ;
end
else begin
Q <= D ; //Q = D at posedge of clock
end
end
在复位信号为 0 时,Q 端被 assign 语句赋值,始终输出为 0。
在复位信号为 1 时,Q 端被 deassign 语句取消赋值,在时钟上升沿被重新赋值
always @(posedge clk) begin
Q <= D ; //Q = D at posedge of clock
end
always @(negedge rstn) begin
if(!rstn) begin
assign Q = 1'b0 ; //change Q value when reset effective
end
else begin //cancel the Q value overlay,
deassign Q ; //and Q remains 0-value until the coming of clock posedge
end
end
@(negedge clk) ;
force test.u_counter.cnt_temp = 4'd6 ;
force test.u_counter.cout = 1'b1 ;
#40 ;
@(negedge clk) ;
release test.u_counter.cnt_temp ;
release test.u_counter.cout ;
关键词:模块,端口,双向端口,PAD
结构建模方式有 3 类描述语句:
①Gate(门级)例化语句
②UDP(用户定义原语)例化语句
③module(模块)例化语句
变量声明,数据流语句,行为级语句,低层模块例化及任务和函数这 5 部分出现顺序、出现位置都是任意的。但是,各种变量都应在使用之前声明。变量具体声明的位置不要求,但必须保证在使用之前的位置。
module module_name
#(parameter_list)
(port_list) ;
Declarations_and_Statements ;
endmodule
一般将不带类型、不带位宽的信号变量罗列在模块声明里
一个模块如果和外部环境没有交互,则可以不用声明端口列表
端口类型(端口方向): 输入(input),输出(output)和双向端口(inout)。
input、inout 类型不能声明为 reg 数据类型
output 可以声明为 wire 或 reg 数据类型
端口隐式的声明为 wire 型变量
output reg DOUT ;
reg 型端口要么在 module 声明时声明,要么在 module 实体中声明
module pad(
input DIN, OEN ,
input [1:0] PULL ,
inout PAD ,
output reg DOUT
);
module pad(
input DIN, OEN ,
input [1:0] PULL ,
inout PAD ,
output DOUT
);
reg DOUT ;
关键字:例化,generate,全加器,层次访问
**模块例化:**在一个模块中引用另一个模块,对其端口进行相关连接
//full_adder1 u_adder0(
// .Ai (a[0]),
// .Bi (b[0]),
// .Ci (c==1'b1 ? 1'b0 : 1'b1),
// .So (so_bit0),
// .Co (co_temp[0]));
//顺序端口连接
full_adder1 u_adder1(
a[1], b[1], co_temp[0], so_bit1, co_temp[1]);
从模块内部来讲,input 端口必须是 wire 型变量
从模块内部来讲,output 端口可以是 wire 或 reg 型变量
从模块外部来讲,inout 端口必须连接 wire 型变量
悬空端口
但是,例化时一般不能将悬空的 input 端口删除,否则编译会报错
位宽不匹配时,端口会通过无符号数的右对齐或截断方式进行匹配
连接端口的信号类型可以是
1)标识符,2)位选择,3)部分选择,4)上述类型的合并,5)用于输入端口的表达式
用 generate 语句可对多个模块进行重复例化
genvar i ;
generate
for(i=1; i<=3; i=i+1) begin: adder_gen
full_adder1 u_adder(
.Ai (a[i]),
.Bi (b[i]),
.Ci (co_temp[i-1]), //上一个全加器的溢位是下一个的进位
.So (so[i]),
.Co (co_temp[i]));
end
endgenerate
通过使用一连串的 . 符号对各个模块的标识符进行层次分隔连接
层次访问多见于仿真中。
关键词: defparam,参数,例化,ram
当一个模块被另一个模块引用例化时,高层模块可以对低层模块的参数值进行改写
参数覆盖有 2 种方式
可以用关键字 defparam 通过模块层次调用的方法,来改写低层次模块的参数值。
defparam u_ram_4x4.MASK = 7 ;
将新的参数值写入模块例化语句
如果有模块在端口声明时的参数,那么实体中的参数将视为 localparam 类型,使用 defparam 将不能改写模块实体中声明的参数
对已有模块进行例化并将其相关参数进行改写时,不要采用 defparam 的方法。除了上述缺点外,defparam 一般也不可综合
模块在编写时,如果预知将被例化且有需要改写的参数,都将这些参数写入到模块端口声明之前的地方(用关键字井号 # 表示)。这样的代码格式不仅有很好的可读性,而且方便调试
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。