1
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Can you share some sample code or a diagram? Would a SystemVerilog solution be acceptable? \$\endgroup\$ – Greg Sep 13 '15 at 3:54
  • \$\begingroup\$ @Greg, I updated with some example code. SystemVerilog is acceptable, but I'd prefer no interfaces, because there have been bugs encountered with some of the tools using SystemVerilog at these levels of the design hierarchy. \$\endgroup\$ – mattgately Sep 14 '15 at 20:18
1
\$\begingroup\$

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
    
\$\endgroup\$
  • \$\begingroup\$ I like your thoughts. The problem with the solution above is that it assumes that the different drivers simply drive a single contiguous slice of the vector, so that the looping and masking that I talked about boils down to a concatenation of these different contiguous sections. However, this is not the case. Also, you mentioned using an interface (an array of interfaces I assume). That still doesn't solve the problem, because I still need to specify modports of each index separately or it results in the same inout ports when synthesized. \$\endgroup\$ – mattgately Sep 16 '15 at 16:11
  • \$\begingroup\$ For the first solution, it does not require continuous slicing. You can have multiple inputs/outputs (ex .sig_o1(sig[17]), .sig_o2(sig[3:1])), or stitch the connection one level up (.sig_o({sig[17],sig[3:1]})), or combination. For the interface solution, a single interface with all the signals inside is fine; modports are not required (on the synthesizers I used). Keep in mind that inout does not mean tri-state; tri-states are a subset of inout. When using always_comb/always_ff any multi-driver should flagged in simulation, lint, synthesis, and logical equivalence checking. \$\endgroup\$ – Greg Sep 16 '15 at 17:24
  • \$\begingroup\$ This is some good info on port aliases. However, it really doesn't answer my question, since the port aliasing you have shown is not really parameterizable and loopable using a mask for each location as I am needing. Your solution was assuming some fixed arrangement that would have to be hardcoded into the RTL. And while the interface implementation without modports is ideal (in fact it is what I started with), I am specifically needing a solution that has every port direction fixed, whereas an interface without modports is synthesized to an inout port. \$\endgroup\$ – mattgately Jul 29 '16 at 14:00
  • \$\begingroup\$ It sounds like you hit the limit of what your synthesizer can optimize for. In simulation, inout works for Verilog and an interface (no modports) for SystemVerolg. You may want to try a different synthesizer version and/or vendor. Your structure is unorthodox, so even if a synthesizer could do want you want, you may need on-site assistance from the vendor's Application Engineer to look at your design. Beyond that, the only other idea I can think of is for you to create a script that can figure out and generate the port direction for you. \$\endgroup\$ – Greg Aug 2 '16 at 20:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.