FPGA的雙向口在FPGA的設計應用中使用及其廣泛,如I2C接口中的SDA,3線制的SPI接口中的數據線,傳統控制總線中的數據總線,以及內存的訪問DDR3/DDR4的數據總線等都是雙向訪問的。雙向訪問涉及到的概念比較多,如三態的概念,高阻的概念,輸入、輸出引腳合並,輸入輸出分時復用等概念,因此初學者往往比較迷惑,覺得無所適從,本文從底層基本原理入手,揭示雙向口的機理,並用Verilog程序開發為例一步步引導大家如何使用雙向口(inout)的使用與開發。
- 雙向口涉及的基本模型
- 三態門
為了描述方便,這里給兩個命名tri0和tri1(tri是三態門(tri-state的縮略寫法,其實在Verilog語法中有兩個模型與之對應,分別為bufif0,bufif1。圖1,2中的oe在傳統的三總線結構中,通常對應OE(讀)或WE(寫)。
-
-
- tri0
-
bufif0是三態門模型,其例化格式如下:
bufif0 tri0 (out, in, oe); //tri0是bufif0的例化名。
其電路形態形態如圖1:
圖1 bufif0
在這兩個模型中,oe端決定輸出的形態,在tri0的模型中,如果oe為’0’, out就得到out0(out0是FPGA內部邏輯產生的值)的值,最終輸出到端口PAD上。如果 oe為’1’,此時三態門的輸出為高阻狀態,在Verilog 描述中用’Z’表示,即三態門與外界是斷開狀態,如圖2所示。
圖2
圖1,2中的oe在傳統的三總線結構中,通常對應OE(讀)或WE(寫)。
-
-
- tri1
-
bufif1是另一種三態門模型,其例化格式如下:
bufif1 tri1 (out, in, oe); //tri1是bufif1的例化名。
其電路形態形態如圖3:
圖3 buffif1
在這兩個模型中,oe端決定輸出的形態,在tri1的模型中,如果oe為’1’, out就得到out0(out0是FPGA內部邏輯產生的值)的值,最終輸出到端口PAD上。如果 oe為’0’,此時三態門的輸出為高阻狀態,在Verilog 描述中用’Z’表示,即三態門與外界是斷開狀態,如圖4所示。
圖4
圖3,4中的oe在傳統的三總線結構中,通常對應OE#(讀)或WE#(寫)。
- 輸入、輸出在雙向口合並
FPGA的I/O基本上都支持雙向數據操作,但是由於對外輸出端口只有一個,因此需要在端口處合並。
-
- 利用tri0合並,如圖5,
圖5
在圖5中,輸出流向從FPGA內部邏輯out0–>out–>PAD;輸入流向PAD–>in–>FPGA 內部邏輯。
-
- 利用tri1合並,如圖6
圖6
在圖6中,輸出流向從FPGA內部邏輯–>out0–>out–>PAD;輸入流向PAD–>in–>FPGA 內部邏輯。
-
- 輸入、輸出分時復用。
從圖5,6可以看出,由於PAD 共享輸入、輸出。一般在推拉驅動模型中,三態門的輸出能力相對較強,考慮到如果接到FPGA外部器件有同樣的接口,應該嚴格控制他們的時序關系,以免發生短路。如圖7,
圖7
在圖7中如果a,b兩個器件同時輸出(兩個器件的oe都為’1’),如果恰好一個器件輸出為高,一個為低,則會引起短路現象。因此要嚴格控制時序,保證a,b兩個器件避開由內部邏輯同時驅動輸出的情況。只有在兩個器件oe一個為高,另一個為低,或者兩個器件的oe都為低的時候,兩個器件的端口才能連在一起。
- 分時復用的實現
- 推拉結構
在傳統的工業控制總線中,分為主從模式。一般MCU或FPGA為MASTER,SRAM 、EPROM等器件為從模式。在這種模式下,FPGA生成控制信號oe, 同時取反后接到對方的OE端上。如圖8:
圖8
圖8中,由於oe是由一個主器件控制,因此實現推拉模式,即主器件輸入,從器件輸出;主器件輸出,從器件輸入。從輸出角度看,在輸出的時段內,高低電平直接輸出,這一點不同后面要介紹的漏極開路(OD)帶上拉電阻的結構。
推拉MOS管模型如圖9:
圖9
例1:Master 模式單線控制雙向接口(Verilog)。
門級描述:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
module bidir_gate
(
input clk,
input rst,
inout [3:0] a,
output noe,
output reg [3:0] in_val
);
reg [3:0] counta, countb;
assign noe = ~counta[3];
bufif1 tri1_0(a[0], countb[0], ~noe);
bufif1 tri1_1(a[1], countb[1], ~noe);
bufif1 tri1_2(a[2], countb[2], ~noe);
bufif1 tri1_3(a[3], countb[3], ~noe);
always@(posedge clk or posedge rst)
if(rst)
begin
counta <= 0;
countb <= 0;
end
else
begin
counta <= counta + 1;
if(counta == 15)
countb <= countb + 1;
if(noe)
in_val <= a;
end
endmodule
|
RTL描述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
module bidir_RTL
(
input clk,
input rst,
inout [3:0] a,
output noe,
output reg [3:0] in_val
);
reg [3:0] counta, countb;
assign noe = ~counta[3];
assign a = (!noe) ? countb : 4'bZZZZ; //雙向口輸出
always@(posedge clk or posedge rst)
if(rst) begin
counta <= 0;
in_val <= 0;
end
else begin
counta <= counta + 1;
if(counta==15)
countb <= countb + 1;
if(noe)
in_val <= a; //獲得輸入,可以給FPGA內部其它模塊使用
end
endmodule
|
在RTL的描述中可以看出,在Verilog中直接使用高阻4’bZZZZ就起到了三態門的效果,因此應習慣這種使用方法。
例2:Master 模式雙向結構(Verilog RTL), 結構模式參看例1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
module bidir_gate
(
input clk,
input rst,
inout [3:0] a,
output noe,
output nwe,
output reg [3:0] in_val
);
reg [3:0] counta, countb;
assign noe = ~counta[3];
assign nwe = counta[3]; //采用tri0模型
assign a = ~nwe ? countb : 4'bZZZZ;
always@(posedge clk or posedge rst)
if(rst) begin
counta <= 0;
end
else begin
counta <= counta + 1;
if(counta==15)
countb <= countb + 1;
if(~noe) in_val <= a;
end
endmodule
|
-
-
- 上拉電阻結構(OD結構)
-
上拉電阻結構適合總線模型,如I2C總線,485總線等多master多slave的結構。在上拉電阻的結構中,雙向口一般不需要讀(noe)、寫(nwe)控制接口配合。但需要協議配合實現。以I2C 為例,在主、從的結構中都采用OD的方式如圖10,因此多個器件的輸出端可以直接通過連線接在一起。但在由於輸出沒有推拉結構,在期望高電平輸出時,由於MOS管關閉,實際輸出為也為高阻,因此需要在總線上結上拉電阻,以保證在輸出階段且輸出為高電平時,可以得到確保的高電平狀態,如圖11,
圖10 OD結構
圖11
在OD(或OC)的設計中,Verilog描述在輸入、低電平輸出時與推拉結構一致,只有在輸出高電平時不同。在輸出高電平時要確保真正輸出的是高阻。I2C總線接口就是標准的OD結構,在SDA,SDL都要加上拉電阻,如圖12
圖12
由於inout信號一般只在端口使用,因此在FPGA的內部邏輯(內部模塊)將會把inout(雙向口)變換成input, output類型進行傳遞, 具體的使用見例3.
例3:I2C接口Verilog描述。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
module I2C_intereface
(
inout SCL,
inout SDA,
input [7:0] datain,
output [7:0] dataout
);
wire SCL_in;
wire SDA_in;
wire SCL_out;
wire SDA_out;
assign SCL = SCL_out ? 1’bZ : 1’b0; //這里處理方式與推拉結構不同。
assign SCL_in = SCL;
assign SDA = SDA_out ? 1’bZ : 1’b0;
assign SDA_in = SDA;
I2C I2C_inst
(
.SCL_in (SCL_in),
.SDA_in (SDA_in),
.SCL_out (SCL_out),
.SDA_out (SDA_out),
.datain (datain),
.dataout (dataout)
)
endmodule
|
上面程序中,輸入直接賦值給SDA_in,是全階段賦值,即把輸出階段也給輸入端賦值,因此在I2C 的程序中,何時使用輸入的值,應有嚴格定時。關於I2C的時序描述請參照I2C部分內容。