【FPGA基础】双向端口inout端口的使用指北
在查阅了各种书和帖子之后,总结了以下inout端口的使用注意事项。
(以下资料来源:
《Xilinx FPGA开发实用教程 第二版》
https://www.cnblogs.com/sea-wind/p/4924567.html 《FPGA中的INOUT接口和高阻态》
https://blog.csdn.net/kebu12345678/article/details/80587614 《FPGA中inout端口使用方法总结 (Verilog)》
https://blog.csdn.net/u013608424/article/details/72865722 《FPGA学习之inout双向口》
)
首先是一些要先理解的基本概念:
0、什么是双向端口inout端口
顾名思义,双向端口既可以作为输入端口接收数据,也可以作为输出端口发出数据,它对数据的操作是双向的。双向端口在综合时是以三态门的形式存在的,其典型结构如图所示。
1、三态门
在Xilinx的《XST User Guide》中给出了三态门的verilog描述
1 // Tristate Description Using Concurrent Assignment 2 3 // Combinatorial Always Block Can be Used too. 4 5 module v_three_st_2 (T, I, O); 6 7 input T, I; 8 9 output O; 10 11 assign O = (~T) ? I: 1’bZ; 12 13 endmodule
上述描述表明,当控制信号T=1时,管子被置为高阻态,输出O为高阻态;当控制信号T=0时,管子开通,输出O=输入I。(跟控制信号T是高有效或低有效有关)
2、高阻和悬空
三态门中有一个状态是高阻。高阻,即可以认为是没有输出,作为输出端口而言,对下级电路没有任何影响。悬空是针对输入端口来说的,也就是说没有接输入。这也就意味着,实际上高阻和悬空是一个状态,在HDL语言里都表示为Z。
也就是说,一个输出端口在高阻态的时候,其状态是由于其相连的其他电路决定的,可以将其看作是输入。
双向端口用作输出时,就和平常一样,但双向端口作输入引脚时需要将此引脚置为高阻态,这样其电平就可以由外部输入信号决定了(这是高阻态的特性)。
在上述基本概念的基础上,讨论三个方面的问题:
一、双向端口的实现原理
通过上面的基本概念应该已经清楚双向端口的实现原理了,只要搞清楚上面管子的开通状态与整个双向端口的对外特性之间的关系就行了。
当上面的管子开通时,此时数据可以从上面的管子中通过,此时双向端口为输出端口,Device IO的赋值 from FPGA。
当上面的管子被置为高阻态时,数据只能从下面的管子通过,此时双向端口为输入端口,Device IO的赋值 to FPGA。
而控制信号T与管子开关状态之间的关系下面结合代码来讲。
二、双向端口的Verilog实现
在Verilog中实现双向端口,首先要明确inout端口的变量类型。inout端口只能被定义为net型变量,只能采用assign赋值语句,不能在always块内使用,详细解释可以看我的另一篇博客《【Verilog HDL】Verilog的端口类型以及端口连接规则 》。要注意这一点与VHDL中双向端口是使用方法不同。
接下来的代码展示了在输入情况下和输出情况下的赋值语句。
1 module inout_def(clk,data_inout) 2 input clk; 3 inout data_inout; 4 reg data_out; 5 reg data_out_control; 6 //define data_out 7 8 //define data_out_control 9 10 //assign data_inout 11 assign data_inout=data_out_control?data_out:1'bz; 12 13 //assign data_in 14 wire data_in; 15 assign data_in=(!data_out_control)&data_inout; 16 17 endmodule
控制信号data_out_control决定了双向端口data_inout的特性。当data_out_control=1时,data_inout作输出,此时将data_out的值赋给data_inout;当data_out_control=0时,data_inout作输入,将data_inout赋给data_in。实际上,当inout端口面向输出处于高阻态时,即data_out_control=0时,inout端口就相当于输入,所以可以直接当作input端口使用。
// 默认inout端口作为接收使用,即输入,可以直接接到例化端口上 receive_data RE1( // Can .sysclk (sysclk ), .rst (rst ), .recv_fifo_empty (recv_fifo_empty_1 ),// input .open (open1 ),// input .din (dinout_a ), .ubcWrfifo_rden (ubcWrfifo_rden_1 ),// cpu input .ubcWrLenfifo_rden (ubcWrLenfifo_rden_1 ),// cpu input .ubcWrfifo32_rden (ubcWrfifo32_rden_1 ), .ubcWrfifo32_dout (ubcWrfifo32_dout_1 ), .ubcWrfifo_dout (ubcWrfifo_dout_1 ),// output .ubcWrLenfifo_dout (ubcWrLenfifo_dout_1 ),// output .ubcWrLenfifo_empty (ubcWrLenfifo_empty_1 ),// output .recv_fifo_rden_b (recv_fifo_rden_b_1 ),// output .CS_B (CS_B_1_rx ),// output .start_irq (start_irq_1 ),// output .end_irq (end_irq_1 ) // output ); // 当inout端口作为发送使用,即输出时,用assign语句 assign dinout_a = ( open_a_send ) ? dout_a : 8'bz;
三、双向端口的仿真
编写测试模块时,对于inout类型的端口,需要定义成wire型变量,而其他输入端口都定义成reg型,这两者是有区别的。
当上面的例子中的data_inout用作输入时,需要赋值给data_inout,其余情况可以断开。此时可以用assign语句实现:
assign data_inout = link ? data_in_t : 1'bz;
其中的link,data_in_t是reg型变量,在tb文件中赋值。
另外,可以设置一个输出端口观察data_inout用作输出的情况:
wire data_out;
assign data_out_t = (!link) ? data_inout : 1'bz;
需要注意的是:当给data_inout赋值的时候(它作输入端口时),只能在原INOUT数据为高阻态时才可以赋值,所以link信号即该INOUT数据为高阻态时的控制信号,也就是说link = !data_out_control;当不需要测试文件给data_inout数据赋值的时候,测试文件的data_inout接口因为高阻态,从而不影响源文件data_inout接口的其它操作。