Verilog HDL入门(2)

Verilog 基础语言2

2.第二章内容

门级建模 内建基元(原语)门
Verilog HDL提供下列内建基元门

多输入门: 与门(and)、与非门(nand)、或门(or)、或非门(nor)、异或门(xor)、同或门(xnor)
多输出门: 缓冲器(buf)、非门(not)
三态门: bufif0、bufif1、notif0、notif1
上拉、下拉门: pullup、pulldown
MOS开关: cmos、nmos、pmos、rnmos、rpmos
双向开关: tran、tranif0、tranif1、rtran、rtranif0、rtranif1

设计中,可以用实例引用语句来描述具体的门。
gate_type [instance_name] (term1,term2,…,termN);
instance_name:可选的(实例名称)
gate_type是上面列出的某种门
term1,term2,…,termN:与名instance_name,类型gate_type门的输入/输出端口相连的线网或变量(模块引脚名称)

同一类型门的多个实例能够在一条语句结构中定义
gate_type
[instance_name1] (term1,term2,…,termN),
[instance_name2] (term1,term2,…,termN),
…,
[instance_name3] (term1,term2,…,termN),

多输入门
内置的多输入门:与门(and)、与非门(nand)、或门(or)、或非门(nor)、异或门(xor)、同或门(xnor)

这些逻辑门只有单个输出,1个或多个输入。多输入门实例引用句法:
muliple_input_gate_typeinstance_name;

例子:
and uland(out1,in1,in2); //定义名位uland的双输入与门,输出out1,两个输入
and u2and(req,sw_data[15],sw_data[15],ack[2],ack[1]);

1
2
3
4
xor 
(qpr,byte_a,byte_b,byte_c),
(mlock,mprot[0],mprot[1]),
(xparity,intr_vec[2],intr_vec[1],intr_vec[0],intr_vec[3]);

多输入门的真值

多输出门:缓冲器(buf)、非门(not)
这两种类型门只有一个输入,输出可以是一个或者几个,实例引用语法
muliple_input_gate_type instance_name;
最后的端口是输入端口,其余的端口全部为输出端口

例子:
buf u5buf(clk_tx,clk_rx,clk_io1,clk_spi,clk_core); //缓冲门语句,
clk_core 是u5buf缓冲门的输入,该缓冲门有四个输出clk_tx,clk_rx,clk_io1,clk_spi。

not u8not(phase_a_rdy,phase_b_rdy,ready);
ready是u8not非门唯一的输入端口。有两个输出phase_a_rdy,phase_b_rdy。

多输出门真值

buf门真值

buf 0 1 x z
输出 0 1 x z

not门真值

not 0 1 x z
输出 1 0 x x

三态门:

1
bufif0、bufif1、notif0、notif1

4种门均有一个输出、一个数据输入和一个控制输入,三态门基本语法:
tristate_gate instance_name;
第一个端口OutputA是输入端口,第二端口InputB是数据输入,第三端口ControlC是控制输出

控制输入,输出可被驱动到高阻态,即值Z.
bufif0,控制端输入为1,输出为Z;否则数据从输入传输至输出; bufif0 其中的0就是条件
bufif1,控制端输入为0,输出为Z;否则数据从输入传输至输出; bufif1 其中的1就是条件
notif0,控制端输入为1,输出为Z;否则输入数据的非传输至输出;notif0 其中的0就是条件
notif1,控制端输入为0,输出为Z;否则输入数据的非传输至输出;notif1 其中的1就是条件

例:
bufif1 u7bufif1(dbus,mem_data,strode); //strode为0,名u7bufif1的三态门(bufif1)输出线dbus驱动到高阻态;否则mem_data的值被传输至dbus。
bufif0 u3bufif0(paddr,abus,probe);//probe为1,paddr驱动到高阻态;否则abus的值被传输至paddr。

高阻态(可能是高电压可能是低电压)
bufif0的真值

bufif0 control
0 1 x z
DATA 1 0 z x x
0 0 z x x
x x z x x
z x z x x

bufif1的真值

bufif1 control
0 1 x z
DATA 1 z 1 x x
0 z 0 x x
x z x x x
z z x x x

notif0的真值

notif0 control
0 1 x z
DATA 1 0 z x x
0 1 z x x
x x z x x
z x z x x

notif1的真值

notif1 control
0 1 x z
DATA 1 z 0 x x
0 z 1 x x
x z x x x
z z x x x

上拉门和下拉门(电阻):pullup、pulldown
这两种门只有一个输出,没有输入,上拉门(电阻)将输出置1;下拉门(电阻)输出置0
语法:
pull_gate instance_name;

该门的端口例表只包含1个输出,
pullup u0pullup(core_pwr); //此上拉门(电阻)实例名为u0pullup,其输出core_pwr被连接到高电平1.

