Pages

Thursday, 24 November 2011

Handling Bidirectional pins/ports in Verilog HDL

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.

  1. 'inout' can not be declared as 'reg', rather, it's of type net ('wire')
  2. 'wire' can not be used in a procedural block (but can be used in a continuous assignment (aka dataflow modeling style - keyword ‘assign’).
  3. 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