Internal Representation in v2.9

A project log for VHDL library for gate-level verification

Collection of ASIC cells and ProASIC3 "tiles" in VHDL so I can design and verify optimised code without the proprietary libraries

Yann Guidon / YGDESYann Guidon / YGDES 08/31/2020 at 00:450 Comments

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 :

Library ieee;
    use ieee.std_logic_1164.all;
Library work;
    use work.all;
    use work.PA3_genlib.all;

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);
end generic_gate2;

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;
end trace;

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;
    return gate_instance_counter;
end Gate_Census;

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...

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

in update_select_gate():
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;
end if;

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.

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".

Depth list

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).