MOS开关:cmos、nmos、pmos、rnmos、rpmos
可以用来给单向开关建模,也就是说,通过设置控制输入的值(1/0)可以接通或者关闭从输入流向输出的数据流
pmos(p类型MOS管)、nmos(n类型MOS管)、rnmos(r代表电阻)和rpmos开关有一个输出、一个输入和一个控制输入;
示例引用这类门语法
gate_type [instance_name] (OutputA,InputB,ControlC);
第一个端口为输出,第二个端口为输入,最后一个端口是控制输入 。
若nmos和rnmos开关的控制输入为0,pmos和rpmos开关的控制输入为1,则开关关闭。输出z;
若nmos和rnmos开关的控制输入为1,pmos和rpmos开关的控制输入为0,则开关打开。输入数据传输致输出;

rnmos和rpmos在输入引线和输出引线之间存在比较搞得阻抗(电阻)。当数据从输入传输至输出时,由于开关阻抗的存在,数据型号的强度会出现衰减。
实例:
pmos u9pmos(iol_bus,read_data,gate_ctrl); //实例名u9pmos的pmos开关,输入read_data,输出iol_bus,控制信号gate_ctrl.
rnmos u2rnmos(control_bit,ready_bit,hold);

MOS开关的真值

pmos和rpmos的真值

pmos control
rpmos 0 1 x z
Data 0 0 z x x
1 1 z x x
x x z x x
z z z z z

nmos和rnmos的真值

nmos control
rnmos 0 1 x z
Data 0 z 0 x x
1 z 1 x x
x z x x x
z z z z z

cmos(是互补型MOS)和rcmos(电阻型MOS)开关具有一数据输出,一个数据输入和两个控制信号输入,语法
(r)cmos instance_name;
第一端口输出,第二端口输入,第三个端口为N沟道控制输入,第四个端口P沟道控制输入,

双向开关:

1
tran、rtran、tranif0、tranif1、rtranif0、rtranif1

双向开关,数据可以双向流动,并且数据通过开关传播时没有延时。
后四个开关设置合适得控制信号而关闭,但tran和rtran这两种开关不能关闭:
语法(r是有电阻得开关)
(r)tran instance_name; //端口只有两个端口,并且无条件得双向流动,

其他双向开关实例:
gate_type instance_name; //第三个端口是控制端口

tranif0、rtranif0 设置ControlC为1,双向数据禁止流动,为0,则流动
tranif1、rtranif1 设置ControlC为0,双向数据禁止流动,为1,则流动

rtran、rtranif0、rtranif1 这三种阻抗双向开关,信号经过时,信号强度会出现衰减

门延迟(在测试文件使用 tb)
信号从任何门得输入到其输出传输延迟可以用门延迟来定义,在门实例引用语句种可以指定门延迟。实例引用带延迟参数得门语句如下:
gate_type [delay]instance_name;
delay 门延迟,输入到输出延迟,没有指定,则为0
门延迟最多由3类延迟值组成:上升延迟、下降延迟、截至延迟
门延迟可指定包含0、1、2、3个延迟值,下表列出了指定不同延迟值个数是,delay的4种表示方式

实例数组
需要多次实例引用时,门实例应用语句种可以指定一个范围,生成多个实例
gate_type [delay] instance_name leftbound:rightbound;
leftbound:rightbound 是任意常数表达式,(可以为负数,)

wire[3:0] irq,ctrl,sense;
//线网数组irq[0] irq[1] irq[2] irq[3]
//ctrl[0] ctrl[1] ctrl[2] ctrl[3]
//sense[0] sense[1] sense[2] sense[3]
nand u8nand(irq,ctrl,sense);

语句等价于

1
2
3
4
5
nand 
u8nand3 (irq[3],ctrl[3],sense[3]);
u8nand2 (irq[2],ctrl[2],sense[2]);
u8nand1 (irq[1],ctrl[1],sense[1]);
u8nand0 (irq[0],ctrl[0],sense[0]);

注意:当指定实例数组时,必须明确得定义实例名

隐含线网
没有特别声明的线网被默认为1位线网,但是用户可以用default_nettype编译指令设置默认(缺省)的线网类型。编译指令格式: default_nettype net_type
说明:
default_nettype wand 有了这条编译指令,所有后续未声明的线网全部由被定义wand类型 default_nettype 编译指令必须出现在模块定义的外面,并且在遇到下一个同样的编译指令或遇到resetall编译指令之前一直保持有效 default_nettype编译指令后面跟着一个 none 值, 就可以把已默认的线网定义取消掉, 这种情况下,编译器若发现没有声明类型的任何线网都将报告出错

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module mux4x1(y,d0,d1,d2,d3,s0,s1)

output y;
input d0,d1,d2,d3,s0,s1;
wire t3,t2,t1,t0;
wire s0bar,s1bar;

and
(t0,s0bar,s1bar,d0),
(t1,s0,s1bar,d1),
(t2,s0bar,s1,d2),
(t3,s0,s1,d3);

not
(s0bar,s0),
(s1bar,s1);

or(y,t1,t2,t3,t4);

endmodule

2-4编码器举例(译码器电路)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module dec2x4(a,b,enable,y);
input a,b,enable;
output [0:3]y;
wire abar,bbar;

not
(abar,a),
(bbar,b);

nand
(y[0],enable,abar,bbar),
(y[1],enable,abar,b),
(y[2],enable,a,bbar),
(y[3],enable,a,b);
endmodule

