Electronic – removing inout from port arrays

digital-logicrtlsynthesissystem-verilogverilog

This question is in the context of using verilog/systemverilog for synthesizable RTL.

I have some vector signals that are going across module boundaries that are currently defined as inout ports.
The reason they are inout ports is that some of the vector indices are assigned in some places within the hierarchy, while others are assigned elsewhere.
However, it is hugely beneficial to be able to consider these as one large shared bus that can route to many locations.

The problem is that I am using these inout ports in synthesized RTL and some of the backend tools have issues with these inouts when it comes to low-power synthesis and logical equivalency checking.

There is really only one driver ever to these signals, so they are never really needed to be inout. The problem is just that some indices would be ideally marked as input, while others are marked as output.

In the past a solution has been to use sparse vectors, one for each hierarchical location that could be assigning one of these indices. Then, they are all combined, using generate blocks and location masks to determine how to map each of the possible drivers to the final signal.

This approach ends up being a lot of overhead and requires doing some level of masking/mapping at each hierarchical location, and all of this is only to avoid having singly-driven ports defined as inout direction.

Since my backend tool flow requires not using inout ports in this scenario, does someone have another approach that may be more coding efficient?

Here is some sample code to illustrate (untested):

My ideal code is as follows:

module driver0(inout [1:0] sig);
    assign sig[0] = 'b1;  // drive with some real value
endmodule

module driver1(inout [1:0] sig);
    assign sig[1] = 'b1;  // drive with some real value
endmodule

module top();
    wire sig[1:0];
    driver0 u0(.sig(sig));
    driver1 u1(.sig(sig));
endmodule

No bits of the sig vector are multi-driven, they are just driven in different modules.

My non-inout solution is follows:

module driver0(output [1:0] sig);
    assign sig[0] = 'b1;  // drive with some real value
    assign sig[1] = 'b0;  // clear unused indices
endmodule

module driver1(output [1:0] sig);
    assign sig[1] = 'b1;  // drive with some real value
    assign sig[0] = 'b0;  // clear unused indices
endmodule

module top();
    wire [1:0] sig, sig0, sig1;
    driver0 u0(.sig(sig0));
    driver1 u1(.sig(sig1));

    assign sig = sig0 | sig1;
endmodule

It requires having a temporary sparse vector for each possible driver and then combining them.
This was just an example with a 2-bit vector and two drivers.
You can imagine that it takes quite a bit more effort and wasted code when the vector is large, as it requires looping over each index and then having some mechanism for determining which bits are driven in each hierarchical level.

Best Answer

The simplest solution is split output and input for the driver modules.

module driver0 #(paramerter N=8)(input [N-1:0] sig_in, output sig_out);
    assign sig_out = 'b1;  // drive with some real value
endmodule

module driver1 #(paramerter N=8)(input [N-1:0] sig_in, output sig_out);
    assign sig_out = 'b1;  // drive with some real value
endmodule

You can allow the output to also be an input of the same module in top

module top();
    paramerter N=8;
    wire [N-1:0] sig;
    driver0 #(N) u0(.sig_in(sig), .sig_out(sig[0]));
    driver1 #(N) u1(.sig_in(sig), .sig_out(sig[1]));
endmodule

Or you can prevent the driver's output to feedback by slicking the array for the input. This may take a bit more caution. You could partition the inputs signals as two or more as well. Which ever is more intuitive easier manage for the project.

module top();
    paramerter N=8;
    wire [N-1:0] sig;
    driver0 #(N-1) u0(.sig_in(sig[N-1:1]), .sig_out(sig[0]));
    driver1 #(N-1) u1(.sig_in({sig[N-1:2],sig[0]}), .sig_out(sig[1]));
endmodule

Since IEEE Std 1364-2001, Verilog allows some fancy port definitions. See IEEE Std 1364-2001 § 12.3.3 Port declarations or SystemVerilog's IEEE Std 1800-2012 § 23.2.2 Port declarations
Support for this may very across tools as this practices is not common. If your synthesizer has issues with interface it may have issues with port aliasing as well.

module driver0 ( .sig({sig_in,sig_out}) );
  input sig_in;
  output sig_out;
  assign sig_out = 'b1;  // drive with some real value
endmodule

module driver1 ( .sig({sig_in,sig_out}) );
  input sig_in;
  output sig_out;
  assign sig_out = 'b1;  // drive with some real value
endmodule

module top();
    wire [1:0] sig;
    driver0 u0(.sig(sig));
    driver1 u1(.sig(sig));
endmodule

If the bus is going across several lays of hierarchy then the above solution will likely be tedious to manage. An interface should be used. Some guidelines to reduce synthesis limitations:

  • Define all signals as a logic type. Only exception are tri-states where the bits are expected to have two or more drivers.
  • Define all tri-states as wire or tri
  • Assign all logic types within a always_ff, always_comb, always_latch (if latch is necessary block. logic types can be assigned with assign statements, but not all tools validate driving conflicts with assign
  • Keep tri-states assignments as simple of possible
    • Ex: assign myif.io = drive_enable ? io_out : 'z;
  • Do not use interface as a port in the top most module that will be synthesized. Many current synthesizers will flatten and localize interfaces with escaped names. If needed, create wrapper module as a translation layer between interface and top most port signals. Ex:

    interface my_interface(input clk, 
        /*May need to declare top port level tri-stats as an interface port*/
        inout [7:0] io, ...
        /*Optional output logic ... , input ... */ );
      logic in_a, in_b, out;
      //wire [7:0] io; // internal tri-state
      ...
    endinterface : my_interface
    module same_port #(parameter N=1) (a,a); // Synthesizer might support this, see manual
      inout [N-1:0] a;
    endmodule : same_port
    ...
    // RTL top with interface as port
    module my_top(my_interface myif);
      ...
      sub sub0 ( .myif(myif), .*);
      ...
    endmodule : my_top
    // wrapper to use for synthesis
    module synth_top( output logic out, input in_a, in_b, clk, inout [7:0] io, ...);
      my_interface myif( .* );
      my_top mytop( .myif(myif) );
    
      // connect myif sigs with port sigs thate are not already connected with .*
      always_comb begin : conn_in
        myif.in_a = in_a;
        myif.in_b = in_b;
        ...
      end : conn_in
      always_comb begin : conn_out
        out = myif.out;
        ...
      end : conn_out
      /* connect tri-state this way may not be supported by all synthesizers,
       * refer to manual */
      //same_port #(8) conn_inout_io(io, myif.io); 
      ...
    endmodule : synth_top