Verilog HDL入门(2)

Verilog 基础语言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

设计中,可以用实例引用语句来描述具体的门。

1
gate_type [instance_name] (term1,term2,...,termN);
  • instance_name:可选的(实例名称)
  • gate_type:上面列出的某种门
  • term1,term2,...,termN:与名 instance_name、类型 gate_type 门的输入/输出端口相连的线网或变量(模块引脚名称)

同一类型门的多个实例能够在一条语句结构中定义。

1
2
3
4
5
gate_type
[instance_name1] (term1,term2,...,termN),
[instance_name2] (term1,term2,...,termN),
...,
[instance_name3] (term1,term2,...,termN),

多输入门

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

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

1
muliple_input_gate_type[instance_name](OutputA,Input1,Input2,...,InputN);

例子:

1
2
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)

这两种类型门只有一个输入,输出可以是一个或者几个,实例引用语法:

1
muliple_input_gate_type [instance_name](out1,out2,...,outN,InputA);

最后的端口是输入端口,其余的端口全部为输出端口

例子:

1
buf u5buf(clk_tx,clk_rx,clk_io1,clk_spi,clk_core);	//缓冲门语句,

clk_core 是 u5buf 缓冲门的输入,该缓冲门有四个输出 clk_tx、clk_rx、clk_io1、clk_spi。

1
not u8not(phase_a_rdy,phase_b_rdy,ready);

ready 是 u8not 非门唯一的输入端口。有两个输出 phase_a_rdy、phase_b_rdy。

多输出门真值

buf 门真值

buf01xz
输出01xz

not 门真值

not01xz
输出10xx

三态门

bufif0、bufif1、notif0、notif1

4 种门均有一个输出、一个数据输入和一个控制输入,三态门基本语法:

1
tristate_gate [instance_name](OutputA,InputB,ControlC);

第一个端口 OutputA 是输入端口,第二端口 InputB 是数据输入,第三端口 ControlC 是控制输出

控制输入,输出可被驱动到高阻态,即值 Z。

  • bufif0:控制端输入为 1,输出为 Z;否则数据从输入传输至输出;bufif0 其中的 0 就是条件
  • bufif1:控制端输入为 0,输出为 Z;否则数据从输入传输至输出;bufif1 其中的 1 就是条件
  • notif0:控制端输入为 1,输出为 Z;否则输入数据的非传输至输出;notif0 其中的 0 就是条件
  • notif1:控制端输入为 0,输出为 Z;否则输入数据的非传输至输出;notif1 其中的 1 就是条件

例:

1
2
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 的真值

bufif0control
01xz
DATA10zxx
00zxx
xxzxx
zxzxx

bufif1 的真值

bufif1control
01xz
DATA1z1xx
0z0xx
xzxxx
zzxxx

notif0 的真值

notif0control
01xz
DATA10zxx
01zxx
xxzxx
zxzxx

notif1 的真值

notif1control
01xz
DATA1z0xx
0z1xx
xzxxx
zzxxx

上拉门和下拉门(电阻):pullup、pulldown

这两种门只有一个输出,没有输入,上拉门(电阻)将输出置 1;下拉门(电阻)输出置 0

语法:

1
pull_gate [instance_name](OutputA);

该门的端口列表只包含 1 个输出。

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 开关有一个输出、一个输入和一个控制输入。

示例引用这类门语法:

1
gate_type [instance_name] (OutputA,InputB,ControlC);

第一个端口为输出,第二个端口为输入,最后一个端口是控制输入。

若 nmos 和 rnmos 开关的控制输入为 0,pmos 和 rpmos 开关的控制输入为 1,则开关关闭,输出 z;
若 nmos 和 rnmos 开关的控制输入为 1,pmos 和 rpmos 开关的控制输入为 0,则开关打开,输入数据传输至输出。

rnmos 和 rpmos 在输入引线和输出引线之间存在比较高的阻抗(电阻)。当数据从输入传输至输出时,由于开关阻抗的存在,数据信号的强度会出现衰减。