主从触发器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module ms_dflip_flop(d,c,q,qbar);
input d,c;
output q,qbar;
wire notd,notc,d1,d2,ybar,y,y1,y2;
not
u9nt(notd,d),
u10nt(notc,d),
u11nt(noty,y);



nand
u1nd(d1,d,c),
u2nd(d2,notd,c),
u3nd(y,d1,ybar),
u4nd(ybar,y,d2),
u5nd(y1,y,notc),
u6nd(y2,notc,noty),
u7nd(q,y1,qbar),
u8nd(qbar,q,y2);

endmodule

奇偶校验电路 9位奇偶校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module parity_9_bit(d,even,odd);
input [0:8]d; /* 数据位 */
output even,odd;
wire e0,e1,e2,e3,f0,f1,f2,h0;

xor
u0xor(e0,d[0],d[1]),
u1xor(e1,d[2],d[3]),
u2xor(e2,d[4],d[5]),
u3xor(e3,d[6],d[7]),
u3xor(f0,e1,e2),
u4xor(f1,e2,e3),
u6xor(h0,f1,f2),
u7xor(even,h0,d[8]);

not
u8not(odd,even);
endmodule

用户定义的原语(基元UDP)
UDP的实例语句与基本门的实例语句完全相同,既UDP实例语句的语法与基本门的实例语句语法一致。

UDP的定义
UDP可以使用具有如下语法的UDP语句来定义:
primitive UDP_name(OutputName,List_of_inputs);
Output_declarations //输出声明 这里类似于模块
List_of_input_declarations //输入声明列表 这里类似于模块
[Reg_declaration] //寄存器说明
[Initial_statement] //初始化语句

table
List_of_table_entries //表格条目列表
endtable
endprimitive

可以使用如下形式的语句来定义:
primitive UDP_name(Output_declarations,List_of_input_declarations);
[Reg_declaration] //寄存器说明
[Initial_statement] //初始化语句

table
List_of_table_entries //表格条目列表
endtable
endprimitive

UDP的定义不依赖于模块定义,因此出现在模块定义以外。也可以在单独的文本文件中定义UDP
UDP只能有一个输出和一个或多个输入。第一个端口必须是输出端口。此外,输出取值0、1、x(不允许z值),输入中出现z以x处理,UDP的行为以表的形式描述
UDP中可以描述下面两类行为:
(1)组合电路
(2)时序电路(沿触发和电平触发)

组合逻辑的UDP
表规定了不同的输入组合和相对应的输出值。没有指定的任意组合输出为x。 多路选择器为例说明
primitive mux_2by1(y,a,b,select);
output y;
input a,b,select;

table
//注:下一行仅作为注释
//a b select : y
0 ? 1 : 0;
1 ? 1 : 1;
? 0 0 : 0;
? 1 0 : 1;
0 0 x : 0;
endtable
endprimitive

字符?代表不必关心相应变量的具体值,可以是0、1、x。 输入端口的次序必须与表中各项的次序匹配,既表中的第一列对应于原语端口队列的第一个输入(例子中为a),第二列是b,第三列是select。在该多路选择器的表中没有一项的输入组合为01x(还缺少其他一些组合项);这种情况,输出的缺省植为x(对其他未定义的输入组合项也是如此)。

module mux_4by1(y,a,b,c,d,select);
input a,b,c,d;
input [2:1] select;
output y;
parameter tRISE = 2, tFALL = 3;

mux_2by1 #(tRISE,tFALL) //实例名是可选的,此处引用多路选择器时,没有指定实例名
(temp1,a,b,select[1]),
(temp2,c,d,select[1]),
(y,temp1,temp2,select[1])
endmodule
在UDP实例中,总共可以指定2个延迟,这是由于UDP的输出可以取值0、1、x(无截止延迟)

时序逻辑UDP
时序逻辑UDP中,使用1位寄存器描述内部状态。该寄存器的值是时序电路UDP的输出值
共有两种不同类型的时序UDP;一种时序UDP是电平敏感行为的模型;另一种是跳变沿触发行为模型。
时序电路UDP使用寄存器当前值和输入值决定寄存器的下一状态(何后继的输出)

状态变量的初始化
使用一条过程性复制语句对时序逻辑UDP的状态进行初始化
initial reg_name = 0,1,or x;
初始化语句在UDP定义中出现

电平触发的时序逻辑UDP
下面是D锁存器建模的电平触发的时序逻辑的UDP举例,只要时钟为低电平0,数据就从输入传递到输出;否则输出值被锁存。
primitive latch(q,clk,d);
output q;
input clk,d;
reg q; //-> output reg q;

table
// clk d q(state) q(next)
0 1 :? :1;
0 1 :? :0;
1 ? :? :-;
endtable
endprimitive
-字符表示值”无变化”。注意UDP的状态存储在变量q中

沿触发的时序逻辑UDP
用跳变沿触发的时序逻辑UDP为D型沿触发的触发器建模。一条初始化语句用于初始化该触发器的状态。
primitive d_edge_flip_flop(q,clk,data);
output q;
input clk,data;
reg q;
initial q = 0;
table
//clk data q(state) q(next)
(01) 0 : ? : 0;
(01) 1 : ? : 1;
(0x) 1 : 1 : 1;
(0x) 0 : 0 : 0;
//忽略时钟负边沿;
(?0) ? : ? : -;
//忽略在稳定时钟上的数据变化
? (??): ? : -;

