原创 转贴:双向端口I/O的研究

2010-1-23 16:47 3433 10 10 分类: FPGA/CPLD

http://blog.ednchina.com/quiye/269176/message.aspx


 


 


最近项目中要用到双向三态口,于是调用ALTERA 的Q2里LPM.



从上到下分别是真值表,和RTL view里的视图及原理图模块.由于是双向口所以在modelsim里防真.


以前曾自己用verilog写的


module bidir_infer(data,read_write);
input read_write;
inout [1:0] data;
reg[1:0] data_reg,state;//data_reg为映象寄存器,state为寄存器
always@(read_write or data)
begin
  if(read_write==0)//0为输出,1为输入,0时state寄存器的值附给映象寄存器,映象寄存器输出到data端口
                    //1时为输入,data值直接附给state寄存器,且三态口要断开。
     data_reg<=state;
  else
     state<=data;
end
assign data=(read_write==0)?data_reg:2'bz;//1时assign语句data是输出,
endmodule


其testbench


module bidir_test ;
    reg read_writet;
    reg [1:0] data_in;
    wire [1:0] datat,data_out;
    bidir_infer uut (datat,read_writet);
    assign datat=(read_writet==1)?data_in:2'bz;
    assign data_out=(read_writet==0)?datat:2'bz;
    initial
       begin
        read_writet=1;
        data_in=11;
        #100  data_in=10;
        #100  data_in=01;
        #100  data_in=11;
        #100  read_writet=0;       
        #100  read_writet=1;
        data_in=10;
        #100  data_in=01;
        #100  read_writet=0;


       end
endmodule


但是调用LPM的话,是否需要assign 语句使其赋高阻Z呢?


assign datat=(read_writet==1)?data_in:2'bz; 


 assign data_out=(read_writet==0)?datat:2'bz;


从真值表来看是不需要的,于是查了下相关资料,借鉴了下面一部分:


FPGA 中双向端口的设计原理和Verilog硬件语言程序设计
    首先介绍双向端口在 FPGA 内部硬件资源是怎样实现的。 在 FPGA 中 它是通过对三态门控制来实现双向端口的,比如在 Xilinx 的Spartan2E 中 的图例(如图1 所示):


 
图1 双向端口的硬件图
    当z=0 时,上面输出的管子开通,此时数据可以从上面的管子中输出,这时双向端口就作为输出口;当z=1 时,上面的管子被置为高阻态,数据不能从上面的管子输出,此时数据只可以从下面的管子由外向内输入,这时的双向端口是输入口。限于篇幅,我们做一个简单的模型来说明双向 端口的设计。下面我们用 Verilog 硬件语言进行双向端口的程序设计,为了看出双向端口分别作为输入端口和输出端口的功能,我们的模块分别定义一个数据输入口 din 和一个数据输出口 dout,一个三态门选通信号 z,触发 时钟 clk,还有双向端口 dinout。 我们设数据为8 位宽。 图2为该模块图:
 
图2 定义的模块图
    输入口din 定义:input [7:0] din;当双向端口 dinout作为输出口时,我们从 din 端口输入数据到模块中,让数据从dinout口出来。
    输出口 dout定义:output [7:0] dout;当双向端口dinout作为输入口时,我们让数据从dinout口 输入,从输出口dout输出。
    双向端口dinout定义:inout[7:0] dinout;
三态门选通信号z:input z;
    当z=1 时,把三态门置为高阻态,这时 dinout 作为输入口用;当z=0 时,开通三态门,这时 dinout 作为输出口用。
     三态门控制语句为: asigndinout=(!z)?din_reg:8'bz;
     总的完整程序如下:
module dinout(din,z,clk,dout,dinout);
input [7:0] din;
input z;
input clk;
output[7:0] dout;
inout [7:0] dinout;
reg [7:0] dout;
reg [7:0] din_reg;
asign dinout= (!z)?din_reg:8'bz;
always @ (posedge clk)
begin
if(!z)
   din_reg=din;
else
   dout="dinout";
end
   Endmodule


2、 仿真及初始化双向端口
   下面我们对上述程序进行时序仿真。 这里我们选用的 FPGA 芯片为 Xilinx 的 Spartan2E 系列,型 号为 xc2s300e-7pq208,在ISE Foundation6.1 软件中综合及布局布线,并用 Modelsim Simulator进行时序仿真。
   当双向端口dinout作为输出口时,我们不需要 对它进行初始化,只要开通三态门。
   我们设定在200ns后,让数 据10,11,12,13,14,15,16,17,18,19,20 依次从 din 口输入,然后用20ns的采样时钟从dinout口输出。
    它的测试仿真顶层模块为
