Note
This is a WIP Beta version. All content is subject to change.
Developer Guide¶

This section will teach you how to develop custom component implementations and integrate them into CIRCA’s approximation flow. The covered topics are:
You can find more detailed technical information on the framework’s modules in the module index.
Basics¶
CIRCA is designed to be:
- General: The framework should not be restricted to certain circuit types, error metrics, approximation and search techniques, or specific target technologies.
- Modular: The framework architecture should enable the exchange of certain processing steps without affecting other steps. Modularity is key for the evaluation and the comparison of different techniques under a consistent experimental setup.
- Compatible: The framework, in particular its inputs and outputs, should connect to other, widely-used academic and commercial front-end and back-end tools, e.g., tools synthesizing circuits for ASIC or FPGA technology.
- Extensible: The framework should facilitate the swift implementation and evaluation of new techniques.
To achieve this, CIRCA’s approximation flow is handled by independent stages and processing blocks (short: components) which communicate using well- defined interfaces. Each component can be extended by custom implementations and provides a specific piece of functionality which is needed for the approximation process. Integrating your own component implementations allows you to easily test different search strategies, approximation techniques, and quality assurance methods.
The framework provides the necessary structure for the components to work independently. CIRCA utilizes data structures as well as classes which can be accessed and interpreted by all components, enabling them to work together effectively.
Component implementation¶
The customizable components of CIRCA are classes, implementing a specific interface. The interfaces are specified by abstract base classes from which the custom implementations must be derived. They were implemented using Python’s Abstract Base Classes module, so their subclasses have to implement some methods in order to be instantiatable. The concept of abstract base classes ensures that new implementations provide the functions used by CIRCA in its standard approximation flow.
To create a custom component implementation, simply create a subclass of the abstract base class for that component and make sure to implement all of the base class’s abstract methods (note that it is possible to derive from an already existing subclass to minimize implementation effort).
Each abstract base class provides a dictionary SUPPORTED_SUBCLASSES
. The key corresponds to the method’s name used in the configuration file, e.g., AnnotatedCandidates. The value is a lamda expression, calling the constructor of the corresponding implementation. Once an implementation is finished, i.e., the subclass, add the key-value pair to the dictionary to make the implementation available.
Component classification¶
We differentiate between two types of customizable components: exchangeable components and extensible components.
- Exchangeable components are the stages and processing blocks Input, QualityAssurance, Estimation, Search, and Output. Exactly one implementation of each component is used when CIRCA is executed. The used implementations have a great impact on the approximation process and on the quality of the outcome.
- Extensible components can be seen as functionality which is either used or not used in an approximation process. Extensible components include approximators from the Approximation block and ErrorMetrics. While the number of exchangleable components in an approximation process is restrricted to exactly one, the user has no upper limit on the number of employed extensible components.
Important data structures and classes¶
In order to implement your custom components, you need to know the data structures, classes, and interfaces you will be working with. Since most of them are already documented extensively in the code, their implementation details will be omitted here. Instead, to improve the understanding of the objects, an overview of the scope of the particular object is given from a high-level perspective.
GeneralInformation¶
The GeneralInformation
class acts as a container for information used by several components and is instantiated once in the Input stage. After instantiation, it is passed to every other stage when they are created.
The GeneralInformation
object’s content includes paths to the configuration and input circuit files, information on the input circuit, and references to the data structures Candidates, Variants, as well as nodes and circuits.
When creating an Input component implementation, your job is to setup the GeneralInformation
instance and usually also extract PI/PO (primary input/primary output) information, either manually or using the existing method. The PI/PO information is needed, for example, in the quality assurance step. For the automatic extraction of PI/PO information from Verilog code, GeneralInformation
provides a method.
Candidates¶
A candidate is a part of the input circuit which can be replaced by an approximate component. Usually, the arithmetic units in the data path of a circuit are specified as candidates. The selection of candidates in a circuit is crucial since the selection of candidates directly influences the quality of the approximated outcome.
Candidates are represented by the Candidate
class. Each Candidate
instance must have a unique name and specify local quality constraints, i.e., local error metrics with error bounds. The local quality constraints of a candidate Every candidate has a reference to its original implementation (a circuit file containing only the candidate from the input circuit). From the candidate’s original implementation, the candidate’s variants are generated and stored in a candidate-specific directory.
A candidate can represent different parts of a circuit, e.g., a register, a wire, or an entire circuit module. Depending on the component or part of the circuit represented by the candidate, the candidate may have to be treated differently and may offer different methods. Thus, the Candidate
class is abstract and cannot be instantiated. A specific implementation of a candidate is described in a subclass, derived from the Candidate
class. In this way, CIRCA does not restrict candidates to a specific parts of a circuit. Furthermore, the candidate’s type can be used to derive a unique name for the candidates.
All candidates are stored in a CandidateSet
, which is a basic data structure containing the Candidate
instances and the path to the directory in which the candidate files are stored. It is accessible through the GeneralInformation
instance. Candidates can be added and removed by all components.
Variants¶
A variant is an unique instance of a candidate with a certain degree of approximation applied. The relation of a variant to its candidate can be compared to the relation between an object and its class. The candidate specifies the part of the input circuit to approximate, at least one approximation method and some quality constraints. The variants are the resulting circuits, each having only one approximation method and possibly adjusted quality constraints.
Variants do not need a name, since they generate a unique ID value based on their candidate’s name, their approximation method, and their local quality constraints. Since the ID can become very long, a hash value is generated which can be used instead. The Variant
instances have a reference to their candidate and to their circuit file.
Variant
objects can be created in two ways: A Candidate
instance can generate its own original variant, which is basically a copy of the candidate’s part of the input circuit. Each Variant
instance can then generate its “neighboring” variants by either switching the approximation method or increasing one error bound by one step. These variants are called neigbors or children of the variant generating them. The variants created this way do not have a circuit file however. They must be chosen for the Approximation stage where these files are created.
All variants are stored in a VariantSet
, a data structure similar to the CandidateSet
with a slightly different internal structure. It can also be accessed through the GeneralInformation
instance and manipulated by all stages. Throughout the approximation process, the Approximation stage creates new variants and adds them to the VariantSet
; hence, the set is continuously extended. This can be seen as an on-the-fly generation of an approximate component library. That is, if an existing approximate component library is loaded into CIRCA, it would either represent or even replace the VariantSet
.
Note that a variant’s ID (or hash) is unique. Thus, each variant is only created once in an approximation process which improves efficiency.
Nodes and Circuits¶
In the approximation process, approximated variants of all specified candidates are generated. Then, CIRCA replaces the original implementation of a candidate with its approximated variants to create an approximated circuit. These approximated circuits are represented by instances of the Circuit
class. An instance holds information about the circuit file and the result of the verification, i.e., whether the circuit’s quality has been validated by the QualityAssurance stage and whether the circuit has passed the check.
All Circuit
instances are stored in an ApproximatedCircuits
instance representing the directory in which the circuit files are stored. A circuit can be stored in Verilog and in BLIF format.
An approximated circuit is uniquely described by the combination of variants used to implement the candidates. We denote this combination as circuit configuration. Since the search space is modeled as a graph structure, we identify, in the context of the search, configurations with Node
s. A Node
is a representation of a circuit in another context, providing different information and functionality. Once a Node
’s circuit has been generated, the corresponding Circuit
instance and the Node
are connected by bidirectional references. The Node
class specifies the interface for traversing the search space and working with circuits without having to provide detailed circuit information on this abstract level. The components use Node
s as input and output of their functions, i.e., as an interface.
A Node
has a method for generating its children, similar to the Variant
class. The children are those nodes, providing a circuit configuration with only one candidate implementing a different variant than the parent and this variant has a local error bound increased by one step compared to the parent’s variant. For example, consider a node with the configuration {C1, WC1; C2, WC3}, C1, WC1 being a candidate’s variant with an worst-case error bound of 1 and C2, WC3 a candidate’s variant with a worst-case error bound of 3. Then, the children of this node are: {C1, WC2; C2, WC3} and {C1, WC1; C2, WC4} (assuming a step-size of 1). Note that this means that a Node
instance could generate a child Node
with the same configuration as its parent, by changing a candidate to its previous variant. Avoiding these circles is part of the Search stage component and can be accomplished using the Node
’s id
attribute.
There is no central data structure storing all Node
s as this is considered to be the Search component’s task and many nodes are likely to be never actually used. Make sure to look into the Node
documentation if you want to implement your own stage component.
Exchangeable components¶
Exchangeable components share many properties which must be considered when developing a custom implementation. Here you can find the features they have in common and detailed documentation of all five components.
Component details¶
Extensible components¶
Extensible components have very little in common since they are used for very specialized purposes and not in a generalized manner like the exchangeable stage components. They are used on the same level as many of the specialized data structures which cannot be customized (but may be made customizable in the future). You can create as many custom implementations for these as you like to enrich the framework with new possibilities and use them independently with built in or custom component implementations. However, it can be useful to create approximation methods or error metrics that are especially suitable to use in conjunction with a specific custom component.
Utils¶
The CIRCA framework provides some utility modules for working with the configuration file, generating log messages and accessing the file system and OS functions.
Configuration utils cfg_util
¶
The cfg_util
module provides a configuration file parser CfgParser
to easily get option
values or dictionaries of options and their values from a whole section of the config file. It also
includes a parser function parseQualityConstraintSpecifier
to parse the values from a quality
constraint specification string as defined in the user guide’s subsection about the configuration file
Logging utils logutils
¶
CIRCA comes with a relatively extensive logging functionality that lets you write logging output to files or to the console with five priority levels. Log messages to the console are also colored based on their level. To include logging in a custom module, simply include this code in the module:
import logging from circa.utils.logutils import IndentationAdapter log = logging.getLogger("circa.base.<your_module_name>") log = IndentationAdapter(log, offset=7)
Where <your_module_name>
is the name you want to appear as source of the log message. You can
adjust the offset
parameter of the IndentationAdapter
to increase or decrease the amount of
indentation that is prepended to the message. For writing a log message, use one of the following
functions:
log.debug("Message of low importance, only for debugging") log.info("General message for the user, can be ignored safely") log.warning("Something is not right but the process will continue") log.error("Something unexpected happened that might lead to a crash") log.critical("Something very bad happened and the process will be stopped")