endtable
endprimitive
表项(01)表示0转换到1,表项(0x)表示从0转换到x,表项(?0)表示从任意值(0、1、x)转换到0,表项(??)表示任意切换,对任意未定义的转换,输出缺省为x。
假定上面的UDP定义了d_edge_flip_flop,现在就能够在模块中像基元门那样,实例引用该原语(基元)
module register4(clk,data_in,data_out);
input clk;
input [0:3]data_in;
output [0:3]data_out;
d_edge_flip_flop
u0_d_edge_flip_flop(data_out[0],clk,data_in[0]),
u1_d_edge_flip_flop(data_out[1],clk,data_in[1]),
u2_d_edge_flip_flop(data_out[2],clk,data_in[2]),
u3_d_edge_flip_flop(data_out[3],clk,data_in[3]);
endmodule

沿触发的和电平敏感的混合行为
在同一个表中能够混合电平触发和沿触发项。这种情况下,边沿变化在电平触发之前处理,既电平触发项覆盖边沿触发项。
primitive d_async_ff(q,clk,clear,data);
output q;
input clk,clear,data;
reg q;
table
//clk clear data q(state) q(next)
(01) 0 0 : ? : 0;
(01) 0 1 : ? : 1;
(0x) 0 1 : 1 : 1;
(0x) 0 0 : 0 : 0;
//忽略时钟负边沿:
(?0) 0 ? : ? : -;
(??) 1 ? : ? : 0;
? 1 ? : ? : 0;
endtable
endprimitive

表项的总结
所有可以用于UDP原语表项中的值

符号 | 意义
0 | 逻辑0
1 | 逻辑1
x | 未知值
? | 0、1、x中的任一个
b | 0或1中任选一个
- | 不变
(AB) | 值由A变到B
* | 与(??)相同 任意值变换到任一值
r | 上跳变沿,与(01)相同
f | 下跳变沿,与(10)相同
p | (01)、(0x)和(x1)的任一种
n | (10)、(1x)和(x0)的任一种

negedge 下降沿 posedge 上升沿

1
# 3.第三章内容

数据流模型
连续赋值语句常用来建立数据流的行为模型;过程性赋值语句用来为时序电路建立行为模型。组合逻辑电路行为模型的最好方法时使用连续赋值语句

连续赋值语句
连续赋值语句可以用来对线网进行赋值(不能用来对寄存器进行赋值),它的格式如下(简单格式)
assign LHS_target = RHS_expression;
举例
//线网声明:
wire [3:0]frm_rdy,coh_rdy,hrd_tag;

//连续赋值语句
assign hrd_tag = coh_rdy & frm_rdy;

连续赋值语句 被赋值目标是hrd_tag;coh_rdy & frm_rdy,特别注意,连续赋值语句种一定由关键字assign
连续赋值语句在什么时候执行,只要右侧表达式中的操作数有事件发生(既操作数值改变)时,就会计算右侧表达式;若新的结果值与原来的值不同,则把新的结果值赋给左侧的被赋值目标

若coh_rdy 或 frm_rdy 发生了变化,就会计算右侧的表达式。若右侧表达式的值发生了变化,则把新计算出的值赋给线网 hrd_tag
连续赋值的目标可以是类型: 标量线网、向量线网、矩阵中的一个元素(该矩阵可以是标量线网类型的,也可以是向量线网类型的)、向量线网的某一位、向量线网的部分位、上述各种类型的拼接体。

标量线网 如:wire a,b;
向量线网 如:wire [3:0] a,b;

下面,被赋值的目标是一个标量线网和一个向量线网的拼接体
wire carry_out,carry_in;
wire [3:0] sum,a,b;

assign {carry_out,sum} = a + b + carry_in;
a和b的位宽是4位,所以加法运算能够产生最大为5位的结果。因此左侧表达式的宽度指定为5位(carry_out 1位,sum 4位),最终这个赋值语句将右侧表达式最右边(低)4位的值赋给sum,第五位(进位)的值赋值给carry_out。

例子说明如何在一条连续赋值语句中进行多次赋值
assign mux_out = (select == 0)?Input_a:’bz, //‘bz 表示32位位宽
mux_out = (select == 1)?Input_b:’bz,
mux_out = (select == 2)?Input_c:’bz,
mux_out = (select == 3)?Input_d:’bz;

上面赋值语句是下面4条独立连续赋值语句的简化书写格式。
assign mux_out = (select == 0)?Input_a:’bz;
assign mux_out = (select == 1)?Input_b:’bz;
assign mux_out = (select == 2)?Input_c:’bz;
assign mux_out = (select == 3)?Input_d:’bz;

例子:
wire dtag;
parameter SIZE = 7;
wire [SIZE:0]padded_dtag;

assign padded_dtag = {SIZE{1’b0},dtag};

若没有对连续赋值的目标类型进行声明,则将把它默认为标量线网。
assign mc_noburst = dma_lock; //没有对mc_noburst经行声明,mc_noburst被默认为1位的线网。

