Skip to content

Gate Type

SJulianS edited this page Sep 8, 2021 · 51 revisions

A gate type describes the functionality of a standard cell and is a component of a gate library. Each gate within a netlist represents an instance of a gate type. HAL allows standard properties (GateTypeProperty) to be assigned to gate types to distinguish between their general functionality and facilitate automated analysis. A single gate type might have multiple properties depending on its purpose. Available base types are:

  • combinational: a combinational gate type.
  • sequential: a sequential gate type.
  • power: a gate type that is connected to a power/voltage source.
  • ground: a gate type that is connected to ground.
  • lut: a LUT gate type generating its output from an initialization string.
  • ff: a FF gate type generating its outputs from its internal state while adhering to a clock.
  • latch: a latch gate type generating its outputs from its internal state whenever enabled.
  • ram: a RAM gate type storing large amounts of data that can be accessed by providing an address.
  • io: an IO gate type that takes care of the global in- and outputs to the chip.
  • dsp: a DSP gate type that accelerates the computation of some mathematical operations.
  • mux: a MUX gate type choosing one of many input signals depending on one or more select inputs.
  • buffer: a (potentially tri-state) buffer gate type.
  • carry: a carry gate type implementing some form of carry logic.

Gate Type Information

A new gate type can be constructed only by calling create_gate_type on a gate library. On construction, each gate type is assigned a unique ID that can be read using get_id. The user must explicitly provide a base type(s) when creating a new gate type. The name of a gate type can be accessed using get_name and may not be changed. The properties of a gate type are returned by using get_properties. To simply check whether a gate type is of a certain property, has_property may be used. Furthermore, the gate library that a gate type is assigned to can be retrieved using get_gate_library.

gl = netlist.get_gate_library()                                                    # get the current gate library
gt = gl.create_gate_type("new_gt", set([hal_py.GateTypeProperty.combinational]))   # create new gate type with name "new_gt"
id = gt.get_id()                                                                   # get ID of gate type
name = gt.get_name()                                                               # get name of gate type
properties = gt.get_properties()                                                   # get all properties (here: combinational)
gt.has_property(hal_py.GateTypeProperty.combinational)                             # returns "True"
gl = gt.get_gate_library()                                                         # returns the gate library

Two gate types may be evaluated for equality using the == and != operators. Currently, equality is determined only using a gate type's ID and gate library.

Managing Pins

Commonly, each gate type comes with a number of pins. A pin can have one of four different directions (PinDirection): it can be an input, output, inout, or internal pin. Furthermore, each pin can be assigned a pin type (PinType) in case it implements some special functionality. The default pin type is none and will be used in case no pin type is specified. Otherwise, one can choose from the following pin types:

  • none: default pin type.
  • power: an input pin that is connected to a power source.
  • ground: an input pin that is connected to ground.
  • lut: a LUT output pin generating its Boolean function from an initialization string.
  • state: a state output pin generating its output value from the internal state of a sequential gate type.
  • neg_state: a state output pin generating its output value from the negated internal state of a sequential gate type.
  • clock: an input pin connected to a clock signal or an output pin emitting a clock signal.
  • enable: an input pin acting as an enable switch for the gate type.
  • set: an input pin setting the internal state of a sequential gate type to 1.
  • reset: an input pin resetting the internal state of a sequential gate type to 0.
  • data: an input or output pin through which data is fed to or from a gate type.
  • address: an input or output pin through which one bit of an address is provided.
  • io_pad: an input, output, or inout pin that acts as an IO pad to the netlist.
  • select: an input pin selecting the input signal of a MUX.

Pins can be added using the add_pin and add_pins functions by providing at least a pins name and direction. Optionally, the pin type can also be specified and will be set to none otherwise. For legacy reasons, the functions add_input_pin, add_input_pins, add_output_pin, and add_output_pins are also still available. In order to retrieve a list of all pins in the exact order they have been added to the gate type (i.e., when parsing them from a standard cell library), the function get_pins may be used. Again, for backwards compatibility the functions get_input_pins or get_output_pins may also be called.

gt.add_pin("A", hal_py.PinDirection.input)                           # add an input pin called "A"
gt.add_pin("B", hal_py.PinDirection.input)                           # add an input pin called "B"
gt.add_pin("S", hal_py.PinDirection.input, hal_py.PinType.select)    # add an input select pin called "S"
gt.add_pin("O", hal_py.PinDirection.output)                          # add an output pin called "O"
out_pins = gt.get_pins()                                             # get all pins as a list

