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
:str
orpathlib.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
:list
ofdict
ofstr: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
ofdict
ofstr:bool
orstr: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".
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 )