How to shoot yourself in the foot with inout signals
They say that no good deed goes unpunished. Here’s an example of a footgun in VHDL which stems from in my view an eminently reasonable desire to keep the entity interface definitions compact.
When defining entities in VHDL, I try to keep the entity definition compact and keep the rate of mistakes low by grouping individual signals into records, so that they could be manipulated as a unit. For example you would have a package definition that defines a record like so (library use declarations omitted for brevity):
package mypkg is
type mytype is record
field1: std_ulogic;
field2: std_ulogic;
end record;
end package;
From here, this allows you to make the entity definition compact if it takes mytype
as a parameter:
entity myentity is
port (
one_interface: in mypkg.mytype
)
end entity;
This avoids the need to specify field1
and field2
as separate signals, and if the fields are logically grouped, you can manipulate one_interface
as a unit. So far so good.
Now look at a similar but slightly different situation, where one_interface
is a inout
parameter instead:
entity myentity is
port (
one_interface: inout mypkg.mytype
)
end entity;
This makes one_interface
a bidirectional signal, which is usually used to implement multi-driver buses. This declaration allows you to have multiple drivers attached to that bus like so:
entity myentity2 is
port (
param: inout std_logic
)
end entity;
architecture rtl of myentity2 is
begin
param <= ‘1’;
param <= ‘Z’;
end architecture;
The code above drives the port param
with two values at the same time. Normally this is not something you want to have appear in your design, except if you are designing a multiple driven bus which relies on a synchronization protocol to ensure that only one driver is active at any one time.
The inout
parameters are a place where the underlying physics of the electronic circuits described in your VHDL design show through. They are also typically not implementable in a typical FPGA, unless the multiply-driven param
will be constrained to a tri-state capable FPGA output pin. But they do come up in important circuitry such as DDR memory controllers, which use tri-state capable drivers for ferrying the data back and forth between your FPGA device and the DDR3 RAM chips.
This usually gets sorted out in the synthesis phase. But in order to have a controllably maintainable design, you also want the ability to confirm (or better yet, test) this behavior in simulation. This means it is important that the above expression can be modeled by your simulator correctly.
VHDL achieves this by using a mechanism called “resolution”. Basically, when defining types (or, rather, subtypes as it were), it is possible to provide a function which explains what should happen if multiple drivers exist on a port or a signal. The logic types from the standard library ieee.std_logic_1164
define two sets of types, based on std_ulogic
and std_logic
, which are images of one another, except the “ulogic” type is unresolved logic type, whereas the “logic” type is the resolve logic type. The ieee
standard library defines this approximately as:
type std_ulogic is (‘1’, ‘0’, ‘Z’, ‘U’, ‘X’, -- …);
);
subtype std_logic is <some_resolution_function> std_ulogic;
This allows the simulator to know that your two assignments to param
should resolve to 1
(since one driver is in a high-impedance state, and the other is in the state 1
).
This is where things get tricky for us. Suppose we go back to our earlier example and now have:
architecture rtl of myentity2 is
signal signal1, signal2: mypkg.mytype
begin
one_interface <= signal1;
one_interface <= signal2;
end architecture;
What will the result be?
In this case, the result of the assignments is that the values of one_interface
are all X
values!!! Why? Well, our entity declaration said that the type is inout
, but we never provided a resolution function! We didn’t give the simulator a way to figure out how to handle this situation. In tools like Vivado, the simulation of such circuitry runs without any warnings, but definitely does not give the result you would want here.
What to do? We could try providing a resolution function for our type mypkg.mytype
. However, this approach holds a hidden trap too. There is no reason to assume that your synthesis tool will know what to do with your custom resolution function. Sure, the simulator will apply it according to the language standard. However, synthesis tools are not required to support any of these generic constructs.
So, my solution for now is to “simply” abandon the use of inout
parameters for any types that aren’t the standard IEEE library types. I would rewrite the entity declaration as:
entity myentity is
port (
field1: inout std_logic
; field2: inout std_logic
)
end entity;
From here, I’d proceed to assign values to the fields separately. Note also that using std_ulogic
type for these two signals here is incorrect, since the type std_ulogic
does not have an appropriate resolution function.
This is quite a lame result, and I dislike that we’re actively discouraged from using aggregate types in VHDL due to the way tools support this feature (or don’t). But, here we are.