Furthermore, a number of functions is provided to interact with and manipulate a pin's direction and type. Using assign_pin_type, a pin's type can be changed at any time. Note that it is not possible to change a pin's direction after creation of the pin. By calling get_pin_direction or get_pin_type one can retrieve the direction or type of a single pin. Calling get_pin_directions or get_pin_types returns a dict from all pins of a gate type to their directions or types. Finally, get_pins_of_direction and get_pins_of_type can be used to get all pins of a desired direction or type.

gt.assign_pin_type("D", hal_py.PinType.data)                       # assign type 'data' to pin "D"
dir = gt.get_pin_direction("A")                                    # get the direction of pin "A"
type = gt.get_pin_type("A")                                        # get the type of pin "A"
direction_dict = gt.get_pin_directions()                           # get the mapping from pins to direction
type_dict = gt.get_pin_types()                                     # get the mapping from pins to type
input_pins = gt.get_pins_of_direction(hal_py.PinDirection.input)   # get all input pins off the gate type
address_pins = gt.get_pins_of_direction(hal_py.PinType.address)    # get all address pins off the gate type

Managing Pin Groups

So called pin groups are used to facilitate multi-bit pins. Before being able to set up such a group, the respective pins of that group need to be created using the previously mentioned functions. Then, pin groups can be created using assign_pin_group respectively. The function awaits a name for the group and a map from pin names to an indices as input. The individual pins keep their name but can be addressed using the group name and their index during netlist parsing. All available pin groups of a gate type can be accessed using get_pin_groups. To get all pins of a group, one can call get_pins_of_group to retrieve a map from index to pin name containing all pins assigned to the specified group.

gt.add_pin("A(0)", hal_py.PinDirection.input)        # add an input pin called "A(0)"
gt.add_pin("A(1)", hal_py.PinDirection.input)        # add an input pin called "A(1)"
gt.assign_pin_group("A", {0 : "A(0)", 1 : "A(1)"})   # assign both pins to a pin group with name "A"
groups = gt.get_pin_groups()                         # get all pin groups of the gate type
pins = gt.get_pins_of_group("A")                     # get all pins belonging to group "A" as well as their indices

Boolean Functions

A gate type should feature one Boolean function for each of its output pins to describe the output's value as a function of its inputs. A Boolean function may be added using add_boolean_function by providing a name for the function and the Boolean function itself. The variables of the Boolean function should correspond to the names of the input pins of the gate. To get all Boolean function of a gate type, get_boolean_functions should be used.

gt.add_pin("A", hal_py.PinDirection.input)         # create input pin "A"
gt.add_pin("B", hal_py.PinDirection.input)         # create input pin "B"
gt.add_pin("O", hal_py.PinDirection.output)        # create output pin "O"
bf = hal_py.BooleanFunction.from_string("A & B")   # create a new Boolean function representing an AND
gt.add_boolean_function("O", bf)                   # add the Boolean function named after the output pin

LUT Gate Types

It lies within the nature of LUTs that their implemented function varies depending on an initialization string provided to each LUT at device configuration. Thus, gates of a lut gate type do not necessarily need to implement the same function.

In addition to the functions described above, these special gate types need to implement some logic to deal with initialization strings that are read from the netlist file during parsing. At least for Xilinx devices, the initialization of a LUT usually ends up in the generics of the respective gate instance. Thus, we expect such data to be stored within the data container functionality inherited by each gate. The functions set_config_data_category and set_config_data_identifier may then be used to set up the search path within the data container. HAL will then look for the data entry specified by the tuple (config_data_category, config_data_identifier) to load the bitstream from the respective value. Using 'set_lut_init_ascending' HAL can be told to interpret the initialization string in ascending or descending order. Corresponding get functions are provided as well and are used internally by HAL. For the Xilinx UNISIM gate library, the respective configuration looks as follows:

gt = gl.create_gate_type("lut_type", set([hal_py.GateTypeProperty.combinational, hal_py.GateTypeProperty.lut]))   # create an empty LUT gate type
gt.set_config_data_category("generic")   # set data category to "generic"
gt.set_config_data_identifier("INIT")    # set data identifier to "INIT"
gt.set_lut_init_ascending(True)          # set ascending order

To tell an output pin to generate its Boolean function from the initialization string during netlist parsing, simply assign it the pin type lut.

