关键词:MOS, CMOS, 双向开关, PAD
开关级建模是比门级建模更为低级抽象层次上的设计。在极少数情况下,设计者可能会选择使用晶体管作为设计的底层模块。随着电路设计复杂度及相关先进工具的出现,以开关为基础的数字设计慢慢步入黄昏。目前,Verilog 仅仅提供了用逻辑值 0、1、x、z 作为相关驱动强度的数字设计能力,因此,Verilog 中晶体管也仅被当做导通或截止的开关。
MOS 开关
MOS 开关有 2 种,用如下关键字声明:
nmos(N 类型 MOS 管) pmos(P 类型 MOS 管)
rnmos (带有高阻抗的 NMOS 管) rpmos(带有高阻抗的 PMOS 管)
MOS 管用来为开关逻辑建模,数据从输入流入输出,可通过适当设置来开、关数据流。
带有阻抗的 MOS 管,源极到漏极的阻抗较高,且在传递信号时会减小信号的强度。
MOS 管开关结构图如下所示。
例化时,MOS 管第一个端口为输出端,第二个端口为数据输入端,第三个端口为控制输入端。
实例
//tri
pmos pmos1 (OUTX, IN1, CTRL1) ;
//no instantiation name
nmos (OUTX1, IN1, CTRL2) ;
//tri
pmos pmos1 (OUTX, IN1, CTRL1) ;
//no instantiation name
nmos (OUTX1, IN1, CTRL2) ;
MOS 管真值表如下所示,与三态门非常相似。
nmos | 控制端 | pmos | 控制端 | |||||||
---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | x | z | 0 | 1 | x | z | |||
0 | z | 0 | 0/z | 0/z | 0 | 0 | z | 0/z | 0/z | |
1 | z | 1 | 1/z | 1/z | 1 | 1 | z | 1/z | 1/z | |
x | z | x | x | x | x | x | z | x | x | |
z | z | x | x | x | z | x | z | x | x |
CMOS 开关
CMOS 开关用关键字 cmos 和 rcmos (带有高阻抗)声明。
CMOS 有一个数据输出,一个数据输入和 2 个控制输入,结构示意图如下:
信号 PControl 与 Ncontrol 通常是互补的。当信号 Ncontrol 为 1 且 PControl 为 0 时,开关导通。 当信号 Ncontrol 为 0 且 PControl 为 1 时,开关输出为高阻。可以将 CMOS 开关看做是 NMOS 与 PMOS 开关的组合体。
例化时,CMOS 管第一个端口为输出端,第二个端口为数据输入端,第三个端口为 Ncontrol 控制输入端,第四个端口为 Pcontrol 控制输入端。
CMOS 开关例化格式如下。
实例
//coms
cmos c1 (OUTY, IN1, NCTRL, PCTRL) ;
//no instantiation name
cmos (OUTY1, IN1, NCTRL, PCTRL) ;
//coms
cmos c1 (OUTY, IN1, NCTRL, PCTRL) ;
//no instantiation name
cmos (OUTY1, IN1, NCTRL, PCTRL) ;
既然 CMOS 可以看做是 NMOS 与 PMOS 开关的组合体,所以还可以用这两种 MOS 开关去搭建 CMOS 开关,如下:
实例
//the same 2-way instantiation of cmos
nmos n2 (OUTY, IN1, NCTRL) ;
pmos p2 (OUTY, IN1, PCTRL) ;
//the same 2-way instantiation of cmos
nmos n2 (OUTY, IN1, NCTRL) ;
pmos p2 (OUTY, IN1, PCTRL) ;
CMOS 真值表与 MOS 开关类似,注意 Ncontrol 与 Pcontrol 信号的互补性。
双向开关
NMOS、PMOS、CMOS 开关门都是从漏极向源极导通,方向是单向的。Verilog 中还提供了双向导通的开关器件,数据可以双向流动,两边的信号都可以是驱动信号。
双向开关及其阻抗模式的关键字声明如下:
tran tranif1 tranif0 rtran rtranif1 rtranif0
双向开关结构图如下:
tran 开关为两个信号直接的缓存,inout1 或 inout2 均可以是驱动信号。
tranif1 仅当 control 信号为 1 时,开关两边的信号导通。当 control 为 0 时,两个信号断开,有驱动源的信号会和驱动源保持一致的信号值,没有驱动源的信号则呈现为高阻状态。
tranif0 同理。
因此,双向开关常用来进行总线或信号之间的隔离。
例化时,双向开关前
两个端口为数据端,第三个端口为 control 控制输入端。
双向开关例化举例如下:实例
tranif0 tr0 (inout1, inout2, control) ;
//no instantiation name
tranif1 (inout1, inout2, control) ;
hr>电源和地
tranif0 tr0 (inout1, inout2, control) ;
//no instantiation name
tranif1 (inout1, inout2, control) ;
晶体管级电路需要源极(Vdd, 逻辑 1)与地极(Vss, 逻辑 0),分别用关键字 supply1 和 supply0 来定义。 用法如下:
实例
supply1 VDD ;
supply0 GND ;
wire siga = VDD ; //siga is connected to logic 1
wire sigb = GND ; //sign is connected to logic 0
hr>PAD 模型仿真
supply1 VDD ;
supply0 GND ;
wire siga = VDD ; //siga is connected to logic 1
wire sigb = GND ; //sign is connected to logic 0
在《Verilog 教程》的《5.1 Verilog 模块与端口》一节中,涉及过 PAD 模型的编写与仿真。下面,利用三态门对 PAD 模型进行重塑,上下拉功能固定,并利用双向开关对 PAD 连接性进行测试。
利用三态门编写的带有 pullup 功能的 pad 模型如下,pulldown 功能的 pad 模型切换下注释即可。
实例
module PADUP(
//DIN, pad driver when pad configured as output
//OEN, pad direction(1-input, o-output)
input DIN, OEN ,
inout PAD ,
//pad load when pad configured as input
output DOUT
);
//input:(not effect pad external input logic), output: DIN->PAD
bufif0 (PAD, DIN, OEN) ; //0-output
bufif1 (DOUT, PAD, OEN) ; //1-input
pullup (PAD);
//pulldown (PAD); //pulldown
endmodule
利用双向开关控制 PAD IO 连接性的 testbench 编写如下。
module PADUP(
//DIN, pad driver when pad configured as output
//OEN, pad direction(1-input, o-output)
input DIN, OEN ,
inout PAD ,
//pad load when pad configured as input
output DOUT
);
//input:(not effect pad external input logic), output: DIN->PAD
bufif0 (PAD, DIN, OEN) ; //0-output
bufif1 (DOUT, PAD, OEN) ; //1-input
pullup (PAD);
//pulldown (PAD); //pulldown
endmodule
测试流程为 PAD0/1 互连,然后 PAD0 作为输出,PAD1 作为输入,驱动 PAD0, 读取 PAD1 的值。然后两者方向各自取反,驱动 PAD1 读取 PAD0 的值。
PAD2/3 测试过程完全一样。
实例
`timescale 1ns/1ns
module test ;
parameter PULL_UP = 1 ;
parameter PULL_DOWN = 0 ;
parameter IO0_OUT = 0 ;
parameter IO1_OUT = 1 ;
parameter IO2_OUT = 2 ;
parameter IO3_OUT = 3 ;
parameter IO0_IN = 0 ;
parameter IO1_IN = 1 ;
parameter IO2_IN = 2 ;
parameter IO3_IN = 3 ;
reg [3:0] DIN, OEN ;
wire [3:0] DOUT ;
wire [3:0] PAD ;
//test connection control, using tranif1
reg [1:0] con_ena ;
tranif1 (PAD[0], PAD[1], con_ena[0]);
tranif1 (PAD[2], PAD[3], con_ena[1]);
reg err = 0;
task test_io_conn;
//test pull
input pull_type ;
//test conn
input [1:0] xout ; //output postion
input [1:0] yin ; //output postion
DIN[xout] = ~pull_type ;
# 20 ;
if (DOUT[yin] != ~pull_type) begin
$display("write value and get value is: %h, %h", ~pull_type, DOUT[yin]);
err |= 1 ;
end
DIN[xout] = pull_type;
# 20 ;
if (DOUT[yin] != pull_type) begin
$display("write value and get value is: %h, %h", pull_type, DOUT[yin]);
err |= 1 ;
end
endtask
initial begin
con_ena = 2'b01 ;
OEN = 4'b1111 ;
#13 ;
//test between io0/io1
OEN[0] = 0 ;
OEN[1] = 1 ; //gpio0 -> gpio1
test_io_conn(PULL_UP, IO0_OUT, IO1_IN);
OEN[1] = 0 ;
OEN[0] = 1 ; //gpio0 -> gpio1
test_io_conn(PULL_UP, IO1_OUT, IO0_IN);
OEN = 4'b1111 ;
con_ena = 2'b10 ;
OEN[2] = 1'b0 ;
OEN[3] = 1'b1 ;
test_io_conn(PULL_DOWN, IO2_OUT, IO3_IN);
OEN[3] = 1'b0 ;
OEN[2] = 1'b1 ;
test_io_conn(PULL_DOWN, IO3_OUT, IO2_IN);
end
PADUP u_pad_up0( DIN[0], OEN[0], PAD[0], DOUT[0]) ;
PADUP u_pad_up1( DIN[1], OEN[1], PAD[1], DOUT[1]) ;
PADDOWN u_pad_down3( DIN[2], OEN[2], PAD[2], DOUT[2]) ;
PADDOWN u_pad_down4( DIN[3], OEN[3], PAD[3], DOUT[3]) ;
initial begin
forever begin
#100;
//$display("---gyc---%d", $time);
if ($time >= 1000) begin
$finish ;
end
end
end
endmodule // test
`timescale 1ns/1ns
module test ;
parameter PULL_UP = 1 ;
parameter PULL_DOWN = 0 ;
parameter IO0_OUT = 0 ;
parameter IO1_OUT = 1 ;
parameter IO2_OUT = 2 ;
parameter IO3_OUT = 3 ;
parameter IO0_IN = 0 ;
parameter IO1_IN = 1 ;
parameter IO2_IN = 2 ;
parameter IO3_IN = 3 ;
reg [3:0] DIN, OEN ;
wire [3:0] DOUT ;
wire [3:0] PAD ;
//test connection control, using tranif1
reg [1:0] con_ena ;
tranif1 (PAD[0], PAD[1], con_ena[0]);
tranif1 (PAD[2], PAD[3], con_ena[1]);
reg err = 0;
task test_io_conn;
//test pull
input pull_type ;
//test conn
input [1:0] xout ; //output postion
input [1:0] yin ; //output postion
DIN[xout] = ~pull_type ;
# 20 ;
if (DOUT[yin] != ~pull_type) begin
$display("write value and get value is: %h, %h", ~pull_type, DOUT[yin]);
err |= 1 ;
end
DIN[xout] = pull_type;
# 20 ;
if (DOUT[yin] != pull_type) begin
$display("write value and get value is: %h, %h", pull_type, DOUT[yin]);
err |= 1 ;
end
endtask
initial begin
con_ena = 2'b01 ;
OEN = 4'b1111 ;
#13 ;
//test between io0/io1
OEN[0] = 0 ;
OEN[1] = 1 ; //gpio0 -> gpio1
test_io_conn(PULL_UP, IO0_OUT, IO1_IN);
OEN[1] = 0 ;
OEN[0] = 1 ; //gpio0 -> gpio1
test_io_conn(PULL_UP, IO1_OUT, IO0_IN);
OEN = 4'b1111 ;
con_ena = 2'b10 ;
OEN[2] = 1'b0 ;
OEN[3] = 1'b1 ;
test_io_conn(PULL_DOWN, IO2_OUT, IO3_IN);
OEN[3] = 1'b0 ;
OEN[2] = 1'b1 ;
test_io_conn(PULL_DOWN, IO3_OUT, IO2_IN);
end
PADUP u_pad_up0( DIN[0], OEN[0], PAD[0], DOUT[0]) ;
PADUP u_pad_up1( DIN[1], OEN[1], PAD[1], DOUT[1]) ;
PADDOWN u_pad_down3( DIN[2], OEN[2], PAD[2], DOUT[2]) ;
PADDOWN u_pad_down4( DIN[3], OEN[3], PAD[3], DOUT[3]) ;
initial begin
forever begin
#100;
//$display("---gyc---%d", $time);
if ($time >= 1000) begin
$finish ;
end
end
end
endmodule // test
仿真结果如下。
由图可知,13ns 之内,4 个 PAD 均为输入时,PAD 值均与 pull 功能对应,即 PAD0-1 均有上拉功能,PAD2-3 均有下拉功能。
13-53ns 之内,PAD0 作为输出,PAD1 作为输入,并且相连,两者的逻辑值变化一致。同理,53ns-93ns 之内,PAD1 作为输出,PAD0 作为输入, 相连状态下两者逻辑值也是一致的。这说明 PAD0/1 的输入输出功能都是正常的。
PAD2/3 结果也类似,这里不再做说明。