Handling Bidirectional pins/ports in Verilog HDL:
How to code a module with bidirectional ports and how to write a test-bench for it.
Let us first understand why it is a little tricky for a novice.
- 'inout' can not be declared as 'reg', rather, it's of type net ('wire')
- 'wire' can not be used in a procedural block (but can be used in a continuous assignment (aka dataflow modeling style - keyword ‘assign’).
- A 'reg' type can only be updated (on the left hand side of the assignment statement) inside procedural blocks, that is, either in ‘initial’ or an ‘always’ block.
This means that an 'inout' type port may not be used as is.
The workaround is to use an additional enable signal/variable and a temporary buffer.
The enable signal will facilitate selection whether the 'inout' port be an output or an input. The temporary register (this will be used as output within the always block) used gets value from the process (could be another procedural or continuous assignment block) running within the module.
Summary:
procedural block - initial and always blocks (behavioral-constructs)
'wire' type as output. No register on LHS of assignment.
continuous assignment - one that starts with 'assign' keyword (data-flow constructs)
'reg' type as output. No wires on LHS.
An illustration will help:
Device Under Test:
module io_block(
sda_oe,
sda,
m_clk,
m_rst
);
reg [2:0] from_process;
inout sda;
input m_clk, m_rst, sda_oe;
reg sda_buff;
assign sda = (sda_oe) ? sda_buff : 1'bz;
always @(m_clk, m_rst) begin
if (m_rst) begin
sda_buff <= 0;
end
else begin
if (sda_oe != 0)
sda_buff <= from_process[1]; //replace from_process
end
end
always @(m_clk, m_rst) begin :process
if (m_rst) begin
from_process <= 0;
end
else begin
if (sda_oe != 0)
from_process <= from_process+1;
end
end
endmodule
Test-bench:
module io_block_tb;
// Inputs
reg sda_oe;
reg m_clk;
reg m_rst;
reg sda_read_buff;//stores input to be forced
// Bidirs
wire sda;
// Instantiate the Unit Under Test (UUT)
io_block dut(
.sda_oe(sda_oe),
.sda(sda),
.m_clk(m_clk),
.m_rst(m_rst)
);
//clock:
always
#2 m_clk = ~ m_clk;
initial begin
// Initialize Inputs
sda_oe = 0; m_clk = 0; m_rst = 1;
#5 m_rst = 0;
sda_read_buff =1; //acts as input
#5 sda_read_buff =0;
#5 sda_read_buff =1;
#50 sda_oe = 1; //sda as an output
#50 sda_oe =0; //sda as an input
#100 sda_read_buff =0;
end
assign sda = (sda_oe) ? 1'bz : sda_read_buff ; //sda as an output
endmodule
sir, what ever u have written about the declaration of the inout in verilog using one enable and one temporary register, is this the one of the way we adopt to solve this problem or this is the hard and fast rule???
ReplyDeleteAs far I know, this is the way bidirectional port can be handled. The problem is with the conflict between continuous assignment and procedural block.
DeleteBy the way, sorry for the late reply as I didn't expect any comments.
DeleteThis seems to be going good so I will be checking for comments/queries more often :)
Hi Ajay Sir,
ReplyDeleteI believe there is a small typo. This code couldn't have complied because the instantiation of dut done in TB has left one port unassigned i.e "scl".
module io_block(sda_oe, scl, sda, m_clk, m_rst );
io_block dut(.sda_oe(sda_oe), .sda(sda), .m_clk(m_clk), .m_rst(m_rst) );
Doesn't it need a port assigned to it
-Pankaj
Hi Pankaj
DeleteWell spotted! this module is an excerpt from a bigger module and scl could have been left out. I will remove scl bit so that it doesn't create confusion. Though, there won't be any problem in compilation or synthesis.
Cheers.