实例:

1
2
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 的真值

pmoscontrol
rpmos01xz
Data00zxx
11zxx
xxzxx
zzzzz

nmos 和 rnmos 的真值

nmoscontrol
rnmos01xz
Data0z0xx
1z1xx
xzxxx
zzzzz

cmos(是互补型 MOS)和 rcmos(电阻型 MOS)开关具有一个数据输出、一个数据输入和两个控制信号输入,语法:

1
(r)cmos [instance_name](OutputA,InputB,NControlC,PControlD);

第一端口输出,第二端口输入,第三个端口为 N 沟道控制输入,第四个端口 P 沟道控制输入。

双向开关:

tran、rtran、tranif0、tranif1、rtranif0、rtranif1

双向开关,数据可以双向流动,并且数据通过开关传播时没有延时。

后四个开关设置合适的控制信号而关闭,但 tran 和 rtran 这两种开关不能关闭。

语法(r 是有电阻的开关):

1
(r)tran [instance_name](SignalA,SignalB);	//端口只有两个端口,并且无条件地双向流动,

其他双向开关实例:

1
gate_type [instance_name](SignalA,SignalB,ControlC); //第三个端口是控制端口

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

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

门延迟(在测试文件使用 tb)

信号从任何门的输入到其输出传输延迟可以用门延迟来定义,在门实例引用语句中可以指定门延迟。实例引用带延迟参数的门语句如下:

1
gate_type [delay][instance_name](terminal_list);

delay 门延迟,输入到输出延迟,没有指定,则为 0

门延迟最多由 3 类延迟值组成:上升延迟、下降延迟、截止延迟

门延迟可指定包含 0、1、2、3 个延迟值,下表列出了指定不同延迟值个数时,delay 的 4 种表示方式

实例数组

需要多次实例引用时,门实例应用语句中可以指定一个范围,生成多个实例

1
gate_type [delay] instance_name [leftbound:rightbound](list_of_terminal_names);

leftbound:rightbound 是任意常数表达式(可以为负数)。

1
2
3
4
5
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 编译指令设置默认(缺省)的线网类型。编译指令格式:

1
`default_nettype net_type 

说明:

1
`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
23
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 语句来定义:

1
2
3
4
5
6
7
8
9
10
primitive UDP_name(OutputName,List_of_inputs);
Output_declarations //输出声明 这里类似于模块
List_of_input_declarations //输入声明列表 这里类似于模块
[Reg_declaration] //寄存器说明
[Initial_statement] //初始化语句

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

可以使用如下形式的语句来定义:

1
2
3
4
5
6
7
8
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。以多路选择器为例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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(对其他未定义的输入组合项也是如此)。

1
2
3
4
5
6
7
8
9
10
11
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 的状态进行初始化:

1
initial reg_name = 0,1,or x;

初始化语句在 UDP 定义中出现。

电平触发的时序逻辑 UDP

下面是 D 锁存器建模的电平触发的时序逻辑的 UDP 举例,只要时钟为低电平 0,数据就从输入传递到输出;否则输出值被锁存。

1
2
3
4
5
6
7
8
9
10
11
12
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 型沿触发的触发器建模。一条初始化语句用于初始化该触发器的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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,现在就能够在模块中像基元门那样,实例引用该原语(基元):

1
2
3
4
5
6
7
8
9
10
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

沿触发的和电平敏感的混合行为

在同一个表中能够混合电平触发和沿触发项。这种情况下,边沿变化在电平触发之前处理,即电平触发项覆盖边沿触发项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 中的任一个
b0 或 1 中任选一个
-不变
(AB)值由 A 变到 B
*与 (??) 相同,任意值变换到任一值
r上跳变沿,与 (01) 相同
f下跳变沿,与 (10) 相同
p(01)、(0x) 和 (x1) 的任一种
n(10)、(1x) 和 (x0) 的任一种

negedge 下降沿,posedge 上升沿