gt_lut.add_pin("O", hal_py.PinDirection.output, hal_py.PinType.lut)

Sequential Gate Type

Sequential gates can, for example, be flip-flops (ff) or latches (latch) with the difference being that a flip-flop adheres to a clock signal while a latch can merely be enabled and disabled. Some FPGA netlists also contain an initialization value for the respective flip-flop or latch. Similar to LUTs, the location of this value needs to be specified for each gate type such that HAL can extract it from the data stored alongside the gate. Since this initial value comprises only a single bit, it suffices to use the functions set_config_data_category and set_config_data_identifier and no bit-order needs to be specified.

gt = gl.create_gate("ff_type", set([hal_py.GateTypeProperty.sequential, hal_py.GateTypeProperty.ff]))   # create an empty FF gate type
gt.set_config_data_category("generic")   # set data category to "generic"
gt.set_config_data_identifier("INIT")    # set data identifier to "INIT"

For sequential gates, the output signal depends on the internal state of the gate itself and is controlled by, e.g., a clock or an enable signal. Since this cannot appropriately be described using a single Boolean function for each output pin, a dedicated mechanism to determine the output is implemented within HAL. For this reason, output pins that depend on the internal state need to be assigned the pin type state or neg_state accordingly. The latter command assigns the pin to a group of pins that negates the state before applying it to the output. Note that HAL itself only stores these information and it is up to the user to make sense of them. For example, the Netlist Simulator Plugin shipped alongside HAL uses these information to simulate sequential circuits. In the example below, the output of pins "Q" and "QN" are fully determined by the internal state of the flip-flop with "QN" additionally inverting the output.

gt.add_pin("Q", hal_py.PinDirection.output, hal_py.PinType.state)
gt.add_pin("QN", hal_py.PinDirection.output, hal_py.PinType.neg_state)

Both flip-flops and latches require additional Boolean functions to fully specify their internals. They both share functions for asynchronous set and reset called preset and clear. Those functions operate on the input signals of the gate and set the internal state to 1 (preset) or 0 (clear) whenever evaluating to 1 independent of the clock (asynchronous). The example below shows the definition of a high-active preset and a low-active clear signal. A special case arises if both clear and preset evaluate to true at the same time. For this purpose, set_set_reset_behavior can be used to define the action that is taken in that case. More information on this can be found in the official Python API documentation.

gt.add_pin("S", hal_py.PinDirection.output)
gt.add_pin("R", hal_py.PinDirection.output)
gt.add_boolean_function("preset", hal_py.BooleanFunction.from_string("S"))   # high-active asynchronous set
gt.add_boolean_function("clear", hal_py.BooleanFunction.from_string("!R"))   # low-active asynchronous reset

Some Boolean functions are specific to a flip-flop or a latch and thus require distinguishing between them. A flip-flop requires the specification of the Boolean functions clock and a next_state function while a latch requires enable and data functions.

Flip-Flop

Whenever clock evaluates to true, the result of the Boolean function called next_state is forwarded to the previously specified output pins. This also allows for a clock enable signal to be taken into consideration within the clock function as shown below. Furthermore, input pins carrying a clock signal such as "C" need to be declared clock pins by assigning pin type clock to them, i.e., in order to facilitate netlist simulation.

gt.add_pin("I1", hal_py.PinDirection.input)
gt.add_pin("I2", hal_py.PinDirection.input)
gt.add_pin("C", hal_py.PinDirection.input, hal_py.PinType.clock)                      # clock pin
gt.add_pin("CE", hal_py.PinDirection.input, hal_py.PinType.enable)                    # clock enable pin
gt.add_boolean_function("clock", hal_py.BooleanFunction.from_string("C & CE")         # clock function
gt.add_boolean_function("next_state", hal_py.BooleanFunction.from_string("I1 + I2")   # compute next state

Latch

Latches are not controlled by a clock and simply emit their state whenever they are enabled. Hence, as soon as enable evaluates to true, the result of the data function is passed to the previously specified output pins.

gt.add_pin("I1", hal_py.PinDirection.input)
gt.add_pin("I2", hal_py.PinDirection.input)
gt.add_pin("EN", hal_py.PinDirection.input, hal_py.PinType.enable)              # enable pin
gt.add_boolean_function("enable", hal_py.BooleanFunction.from_string("EN")      # enable function
gt.add_boolean_function("data", hal_py.BooleanFunction.from_string("I1 + I2")   # compute next state
Clone this wiki locally