Core Modules¶
Modules:
-
fxp_arithmetic–
Classes:
FxpArithmetic
dataclass
¶
Methods:
-
cut_as_integer–Cutting input number to integer directly (more like in hardware)
-
round_to_integer–Mathematical Round function for number
Modules:
-
base_graph– -
graph_iterators– -
graph_rewriting– -
vf2–
Classes:
-
BaseGraph–All iterators in this class are guaranteed to have a fixed
Functions:
-
bfs_iter_down–Iterate graph nodes in breadth first.
-
bfs_iter_up–Iterate graph nodes in breadth first.
-
get_rewriteable_matches–Yield all matches that do produce dangling edges and do not overlap with previous matches.
-
produces_dangling_edge–Check if there are dangling edges attached to non-interface nodes.
BaseGraph
¶
Bases: Graph[T]
All iterators in this class are guaranteed to have a fixed order. That means, the order of iteration is only allowed to change when the data Tucture is altered. If the content of two GraphDelegates is the same and they were built in the same way, then their iteration order is the same as well.
Note
This class is not thread-safe.
Note
We are not providing methods for removal of nodes or edges on purpose. If you need to remove nodes or edges, you should create a new GraphDelegate. Manipulation of the graph should usually be done in a dedicated build phase.
Methods:
-
get_edges–Iterator over edges in a fixed but unspecified order.
-
get_predecessors–Iterator over node predecessors in a fixed but unspecified order.
-
get_successors–Iterator over node successors in a fixed but unspecified order.
-
iter_edges–Iterator over edges in a fixed but unspecified order.
-
iter_nodes–Iterator over nodes in a fixed but unspecified order.
get_edges
¶
Iterator over edges in a fixed but unspecified order.
get_predecessors
¶
get_predecessors(node: T) -> Iterator[T]
Iterator over node predecessors in a fixed but unspecified order.
get_successors
¶
get_successors(node: T) -> Iterator[T]
Iterator over node successors in a fixed but unspecified order.
iter_edges
¶
Iterator over edges in a fixed but unspecified order.
bfs_iter_down
¶
bfs_iter_down(
successors: NodeNeighbourFn, predecessors: NodeNeighbourFn, start: set[T]
) -> Iterator[T]
bfs_iter_down(
successors: NodeNeighbourFn, predecessors: NodeNeighbourFn, start: T
) -> Iterator[T]
Iterate graph nodes in breadth first.
This ensures that no node will be visited before each of its predecessors was visited.
bfs_iter_up
¶
bfs_iter_up(
successors: NodeNeighbourFn, predecessors: NodeNeighbourFn, start: T
) -> Iterator[T]
Iterate graph nodes in breadth first.
This ensures that no node will be visited before each of its successors was visited.
get_rewriteable_matches
¶
get_rewriteable_matches(
original: Graph[T], matches: Iterable[dict[TP, T]], interface_nodes: Iterable[TP]
) -> Iterator[dict[TP, T]]
Yield all matches that do produce dangling edges and do not overlap with previous matches.
The matches returned by this function are considerd safe to be rewritten in a single rewriting step in any order, without having to run an additional matching step and without producing dangling edges.
:param original: The original graph. :param matches: The matches to check. :param interface_nodes: All nodes in the pattern that are belong to the interface. These nodes are considered to be preserved during rewriting. Thus, the edges connected to these nodes are not considered dangling.
Intermediate Representation¶
Modules:
-
datagraph– -
datagraph_impl– -
datagraph_rewriting– -
deserializer– -
serializer–
Classes:
-
AttributeMapping– -
DataGraph–Note that the fact, that
add_nodeand similar methods take -
DataGraphImpl–Keep constructor signature when subclassing!
-
IrDeserializerLegacy–Deserializes to the legacy format.
-
IrSerializerLegacy–Serializer for the legacy format.
Functions:
-
attribute–Create AttributeMapping from other (native) data types recursively.
AttributeMapping
¶
Bases: Mapping[str, Attribute]
Methods:
-
merge–merge over nested mappings recursively
-
new_with–replace key, value pairs in self by key, value pairs in kwargs
-
update_path–update the entry found when following the path into nested Mappings
merge
¶
merge over nested mappings recursively
So instead of replacing a value found under a key, this checks wether that value is again an AttributeMapping and if so, updates it by the corresponding Mapping found in other. This happens recursively.
Opposed to that new_with and the | operator
only allow to update values in the most outer
AttributeMapping. Therefore using these to update a value
in a nested structure, users would have to recreate
the whole outer mapping structure until they
reach the mapping they want to update.
Use Case:
If you would want to write
You can instead
data = AttributeMapping(a=AttributeMapping(b=1, c=2))
data = data.merge(dict(a=dict(b=3)))
assert data["a"]["c"] == 2
If you want to update a single value, you can use
the update_path() function instead to avoid
having to build all the nested dictionaries.
new_with
¶
new_with(**kwargs: Attribute) -> AttributeMapping
replace key, value pairs in self by key, value pairs in kwargs
DataGraph
¶
Bases: ReadOnlyDataGraph[N, E], Graph[str, AttributeMapping], Protocol[N, E]
Note that the fact, that add_node and similar methods take
an argument of type Node while the nodes and edges properties
produce mappings over the generic type parameters E and N.
This makes DataGraph covariant over N and E, meaning
that DataGraph[SpecialNode, SpecialEdge] is a subtype
of DataGraph[Node, Edge]. It also means that every implementation
detail in a DataGraph implementation needs to assume that
nodes and edges are of the most general Node or Edge type.
Methods:
-
add_edges–Updates edge in case it exists already. Possibly already existing nodes remain unchanged.
-
add_nodes–Updates node in case it exists already.
-
clear–Get a new empty graph with attributes, nodes, edges removed.
-
remove_edge–Will not remove nodes, even if they become isolated.
-
remove_node–Will remove node and all connected edges.
add_edges
abstractmethod
¶
Updates edge in case it exists already. Possibly already existing nodes remain unchanged.
add_nodes
abstractmethod
¶
add_nodes(*args: Node | tuple[str, AttributeMapping] | str) -> Self
Updates node in case it exists already.
remove_edge
abstractmethod
¶
Will not remove nodes, even if they become isolated.
DataGraphImpl
¶
DataGraphImpl(
factory: NodeEdgeFactory[N, E],
attributes: AttributeMapping,
graph: Graph[str, AttributeMapping],
node_attributes: AttributeMapping,
)
Bases: DataGraph[N, E]
Keep constructor signature when subclassing!
Methods:
-
add_edge–Updates edge in case it exists already. Possibly already existing nodes remain unchanged.
-
add_edges–Updates edges in case they exist already. Possibly already existing nodes remain unchanged.
-
add_node–Updates node in case it exists already.
-
add_nodes–Updates nodes in case they exist already.
-
clear–Get a new empty graph with attributes, nodes, edges removed.
-
remove_edge–Will not remove nodes, even if they become isolated.
-
remove_node–Will remove node and all connected edges.
add_edge
¶
add_edge(
src: str | Edge, dst: str | None = None, attributes: AttributeMapping | None = None
) -> Self
Updates edge in case it exists already. Possibly already existing nodes remain unchanged.
add_edges
¶
Updates edges in case they exist already. Possibly already existing nodes remain unchanged.
add_node
¶
add_node(name: str | Node, attributes: AttributeMapping | None = None) -> Self
Updates node in case it exists already.
add_nodes
¶
add_nodes(*nodes: Node | tuple[str, AttributeMapping] | str) -> Self
Updates nodes in case they exist already.
remove_edge
¶
Will not remove nodes, even if they become isolated.
IrDeserializerLegacy
¶
Deserializes to the legacy format.
The only difference is that attributes are not stored in a dedicated field but at the top-level dict.
Use this if you need to load an IR from data that was serialized using the
IrData data types.
IrSerializerLegacy
¶
Serializer for the legacy format.
The only difference is that the attributes are not saved in a dedicated field, but instead directly in the top-level dict.
Use this if you need to store the data in a format that can be read
by the old IrData based implementations.
attribute
¶
attribute(**kwargs: AttributeConvertable) -> AttributeMapping
attribute(
mapping: Mapping[str, AttributeConvertable], /, **kwargs: AttributeConvertable
) -> AttributeMapping
Create AttributeMapping from other (native) data types recursively.
The implementation assumes that any encountered AttributeMapping objects are correct, ie. they only contain Attributes themselves.
Plugin System¶
Modules:
-
loading– -
plugin_loader_base– -
plugin_spec– -
read_specs_from_toml–Provides ways to use and extend the elasticai.creator plugin system.
Classes:
-
PluginLoaderBase–PluginLoader for Ir2Verilog passes.
-
PluginSpec–The specification of a plugin.
Functions:
-
build_plugin_spec–inspect spec_type and build an instance of it from the dictionary
d. -
import_symbols–import names from a module and yield the resulting objects.
-
read_plugin_dicts_from_package–read the meta.toml file from the package returning the list of plugin dictionaries.
PluginSpec
dataclass
¶
PluginSpec(
name: str,
target_platform: str,
target_runtime: str,
version: str,
api_version: str,
package: str,
)
The specification of a plugin.
Typically built by reading a dictionary from a toml file and
building the spec using <
You can achieve your goals just as well with the PluginDict dictionary.
That is defined as an alias for dict[str, str | tuple[str, ...]].
build_plugin_spec
¶
build_plugin_spec(d: PluginMap, spec_type: type[SpecT]) -> SpecT
inspect spec_type and build an instance of it from the dictionary d.
Missing field raise an error while extra fields will be ignored.
import_symbols
¶
import names from a module and yield the resulting objects.
Torch2IR¶
Transform PyTorch models to IR.
The translation process is highly customizable.
(Torch2Ir)[elasticai.creator.torch2ir.Torch2Ir] is responsible
for the translation process and will call module handlers to
extract attributes from modules. These attributes are then
attached to the corresponding nodes in the IR.
Each module handler is a function that takes a PyTorch module and returns a dictionary with the extracted attributes.
The Torch2Ir class features some factory methods as class
methods, e.g., (Torch2Ir.get_default_converter)[elasticai.creator.torch2ir.Torch2Ir.get_default_converter].
Those will create a new Torch2Ir instance and register some
preconfigured module handlers.
However, you are free to extend or alter the behaviour of the
translation process by registering your own module handlers.
IR2Torch¶
Modules:
-
ir2torch–
Classes:
-
Ir2Torch–
Ir2Torch
¶
Methods:
-
__call__–Rebuild the original pytorch model from a given IR.
__call__
¶
__call__(
ir_root: DataGraph,
registry: Registry[DataGraph],
state_dict: dict[str, Any] | None = None,
) -> Module
Rebuild the original pytorch model from a given IR.
Implemenation names containing dots will result in the corresponding modules sorted
into a corresponding object hierarchy. E.g., for the implementation
name 'top.linear' we will create a pytorch container module under the name
'top' and add the linear layer to it under the name 'linear'. Note that this
is an implementation detail of Ir2Torch and not a semantic meaning assigned to
the '.' character.
:param: ir: You need to make sure that Ir2Torch has a type handler for each implementation in ir
:param: state_dict: You can optionally pass a state dict. This should be a state dict created
from the original model via nn.Module.state_dict. As the Torch2Ir stage got rid of all
duplicate submodules, we will strip all unknown keys from the state_dict and then load it.
IR2VHDL¶
Modules:
-
ir2vhdl– -
language– -
vhdl_template–
Classes:
-
EntityTemplateDirector–Turn a vhdl prototype into a template.
-
EntityTemplateParameter–Find the entity in vhdl code and make its name available as a template paramter.
-
Instance–Represents an entity that we can/want instantiate.
-
InstanceFactory–Automatically creates Instances from VhdlNodes based on their
typefield. -
Node–Extending ir.core.Node to an hdl specific node.
-
PluginLoader–PluginLoader for Ir2Vhdl passes.
-
Shape– -
ValueTemplateParameter–Find a value definition and make it settable via a template parameter.
EntityTemplateDirector
¶
Turn a vhdl prototype into a template.
Resulting template will have a parameter called entity
that you can set to define the name of the represented
entity.
Replace constants/variables/generics with add_value().
Methods:
-
add_value–add a parameter for a generic/constant/variable/signal.
add_value
¶
add_value(name: str) -> EntityTemplateDirector
add a parameter for a generic/constant/variable/signal.
EntityTemplateParameter
¶
Bases: AnalysingTemplateParameter
Find the entity in vhdl code and make its name available as a template paramter.
Assumes that there is only one entity in the provided prototype.
NOTE: Will only replace occurences where name is followed and preceded by a non-word character, ie.
for an entity named 'skeleton' the occurence 'architecture rtl of skeleton is' will be
replaced by 'architecture rtl of $entity is' but the occurences 'skeleton_pkg',
'skeleton;', 'skeleton-' or skeleton. will remain unaltered.
IMPORTANT: This detail is likely to change in the future.
Instance
¶
Represents an entity that we can/want instantiate.
The aggregates all the knowledge that is necessary to instantiate and use the corresponding entity programmatically, when generating vhdl code.
InstanceFactory
¶
Automatically creates Instances from VhdlNodes based on their type field.
Node
¶
Bases: Node, Protocol
Extending ir.core.Node to an hdl specific node.
This node contains all knowledge that we need to create and use an instance of an hdl component. However, this becomes a little bit complicated because vhdl differentiates between
Attributes:
implementation:: The name of the implementation, e.g., entity name in vhdl or module name for verilog, will be used to derive the architecture name.
E.g., if the implementation is "adder", we will instantiate the entity work.adder(rtl).
CAUTION: This behaviour is subject to change. Future versions might require the full entity name
PluginLoader
¶
Shape
¶
- depth, width
- depth, width, height
Usually width is kernel_size, depth is channels
ValueTemplateParameter
¶
ValueTemplateParameter(name: str)
Bases: TemplateParameter
Find a value definition and make it settable via a template parameter.
Searches for vhdl value definitions of the form identifier : type
or identifier : type := and transforms them into identifier : type := $identifier.
Essentially allows to replace generics as well as variable or signal initializations.
IR2Verilog¶
Modules:
-
ir2verilog– -
language– -
templates–
Classes:
-
Instance–Represents a Verilog module instance.
-
Node–Extending ir.core.Node to an hdl specific node.
-
PluginLoader–PluginLoader for Ir2Verilog passes.
-
Shape– -
TemplateDirector–Director for building verilog templates.
Instance
¶
Represents a Verilog module instance.
Aggregates all knowledge necessary to instantiate and use a Verilog module programmatically when generating code.
Methods:
-
define_signals–Generate wire definitions for all ports.
-
instantiate–Generate Verilog module instantiation code.
Node
¶
Bases: Node, Protocol
Extending ir.core.Node to an hdl specific node.
This node contains all knowledge that we need to create and use an instance of an hdl component. However, this becomes a little bit complicated because vhdl differentiates between
Attributes:
implementation:: The name of the implementation, e.g., entity name in vhdl or module name for verilog, will be used to derive the architecture name.
E.g., if the implementation is "adder", we will instantiate the entity work.adder(rtl).
CAUTION: This behaviour is subject to change. Future versions might require the full entity name
PluginLoader
¶
Shape
¶
- depth, width
- depth, width, height
Usually width is kernel_size, depth is channels
TemplateDirector
¶
Director for building verilog templates.
Most methods correspond to verilog language constructs. Building the final template can be expensive! Typically you only want to do this once. E.g.:
from string import Template
from elasticai.creator.ir2verilog import type_handler, Code, TemplateDirector, Implementation
class _ExplodingTemplate:
def substitute(self, params):
raise RuntimeError("Template not initialized!")
_template = None
@type_handler
def fir_filter(impl: Implementation) -> Code:
global _template
if _template is None:
_template = (
TemplateDirector()
.define_scoped_switch("USE_EXT_WEIGHTS", False)
.define_scoped_switch("USE_EXT_MAC", False)
.parameter("BITWIDTH")
.parameter("LENGTH")
.localparam("FILT_COEFFS")
.localparam("NUM_MULT_UNIT")
.add_module_name()
.set_prototype(res.read_text(package_path, "verilog/filter_fir_full.v"))
.build()
)
return _template.substitute(impl.attributes)
Methods:
-
define_scoped_switch–Add a switch for a define that is scoped to the module name.
define_scoped_switch
¶
Add a switch for a define that is scoped to the module name.
The switch will be prefixed with the value that users provide as module_name to the render call.
:param name: String with name of the switch/define name
:param default: Setting switch for defining output state (True=set, False=undefine)
HDL Generator¶
HDL (Hardware Description Language) Generator Abstraction.
This module provides a unified interface for generating VHDL and Verilog code. It abstracts away the differences between the two languages, allowing you to write code generation logic once and target either HDL.
The abstraction uses a Protocol to define the interface, with private concrete implementations for each language. Use create_generator() to obtain a generator.
Example
from elasticai.creator.hdl_generator import create_generator, HDLLanguage
# Create a generator for VHDL - returns HDLGenerator protocol type
generator = create_generator(HDLLanguage.VHDL)
# Create signals/wires - returns HDLSignal protocol type
clk = generator.create_signal("clk", width=1)
data = generator.create_signal("data", width=8)
# Instantiate a module/entity - returns HDLInstance protocol type
instance = generator.create_instance(
node=my_node,
generics={"WIDTH": "8"},
ports={"clk": clk, "data": data}
)
# Generate code
for line in instance.instantiate():
print(line)
Modules:
-
factory–HDL generators for VHDL and Verilog code generation.
-
protocols–Protocol definitions for HDL abstraction.
-
verilog_impl–Verilog implementation of HDL abstractions.
-
vhdl_impl–VHDL implementation of HDL abstractions.
Classes:
-
HDLGenerator–Protocol defining the interface for HDL code generation.
-
HDLInstance–Protocol for HDL module/entity instances.
-
HDLLanguage–Supported HDL languages.
-
HDLSignal–Protocol for HDL signals/wires.
-
HDLTemplateDirector–Protocol for HDL template directors.
Functions:
-
create_generator–Create an HDL generator for the specified language.
HDLGenerator
¶
Bases: Protocol
Protocol defining the interface for HDL code generation.
This protocol is generic over the signal type to provide static type safety. When you create a VHDL generator, signals are typed as VHDL Signal objects. When you create a Verilog generator, signals are typed as Verilog BaseWire objects.
This prevents accidentally mixing VHDL signals with Verilog instances (and vice versa) at compile time, catching errors before runtime.
Example
from elasticai.creator.hdl_generator import create_generator, HDLLanguage
from elasticai.creator.hdl_ir import Node, Shape
# Create a generator for VHDL - returns HDLGenerator[Signal]
vhdl_gen = create_generator(HDLLanguage.VHDL)
# Create signals - returns Signal (VHDL-specific type)
clk = vhdl_gen.create_signal("clk", width=1)
data_in = vhdl_gen.create_signal("data_in", width=8)
# Create a Verilog generator - returns HDLGenerator[BaseWire]
verilog_gen = create_generator(HDLLanguage.VERILOG)
# This would be a TYPE ERROR caught by the type checker:
# vhdl_gen.create_instance(ports={"clk": verilog_gen.create_signal("clk")})
Methods:
-
create_instance–Create a module/entity instance.
-
create_null_signal–Create a signal/wire that doesn't need definition.
-
create_signal–Create a signal/wire.
-
create_template_director–Create a template director for building HDL templates.
create_instance
¶
create_instance(
node: Node,
generics: dict[str, str] | None = None,
ports: dict[str, TSignal] | None = None,
) -> HDLInstance
Create a module/entity instance.
Parameters:
-
(node¶Node) –The node representing the module/entity to instantiate.
-
(generics¶dict[str, str] | None, default:None) –Generic/parameter values (name -> value). Optional.
-
(ports¶dict[str, TSignal] | None, default:None) –Port connections (port_name -> signal). Must be signals created by the same generator. Optional.
Returns:
-
HDLInstance–An HDL instance that can generate instantiation code.
Note
The type system ensures you can only pass signals created by the same generator. Mixing VHDL signals with a Verilog generator (or vice versa) will be caught as a type error.
create_null_signal
¶
create_signal
¶
Create a signal/wire.
Creates a VHDL signal or Verilog wire depending on the language.
Parameters:
-
(name¶str) –The signal/wire name.
-
(width¶int | None, default:None) –The bit width. If None or 1, creates a single-bit signal. Otherwise creates a vector signal/wire.
Returns:
-
TSignal–A language-specific signal that can be used with create_instance.
create_template_director
¶
create_template_director() -> HDLTemplateDirector
Create a template director for building HDL templates.
Template directors allow you to convert prototype HDL code into parameterized templates.
Returns:
-
HDLTemplateDirector–An HDL template director for building templates.
Example
gen = VHDLGenerator()
prototype_code = '''
entity adder is
generic (WIDTH : natural := 8);
port (
a, b : in std_logic_vector(WIDTH-1 downto 0);
sum : out std_logic_vector(WIDTH-1 downto 0)
);
end entity;
'''
template = (
gen.create_template_director()
.set_prototype(prototype_code)
.add_parameter("WIDTH")
.build()
)
# Use the template
code = template.substitute(entity="my_adder", WIDTH="16")
HDLInstance
¶
Bases: Protocol
Protocol for HDL module/entity instances.
Represents either a VHDL entity instance or a Verilog module instance.
Methods:
-
define_signals–Generate HDL code lines to define all signals/wires used by this instance.
-
instantiate–Generate HDL code lines to instantiate this module/entity.
Attributes:
-
implementation(str) –The name of the module/entity being instantiated.
-
name(str) –The instance name.
HDLSignal
¶
Bases: Protocol
Protocol for HDL signals/wires.
Represents either a VHDL signal or a Verilog wire.
Methods:
-
define–Generate HDL code lines to define this signal/wire.
-
make_instance_specific–Create a signal/wire with an instance-specific name.
Attributes:
define
abstractmethod
¶
Generate HDL code lines to define this signal/wire.
HDLTemplateDirector
¶
Bases: Protocol
Protocol for HDL template directors.
Provides a builder interface for creating HDL templates from prototype code.
Methods:
-
add_parameter–Add a template parameter (generic/parameter).
-
build–Build the final template.
-
set_prototype–Set the prototype HDL code to use as a template base.
add_parameter
abstractmethod
¶
add_parameter(name: str) -> HDLTemplateDirector
Add a template parameter (generic/parameter).
Parameters:
Returns:
-
HDLTemplateDirector–Self for method chaining.
build
abstractmethod
¶
Build the final template.
Returns:
-
Template–A string.Template that can be used to generate code.
set_prototype
abstractmethod
¶
set_prototype(prototype: str) -> HDLTemplateDirector
Set the prototype HDL code to use as a template base.
Parameters:
Returns:
-
HDLTemplateDirector–Self for method chaining.
create_generator
¶
create_generator(language: Literal[VHDL]) -> HDLGenerator[Signal]
create_generator(language: Literal[VERILOG]) -> HDLGenerator[BaseWire]
Create an HDL generator for the specified language.
This factory function provides static type safety through overloads: - When you pass HDLLanguage.VHDL, you get HDLGenerator[Signal] - When you pass HDLLanguage.VERILOG, you get HDLGenerator[BaseWire]
This means the type checker can verify that you don't mix VHDL signals with Verilog instances (or vice versa) at compile time.
Parameters:
-
(language¶HDLLanguage) –The target HDL language (VHDL or VERILOG).
Returns:
-
HDLGenerator[Signal] | HDLGenerator[BaseWire]–An HDL generator with the appropriate signal type.
Example
from elasticai.creator.hdl_generator import create_generator, HDLLanguage
# Create VHDL generator - typed as HDLGenerator[Signal]
vhdl_gen = create_generator(HDLLanguage.VHDL)
vhdl_signal = vhdl_gen.create_signal("data", width=8) # Returns Signal
# Create Verilog generator - typed as HDLGenerator[BaseWire]
verilog_gen = create_generator(HDLLanguage.VERILOG)
verilog_wire = verilog_gen.create_signal("data", width=8) # Returns BaseWire
# Type checker will catch this error:
# vhdl_gen.create_instance(ports={"data": verilog_wire}) # TYPE ERROR!
HDL IR¶
Classes:
Node
¶
Bases: Node, Protocol
Extending ir.core.Node to an hdl specific node.
This node contains all knowledge that we need to create and use an instance of an hdl component. However, this becomes a little bit complicated because vhdl differentiates between
Attributes:
implementation:: The name of the implementation, e.g., entity name in vhdl or module name for verilog, will be used to derive the architecture name.
E.g., if the implementation is "adder", we will instantiate the entity work.adder(rtl).
CAUTION: This behaviour is subject to change. Future versions might require the full entity name