The development cycle for v2.10 starts and it's a good time to discuss how v2.9 works, in particular for the internal data structures. They are well suited for what they do in v2.9 but, knowing what I know now, it is obvious I should have left more margin in the code. Going back to v2.9 is also a way to evaluate what I could change without breaking what (fragile) code exists and works until now.
Note : I will explicitly cover the mechanics behind the "probe" mode only. The "fast" mode and the "simple" version are not at stake here. What I want is a better framework to analyse a netlist with not just ports and boolean logic, but also latches and RS FF.
The very first mechanism is the census of the gates. Each instance gets its own unique number during elaboration, thanks toa little trick with the generics system. Here is the example of the generic 2-inputs gate :
entity generic_gate2 is
generic( Gate_Number : integer := Gate_Census;
exclude: std_logic_vector := "");
port( A, B : in std_logic;
LUT : in std_logic_vector;
Y : out std_logic);
architecture trace of generic_gate2 is
signal LUT4 : LookupType4;
LUT4 <= Inferlut(LUT, LUT4'instance_name, Gate_Number, 3, exclude);
Y <= Lookitup2(A, B, LUT4, Gate_Number) after ns;
The Generic's value is given by the Gate_Census procedure, which is pretty basic :
-- just count the number of registered gates
impure function Gate_Census return integer is
gate_instance_counter := gate_instance_counter + 1;
So yes it's just a counter, so far. But it is called for each and every analysed gate, and updates the shared variable gate_instance_counter every time. After elaboration, not only does each gate has a unique number (very useful for later) but the analyser knows how many gates to check. This is used at the end of the elaboration to allocate room for all of them, in an array called GateList.
List of gates
After elaboration, the VHDL simulator initialises the signals. In the example gate above, the first line is executed first (for each instance of the component):
LUT4 <= InferLUT(LUT, LUT4'instance_name, Gate_Number, 3, exclude);
That's quite tricky and it tries to do a lot at once because there is so much to initialise...
- First, it has to write to a signal because otherwise, the execution of the InferLUT function would not happen at the right time (before the others). But since all the parameters shouldn't change (yes, right) this initialisation function is called just once.
- The first argument is the desired contents of the LUT, which can be given directly as a literal value (as in PA3_definitions.vhdl), as a generic or a port (as in the example, though this is not very "clean" because it creates the risk of re-running the initialisation routine)
- The next argument is the text string that identifies the gate, for human consumption and pretty-reporting.
- The Gate_Number is given so the function knows where to put the gate's definition.
- 3 is the last valid index in the LUT (it's the size)
- exclude is a generic that eventually flags which input combinations not to care about.
So after the delta cycles of t=0 are over, GateList contains the list of all the gates. There is no real order, it's mostly defined by the inclusion order and this is in theory something that you shoudn't rely on (though GHDL does a great job of preserving the writing order).
Note that all the gates are included and counted but not all should be analysed : gates with 0 or 1 input are "degenerate" because a fault on them is equivalent to a fault on the input of the larger tested gates. The list of gate number to actually analyse is stored in indexPtr and its size is registered_instance_counter
indexPtr := new integer_array(0 to gate_instance_counter);
in function InferLUT:
-- register into the list of updates/display:
if lastbit > 0 then
indexPtr(registered_instance_counter) := gate_number;
registered_instance_counter := registered_instance_counter + 1;
The indexPtr array might not be filled, as registered_instance_counter can be less than gate_instance_counter. The extra unused space is just left alone... But at least the list is contiguous :-)
Specific gates can be hidden or ignored with this added layer, so we can focus on actual logic. It is mostly used by update_histogram() and display_histogram(). The integral list of gates can still be displayed/dumped with the runtime parameter -ggate_select_number=-1
By the time you use VectGen.vhdl, a trick is introduced and it matters a lot for the rest of the code :
Ports are given by a negative "gate number" value and the meaning depends on where the number is found.
- If the negative number is found at the output of a gate, the number must represent an output port.
- Similarly, input ports only make sense for inputs of gates.
So the input ports and output ports require fewer infrastructure and are represented by a couple of vectors:
-- notice the swap : the input vector is seen as outputs by the following gates...
shared variable output_records : input_records_access; -- the array of descriptors of the output vector
shared variable input_records : output_record_access; -- array of the descriptors of the input
Also notice that having negative numbers for the ports creates the corner case where the port index is 0. The consequence is that the first gate has index=1. There is no "gate number 0".
Once VectGen.vhdl is being used, a new structure appears, it's the "depth list", or an array of arrays of gate numbers. This is painful to build but invaluable for the next steps (up to the generation of the test vectors).