在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       else if(cnt==10'd999)

           cnt       else

           cnt

always@(posedge i_clk or negedge i_rst_n)

      if(i_rst_n==1'b0)

           clk_out       else if(cnt==10'd999)

           clk_out

always@(posedge i_clk or negedge i_rst_n)

      if(i_rst_n==1'b0)begin

           sda_out            sda_en             end

      else begin

           sda_out            sda_en             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       else if(cnt==10'd999)

           cnt       else

           cnt

always@(posedge i_clk or negedge i_rst_n)

      if(i_rst_n==1'b0)

           clk_out       else if(cnt==10'd999)

           clk_out

always@(posedge i_clk or negedge i_rst_n)

      if(i_rst_n==1'b0)begin

           sda_out            sda_en             end

      else begin

           sda_out            sda_en             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       else if(cnt==10'd499)

           cnt       else

           cnt

always@(posedge i_clk or negedge i_rst_n)

      if(i_rst_n==1'b0)

           clk_out       else if(cnt==10'd499)

           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的博客