线网声明赋值
连续赋值可以作为线网声明的一部分,这样赋值称为线网声明赋值。
wire [3:0]qmv_wr = 4’b0;
wire frm_wait = ‘b1;

wire ictr_gt_qctr = ictr > qctr,
qctr_gt_ictr = qctr > ictr;

wire [(8*12-1):0]dbg_dump_rpt = “dbg_dump_rpt”;
线网声明赋值不但声明了线网,还对声明的线网进行连续赋值。
线网声明赋值是 :声明线网,然后编写连续赋值语句的一种简便形式。
wire wr_cycle; 等价于线网声明赋值语句 wire wr_cycle = ‘b1;
assign wr_cycle = ‘b1; ==========================>

不允许对同一线网进行多个线网声明赋值。若必须进行多个赋值,则必须使用连续赋值语句。

赋值延迟()
assign #6 dbg_data = int_data|| peg_cntxt;
#6:上述赋值语句中指定的延时,指从右侧表达式中任一操作数的变化,到右侧表达式重新计算,再把计算结果赋给左侧的目标总共需要6个时间单位的延迟,若在时刻5,int_data值发生变化,则在时刻5重新计算赋值语句的右侧表达式,并在时刻11(=5+6)把计算出的新值赋给dbg_data
以最后一次右边变化计算出的结果延时后,赋值给左边

在右侧得值传递给左侧之前,右侧得值发生了变化,会怎么样?最后一次值得变化将起决定作用,
assign #4 peg_free = xbid_par;
延时期间右侧表达式发生得变化会被滤除
(需要看书,才能理解此处)
在把右式得变化传播到左式之前,右式必须至少能够在该延迟期间保持右式值不变;
在延迟期满,若右式得值发生了变化,则前面得值就不能传播到输出。

在每个延迟得声明中,总共有3个延迟值可以被指定
上升延迟值、下降延迟值、截止延迟值
格式:
assign #(rise,fall,turn-off) LHS_target = RHS_expression;

三个延迟值为0时,如何在连续赋值语句中指定这些延迟
//一个延迟参数
assign #4 biu_par = fe_par ||wsp_par; //上升延迟、下降延迟、截止延迟(既变化到Z的延迟)和变化到x的延时相同,都为4

//两个延迟参数
assign #(4,8)biu_par = rd_trg; //上升延迟4、下降延迟8、截止延迟和x的延迟相同,为4和8的最小值,4

//三个延迟参数
assign #(4,8,6)fe_arb = &fe_dbus; //上升延迟4、下降延迟8、截止延迟6 变化到x的延迟为4

//没有延迟参数
assign fe_dbus = rd_address[7:4]; //所有延迟为0

右侧表达式的值从非0向量变化到0向量,则使用下降延迟;
右侧表达式的值变化到z,则使用截止延迟,其余的情况都使用上升延迟

线网延迟
延迟可以在线网声明中定义,
wire #5 mem_write;
该延迟指的是mem_write驱动源的值发生改变到线网mem_write本身的值发生改变的延迟。

assign #2 mem_write = chunk_valid & flop_valid;
假设flop_valid 变化导致重新计算右侧表达式,结果和以前不同,则在2个时间单位后,重新赋值个mem_write;
但是mem_write的线网延迟,对线网mem_write的赋值发生在7个时间单位后,

首先赋值延迟起作用,然后加上线网延迟产生的作用;

线网声明赋值中, 指定了延迟,这个延迟不是线网延迟,是赋值延迟,
nc_data进行的线网声明赋值, 2个时间单位指的是赋值延时,不是线网延迟(线网延迟,声明线网时不能有赋值)
wire [3:0] #2 nc_data = si_data-mem_wdata;

主/从触发器

1
2
3
4
5
6
7
8
9
10
11
12
13
module msdff_dataflow(
input wire d, clk,
output wire q,q_bar
);
wire not_clk, not_d,not_y,y,d1,d2,y_bar,y1,y2;
assign not_d =~d;
assign not_clk =~clk;
assign not_y = ~y;

assign d1 =~(d & clk);
assign d2 = ~(clk & not_d);

endmodule

行为级建模
过程性结构
两种语句是对设计进行行为级建模的主要结构 initial 、always
模块中可以包含任意个 initial always 语句 ,这些语句相互之间是并行执行,
一条initial always 语句的执行会产生一个单独的控制流、所有的initial always 都是再0时刻开始并行的执行。

initial 语句只执行一次,在仿真开始时(0时刻)执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
parameter SIZE = 1024;
reg [7:0] vld_ram[0:SIZE-1];
reg speed_reg;

initial
begin: seq_blk_a
integer index;

speed_reg = 0;

