Module circuitsim.simulation
High-level simulation API.
Expand source code
"""High-level simulation API."""
import tempfile
from pathlib import Path
import circuitgraph as cg
from natsort import natsorted
from circuitsim.codegen import generate_testbench
from circuitsim.simulators import (
    available_simulators,
    compile_simulator,
    execute_simulator,
    parse_simulation_output,
)
class CircuitSimulator:
    """Class for circuit simulation."""
    def __init__(self, ckt, working_dir=None, simulator="iverilog"):
        """
        Create new simulator.
        Parameters
        ----------
        ckt: circuitgraph.Circuit
                The circuit to simulate.
        working_dir: str or pathlib.Path
                The directory to write simulation files to. If `None`, a
                temporary directory will be used.
        simulator: str
                The simulator to use. One of ['iverilog', 'verilator', 'vcs'].
        """
        if simulator not in available_simulators:
            raise ValueError(
                f"Invalid simulator '{simulator}'. Must be one of "
                f"{available_simulators}."
            )
        if ckt.is_cyclic():
            raise ValueError("Cannot simulate cyclic circuit")
        self.simulator = simulator
        self.ckt = ckt
        if working_dir is None:
            self.temp_dir = tempfile.TemporaryDirectory(
                prefix=f"circuitsim_{ckt.name}_"
            )
            self.working_dir = Path(self.temp_dir.name)
        else:
            self.temp_dir = None
            self.working_dir = Path(working_dir)
            self.working_dir.mkdir(exist_ok=True)
        self.inputs = natsorted(list(ckt.inputs()))
        self.outputs = natsorted(list(ckt.outputs()))
        self._initialized = False
    def __del__(self):
        """Remove temporary directory if necessary."""
        if self.temp_dir:
            self.temp_dir.cleanup()
    def _initialize_simulator(self):
        generate_testbench(
            self.working_dir, self.ckt.name, self.inputs, self.outputs, self.simulator
        )
        cg.to_file(self.ckt, self.working_dir / f"{self.ckt.name}.v")
        netlists = [self.working_dir / "tb.v", self.working_dir / f"{self.ckt.name}.v"]
        compile_simulator(self.simulator, netlists, self.working_dir)
        self._initialized = True
    def simulate(self, vectors, num_processes=1, allow_x=False):
        """
        Execute the simulator on a list of vectors.
        Parameters
        ----------
        vectors: list of dict of str:bool
                The vectors to simulate. Each vector is represented as a
                dictionary mapping an input to a logical value.
        num_process: int
                The number of simulation processes to run. Specifying a
                number more than 1 will cause multiple simulations to be
                executed in parallel.
        allow_x: bool
                If True, the inputs/outputs can contain "don't care" or "x"
                values in addition to 0 and 1. Specify an don't care value
                for an input by setting it to "x". The outputs will then be
                returned as a dict of str:str, where each output is either
                "0", "1", or "x".
        Returns
        -------
        list of dict of str:bool or str:str
                The simulation outputs. Each vector is represented as a
                dictionary mapping an output to a logical value. If `allow_x`
                is True, then instead of logic values, each output will be
                mapped to either "0", "1", or "x".
        """
        if not self._initialized:
            self._initialize_simulator()
        if self.simulator == "verilator" and allow_x:
            raise ValueError("Cannot use x-based simulation with verilator")
        execute_simulator(
            self.simulator,
            self.inputs,
            vectors,
            self.working_dir,
            num_processes,
            allow_x=allow_x,
        )
        return parse_simulation_output(
            self.working_dir, self.outputs, num_processes, allow_x=allow_x
        )
Classes
class CircuitSimulator (ckt, working_dir=None, simulator='iverilog')- 
Class for circuit simulation.
Create new simulator.
Parameters
ckt:circuitgraph.Circuit- The circuit to simulate.
 working_dir:strorpathlib.Path- The directory to write simulation files to. If 
None, a temporary directory will be used. simulator:str- The simulator to use. One of ['iverilog', 'verilator', 'vcs'].
 
