三态门指的是门电路的输出有3种状态:高电平,低电平和高阻态。
当两个以上的设备分时驱动同一根信号线时,就需要用到三态门。
任意一个时刻,只能有一个设备驱动信号,其他设备需要设定为高阻态。
否则,如果两个设备同时驱动同一信号,一个设备输出高电平,一个设备输出低电平,对于推挽输出来说,两个设备间相当于上拉管和下拉管直接短路,瞬时大电流会将设备烧毁,造成严重后果。
FPGA中设定一个信号为三态门,在Verilog中,就是设定该信号的类型为inout。
其等效的电路结构如图所示。
图中的sda信号声明为inout,当ENB为高电平HI时,sda由data_o驱动。
当ENB为低电平LO时,ENB控制的门电路处于高阻状态,相当于切断了data_o和sda之间的通路,对于连接到sda信号的其他设备而言,相当于根本不存在data_o这个驱动源。
此时,该FPGA可从sda管脚读取外部数据。
对于FPGA而言,即便只有一个外设与FPGA相连,如果要通过同一根信号既能读取FPGA数据又能向FPGA写入数据,那么该信号线就需要定义为三态门。
为了测试FGPA上的三态门,写了一个小模块inout_if.v,代码如下:
`timescale 1ns/10ps
module inout_if #(
parameter INOUTIF_ID_NUM = 0
) (
input rst_n,
input i_clk,
input [1:0] rd_cs,
input rd_en,
inout sda
);
reg [7:0] cnt;
reg clk_out;
assign sda = ((rd_cs == INOUTIF_ID_NUM) & rd_en) ? clk_out : 1'bz;
always @(posedge i_clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
// reset
cnt <= 8'h0;
clk_out <= 1'b0;
end
else begin
cnt <= cnt + 1'b1;
if (cnt == 2**INOUTIF_ID_NUM) begin
cnt <= 8'h0;
clk_out <= ~clk_out;
end
end
end
endmodule
式中用INOUTIF_ID_NUM表示该模块的ID号,在testbench中可以通过指定1和2来实现模块识别。
模块的功能是对输入时钟分频2^INOUTIF_ID_NUM。
顶层模块tb_inout_if.v代码如下:
`timescale 1ns/10ps
module tb_inout_if ();
wire tb_clk;
wire tb_rst_n;
wire tb_sda;
wire [1:0] tb_rd_cs;
wire tb_rd_en;
reg [1:0] rd_cs;
reg rd_en;
assign tb_rd_cs = rd_cs;
assign tb_rd_en = rd_en;
assign tb_sda = (tb_rd_en == 1'b0) ? tb_clk : 1'bz;
// genrate clock
clk_rst clk_rst_inst(
.i_clk (tb_clk ),
.i_rst_n(tb_rst_n )
);
// user logic
inout_if #(
.INOUTIF_ID_NUM (1)
)
inout_if_inst0(
.rst_n (tb_rst_n ),
.i_clk (tb_clk ),
.rd_cs (tb_rd_cs ),
.rd_en (tb_rd_en ),
.sda (tb_sda )
);
inout_if #(
.INOUTIF_ID_NUM (2)
)
inout_if_inst1(
.rst_n (tb_rst_n ),
.i_clk (tb_clk ),
.rd_cs (tb_rd_cs ),
.rd_en (tb_rd_en ),
.sda (tb_sda )
);
initial
begin
rd_en <= 1'b0;
rd_cs <= 2'b00;
#1000 rd_en <= 1'b1;
rd_cs <= 2'b01;
#1000 rd_cs <= 2'b10;
#1000 rd_cs <= 2'b00;
rd_en <= 1'b0;
end
// dump fsdb file for debussy
initial
begin
$fsdbDumpfile("wave.fsdb");
$fsdbDumpvars;
end
endmodule
辅助时钟和复位信号模块clk_rst.v的代码如下:
`timescale 1ns/10ps
module clk_rst(
output reg i_clk,
output reg i_rst_n
);
parameter CLK_PERIOD = 20;
parameter MULT_RATIO = 10;
parameter RESET_TIME = MULT_RATIO * CLK_PERIOD + 1;
initial
begin
i_rst_n <= 1'b0;
#RESET_TIME i_rst_n <= 1'b1;
end
initial
begin
i_clk <= 1'b0;
forever
#(CLK_PERIOD / 2) i_clk <= ~i_clk;
end
endmodule
编译并调用波形查看器的结果如图所示。

从图中可以看出,当tb_rd_cs为0时,sda由tb_inout_if.v中tb_clk直接驱动;
当tb_rd_cs为1时,sda的信号改为由inout_if_inst0驱动,sda的时钟频率是tb_clk的2*2^1分频;
当tb_rd_cs为2时,sda的信号改为由inout_if_inst1驱动,sda的时钟频率是tb_clk的2*2^2分频;

本文介绍了三态门的概念及其在FPGA中的应用,主要用于多个设备分时驱动同一信号线的情况,避免短路风险。在Verilog中,通过声明信号为inout类型来实现三态门功能。文中还提供了测试模块inout_if.v的代码示例,展示了如何根据控制信号切换不同设备对信号线的驱动,并通过波形图验证了三态门的正确工作。
1853