for(index = 0;index < SIZE; index = index + 1'b1)
vld_ram[index] = 0;

end

begin … end 划定顺序块的界限,顺序块中的语句是顺序执行的
seq_blk_a 顺序块的标签,若过程中没有出现局部声明语句,不需要标签

always语句
always语句是反复执行的, 与initial语句类似地方都是从0时刻开始执行

事件列表写成: @(a or b) @(a,b) @(*)

时序控制
延迟控制、事件控制

延迟控制
#2 tx_addr = rx_addr - 5; //等待延迟,执行过程语句前等待2个单位延迟

延迟控制的延迟可以是任意表达式,不必限制为一个常量,

延迟表达式的值为0,则称为显式零延迟。
#0;
显示零延迟触发一个等待,等待所有其他在当前仿真时刻要被执行的事件执行完毕后,才将其恢复,仿真事件不会前进

若延迟表达式的值为 x、z,等效与零延迟。
若延迟表达式计算结果为负值,则将其二进制补码值作为延迟

若延迟值包含空格字符、表达式,需要用括把延迟值括起来

事件控制
事件控制中,语句的执行是基于事件的:跳变沿敏感事件控制、电平敏感事件控制
跳变沿敏感事件控制 @(negedge rst_n) @(posedge clk)
电平敏感事件控制 @cell_byte
@cell_byte per_frame= cell_byte; //当cell_byte上有事件发生时,才会执行赋值操作

负跳变 正跳变
1 -> x 0 -> x
1 -> z 0 -> z
1 -> 0 0 -> 1
x -> 0 x -> 1
z -> 0 z -> 1

通过一种方式可以隐含地把相应的过程性语句中所有的变量和线网都包含在敏感事件例表中。
@*表明相应的过程性语句对于其内部的任何值的变化都会敏感

always @ *
procedural_statement

@* 把过程性语句内部所有的变量都看作是敏感事件列表的一部分
always @* cpu_reg = master_rag + control_reg;
//隐含地把 master_rag 、control_reg 包含在敏感事件列表中。实际上是下面always语句简写格式
always
@(master_rag,control_reg)
cpu_reg = master_rag + control_reg;

@* 代表在相应块中的任何语句中使用了的变量和线网,还包括在赋值语句中的等号左边的表达式中的序号变量
always @* w[k] = ram&chip;

等价于
@(ram,chip,k)

电平敏感事件控制
电平敏感事件控制中,知道条件变为正后,过程性语句才执行
wait(condition) //等到 condition 为真的时候,才执行下面的语句
procedural_statement

语句块(begin end)
1.顺序语句块( begin end) :语句块中的语句按照给定顺序执行
2.并行语句块( fork join) : 语句并行执行
语句块的表示符是可选的。
若有标识符,可以在语句块内部声明局部变量,还可以被应用

顺序语句块
语句按顺序执行;定界符是begin end
顺序块语法:
begin
[:block_id{declarations}]//{块id[声明]} 标识符
procedural_statement(s) //过程型语句 执行部分
end

1
2
3
4
5
6
7
8
9
10
11
12
begin 	//还可以这么写顺序块
mem_r = sms|mem;

@(negedge clk_sms)
update = mem_rd;
end

begin: lbl_seq_blok //lbl_seq_blok 标识符
reg[1:0] dmac;
dmac = 2'd1;
end
顺序块标记 lbl_seq_blok ;

并行语句块
定界符是fork join ;并行语句块中的语句是并行执行的
在并行语句块内的每条语句中指定的延迟值都是相对于语句块开始执行的时刻的;
并行语句块中的最后一个行为(并不一定是最后一条语句)执行完成时,再继续执行这个并行块后面其他语句。
在执行跳出语句块前必须执行完并行语句块内的所有语句。

就是将并行语句块中所有的语句并行执行完成后,在执行并行块后面的语句
fork
[:block_id{declarations}] //{块id[声明]} 标识符
procedural_statement(s)
join

顺序语句块和并行语句块可以混合使用,下面是两者不同之处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
always
begin :blk_seq_a
#4 pm_write = 5; //S1

fork:blk_par_a //S2
#6 pm_select = 7; //P1

begin :blk_seq_b //P2
wdog_rst = pm_enable; //S6
#5 wdog_intr = wdog_rst;//S7
end

#2 frc_sel = 3; //P3
#4 pm_itcr = 2; //P4
#8 itop = 4; //P5
join

#8 pm_sel = 3; //S3
#4 pcell_id = 52; //S4
#6 $stop; //S5
end

blk_seq_a 顺序语句块,并且块内所有语句(S1、S2、S3、S4、S5)都是按照顺序执行

过程性赋值
过程性赋值是在initial 、 always语句内进行的赋值 ,只能对变量类型赋值,

语句内部延迟
语句内部延迟之前计算的右式,然后进入延迟等待,最后把重新计算的值赋给左式的目标

重复事件控制的时序控制 格式:
repeat(expression) @(event_expression)
这种控制格式是利用一个或多个事件发生次数来指定延迟
hresult = repeat(2) @(negedge tclk) hw_data + hr_data;
既hw_data + hr_data的值,然后等待时钟tclk上出现2个负跳变沿,再把右边值赋给hresult。
等价:
begin
    temp = hw_data + hr_data;
    @(negedge tclk);
    @(negedge tclk);
    hresult = temp;
end

阻塞性过程赋值
赋值操作符是 = 的过程性赋值称为阻塞性过程赋值
a = 52;
阻塞性过程赋值语句,当前语句必须执行完成才能执行下一条赋值语句。

非阻塞性过程赋值
非阻塞性过程赋值语句中,使用赋值符号 <=

特殊例子
initial
begin
    wdog_intr <= 0;
    wdog_intr <= 1;
end
在initial语句执行后,wdog_intr的值为1;
verilog HDL标准规定了对同一个reg变量的非阻塞性赋值,按照赋值语句执行的顺序来执行;
wdog_intr先被赋值为0,然后被赋值为1

一个在always语句里读取一个变量(key_a)的值,又在另一条always语句里面对同一个变量进行赋值,并且两条always语句由同一个时钟沿来控制, 怎么才能确定读操作发生在写操作之前,
答案是:用非阻塞赋值可以确保读操作发生在写操作之前。

always @(negedge clock)
    reg_a <= data;			//在此处reg_a 被赋值

always @(negedge clock)
    reg_b <= reg_a;

何时使用哪种过程性赋值语句?(建议)
    对always语句块外用到的变量进行赋值时,使用非阻塞性赋值(<=)
    计算中间结果的时候,用阻塞性赋值(=)

    always @(posedge clk_piol) begin
        bdir_data = ^spi_rdata;				//用阻塞性赋值计算中间结果
        spi_wdata <= bdir_data + rst_cnt;	//用非阻塞赋值对语句块外变量赋值
    end

    always @(spi_wdata) begin
        spi_pariy <= ^ spi_wdata;
    end

连续赋值(assign)与过程赋值的比较

    过程赋值与连续赋值件的不同之处
过程赋值 连续赋值
出现在initial、always语句中 出现在模块(module)中
过程赋值语句的执行与其周围的其他语句是有关系的 与其他语句并行执行;在右侧操作数的值发生变化时执行
驱动变量 驱动线网
使用 = 、<= 赋值符号 使用 = 赋值符号
无assign关键词 使用assign关键词

条件语句
if(condition_l) begin
procedural_statement_1
end
else if (condition_2) begin
procedural_statement_2
end
else begin
procedural_statement_3
end

case 语句
条件表达式和各分支项表达式都不必是常量表达式, 在case语句中,x、z值作为字符值进行比较。

case语句的条件表达式和分支表达式的长度不同会出现什么情况呢?
    进行任何比较前,把case语句中所有的表达式的位宽都统一
    为这些表达式中最长的一个的位宽。

case语句中的无关位
    case语句的两种其他形式:casez、casex,这些形式对x、z值使用了不同的解释。语法与case语句相同,关键字不同

    casez语句,在casez条件表达式和任意分支项表达式中的值为z的位都会被认为是无关位,既那个位被忽略(不进行比较)
    casex语句,值为x或z的位都会被认为是无关位。

    casez语句示例:
    casez(intr_mask)
        4'b1???:rtc_wdata[4] = 0;
        4'b01??:rtc_wdata[3] = 0;
        4'b001?:rtc_wdata[2] = 0;
        4'b0001:rtc_wdata[1] = 0;
    endcase

字符?可以用来代替字符Z,来表示无关位。
casez语句:若intr_mask的第1位是1(忽略intr_mask的其他位),则rtc_wdata[4] = 0;
casez语句:若intr_mask的第1位是0,第2位是1(忽略intr_mask的其他位),则rtc_wdata[3] = 0;
依次类推

循环语句
有4类循环语句 forever 、repeat、while、for循环

forever 循环语句
    循环语句格式:

    forever
        procedural_statement

    此循环语句连续执行过程性语句。因此为了跳出这样的循环,可以在过程性语句内使用中止语句。同时,在过程语句中必须使用某些方式的时序控制,否则forever循环将在0延迟后永远循环下去。
        forever循环语句的示例:
            initial begin
                clk1hz = 0;
                #5 forever
                #10 clk1hz = ~clk1hz;
            end
        上面代码生成一个时钟波形, clk1hz首先被初始化为0,并一直保持为0到第5个单位时刻。此后每隔10个时间单位,clk1hz反向一次。

repeat 循环语句 指定循环次数
    语法格式
        repeat (loop_count)	//循环次数
            procedural_statement

    按照指定的循环次数来执行过程性语句,循环计数表达式的值为 x、z,则循环的次数按照0处理
    repeat(count)
        sum = sum + 10;

    循环语句与重复事件控制的不同

    repeat(loop_count)			//循环语句
        @(posedge clk_rtc) accum = accum + 1;
    等待clk_rtc上出现正跳变沿,然后对accum进行加1,循环执行loop_count 次

    accum = repeat(loop_count) @(posedge clk_rtc) accum + 1;	//重复事件控制
    首先计算 accum + 1,随后等待在clk_rtc上出现 loop_count 次正跳变,最后赋值。

    repeat(NUM_OF_TIMES) @(negedge zclk); //等待zclk上出现NUM_OF_TIMES下降沿,然后执行紧随在 repeat 语句之后的语句。

while 循环语句
    格式:
    while(condition)		// condition 状态状况
        procedural_statement

    while 和C语言相似,若条件表达式位x、z。它也同样按照0(假)来处理。
    while(shift_by > 0) begin
        acc = acc << 1;
        shift_by = shift_by - 1;
    end

for 循环语句
    for(initial_assignment;condition;step_assignment)	//初始值 条件 步骤
        procedural_statement

    for循环语句会重复执行过程性语句若干次。 与C语言相似

    integer k;

    for(k = 0; k < MAX_RANGE ; k = k +1) begin
        if(hold_data[k] == 0) begin
            hold_data[k] = 1;
        end
        else if(hold_data[k] == 1) begin
            hold_data[k] = 0;
        end
        else	begin
            $display("hold_data[k] is an x or a z")
        end
    end

过程性连续赋值
    过程性连续赋值语句是一种能够在 always initial 语句块中出现的语句。
    这种赋值方式可以改写(Override)所有其他语句对线网或者变量赋值。
    它允许赋值语句中的表达式被连续地驱动进入到变量或线网当中去。
    过程连续赋值语句与连续赋值语句是有区别的,连续赋值语句只能出现在initial always 语句之外
        过程性连续赋值语句有两种类型(推测成对出现)
        (1) assign deassign 过程性值语句:对变量进行赋值
        (2) force release 过程性语句:虽然也可以用于对变量赋值,但主要用于对线网赋值。
        assign force语句在某种意义上是 ”连续的“: 就是当assign force 语句生效时,右式中的操作数的任何变化都会引起赋值语句的重新执行。
        过程性连续赋值的目标不能是变量的部分选择或位选择

assign deassign 语句
    assign	过程性语句 可以改写所有的 过程性赋值语句 对变量进行的赋值。
    deassign 过程性语句 用来结束对变量的连续赋值。
    变量中的值一直保留到它被重新赋值为止。

    module d_flip_flop(d,clear,clock,q);
        input d,clear,clock;
        output reg q;

        always @(clear)
            if(!clear)
                assign q = 0;	//d对q无效
            else
                deassign q;

        always @(negedge clock) q <= d;

    endmodule

    若clear 变为0, assign过程性语句使q清零,而不考虑任何时钟沿时的情况,既clock中 d对q没有影响。
    若clear变为1 ,deassign 语句被执行;这就使得连续性赋值被取消,以后clock就能够对q产生影响。
    若assign语句应用于一个已经用assign进行赋值的变量,则先取消原来assign语句的赋值,然后再进行新的过程性连续赋值。3

    reg[3:0]load_ctr;
    ...
    load_ctr = 0;
    ...
    assign load_ctr = nibble^rtc_count;
    ...
    assign load_ctr = 2;	//先取消前面对load_ctr的assign 赋值,然后进行新的过程性连续赋值

    ...
    deassign load_ctr;	//load_ctr 一直保持值为2
    ...
    assign load_ctr[2] = 1;/* 错误 :reg变量的位选择不能够作为过程性连续赋值的目标*/

    第2个assign 语句在进行下一次赋值前先使得前面的第一个assign语句无效。 在deassign语句执行后,load_ctr 的值将一直保持为2 ,直到出现另一个对该变量的过程性连续赋值。

    assign 语句在某种意义上是 ”连续性“的;既在第1个assign 语句执行后到第2个 assign语句开始执行前, nibble 、rtc_count 上的任何变化将使得第1个assign 语句被重新计算。

force release 语句
    force release 过程性语句与 assign deassign 语句非常相识,不同之处是force release 过程语句不仅能够应用于线网,还能够应用于变量。

    当force语句应用于变量时,变量的当前值被force 语句中表达式的值覆盖;当release 语句应用于变量时,变量中的当前值保持不变,除非对它进行过程性连续赋值(在force语句被执行时),这种情况下,连续赋值为变量确立一个新值。

    当用force过程性语句对线网进行赋值时,该赋值语句将忽略线网所有的其他驱动源,直到对该线网执行release 语句。

    wire test_reset;
    ...
    or #1(test_reset,penable,rtc_intr);

    initial begin
        force test_reset = penable&rtc_intr;
        #5;
        release test_reset;

    end

    force 语句的执行得 test_reset值(由penable & rtc_intr求得) ,改写来自于或门基元(原语)的值,直到执行release 语句,才恢复由或门原语输出驱动 test_reset重新生效。
    当force 语句有效的时候(前5个时间单位内),在penable 和rtc_intr上的任何变化都会使能赋值语句被重新执行。

    例:
    reg[2:0]pr_data;
    ...
    pr_data = 2;
    force pr_data = 1;
    ...
    release pr_data;	//pr_data 保持值1
    ...
    assign pr_data = 5;
    ...
    force pr_data = 3;	//当用force过程性语句对线网进行赋值时,该赋值语句将忽略线网所有的其他驱动源
    ...
    release pr_data ;	//pr_data 的值变为 5
    ...
    force pr_data[1:0] = 3;	/* 错误:reg变量的部分选择不能被设置为过程性连续赋值的目标*/
    
    对pr_data的第1次release 使得pr_data的值保持为1。因为在force语句生效时刻没有别的过程性连续赋值语句对该变量进行赋值。
    在执行后一个release语句后,由于对pr_data的过程性连续赋值语句又开始生效,pr_data被重新赋值为5。
    
    握手协议示例
    always 语句可以用于描述交互进程的行为,如交互式有限状态机的行为。同一个模块内的语句可以通过所有always 语句都可见的变量来进行相互通信。不建议使用在 always 内部声明 reg 变量在always 语句之间传递信息。