Expand source code
class CircuitSimulator: """Class for circuit simulation.""" def __init__(self, ckt, working_dir=None, simulator="iverilog"): """ Create new simulator. Parameters ---------- ckt: circuitgraph.Circuit The circuit to simulate. working_dir: str or pathlib.Path The directory to write simulation files to. If `None`, a temporary directory will be used. simulator: str The simulator to use. One of ['iverilog', 'verilator', 'vcs']. """ if simulator not in available_simulators: raise ValueError( f"Invalid simulator '{simulator}'. Must be one of " f"{available_simulators}." ) if ckt.is_cyclic(): raise ValueError("Cannot simulate cyclic circuit") self.simulator = simulator self.ckt = ckt if working_dir is None: self.temp_dir = tempfile.TemporaryDirectory( prefix=f"circuitsim_{ckt.name}_" ) self.working_dir = Path(self.temp_dir.name) else: self.temp_dir = None self.working_dir = Path(working_dir) self.working_dir.mkdir(exist_ok=True) self.inputs = natsorted(list(ckt.inputs())) self.outputs = natsorted(list(ckt.outputs())) self._initialized = False def __del__(self): """Remove temporary directory if necessary.""" if self.temp_dir: self.temp_dir.cleanup() def _initialize_simulator(self): generate_testbench( self.working_dir, self.ckt.name, self.inputs, self.outputs, self.simulator ) cg.to_file(self.ckt, self.working_dir / f"{self.ckt.name}.v") netlists = [self.working_dir / "tb.v", self.working_dir / f"{self.ckt.name}.v"] compile_simulator(self.simulator, netlists, self.working_dir) self._initialized = True def simulate(self, vectors, num_processes=1, allow_x=False): """ Execute the simulator on a list of vectors. Parameters ---------- vectors: list of dict of str:bool The vectors to simulate. Each vector is represented as a dictionary mapping an input to a logical value. num_process: int The number of simulation processes to run. Specifying a number more than 1 will cause multiple simulations to be executed in parallel. allow_x: bool If True, the inputs/outputs can contain "don't care" or "x" values in addition to 0 and 1. Specify an don't care value for an input by setting it to "x". The outputs will then be returned as a dict of str:str, where each output is either "0", "1", or "x". Returns ------- list of dict of str:bool or str:str The simulation outputs. Each vector is represented as a dictionary mapping an output to a logical value. If `allow_x` is True, then instead of logic values, each output will be mapped to either "0", "1", or "x". """ if not self._initialized: self._initialize_simulator() if self.simulator == "verilator" and allow_x: raise ValueError("Cannot use x-based simulation with verilator") execute_simulator( self.simulator, self.inputs, vectors, self.working_dir, num_processes, allow_x=allow_x, ) return parse_simulation_output( self.working_dir, self.outputs, num_processes, allow_x=allow_x )Methods
def simulate(self, vectors, num_processes=1, allow_x=False)- 
Execute the simulator on a list of vectors.
Parameters
vectors:listofdictofstr:bool- The vectors to simulate. Each vector is represented as a dictionary mapping an input to a logical value.
 num_process:int- The number of simulation processes to run. Specifying a number more than 1 will cause multiple simulations to be executed in parallel.
 allow_x:bool- If True, the inputs/outputs can contain "don't care" or "x" values in addition to 0 and 1. Specify an don't care value for an input by setting it to "x". The outputs will then be returned as a dict of str:str, where each output is either "0", "1", or "x".
 
Returns
listofdictofstr:boolorstr:str- The simulation outputs. Each vector is represented as a
dictionary mapping an output to a logical value. If 
allow_xis True, then instead of logic values, each output will be mapped to either "0", "1", or "x". 
Expand source code
def simulate(self, vectors, num_processes=1, allow_x=False): """ Execute the simulator on a list of vectors. Parameters ---------- vectors: list of dict of str:bool The vectors to simulate. Each vector is represented as a dictionary mapping an input to a logical value. num_process: int The number of simulation processes to run. Specifying a number more than 1 will cause multiple simulations to be executed in parallel. allow_x: bool If True, the inputs/outputs can contain "don't care" or "x" values in addition to 0 and 1. Specify an don't care value for an input by setting it to "x". The outputs will then be returned as a dict of str:str, where each output is either "0", "1", or "x". Returns ------- list of dict of str:bool or str:str The simulation outputs. Each vector is represented as a dictionary mapping an output to a logical value. If `allow_x` is True, then instead of logic values, each output will be mapped to either "0", "1", or "x". """ if not self._initialized: self._initialize_simulator() if self.simulator == "verilator" and allow_x: raise ValueError("Cannot use x-based simulation with verilator") execute_simulator( self.simulator, self.inputs, vectors, self.working_dir, num_processes, allow_x=allow_x, ) return parse_simulation_output( self.working_dir, self.outputs, num_processes, allow_x=allow_x )