Pass – parent, just takes an object (I think it’s a circuit, with InstanceGraphPass it’s a circuit, unclear for others) and assigns it to the pass’s main
InstancePass(Pass) – A pass that finds the paths to all the instances instances in a definition. This includes nested instances (instances which as used in the definitions of other instances).
init - call’s Pass’s init, then creates an empty instances list – main here is a circuit definition
run/_run - BFS search of all instances, return a list of paths to all.
note: callable is meaningless here, since InstancePass never inherited and thus never callable
DefinitionPass - A pass that creates a dictionary containing all the definitions used in a top-level circuit definition. It has the names of the definitions as keys and the definition objects as values. The top-level definition is included in the dictionary.
This is a recursive algorithm working on definitions.
It starts with the top level definition (self.main)
For the current iteration's definition, it gets all that definition's instances and recurs on the definition of each instance IF the instance comes from a definition and not a declaration.
Note: variable instancedefinition can contain either definitions or declarations. This is confusing
Add the current iteration's definition to the pass's dictionary of definitions. The self.definitions dictionary has the name of definitions as keys and the definition objects as values.
Note: if the pass is callable, it's called each time a new definition is added to the dictionary.
__is this really what this is doing? Why is this here? Neither DefinitionPass nor InstancePass are ever referenced___
not true. InstancePass never used, but definition pass subclassed a bunch, like BuildInstanceGraphPass.
subclasses of this use DefinitionPass._run to populate the definitions property of the Pass object
even if this were to do something, shouldn't it need to be changed to call InstancePass to find nested instances?
No. Since instances can't contain instances, only definitions can, this is not a problem.
can an instance have a definition in it's instances list? If so, then isn't this pass broken? - no, according to circuit.py isdefinition, a circuit is a definition if it has instances. Since can't be both a definition and an instance, instances can't contain instances
definition.instances can only have instances. Instances are instances of either definitions or declarations. These are accessed by type(instance)
And what does call do? Nothing has a __call__ method
The passes that subclass this have __call__ methods
BuildInstanceGraphPass
This has a graph which tracks which definitions are dependent on other definitions. Definitions as vertices and instances are directed edges. Each edge points from the definition that uses the instance to the instance's definition.
The graph is stored as a mapping where keys are definitions and values are lists of definitions that the key is dependent on
This inherits DefinitionPass's run, which calls BuildInstanceGraphPass's __call__ method for each definition (and not for declarations)
The __call__ hanldes one definition at a time. For each definition, this
Adds the definition to the graph if its not already in there
For each instance in the definition:
Add the instance's definition to the graph if its not already in the graph
Add an edge indicating dependency from the definition using the instance to the instance's definition
tsortedgraph is a list of (definition, [dependent definitions list]) sorted so that dependent vertices come after their dependencies
InstanceGraphPass -
call BuildInstanceGraphPass to build a dependency graph of definitions for circuit definition passed in as main
set that list as value for self.tsortedgraph
If callable, which it isn't but subclasses might, will call self for each vertex in the sorted graph
How Are Circuit Definitions And Instances Structured
Each definition has a instances list, which is a list of instances in that are added to that definition
Each of those instances may have a definition that is gotten by getting the instances type. This is because an instance's definition is the class it is an instance of. Getting the type of an instance gets the class its an instance of.
Where Do Circuit Declarations, Definitions, and Instances Fit in the Type System? – see magma/circuit.py
CircuitKind – instances of this are circuit declarations.
Declarations are like function declarations in C, Magma declarations declare the ports but do not show how to define the circuit. Declarations cannot contain instances. Declarations are used in use cases including wrappers for CoreIR C++ modules that provide definitions, or as an abstract interface for multiple definitions that are backend-dependent.
AnonymousCircuitType is an instance of this, so what does that make instances of AnonymousCircuit? You can’t have instances of a circuit declaration without a definition, right?
CircuitType is an instance of this, subclass of AnonymousCircuitType
a comment declares instances of CircuitType are instances placed in definitions
DeclareCircuit returns an instance of CircuitKind
Where is this used differently from the result of DefineCircuit?
DefineCircuitKind – instances of this are circuit definitions. This is a subclass of a CircuitKind.
Defintions are like function definitions in C, they define how a circuit works in hardware, such as the instances of other circuit definitions used inside of the current circuit.
Instances of DefineCircuitKind (known as circuit definitions) get a place method, which allows placing circuit instances in the circuit definitions. Place is called by an instance when the instance is created
Since DefineCircuitKind is used as a metaclass, instances of the circuit definitions don’t get the place method
Circuit is an instance of DefineCircuitKind
CircuitType is Circuit’s parent class,
Circuit – has two functions
Instances of Circuit are circuit instances that are placed in circuit interfaces
Subclasses of circuit are CircuitDefinitions
Why does Circuit exist? Why not just make CircuitType’s metaclass to be DefineCircuitkind?
DefineCircuit returns an instance of DefineCircuitKind
CircuitType – instances of this are circuit instances that have a declaration and not a definition.
When an instance of CircuitType is created, it’s __init__ function calls the place of the current active definition, which is stored in the global currentDefinition function
This class (and subclasses of it) are circuit declarations.
Circuit - instances of this are circuit instances that have a definition and not a declaration.
This class (and subclasses of it) are circuit definitions.
How do circuit definition/interface interfaces work?
Each circuit definition and interface has an interface object.
Each Interface instance has a ports dictionary. The dictionary is a mapping of:
key - name of port in interface
value - instance of Magma type, whose name field is a reference to the definition or instance containing this port.
The interface of a definition is created in the following way:
The DeclareInterface function is a factory that produces interface. Each interface is a class that is an instance of the InterfaceKind metaclass. This is accomplished because the superclass of InterfaceKind, Kind, is a subclass of type and so calling InterfaceKind's call eventually reaches Kind's __init__, which constructs a new Interface type using the type __init__ function.
When a CircuitDefinition is created, two things happen:
Using CircuitKind's __new__ method: a new Interface class is created using DeclareInterface.
Using DefineCircuitKind's __new__ method: an instance of that newly created Interface class is created and assigned to the definition's interface field.
The instance is created by calling the Interface class's __call__ function, which calls the interface class's constructor. That constructor is _DeclareInterface's __init__ since the metaclass work used to create the Interface class set _DeclareInterface as its base class.
_DeclareInterface.__init__ creates a dictionary of ports using this process:
for each port name and port type (where each port type is an instance of the Kind object, and is a type subclassing Type), add a dictionary entry whose key is the name of the port and the value is: an instance of the Type. The Type instance's name field is not actually a name, but is an object holding:
a reference to the circuit definition or instance containing the port
the name of the port in the instance
How does compiling magma circuit definitions to coreir modules work?
(optional) – call the compile function that is not part of the CoreIRBackend (CIRB) object. This creates a coreirbackend, calls compile on the coreirbackend with the circuit handed to it
Compile for CIRB –
builds a graph of definition -> list of dependent definitions graph using InstanceGraphPass
Call compile_definition for each definition, in order so that higher-order definitions come after the definitions they are dependent on:
check_interface - verifies that the top-level ports on the definition's interface are bits, arrays, or records and that contained types within these ports are also one of those three things.
convert_interface_to_module_definition - create a PyCoreIR record representing the Magma definition's ports. This is done by calling get_type on each ports in the interface of the Magma definition.
get_type takes in a Magma type and converts it and all Magma types contained in it (like bits inside arrays) to PyCoreIR types.
module_type is PyCoreIR type object, specifically a Record
context.global_namespace.new_module - creates a new CoreIR C++ module wrapped by PyCoreIR using python's C interface with the CoreIR C++ library. This module is added to the global CoreIR namespace.
coreir_module is a PyCoreIR module object
coreir_module.new_definition - adds a definition to CoreIR C++ module, returns a pointer to that module wrapped by PyCoreIR's ModuleDef class.
module_definition is a PyCoreIR moduleDef object
compile_definition_to_module_definition -
create a dictionary output_ports using add_output_port:
key - a Magma type instance representing a port (or part of a port) on the Magma definition
values - a string for accesing the port in the CoreIR object
for each instance in the Magma definition:
the wire the clock from definition to the instance if needed
compile_instance - compile it to a PyCoreIR module and add it to the PyCoreIR module definition
add all output ports of the Magma instance to output_ports
for each instance in the Magma definition
connect all input ports to one output port in the PyCoreIR definition using the connect function
connect -
arguments are:
module_definition is the PyCoreIR module definition,
port is the the type instance for the input magma port
value is the magma type instance for the output port connected to the input port
output_ports is the dictionary previously described
connect recurses until it gets to individual bits, gets the CoreIR select path strings for the input and output ports, and connects them in the PyCoreIR module definition
Ignore all the extra stuff for things like MAGMA_COREIR_FIRRTL, this is old and dead code
for each port on the interface of the Magma definition
connect it to an output port using the same process as for the input ports of the instances in the definition
assign the created definition to coreir_module and return the module