`timescale 1ns/1ps
module dinoutest();
reg [7:0] din;
reg z;
reg clk;
wire [7:0] dout;
wire [7:0] dinout;
integer i;
dinout uut(
.din(din),
.z(z),
.clk(clk),
.dout(dout),
.dinout(dinout)
);


always #10clk=~clk;
initial begin
   din = 0;
   z = 0;
   clk = 0;
   # 200 din="10";
   for(i=0;i<10;i=i+1)
      #20 din="din"+1;
   end
endmodule
该仿真的时序图如图3 所示:
 


可以看出这时双向端口 dinout作为输出口, 输出
从din 输入口输入到模块中的数据。
当双向端口dinout作为输入口时,我们需要对它进行初始化赋值,此时关闭三态门。 而对双向端 口的初始化赋值,如果把它跟一般的输入口一样直接赋 值 给它,则会出错,因为在定义它的时候是wire型了,而 不是reg 型。 在许多 Verilog 书籍和参考资料中,有关双向端口的初始化赋值介绍的很少。这里需要 用到一个force 命令,用来给dinout 输入赋值。 我 们设定在200ns 后,让数据20,19,18,17,16,15,14,13,12,11,10 从dinout口输入模块,然后用 20ns的采样时钟从输出口 dout输 出。以下是它的仿真顶层模块


`timescale 1ns/1ps
module dinoutest();
reg [7:0]din;
reg z;
reg clk;
wire [7:0] dout;
wire [7:0] dinout;
integeri;
dinoutuut(
.din(din),
.z(z),
.clk(clk),
.dout(dout),
.dinout(dinout)
); 
always #10clk= ~clk;
initial begin
z = 1;
clk = 0;
force dinout="20";
# 200 for (i=0;i<10;i=i+1)
#20 force dinout="dinout-1";
end
endmodule
该仿真的时序图如图4 所示:
 
图4   双向端口作为输入口时的时序仿真
   可以看出这时双向端口 dinout作为输入口,数 据从dinout口 输入模块,然后从输出口dout完整地输出来。


 


文中很容易理解的是不需要自己去赋高阻,因为自己编的是模拟双向三态口,而LPM是已经具有双向三态口模型,但是注意到红色那段话:


当双向端口dinout作为输入口时,我们需要对它进行初始化赋值,此时关闭三态门。 而对双向端 口的初始化赋值,如果把它跟一般的输入口一样直接赋 值 给它,则会出错,因为在定义它的时候是wire型了,而 不是reg 型。 在许多 Verilog 书籍和参考资料中,有关双向端口的初始化赋值介绍的很少。这里需要 用到一个force 命令,用来给dinout 输入赋值。 我 们设定在200ns 后,让数据20,19,18,17,16,15,14,13,12,11,10 从dinout口输入模块,然后用 20ns的采样时钟从输出口 dout输 出。以下是它的仿真顶层模块


我也试过当没有用force 的话,是会出错的.


自己写的testbench如下:


initial
begin
 clk = 1'b0; //时钟要赋初值
end
always #10clk= ~clk;


// in
initial
begin
 in = 1'b0;
 in = #100.0 1'b1;
 in = #1100.0 1'b0;
 //in = #2320.0 1'b1;
 //in = #780.0 1'b0;
end


// out
initial
begin
 out = 1'b0;
 out = #1200.0 1'b1;
 out = #2200.0 1'b0;
end


// wreq2
initial
begin
 wreq2 = 1'b0;
 wreq2 = #200.0 1'b1;
 wreq2 = #1200.0 1'b0;
 //wreq2 = #1940.0 1'b1;
 //wreq2 = #880.0 1'b0;
end


// rdreq2
initial
begin
 rdreq2 = 1'b0;
 rdreq2 = #1300.0 1'b1;
 rdreq2 = #2300.0 1'b0;
 //rdreq2 = #940.0 1'b1;
 //rdreq2 = #740.0 1'b0;
end


// wreq
initial
begin
 wreq = 1'b0;
 wreq = #100.0 1'b1;
 wreq = #1100.0 1'b0;
 //wreq = #2320.0 1'b1;
 //wreq = #780.0 1'b0;
end


// rdreq
initial
begin
 rdreq = 1'b0;
 rdreq = #200.0 1'b1;
 rdreq = #1300.0 1'b0;
 //rdreq = #1580.0 1'b1;
end
 // data[ 31 ]
//  initial
//    begin
//     treg_data = 32'bZ;
//    end
  initial
    begin
     data2 = 32'b0;
     # 200 for(i=0;i<50;i=i+1)
             #20 data2=data2+1;
    end
initial
begin
 #100 force data="200";
  for (j=0;j<50;j=j+1)
  #20 force data="data"+1;


 

文章评论0条评论)

登录后参与讨论
我要评论
0
10
关闭 站长推荐上一条 /2 下一条