三态门详解

在FPGA中三态门比较常见,因为FPGA是做为一个高速处理的器件,免不了要进行输入输出数据,常规的输入和输出是分开的两个接口要不停的切换比较麻烦,在FPGA中用的双向口一般都是用三态门来作为输入和输出的,这样优点是只要一个接口就可以输入输出比较节约逻辑资源,但缺点是三态门的处理没有常规两个I/O的方便,这里我们来看看怎样使用三态门,下图是三态门的结构。

三态门的结构

当sda_en为高时SDA作为输出口,输出sda_out的数据,当sda_en为低时三态门是处于高阻态,这时三态门是作为输入口使用,这时输入的数据为SDA的数据。

下面是用vivado 写的一段程序,当做为输出时将我们产生的clk_out的频率输出,代码如下。

module stm
(
i_clk,
i_rst_n,
SDA
);

input i_clk;
input i_rst_n;
inout SDA;

reg sda_en;
reg sda_out;
reg [9:0] cnt;
reg clk_out;

always@(posedge i_clk or negedge i_rst_n)
if(i_rst_n==1'b0)
cnt <= 10'd0;
else if(cnt==10'd999)
cnt <= 10'd0;
else
cnt <= cnt + 1'b1;

always@(posedge i_clk or negedge i_rst_n)
if(i_rst_n==1'b0)
clk_out <= 1'b0;
else if(cnt==10'd999)
clk_out <= ~clk_out;

always@(posedge i_clk or negedge i_rst_n)
if(i_rst_n==1'b0)begin
sda_out <= 1'b0;
sda_en <= 1'b1;
end
else begin
sda_out <= clk_out;
sda_en <= 1'b1; //这里为1表示作为输出口使用
end

assign SDA = (sda_en)? sda_out:1'bz;

endmodule

这个仿真脚本是随意编写的一个,如果你有更好的脚本可以使用自己编写的,我使用的vivado 仿真脚本如下:

module test_tb();

reg i_clk;
reg i_rst_n;

wire SDA;

stm u1
(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.SDA(SDA)
);

initial begin
i_clk = 0;
i_rst_n = 1;
#10;
i_rst_n = 1'b0;
#120;
i_rst_n = 1'b1;
end

always #40 i_clk = ~i_clk;

endmodule

下图是仿真时产生的波形,可以清楚的看到当sda_en为高时,我们输出SDA是等于clk_out的,所以可以看出这时这个三态门是作为输出口使用的。(当然这使能信号sda_en是高输出还是低输出,可以自己去设置我现在写的程序是高输出低输入,也可以高输入低输出只要将程序改一下就可以了,具体自己可以去尝试。)

上图是三态门作为输出口使用的,下面是三态门作为输入使用的代码如下:

module stm
(
i_clk,
i_rst_n,
SDA
);

input i_clk;
input i_rst_n;
inout SDA;

reg sda_en;
reg sda_out;
reg [9:0] cnt;
reg clk_out;

always@(posedge i_clk or negedge i_rst_n)
if(i_rst_n==1'b0)
cnt <= 10'd0;
else if(cnt==10'd999)
cnt <= 10'd0;
else
cnt <= cnt + 1'b1;

always@(posedge i_clk or negedge i_rst_n)
if(i_rst_n==1'b0)
clk_out <= 1'b0;
else if(cnt==10'd999)
clk_out <= ~clk_out;

always@(posedge i_clk or negedge i_rst_n)
if(i_rst_n==1'b0)begin
sda_out <= 1'b0;
sda_en <= 1'b1;
end
else begin
sda_out <= clk_out;
sda_en <= 1'b0; //这里为0表示作为输入口使用
end

assign SDA = (sda_en)? sda_out:1'bz;

endmodule

当做为输入口时,我在vivado仿真脚本里添加了一个输入时钟clk_out,为了加以区别,输入口的clk_out这个的频率是计数到499就翻转而作为输出口的clk_out是计数到999才翻转,所以输人口的clk_out的频率是输出口的clk_out 的频率的两倍,代码如下:

module test_tb();

reg i_clk;
reg i_rst_n;

wire SDA;

stm u1
(
.i_clk(i_clk),
.i_rst_n(i_rst_n),
.SDA(SDA)
);

reg [9:0] cnt;
reg clk_out;

always@(posedge i_clk or negedge i_rst_n)
if(i_rst_n==1'b0)
cnt <= 10'd0;
else if(cnt==10'd499)
cnt <= 10'd0;
else
cnt <= cnt + 1'b1;

always@(posedge i_clk or negedge i_rst_n)
if(i_rst_n==1'b0)
clk_out <= 1'b0;
else if(cnt==10'd499)
clk_out <= ~clk_out;

assign SDA = clk_out;

initial begin
i_clk = 0;
i_rst_n = 1;
#10;
i_rst_n = 1'b0;
#120;
i_rst_n = 1'b1;
end

always #40 i_clk = ~i_clk;

endmodule

SDA作为输入口时,仿真波形如下,大家可以看到当sda_en为低时,即使sda_out上面有我们之前产生的clk_out计数到999的那个频率,但SDA依然是clk_out计数到499时的频率,所以可以看出这时SDA是作为输入口使用的。

功能描述如下图:

sda_en为1时作为输出,为0时为高阻作为输入

最后建议大家用我的程序仿真时,如果作为输出口使用仿真程序请用作为输出口仿真脚本,作为输入口使用时请使用作为输入口的仿真脚本,这两个脚本不是通用的。

文章转载自:taowei1314520的博客

最新文章

最新文章