diff --git a/.github/workflows/test_code.yml b/.github/workflows/test_python_api.yml
similarity index 93%
rename from .github/workflows/test_code.yml
rename to .github/workflows/test_python_api.yml
index 1077aaba7..506f273bf 100644
--- a/.github/workflows/test_code.yml
+++ b/.github/workflows/test_python_api.yml
@@ -1,5 +1,5 @@
---
-name: Lint and Test
+name: Lint and Test Python API
# https://docs.github.com/en/free-pro-team@latest/actions/guides/building-and-testing-python
on:
@@ -34,6 +34,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
+ sudo apt install -y ngspice
python -m pip install --upgrade pip
pip install -r requirements.txt
# pip install .
diff --git a/docs/source/common-python-api.rst b/docs/source/common-python-api.rst
index 3afab7188..dc0471e01 100644
--- a/docs/source/common-python-api.rst
+++ b/docs/source/common-python-api.rst
@@ -87,4 +87,125 @@ Constants
^^^^^^^^^
1. ``COMMON_PLATFORMS_PREFIX_MAP``
- This is a dictionary of common platforms (currently sky130) and their cell naming prefixes. See the ``cell()`` def in the ``generate_verilog()`` function for more information on how to use it.
\ No newline at end of file
+ This is a dictionary of common platforms (currently sky130) and their cell naming prefixes. See the ``cell()`` def in the ``generate_verilog()`` function for more information on how to use it.
+
+2. Simulation (``common.simulation``)
+#####################################################
+This module exports functions used to simulate SPICE testbenches with multiple parameters.
+
+This module supports the use of `Mako `_ templating library to insert parameters into SPICE templates.
+
+Functions
+^^^^^^^^^
+1. ``run_simulations(parameters: dict, platform: str, simulation_dir: str, template_path: str, runs_dir: str, sim_tool: str, num_concurrent_sims: int, netlist_path: str) -> int``
+
+ Generates configurations of all combinations of the given ``parameters`` and runs simulations for each case. The testbench SPICE file, configuration parameters, and the ouptut for each run are generated in ``{simulation_dir}/{runs_dir}``.
+
+ The testbench SPICE file given by ``template_path`` follows the `Mako `_ templating syntax. Use the ``${parameter}`` syntax for inserting parameters in the file. The following parameters are automatically inserted during each run.
+
+ - ``run_number`` (int): The number/index of the run/configuration.
+ - ``sim_tool`` (str): Command for the simulation tool used.
+ - ``platform`` (str): The platform/PDK.
+ - ``template`` (str): Path to the SPICE testbench template.
+ - ``netlist_path`` (str): Absolute path to the SPICE netlist of the design to be simulated.
+
+ Example SPICE template: (From the `Temperature Sensor Generator `_)
+ .. code-block:: spice
+
+ .lib '${model_file}' ${model_corner}
+ .include '${netlist_path}'
+
+ .param temp_var = ${temp}
+ .param vvdd = 1.8
+ .param sim_end = '800m/exp(0.04*temp_var)'
+
+ Each configuration is run/simulated in a directory in the ``runs_dir``. Each run directory contains the final SPICE testbench with the parameters inserted, a ``parameters.txt`` file containing the values of each parameter, and the output log file.
+
+ ``parameters`` is a dict with keys corresponding to the parameter's name and the values of one of the following types.
+
+ 1. A constant value.
+ The value of this parameter will be the same for every configuration/run.
+ .. code-block:: python
+
+ {'param': 'value'}
+
+ 2. Array of integer/float/string constants.
+ Each of the values in the array will be swept.
+ .. code-block:: python
+
+ {'param': [1, 2, 3, 8]}
+ # OR
+ {
+ 'param': {
+ 'values': [1, 2, 3, 8]
+ }
+ }
+
+ 3. Increments.
+ All values starting from ``start`` (included) and ending at ``end`` (included if it is ``start + n * step``) will be swept with a step of ``step``. The default value for ``step`` is ``1``.
+ .. code-block:: python
+
+ {'param': {
+ 'start': 10,
+ 'end': 50,
+ 'step': 10
+ }}
+ # param will take values 10, 20, 30, 40, 50
+
+ Example parameters:
+ .. code-block:: python
+
+ # Runs 10 total simulations
+ # Sweeps through all temperatures from 10 to 100 (both included) with increments of 10.
+ example1 = {
+ 'temp': {'start': 10, 'end': 100, 'step': 10}
+ }
+
+ # Runs 9 total simulations
+ # Sweeps through all the 3 input voltages as well as all the 3 temperatures
+ example2 = {
+ 'input_voltage': [1, 2, 3],
+ 'temp': [20, 30, 40]
+ }
+
+ # Runs 4 total simulations
+ # Duty cycle and aux_spice_path remain the same in all simulations
+ # input_voltage is swept
+ example3 = {
+ 'duty_cycle': 10,
+ 'aux_spice_path': 'auxcell.cdl',
+ 'input_voltage': [1, 2, 3]
+ }
+
+ See the generators' Python files in ``tools/`` for more examples.
+
+ Arguments:
+ - ``parameters`` (dict): Dictionary of parameters. Explained above.
+ - ``platform`` (str): Platform/PDK. (eg: ``sky130hd```)
+ - ``simulation_dir`` (str): Path to the directory where the simulation source files are placed and the outputs will be generated. (Default: ``simulations``)
+ - ``template_path`` (str): Path to the SPICE template file for the testbench. (Default: ``templates/template.sp``)
+ - ``runs_dir`` (str): Path to a directory inside the ``simulation_dir`` directory where the outputs for the simulations will be generated. (Default: ``runs``)
+ - ``sim_tool`` (str): Command for the simulation tool. ``ngspice``, ``xyce``, and ``finesim`` are supported. (Default: ``ngspice``)
+ - ``num_concurrent_sims`` (int): The maximum number of concurrent simulations. (Default: ``4``)
+ - ``netlist_path`` (str): Path to the SPICE netlist inside the ``simulation_dir`` of the design to be simulated. (Default: ``netlist.sp``)
+
+ **Returns (int)**: The total number of simulations run.
+
+ Overall example: (From the `Temperature Sensor Generator `_)
+ .. code-block:: python
+
+ run_simulations(
+ parameters={
+ 'temp': {'start': tempStart, 'end': tempStop, 'step': tempStep},
+ 'model_file': model_file,
+ 'model_corner': platformConfig['model_corner'],
+ 'nominal_voltage': platformConfig['nominal_voltage'],
+ 'design_name': designName
+ },
+ platform="sky130hd",
+ simulation_dir="simulations",
+ template_path=os.path.join("templates", f"tempsenseInst_{simTool}.sp"),
+ runs_dir=f"run/prePEX_inv{num_inv}_header{num_header}/",
+ sim_tool=simTool,
+ netlist_path=dstNetlist
+ )
\ No newline at end of file
diff --git a/openfasoc/generators/common/__init__.py b/openfasoc/generators/common/__init__.py
index ae57c6291..c1ca2645e 100644
--- a/openfasoc/generators/common/__init__.py
+++ b/openfasoc/generators/common/__init__.py
@@ -5,6 +5,8 @@
- `common.verilog_generation`
1. `generate_verilog(parameters: dict, src_dir: str, out_dir: str) -> None`: Used to generate synthesizable Verilog files (for OpenROAD flow) from source Mako-based Verilog templates.
2. `COMMON_PLATFORMS_PREFIX_MAP` (dict): This is a dictionary of common platforms (currently sky130) and their cell naming prefixes.
+- `common.simulation`
+ 1. `run_simulations()`: Used to run SPICE testbenches with multiple parameters.
See individual function documentation for more information on a particular function.
"""
\ No newline at end of file
diff --git a/openfasoc/generators/common/simulation/__init__.py b/openfasoc/generators/common/simulation/__init__.py
new file mode 100644
index 000000000..90150528e
--- /dev/null
+++ b/openfasoc/generators/common/simulation/__init__.py
@@ -0,0 +1,104 @@
+"""A common simulation used in OpenFASOC generators.
+
+Sweeps all combinations of given parameters and runs parallel SPICE simulations with different configurations based on the parameters.
+
+Exported functions:
+- `run_simulations()`
+"""
+
+from os import path, makedirs
+from common.simulation.simulation_config import _generate_configs
+from common.simulation.simulation_run import _run_simulations
+
+def run_simulations(
+ parameters: dict,
+ platform: str,
+ simulation_dir: str = "simulations",
+ template_path: str = path.join("templates", "template.sp"),
+ runs_dir: str = "runs",
+ sim_tool: str = "ngspice",
+ num_concurrent_sims: int = 4,
+ netlist_path: str = "netlist.sp"
+) -> int:
+ """Runs SPICE simulations.
+
+ Generates configurations of all combinations of the given `parameters` and simulates each case. The testbench SPICE file, configuration parameters, and the output for each run are generated in the `simulation_dir/runs_dir` directory.
+
+ The testbench SPICE file given by `template_path` follows the [Mako](https://makotemplates.org) templating syntax. Use the `${parameter}` syntax for inserting parameters in the file. The following parameters are automatically inserted during each run.
+ - `run_number` (int): The number/index of the run/configuration.
+ - `sim_tool` (str): Command for the simulation tool used.
+ - `platform` (str): The platform/PDK.
+ - `template` (str): Path to the SPICE testbench template.
+ - `netlist_path` (str): Absolute path to the SPICE netlist of the design to be simulated.
+
+ Each configuration is run/simulated in a directory in the `runs_dir`. Each run directory contains the final SPICE testbench with the parameters inserted, a `parameters.txt` file containing the values of each parameter, and the output log file.
+
+ `parameters` is a dict with keys corresponding to the parameter's name and the values of one of the following types.
+ 1. A constant value.
+ The value of this parameter will be the same for every configuration/run.
+ ```
+ {'param': 'value'}
+ ```
+
+ 2. Array of integer/float/string constants.
+ Each of the values in the array will be swept.
+ ```
+ {'param': [1, 2, 3, 8]}
+ # OR
+ {
+ 'param': {
+ 'values': [1, 2, 3, 8]
+ }
+ }
+ ```
+
+ 3. Increments.
+ All values starting from `start` and ending at `end` will be swept with a step of `step`. The default value for `step` is `1`.
+ ```
+ {'param': {
+ 'start': 10,
+ 'end': 50,
+ 'step': 10
+ }}
+ # param will take values 10, 20, 30, 40, 50
+ ```
+
+ Arguments:
+ - `parameters` (dict): Parameters used to generate the runs.
+ - `platform` (str): Platform/PDK.
+ - `simulation_dir` (str = "simulations"): Path to the directory where the simulation source files are placed and the outputs will be generated.
+ - `template_path` (str = "templates/template.sp"): Path to the SPICE template file for the testbench. (The template is a SPICE file with [Mako](https://makotemplates.org) templating syntax)
+ - `runs_dir` (str = "runs"): Path to a directory inside the `simulation_dir` directory where the outputs for the simulations will be generated.
+ - `sim_tool` (str = "ngspice"): Command for the simulation tool.
+ - `num_concurrent_sims` (int = 4): The maximum number of concurrent simulations.
+ - `netlist_path` (str = "netlist.sp"): Path to the SPICE netlist of the design to be simulated.
+
+ Returns (int): The number of simulations run.
+ """
+
+ runs_dir_path = path.join(simulation_dir, runs_dir)
+ template = path.join(simulation_dir, template_path)
+
+ if not path.exists(runs_dir_path):
+ makedirs(runs_dir_path)
+
+ config_number = _generate_configs(
+ parameters=parameters,
+ sim_tool=sim_tool,
+ platform=platform,
+ template=template,
+ netlist_path=netlist_path,
+ runs_dir_path=runs_dir_path
+ )
+
+ print(f"Number of configurations: {config_number}")
+ print(f"Number of concurrent simulations: {num_concurrent_sims}")
+
+ _run_simulations(
+ num_configs=config_number,
+ num_concurrent_sims=num_concurrent_sims,
+ sim_tool=sim_tool,
+ runs_dir_path=runs_dir_path
+ )
+
+ return config_number
diff --git a/openfasoc/generators/common/simulation/simulation_config.py b/openfasoc/generators/common/simulation/simulation_config.py
new file mode 100644
index 000000000..135d8d070
--- /dev/null
+++ b/openfasoc/generators/common/simulation/simulation_config.py
@@ -0,0 +1,195 @@
+"""Sweeps all combinations of given parameters and generates the configurations.
+
+This module is part of the simulation common module. Functions for generating final SPICE files from a template are exported.
+
+Configurations are generated from a template SPICE file that follows the [Mako](https://makotemplates.org) syntax. All possible combinations of the input parameters are swept. The final SPICE files are generated from the template by inserting the parameter combinations. Each file is called a "config" or "configuration."
+
+Exported functions:
+- `_generate_configs()`
+- `_generate_run_parameters()`
+- `_generate_config()`
+
+See individual functions for further documentation.
+"""
+
+from os import path, makedirs
+from shutil import rmtree
+from common.verilog_generation import _generate_file
+
+def _generate_configs(
+ parameters: dict,
+ sim_tool: str,
+ platform: str,
+ template: str,
+ netlist_path: str,
+ runs_dir_path: str
+) -> int:
+ """Generates configurations for the simulations.
+
+ Generates a directory for each configuration in the runs directory given by `runs_dir_path`. Each configuration includes a testbench SPICE file (`sim_[run_number].sp`) corresponding to the configuration and a `parameters.txt` file containing the values of each parameter in the particular configuration.
+
+ `parameters` is a dict with keys corresponding to the parameter's name and the values of one of the following types.
+ 1. A constant value.
+ The value of this parameter will be the same for every configuration/run.
+ ```
+ {'param': 'value'}
+ ```
+
+ 2. Array of integer/float/string constants.
+ Each of the values in the array will be swept.
+ ```
+ {'param': [1, 2, 3, 8]}
+ # OR
+ {
+ 'param': {
+ 'values': [1, 2, 3, 8]
+ }
+ }
+ ```
+
+ 3. Increments.
+ All values starting from `start` and ending at `end` will be swept with a step of `step`. The default value for `step` is `1`.
+ ```
+ {'param': {
+ 'start': 10,
+ 'end': 50,
+ 'step': 10
+ }}
+ # param will take values 10, 20, 30, 40, 50
+ ```
+
+ Arguments:
+ - `parameters` (dict): Parameters used to generate the runs.
+ - `sim_tool` (str): Command for the simulation tool.
+ - `platform` (str): Platform/PDK.
+ - `template` (str): Path to the SPICE template file for the testbench. (The template is a SPICE file with [Mako](https://makotemplates.org) templating syntax)
+ - `netlist_path` (str): Path to the netlist file used for simulation. (Absolute path to this file will be added as a Mako parameter)
+ - `runs_dir_path` (str): Path to the directory in which the simulation runs will be generated.
+ """
+ parameters_iterator = {}
+
+ for parameter in parameters.items():
+ parameter_values = []
+
+ if type(parameter[1]) is dict:
+ if 'values' in parameter[1]:
+ parameter_values = parameter[1]['values']
+
+ elif 'start' in parameter[1] and 'end' in parameter[1]:
+ value = parameter[1]['start']
+ step = parameter[1]['step'] if 'step' in parameter[1] else 1
+
+ while value <= parameter[1]['end']:
+ parameter_values.append(value)
+ value += step
+
+ elif type(parameter[1]) is list:
+ parameter_values = parameter[1]
+
+ else:
+ parameter_values = [parameter[1]]
+
+ parameters_iterator[parameter[0]] = {
+ 'values': parameter_values,
+ 'i': 0
+ }
+
+ num_params = len(parameters_iterator.keys())
+ config_number = 0
+ configs_generated = num_params == 0
+ while not configs_generated:
+ config_number += 1
+ _generate_config(
+ run_parameters=_generate_run_parameters(
+ parameters_iterator=parameters_iterator,
+ config_number=config_number,
+ sim_tool=sim_tool,
+ platform=platform,
+ template=template,
+ netlist_path=netlist_path
+ ),
+ config_number=config_number,
+ runs_dir_path=runs_dir_path,
+ template=template
+ )
+
+ change_next_param = True
+ for i, parameter in enumerate(parameters_iterator.items()):
+ if change_next_param:
+ parameter[1]['i'] += 1
+
+ change_next_param = parameter[1]['i'] == len(parameter[1]['values'])
+
+ if change_next_param:
+ parameter[1]['i'] = 0
+
+ if i == num_params - 1 and change_next_param:
+ configs_generated = True
+
+ return config_number
+
+def _generate_run_parameters(
+ parameters_iterator: dict,
+ config_number: int,
+ sim_tool: str,
+ platform: str,
+ template: str,
+ netlist_path: str
+) -> dict:
+ """Generates and returns parameters for a given run.
+
+ Generates and returns the final parameters for a given run from the parameters iterator and the number of the config.
+
+ Arguments:
+ - `parameters_iterator` (dict): A dictionary with keys equal to the parameter name and values of the following format: `{'values': list[str], 'i': int}`. Here `values` is a list of all the possible values the particular parameter can take, and `i` is the value selected for the current config.
+ - `config_number` (str): The number/index of the configuration.
+ - `sim_tool` (str): Command for the simulation tool.
+ - `platform` (str): Platform/PDK.
+ - `template` (str): Path to the SPICE template file for the testbench. (The template is a SPICE file with [Mako](https://makotemplates.org) templating syntax)
+ - `netlist_path` (str): Path to the netlist file used for simulation. (Absolute path to this file will be added as a Mako parameter)
+ """
+ run_parameters = {
+ 'run_number': config_number,
+ 'sim_tool': sim_tool,
+ 'platform': platform,
+ 'template': template,
+ 'netlist_path': path.abspath(netlist_path),
+ 'root_dir': path.abspath('.')
+ }
+
+ for parameter in parameters_iterator.items():
+ run_parameters[parameter[0]] = parameter[1]['values'][parameter[1]['i']]
+
+ return run_parameters
+
+def _generate_config(
+ run_parameters: dict,
+ config_number: int,
+ runs_dir_path: str,
+ template: str
+) -> None:
+ """Generates the files required for a particular configuration.
+
+ Generates the files (SPICE testbench file and `parameters.txt`) for a particular configuration/run in the directory for the run.
+
+ Arguments:
+ - `run_parameters` (dict): Parameters for the particular run.
+ - `config_number` (str): The number/index of the configuration.
+ - `runs_dir_path` (str): Path to the directory in which the simulation runs will be generated.
+ - `template` (str): Path to the SPICE template file for the testbench. (The template is a SPICE file with [Mako](https://makotemplates.org) templating syntax)
+ """
+ run_dir_path = path.join(runs_dir_path, str(config_number))
+
+ if not path.exists(run_dir_path):
+ makedirs(run_dir_path)
+ else:
+ rmtree(run_dir_path)
+ makedirs(run_dir_path)
+
+ open(path.join(run_dir_path, 'parameters.txt'), "w").write(str(run_parameters))
+
+ _generate_file(
+ input_path=template,
+ output_path=path.join(run_dir_path, f"sim_{config_number}.sp"),
+ parameters=run_parameters
+ )
\ No newline at end of file
diff --git a/openfasoc/generators/common/simulation/simulation_run.py b/openfasoc/generators/common/simulation/simulation_run.py
new file mode 100644
index 000000000..df5fb39dd
--- /dev/null
+++ b/openfasoc/generators/common/simulation/simulation_run.py
@@ -0,0 +1,151 @@
+"""Runs SPICE simulations in parallel.
+
+This module is part of the simulation common module. Functions for running simulations from SPICE files generated using the `simulation_config` module are exported.
+
+SPICE files are generated by the `simulation_config` module in sub-directories. Each SPICE file is run using the given simulation tool and the outputs and log files are generated in the same sub-directory. The simulations can be run in parallel with a configurable number of concurrent simulations.
+
+Exported functions:
+- `_run_simulations()`
+- `_run_config()`
+- `_threaded_run()`
+
+See individual functions for further documentation.
+"""
+
+from os import path
+from common.simulation.utils import _print_progress
+import time
+import threading
+import subprocess
+
+def _run_simulations(
+ num_configs: int,
+ num_concurrent_sims: int,
+ sim_tool: str,
+ runs_dir_path: str
+):
+ """Runs (simulates) the generated configurations.
+
+ Runs `num_configs` number of simulations, with configurations in the `runs_dir_path` directory.
+
+ Arguments:
+ - `num_configs` (int): Number of configurations generated in the `runs_dir_path` directory.
+ - `num_concurrent_sims` (int): The maximum number of concurrent simulations.
+ - `sim_tool` (str): Path to the directory in which the simulation runs will be generated.
+ - `runs_dir_path` (str): Path to the directory in which the simulation runs will be generated.
+ """
+
+ simulation_state = {
+ 'ongoing_sims': 0,
+ 'completed_sims': 0,
+ 'failed_sims': 0
+ }
+
+ def thread_on_exit(exit_status: int, state=simulation_state):
+ if exit_status == 0:
+ state['ongoing_sims'] -= 1
+ state['completed_sims'] += 1
+ else:
+ state['ongoing_sims'] -= 1
+ state['failed_sims'] += 1
+
+ start_time = int(time.time())
+ run_number = 1
+
+ while (simulation_state['completed_sims'] + simulation_state['failed_sims']) < num_configs:
+ if simulation_state['ongoing_sims'] < num_concurrent_sims and run_number <= num_configs:
+ _run_config(
+ sim_tool=sim_tool,
+ run_dir=path.join(runs_dir_path, str(run_number)),
+ run_number=run_number,
+ on_exit=thread_on_exit
+ ).start()
+
+ simulation_state['ongoing_sims'] += 1
+ run_number += 1
+
+ _print_progress(num_configs, simulation_state['completed_sims'], simulation_state['failed_sims'], start_time)
+ time.sleep(1)
+
+ _print_progress(num_configs, simulation_state['completed_sims'], simulation_state['failed_sims'], start_time, end='\n')
+
+def _run_config(
+ sim_tool: str,
+ run_dir: str,
+ run_number: int,
+ on_exit
+):
+ """Runs (simulates) a particular configuration.
+
+ Runs the `run_number`th configuration in the `run_dir` directory.
+
+ Arguments:
+ - `sim_tool` (str): Command for the simulation tool.
+ - `run_dir` (str): Path to the directory in which the run configuration exists.
+ - `run_number` (int): The number/index of the run configuration.
+ - `on_exit` (function): An optional function to run when the simulation completes.
+ """
+
+ return threading.Thread(
+ target=_threaded_run,
+ args=(sim_tool, run_dir, run_number, on_exit),
+ daemon=True
+ )
+
+def _threaded_run(
+ sim_tool: str,
+ run_dir: str,
+ run_number: int,
+ on_exit
+):
+ """Runs a particular simulation using `sim_tool` in a `threading.Thread`.
+
+ Arguments:
+ - `sim_tool` (str): Command for the simulation tool.
+ - `run_dir` (str): Path to the directory in which the run configuration exists.
+ - `run_number` (int): The number/index of the run configuration.
+ - `on_exit` (function): An optional function to run when the simulation completes.
+ """
+
+ try:
+ if sim_tool == "ngspice":
+ subprocess.run(
+ [
+ "ngspice",
+ "-b",
+ "-o",
+ f"sim_{run_number}.log",
+ f"sim_{run_number}.sp"
+ ],
+ cwd=run_dir,
+ capture_output=True
+ )
+ elif sim_tool == "xyce":
+ subprocess.run(
+ [
+ "xyce",
+ "-l",
+ f"sim_{run_number}.log",
+ "-o",
+ f"sim_{run_number}",
+ f"sim_{run_number}.sp"
+ ],
+ cwd=run_dir,
+ capture_output=True
+ )
+ elif sim_tool == "finesim":
+ subprocess.run(
+ [
+ "finesim",
+ "-o",
+ f"sim_{run_number}",
+ "-spice",
+ f"sim_{run_number}.sp"
+ ],
+ cwd=run_dir,
+ capture_output=True
+ )
+ except:
+ return on_exit(1)
+
+ on_exit(0)
\ No newline at end of file
diff --git a/openfasoc/generators/common/simulation/utils.py b/openfasoc/generators/common/simulation/utils.py
new file mode 100644
index 000000000..7a028fcdf
--- /dev/null
+++ b/openfasoc/generators/common/simulation/utils.py
@@ -0,0 +1,36 @@
+"""Utility functions for the simulation common module.
+
+Functions for displaying the simulation progress and formatting the output are exported.
+
+Exported functions:
+- `_print_progress()`
+- `_format_elapsed_time()`
+
+See individual functions for further documentation.
+"""
+
+import time
+
+def _print_progress(total_runs: int, completed_sims: int, failed_sims: int, start_time: int, end: str = '\r'):
+ """Displays the simulation progress.
+
+ Displays the number of simulations completed, total simulations and the time elapsed.
+ """
+ print(f"Completed: {completed_sims}. Failed: {failed_sims}. Total: {total_runs}. Elapsed time: {_format_elapsed_time(start_time)}{f'. Average: {_format_elapsed_time(start_time, completed_sims)} per simulation.' if completed_sims > 0 else ''}", end=end)
+
+def _format_elapsed_time(start_time: int, divisor: int = 1):
+ """Formats the elapsed time (in seconds) into hours, minutes, and seconds format.
+ """
+ elapsed_seconds = int((int(time.time()) - start_time) / divisor)
+
+ if elapsed_seconds > 60 * 60:
+ hours, minutes = divmod(elapsed_seconds, 60 * 60)
+ minutes, seconds = divmod(minutes, 60)
+ return f"{hours}h {minutes}m {seconds}s"
+
+ elif elapsed_seconds > 60:
+ minutes, seconds = divmod(elapsed_seconds, 60)
+ return f"{minutes}m {seconds}s"
+
+ else:
+ return f"{elapsed_seconds}s"
\ No newline at end of file
diff --git a/openfasoc/generators/temp-sense-gen/simulations/.gitignore b/openfasoc/generators/temp-sense-gen/simulations/.gitignore
new file mode 100644
index 000000000..e5224d533
--- /dev/null
+++ b/openfasoc/generators/temp-sense-gen/simulations/.gitignore
@@ -0,0 +1 @@
+run
\ No newline at end of file
diff --git a/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_finesim.sp b/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_finesim.sp
index 5ae7a22b0..f6eed1040 100644
--- a/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_finesim.sp
+++ b/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_finesim.sp
@@ -19,11 +19,11 @@
+ method=gear
+ measdgt=10
-.lib '@model_file' @model_corner
-.include '@netlist'
+.lib '${model_file}' ${model_corner}
+.include '${netlist_path}'
-.param temp_var = @temp
-.param vvdd = @voltage
+.param temp_var = ${temp}
+.param vvdd = ${nominal_voltage}
.param sim_end = 800m/exp(0.04*temp_var)
*@modelingxi1 en out VDD VIN VSS TEMP_sensor
@@ -33,7 +33,7 @@
*@verification+ DOUT[19] DOUT[1] DOUT[20] DOUT[21] DOUT[22] DOUT[23] DOUT[2]
*@verification+ DOUT[3] DOUT[4] DOUT[5] DOUT[6] DOUT[7] DOUT[8] DOUT[9] RESET_COUNTERn
*@verification+ SEL_CONV_TIME[0] SEL_CONV_TIME[1] SEL_CONV_TIME[2] SEL_CONV_TIME[3]
-*@verification+ VDD VIN VSS en lc_out out outb tempsenseInst
+*@verification+ VDD VIN VSS en lc_out out outb ${design_name}
vCLK_REF CLK_REF 0 pulse 0 'vvdd' 12u 1n 1n '4/32768' '8/32768'
vRESET_COUNTERn RESET_COUNTERn 0 pwl 0 0 5u 0 '5u+1n' 'vvdd'
diff --git a/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_ngspice.sp b/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_ngspice.sp
index b11ad81a9..e80470212 100644
--- a/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_ngspice.sp
+++ b/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_ngspice.sp
@@ -1,7 +1,7 @@
.OPTION sim_la
.OPTION autostop
-.OPTION temp = @temp
+.OPTION temp = ${temp}
.OPTION
+ artist=2
+ ingold=2
@@ -19,10 +19,10 @@
+ method=gear
+ measdgt=10
-.lib '@model_file' @model_corner
-.include '@netlist'
+.lib '${model_file}' ${model_corner}
+.include '${netlist_path}'
-.param temp_var = @temp
+.param temp_var = ${temp}
.param vvdd = 1.8
.param sim_end = '800m/exp(0.04*temp_var)'
@@ -31,7 +31,7 @@ xi1 CLK_REF DONE DOUT[0] DOUT[10] DOUT[11]
+ DOUT[19] DOUT[1] DOUT[20] DOUT[21] DOUT[22] DOUT[23] DOUT[2]
+ DOUT[3] DOUT[4] DOUT[5] DOUT[6] DOUT[7] DOUT[8] DOUT[9] RESET_COUNTERn
+ SEL_CONV_TIME[0] SEL_CONV_TIME[1] SEL_CONV_TIME[2] SEL_CONV_TIME[3]
-+ en lc_out out outb VDD VSS @design_nickname
++ en lc_out out outb VDD VSS ${design_name}
vCLK_REF CLK_REF 0 pulse 0 'vvdd' 12u 1n 1n '4/32768' '8/32768'
vRESET_COUNTERn RESET_COUNTERn 0 pwl 0 0 5u 0 '5u+1n' 'vvdd'
diff --git a/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_xyce.sp b/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_xyce.sp
index 96aae91bd..e9151e0a1 100644
--- a/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_xyce.sp
+++ b/openfasoc/generators/temp-sense-gen/simulations/templates/tempsenseInst_xyce.sp
@@ -1,7 +1,7 @@
*.OPTION sim_la
.OPTION autostop
-.OPTION temp = @temp
+.OPTION temp = ${temp}
*.OPTION
*+ artist=2
*+ ingold=2
@@ -19,21 +19,21 @@
*+ method=gear
*+ measdgt=10
-.lib '@model_file' @model_corner
-.include '@netlist'
+.lib '${model_file}' ${model_corner}
+.include '${netlist_path}'
-.param temp_var = @temp
+.param temp_var = ${temp}
.param vvdd = 1.8
* sim_end = 800m/exp(0.04*temp_var)
-* .param sim_end = @sim_end
+* .param sim_end = ${sim_end}
xi1 CLK_REF DONE DOUT[0] DOUT[10] DOUT[11]
+ DOUT[12] DOUT[13] DOUT[14] DOUT[15] DOUT[16] DOUT[17] DOUT[18]
+ DOUT[19] DOUT[1] DOUT[20] DOUT[21] DOUT[22] DOUT[23] DOUT[2]
+ DOUT[3] DOUT[4] DOUT[5] DOUT[6] DOUT[7] DOUT[8] DOUT[9] RESET_COUNTERn
+ SEL_CONV_TIME[0] SEL_CONV_TIME[1] SEL_CONV_TIME[2] SEL_CONV_TIME[3]
-+ en lc_out out outb VDD VSS @design_nickname
++ en lc_out out outb VDD VSS ${design_name}
vCLK_REF CLK_REF 0 pulse 0 'vvdd' 12u 1n 1n '4/32768' '8/32768'
vRESET_COUNTERn RESET_COUNTERn 0 pwl 0 0 5u 0 '5u+1n' 'vvdd'
@@ -48,7 +48,7 @@ vVSS VSS 0 dc
c0 lc_out 0 1f
-.TRAN 10n @sim_end
+.TRAN 10n ${sim_end}
.meas tran period TRIG when v(lc_out)=1.0 td=10p rise=2
+ TARG when v(lc_out)=1.0 td=10p rise=3
diff --git a/openfasoc/generators/temp-sense-gen/test.json b/openfasoc/generators/temp-sense-gen/test.json
index 28215c0f0..f05174c2d 100644
--- a/openfasoc/generators/temp-sense-gen/test.json
+++ b/openfasoc/generators/temp-sense-gen/test.json
@@ -2,11 +2,11 @@
"module_name": "tempsenseInst_error",
"generator": "temp-sense-gen",
"specifications": {
- "temperature": { "min": -20, "max": 100 },
+ "temperature": { "min": -20, "max": 100, "step": 20 },
"power": "",
"error": "",
"area": "",
"optimization":"error",
"model" :"modelfile.csv"
}
-}
+}
diff --git a/openfasoc/generators/temp-sense-gen/tools/TEMP_netlist.py b/openfasoc/generators/temp-sense-gen/tools/TEMP_netlist.py
deleted file mode 100644
index 5a10c82e2..000000000
--- a/openfasoc/generators/temp-sense-gen/tools/TEMP_netlist.py
+++ /dev/null
@@ -1,75 +0,0 @@
-##for HSPICE netlist
-import function
-
-# import os
-
-
-def gen_modeling_netlist(srcNetlist, dstNetlist, ninv, nhead) -> None:
- r_netlist = open(srcNetlist, "r")
- lines = list(r_netlist.readlines())
- w_netlist = open(dstNetlist, "w")
-
- netmap1 = function.netmap() # modify here
- netmap1.get_net("x1", None, 1, 1, 1)
- netmap1.get_net("n0", None, ninv + 1, ninv + 1, 1)
- netmap1.get_net("n1", None, 1, 1, 1)
- netmap1.get_net("x2", None, 1, ninv, 1)
- netmap1.get_net("n2", None, 1, ninv, 1)
- netmap1.get_net("n3", None, 2, ninv + 1, 1)
- netmap1.get_net("x3", None, 1, 1, 1)
- netmap1.get_net("n4", None, ninv + 1, ninv + 1, 1)
- netmap1.get_net("x4", None, 1, nhead, 1)
- for line in lines:
- netmap1.printline(line, w_netlist)
-
-
-def gen_temp_netlist(ninv, nhead, aux1, aux2, aux3, aux4, aux5, srcDir) -> None:
- r_netlist = open(srcDir + "/TEMP_ANALOG_lv.v", "r")
- lines = list(r_netlist.readlines())
- w_netlist = open(srcDir + "/TEMP_ANALOG_lv.nl.v", "w")
- port = "X"
- slc_cell = "SLC a_lc_0(.IN(out), .INB(outb), .VOUT(lc_0));"
-
- netmap1 = function.netmap() # modify here
- netmap1.get_net("nn", None, 1, int(ninv), 1)
- netmap1.get_net("n0", None, int(ninv), int(ninv), 1)
- netmap1.get_net("na", aux1, 1, 1, 1)
- netmap1.get_net("nb", aux2, 0, int(ninv) - 2, 1)
- netmap1.get_net("ni", None, 0, int(ninv) - 2, 1)
- netmap1.get_net("n1", None, 1, int(ninv) - 1, 1)
- netmap1.get_net("n2", None, 2, int(ninv), 1)
- netmap1.get_net("ng", aux2, 1, 1, 1)
- netmap1.get_net("n3", None, int(ninv), int(ninv), 1)
- netmap1.get_net("nk", aux2, 1, 1, 1)
- netmap1.get_net("n4", None, int(ninv), int(ninv), 1)
- netmap1.get_net("nm", aux2, 1, 1, 1)
- netmap1.get_net("np", aux3, 1, 1, 1)
- netmap1.get_net("nc", aux3, 1, 1, 1)
- netmap1.get_net("nd", aux4, 1, 1, 1)
- netmap1.get_net("ne", aux4, 1, 1, 1)
- for line in lines:
- line = line.replace("nbout", port)
- netmap1.printline(line, w_netlist)
-
- r_netlist = open(srcDir + "/TEMP_ANALOG_hv.v", "r")
- lines = list(r_netlist.readlines())
- w_netlist = open(srcDir + "/TEMP_ANALOG_hv.nl.v", "w")
-
- netmap1.get_net("nf", aux5, 0, int(nhead) - 1, 1)
- netmap1.get_net("nh", None, 0, int(nhead) - 1, 1)
- netmap1.get_net("no", aux3, 1, 1, 1)
- for line in lines:
- line = line.replace("SLC", slc_cell)
- line = line.replace("nbout", port)
- netmap1.printline(line, w_netlist)
-
- r_netlist = open(srcDir + "/counter_generic.v", "r")
- lines = list(r_netlist.readlines())
- w_netlist = open(srcDir + "/counter.v", "w")
-
- netmap1.get_net("np", aux3, 1, 1, 1)
- for line in lines:
- line = line.replace("nbout", port)
- netmap1.printline(line, w_netlist)
-
- return
diff --git a/openfasoc/generators/temp-sense-gen/tools/TEMP_sensor_template.sp b/openfasoc/generators/temp-sense-gen/tools/TEMP_sensor_template.sp
deleted file mode 100644
index e5c34e179..000000000
--- a/openfasoc/generators/temp-sense-gen/tools/TEMP_sensor_template.sp
+++ /dev/null
@@ -1,7 +0,0 @@
-
-.SUBCKT TEMP_sensor EN OUT VPWR VIN VGND
-@@ xin@x1 EN n@n0 VGND VGND VIN VIN n@n1 sky130_fd_sc_hd__nand2_1
-@@ xii@x2 n@n2 VGND VGND VIN VIN n@n3 sky130_fd_sc_hd__inv_1
-@@ xib@x3 n@n4 VGND VGND VIN VIN OUT sky130_fd_sc_hd__inv_1
-@@ xih@x4 VGND VIN VGND VPWR HEADER
-.ends TEMP_sensor
diff --git a/openfasoc/generators/temp-sense-gen/tools/function.py b/openfasoc/generators/temp-sense-gen/tools/function.py
deleted file mode 100644
index ef7e50a70..000000000
--- a/openfasoc/generators/temp-sense-gen/tools/function.py
+++ /dev/null
@@ -1,372 +0,0 @@
-################ modules for HSPICE sim ######################
-##############################################################
-######### varmap definition ####################
-##############################################################
-### This class is to make combinations of given variables ####
-### mostly used for testbench generation #################
-### EX: varmap1=HSPICE_varmap.varmap(4) %%num of var=4 #######
-### varmap1.get_var('vdd',1.5,1.8,0.2) %%vdd=1.5:0.2:1.8##
-### varmap1.get_var('abc', ........ %%do this for 4 var ##
-### varmap1.cal_nbigcy() %%end of var input###
-### varmap1.combinate %%returns variable comb 1 by 1 ####
-##############################################################
-
-
-class varmap:
- # def __init__(self,num_var):
- # self.n_smlcycle=1
- # self.last=0
- # self.smlcy=1
- # self.bigcy=0
- # self.vv=0
- # self.vf=1
- # self.size=num_var
- # self.map=[None]*self.size
- # self.comblist=[None]*self.size
- # self.nvar=0
- def __init__(self) -> None:
- self.n_smlcycle = 1
- self.last = 0
- self.smlcy = 1
- self.bigcy = 0
- self.vv = 0
- self.vf = 1
- # self.map=[None]
- # self.comblist=[None]
- self.nvar = 0
-
- def get_var(self, name, start, end, step) -> None:
- if self.nvar == 0:
- self.map = [None]
- self.comblist = [None]
- else:
- self.map.append(None)
- self.comblist.append(None)
- self.map[self.nvar] = list([name])
- self.comblist[self.nvar] = list([name])
- self.nswp = (end - start) // step + 1
- for i in range(1, self.nswp + 1):
- self.map[self.nvar].append(start + step * (i - 1))
- self.nvar += 1
-
- def cal_nbigcy(self) -> None:
- self.bias = [1] * (len(self.map))
- for j in range(1, len(self.map) + 1):
- self.n_smlcycle = self.n_smlcycle * (len(self.map[j - 1]) - 1)
- self.n_smlcycle = self.n_smlcycle * len(self.map)
-
- def increm(self, inc) -> None: # increment bias
- self.bias[inc] += 1
- if self.bias[inc] > len(self.map[inc]) - 1:
- self.bias[inc] % len(self.map[inc]) - 1
-
- def check_end(
- self, vf
- ) -> int: # When this is called, it's already last stage of self.map[vf]
- self.bias[vf] = 1
- # if vf==0 and self.bias[0]==len(self.map[0])-1:
- # return 0
- if (
- self.bias[vf - 1] == len(self.map[vf - 1]) - 1
- ): # if previous column is last element
- self.check_end(vf - 1)
- else:
- self.bias[vf - 1] += 1
- return 1
-
- def combinate(self) -> None:
- # print self.map[self.vv][self.bias[self.vv]]
- self.smlcy += 1
- if self.vv == len(self.map) - 1: # last variable
- self.bigcy += 1
- for vprint in range(0, len(self.map)):
- self.comblist[vprint].append(self.map[vprint][self.bias[vprint]])
- # print self.map[vprint][self.bias[vprint]]
- if self.bias[self.vv] == len(self.map[self.vv]) - 1: # last element
- if self.smlcy < self.n_smlcycle:
- self.check_end(self.vv)
- self.vv = (self.vv + 1) % len(self.map)
- self.combinate()
- else:
- pass
- else:
- self.bias[self.vv] += 1
- self.vv = (self.vv + 1) % len(self.map)
- self.combinate()
- else:
- self.vv = (self.vv + 1) % len(self.map)
- self.combinate()
-
-
-##############################################################
-######### netmap ########################
-##############################################################
-### This class is used for replacing lines ################
-### detects @@ for line and @ for nets #######################
-##############################################################
-# -------- EXAMPLE ---------------------------------------#
-### netmap1=netmap(2) %input num_var #########################
-### netmap1.get_var('ab','NN',1,4,1) %flag MUST be 2 char ####
-## netmap2.get_var('bc','DD',2,5,1) %length of var must match#
-# !!caution: do get_var in order, except for lateral prints ##
-### which is using @W => varibales here, do get_var at last ##
-### for line in r_file.readlines():###########################
-### netmap1.printline(line,w_file) #######################
-##############################################################
-
-
-class netmap:
- # def __init__(self,num_var):
- # self.size=num_var
- # self.map=[None]*self.size
- # self.flag=[None]*self.size
- # self.name=[None]*self.size
- # self.nnet=[None]*self.size
- # self.nn=0
- # self.pvar=1
- # self.cnta=0
- # self.line_nvar=0 # index of last variable for this line
- # self.nxtl_var=0 # index of variable of next line
- # self.ci_at=100
- def __init__(self) -> None:
- self.nn = 0
- self.pvar = 1
- self.cnta = 0
- self.line_nvar = 0 # index of last variable for this line
- self.nxtl_var = 0 # index of variable of next line
- self.ci_at = -5
-
- def get_net(
- self, flag, netname, start, end, step
- ) -> None: # if start==None: want to repeat without incrementation(usually for tab) (end)x(step) is the num of repetition
- if self.nn == 0:
- self.map = [None]
- self.flag = [None]
- self.name = [None]
- self.nnet = [None]
- else:
- self.map.append(None)
- self.name.append(None)
- self.flag.append(None)
- self.nnet.append(None)
- if netname == None:
- self.name[self.nn] = 0
- else:
- self.name[self.nn] = 1
- self.map[self.nn] = list([netname])
- self.flag[self.nn] = flag
- if start != None and start != "d2o":
- self.nnet[self.nn] = int((end - start + step / 10) // step + 1)
- if self.name[self.nn] == 1:
- for i in range(1, self.nnet[self.nn] + 1):
- self.map[self.nn].append("")
- else:
- for i in range(1, self.nnet[self.nn] + 1):
- self.map[self.nn].append(start + step * (i - 1))
- elif start == "d2o":
- for i in range(0, end):
- if step - i > 0:
- self.map[self.nn].append(1)
- else:
- self.map[self.nn].append(0)
- i += 1
- else:
- self.map[self.nn] = list([netname])
- for i in range(1, step + 1):
- self.map[self.nn].append(end)
- # self.map[self.nn]=[None]*step
- # for i in range(1,self.nnet[self.nn]+1):
- # self.map[self.nn][i]=None
- self.nn += 1
- # print self.map
-
- def add_val(self, flag, netname, start, end, step) -> None:
- varidx = self.flag.index(flag)
- if start != None:
- nval = int((end - start + step / 10) // step + 1)
- for i in range(1, nval + 1):
- self.map[varidx].append(start + step * (i - 1))
- else:
- for i in range(1, step + 1):
- self.map[varidx].append(end)
-
- def printline(self, line, wrfile) -> None:
- if line[0:2] == "@@":
- # print('self.ci_at=%d'%(self.ci_at))
- self.nline = line[3 : len(line)]
- self.clist = list(self.nline) # character list
- # print(self.clist,self.nxtl_var)
- for iv in range(1, len(self.map[self.nxtl_var])):
- for ci in range(0, len(self.clist)):
- if (ci == self.ci_at + 1 or ci == self.ci_at + 2) and ci != len(
- self.clist
- ) - 1:
- pass
- elif self.clist[ci] == "@":
- # print self.cnta
- self.cnta += 1
- self.line_nvar += 1
- varidx = self.flag.index(
- self.clist[ci + 1] + self.clist[ci + 2]
- )
- if self.name[varidx]:
- wrfile.write(self.map[varidx][0])
- # print(self.map[varidx])
- if type(self.map[varidx][self.pvar]) == float:
- wrfile.write(
- "%e" % (self.map[varidx][self.pvar])
- ) # modify here!!!!
- elif type(self.map[varidx][self.pvar]) == int:
- wrfile.write("%d" % (self.map[varidx][self.pvar]))
- self.ci_at = ci
- elif ci == len(self.clist) - 1: # end of the line
- if (
- self.pvar
- == len(self.map[self.nxtl_var + self.line_nvar - 1]) - 1
- ): # last element
- self.pvar = 1
- self.nxtl_var = self.nxtl_var + self.line_nvar
- self.line_nvar = 0
- self.cnta = 0
- self.ci_at = -6
- # print('printed all var for this line, %d'%(ci))
- else:
- self.pvar += 1
- # self.line_nvar=self.cnta
- self.line_nvar = 0
- # print ('line_nvar= %d'%(self.line_nvar))
- self.cnta = 0
- wrfile.write(self.clist[ci])
- else:
- wrfile.write(self.clist[ci])
- elif line[0:2] == "@W":
- # print('found word line')
- self.nline = line[3 : len(line)]
- self.clist = list(self.nline)
- for ci in range(0, len(self.clist)):
- if ci == self.ci_at + 1 or ci == self.ci_at + 2:
- pass
- elif self.clist[ci] == "@":
- varidx = self.flag.index(self.clist[ci + 1] + self.clist[ci + 2])
- for iv in range(1, len(self.map[varidx])):
- if self.name[varidx]:
- wrfile.write(self.map[varidx][0])
- wrfile.write("%d " % (self.map[varidx][iv]))
- print(
- "n is %d, varidx=%d, iv=%d"
- % (self.map[varidx][iv], varidx, iv)
- )
- self.ci_at = ci
- else:
- wrfile.write(self.clist[ci])
- self.ci_at = -5
- else:
- wrfile.write(line)
-
-
-##############################################################
-######### resmap ########################
-##############################################################
-### This class is used to deal with results ################
-### detects @@ for line and @ for nets #######################
-##############################################################
-# -------- EXAMPLE ---------------------------------------#
-### netmap1=netmap(2) %input num_var #########################
-### netmap1.get_var('ab','NN',1,4,1) %flag MUST be 2 char ####
-## netmap2.get_var('bc','DD',2,5,1) %length of var must match#
-### for line in r_file.readlines():###########################
-### netmap1.printline(line,w_file) #######################
-###### self.tb[x][y][env[]]###############################
-##############################################################
-
-
-class resmap:
- def __init__(self, num_tb, num_words, index) -> None: # num_words includes index
- self.tb = [None] * num_tb
- self.tbi = [None] * num_tb
- self.vl = [None] * num_tb
- self.vlinit = [None] * num_tb
- self.svar = [None] * num_tb
- self.index = index
- self.nenv = 0
- self.num_words = num_words
- self.vr = [None] * (num_words + index) # one set of variables per plot
- self.vidx = [None] * (num_words + index)
- self.env = [None] * (num_words + index)
- # self.vl=[None]*(num_words+index) #one set of variables per plot
- for itb in range(0, len(self.tb)):
- # self.tb[itb].vr=[None]*(num_words+index)
- self.tbi[itb] = 0 # index for counting vars within tb
- self.vl[itb] = [None] * (num_words + index)
- self.vlinit[itb] = [0] * (num_words + index)
-
- def get_var(self, ntb, var) -> None:
- self.vr[self.tbi[ntb]] = var
- # self.vl[ntb][self.tbi[ntb]]=list([None])
- self.tbi[ntb] += 1
- if self.tbi[ntb] == len(self.vr): # ????????
- self.tbi[ntb] = 0
-
- def add(self, ntb, value) -> None:
- if self.vlinit[ntb][self.tbi[ntb]] == 0: # initialization
- self.vl[ntb][self.tbi[ntb]] = [value]
- self.vlinit[ntb][self.tbi[ntb]] += 1
- else:
- self.vl[ntb][self.tbi[ntb]].append(value)
- self.tbi[ntb] = (self.tbi[ntb] + 1) % len(self.vr)
-
- def plot_env(
- self, ntb, start, step, xvar, xval
- ) -> None: # setting plot environment: if ntb=='all': x axis is in terms of testbench
- if ntb == "all":
- self.nenv += 1
- self.xaxis = [None] * len(self.tb)
- for i in range(0, len(self.tb)):
- self.xaxis[i] = start + i * step
- self.vidx[self.nenv] = self.vr.index(xvar)
- # print self.vl[0][self.vidx[self.nenv]]
- print("", self.vl[0][self.vidx[self.nenv]])
- self.env[self.nenv] = [
- i
- for (i, x) in enumerate(self.vl[0][self.vidx[self.nenv]])
- if x == "%s" % (xval)
- ]
- else:
- self.nenv += 1
- self.xaxis = [None] # one output
- self.xaxis = [start]
- self.vidx[self.nenv] = self.vr.index(xvar)
- self.env[self.nenv] = [
- i
- for (i, x) in enumerate(self.vl[0][self.vidx[self.nenv]])
- if x == "%s" % (xval)
- ]
-
- def rst_env(self) -> None:
- self.vidx[self.nenv] = None
- self.env[self.nenv] = 0
- self.nenv = 0
- # print self.vl[0][self.vidx[self.nenv]]
-
- def plot_y(self, yvar) -> None:
- self.yidx = self.vr.index(yvar)
- print("yidx=%d" % (self.yidx))
- # print self.vl[0][self.yidx][self.env[self.nenv][0]]
- print("", self.vl[0][self.yidx][self.env[self.nenv][0]])
- self.yaxis = [None] * len(self.xaxis)
- for xx in range(0, len(self.xaxis)):
- self.yaxis[xx] = self.vl[xx][self.yidx][self.env[self.nenv][0]]
- # plt.plot(self.xaxis,self.yaxis)
- # plt.ylabel(self.vr[self.yidx])
-
- def sort(self, var) -> None:
- varidx = self.vr.index(var)
- for k in range(len(self.vl)): # all testbenches
- self.svar[k] = {} # define dict
- for i in range(len(self.vl[0][0])): # all values
- self.svar[k][self.vl[k][varidx][i]] = []
- for j in range(len(self.vr)): # all variables
- if j != varidx:
- self.svar[k][self.vl[k][varidx][i]].append(self.vl[k][j][i])
- # if k==0:
- # print self.svar[k]
diff --git a/openfasoc/generators/temp-sense-gen/tools/readparamgen.py b/openfasoc/generators/temp-sense-gen/tools/readparamgen.py
index d5f878625..f33b88300 100644
--- a/openfasoc/generators/temp-sense-gen/tools/readparamgen.py
+++ b/openfasoc/generators/temp-sense-gen/tools/readparamgen.py
@@ -5,7 +5,6 @@
import os
import re
import sys
-import time
import matplotlib.pyplot as plt
import pandas as pd
@@ -81,6 +80,7 @@
try:
Tempmin = jsonSpec["specifications"]["temperature"]["min"]
Tempmax = jsonSpec["specifications"]["temperature"]["max"]
+ Tempstep = jsonSpec["specifications"]["temperature"]["step"]
except KeyError as e:
print(
"Error: Bad Input Specfile. 'range o' value is missing under "
@@ -175,6 +175,7 @@
Tmin = float(jsonSpec["specifications"]["temperature"]["min"])
Tmax = float(jsonSpec["specifications"]["temperature"]["max"])
+Tstep = float(jsonSpec["specifications"]["temperature"]["step"])
if (Tmax > 100) or (Tmin < -20):
print(
"Error: Supported temperature sensing must be inside the following range [-20 to 100] Celcius"
diff --git a/openfasoc/generators/temp-sense-gen/tools/result.py b/openfasoc/generators/temp-sense-gen/tools/result.py
deleted file mode 100644
index c02e14b5c..000000000
--- a/openfasoc/generators/temp-sense-gen/tools/result.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import argparse
-import re
-import sys
-
-parser = argparse.ArgumentParser(description="parse simulators' output file")
-parser.add_argument("--tool", "-t", required=True, help="simulator type")
-parser.add_argument(
- "--inputFile", "-i", required=True, help="simulator's output for processing"
-)
-args = parser.parse_args()
-
-file_name = args.inputFile
-tool_name = args.tool
-
-with open(file_name, "r") as rf:
- log_file_text = rf.read()
-
- temp_patten = "TEMP\s*=\s*([0-9\-\.]+)"
- temp_value_re = re.search(temp_patten, log_file_text)
- temp_value = "failed"
- if temp_value_re:
- temp_value = temp_value_re.group(1)
- else:
- temp_value = file_name.split("_")[-1].split(".")[0]
-
- period_pattern = "PERIOD\s*=\s*([0-9\.e-]+)"
- period_pattern_re = re.search(period_pattern, log_file_text)
- period_value = "failed"
- if period_pattern_re:
- period_value = period_pattern_re.group(1)
- else:
- period_pattern = "period\s*=\s*([0-9\.e-]+)"
- period_pattern_re = re.search(period_pattern, log_file_text)
- period_value = "failed"
- if period_pattern_re:
- period_value = period_pattern_re.group(1)
-
- power_pattern = "POWER\s*=\s*([0-9\.e-]+)"
- power_pattern_re = re.search(power_pattern, log_file_text)
- power_value = "failed"
- if power_pattern_re:
- power_value = power_pattern_re.group(1)
- else:
- power_pattern = "power\s*=\s*([0-9\.e-]+)"
- power_pattern_re = re.search(power_pattern, log_file_text)
- power_value = "failed"
- if power_pattern_re:
- power_value = power_pattern_re.group(1)
-
- print(
- "%s %s %s" % (temp_value, period_value, power_value),
- file=open("sim_output", "a"),
- )
diff --git a/openfasoc/generators/temp-sense-gen/tools/result_error.py b/openfasoc/generators/temp-sense-gen/tools/result_error.py
deleted file mode 100644
index 857c8edf9..000000000
--- a/openfasoc/generators/temp-sense-gen/tools/result_error.py
+++ /dev/null
@@ -1,96 +0,0 @@
-import argparse
-import math
-import os
-import sys
-
-parser = argparse.ArgumentParser(description="calculate error values")
-parser.add_argument(
- "--mode",
- "-m",
- required=True,
- help="simulation mode, full: skip power, partial: extract power",
-)
-args = parser.parse_args()
-
-
-simDir = os.path.dirname(os.path.relpath(__file__))
-sim_output_file = open(simDir + "sim_output", "r")
-
-data0 = []
-temp = []
-power_list = []
-sim_output_lines = sim_output_file.readlines()
-for line in sim_output_lines:
- data = line.split()
- temp.append(data[0])
- data0.append(data[1])
- if args.mode == "partial":
- power_list.append(data[2])
-
-frequency_list = []
-data1 = list()
-for i, val in enumerate(data0):
- if val == "failed":
- val_cal = "failed"
- frequency = "failed"
- else:
- frequency = 1 / float(val)
- print("temp: %s, \tfrequency: %f" % (temp[i], frequency))
- val_cal = math.log(1 / float(val)) * ((-20 + i * 20) + 273.15) * 0.01
- frequency_list.append(frequency)
- data1.append(val_cal)
-
-data2 = []
-
-try:
- slope_f = 80 / (data1[len(data1) - 2] - data1[1])
-except:
- print("Error calculation failed")
- sys.exit(0)
-
-for k in data1:
- if k == "failed":
- val = "failed"
- else:
- val = k * slope_f
- data2.append(val)
-
-data3 = []
-offset = data2[1]
-for val in data2:
- if val == "failed":
- val = "failed"
- else:
- val = val - offset
- data3.append(val)
-
-data4 = []
-for i, val in enumerate(data3):
- if val == "failed":
- val = "failed"
- else:
- val = (-20 + i * 20) - val
- data4.append(val)
-
-
-print(os.getcwd(), file=open("all_result", "a"))
-if args.mode == "partial":
- print("Temp Frequency Power Error", file=open("all_result", "a"))
-elif args.mode == "full":
- print("Temp Frequency Error", file=open("all_result", "a"))
-else:
- print("simulation mode - " + args.mode + " is not supported")
- sys.exit(1)
-for idx, line in enumerate(sim_output_lines):
- result_list = line.split()
- if args.mode == "partial":
- print(
- "%s %s %s %s"
- % (result_list[0], frequency_list[idx], power_list[idx], data4[idx]),
- file=open("all_result", "a"),
- )
- elif args.mode == "full":
- print(
- "%s %s %s" % (result_list[0], frequency_list[idx], data4[idx]),
- file=open("all_result", "a"),
- )
diff --git a/openfasoc/generators/temp-sense-gen/tools/simulation.py b/openfasoc/generators/temp-sense-gen/tools/simulation.py
index a92e1686d..d20339360 100644
--- a/openfasoc/generators/temp-sense-gen/tools/simulation.py
+++ b/openfasoc/generators/temp-sense-gen/tools/simulation.py
@@ -1,32 +1,33 @@
-import glob
import os
-import re, math
+import re
import shutil
-import subprocess as sp
import sys
from itertools import product
-import TEMP_netlist
+from simulation_result import get_sim_results, calculate_sim_error
+
+# TODO: Find a better way to import modules from parent directory
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
+from common.simulation import run_simulations
# note netlist type is either prePEX or postPEX
# function returns the location of simulations
def generate_runs(
- genDir,
- designName,
- headerList,
- invList,
- tempList,
- jsonConfig,
- platform,
- mode,
- pdk,
- modeling=False,
- spiceDir=None,
- prePEX=True,
-):
+ genDir: str,
+ designName: str,
+ headerList: list[int],
+ invList: list[int],
+ tempStart: float,
+ tempStop: float,
+ tempStep: float,
+ jsonConfig: dict,
+ platform: str,
+ pdk: str,
+ spiceDir: str | None = None,
+ prePEX: bool = True,
+) -> str:
"""creates and executes simulations (through run_simulations call)"""
simDir = genDir + "simulations/"
- flowDir = genDir + "flow/"
platformConfig = jsonConfig["platforms"][platform]
simTool = jsonConfig["simTool"]
model_file = pdk + "/libs.tech/ngspice/sky130.lib.spice"
@@ -34,121 +35,58 @@ def generate_runs(
if not spiceDir:
spiceDir = genDir + "/work"
- platformSpice = glob.glob(
- genDir + "../../common/platforms/%s/cdl/*.spice" % (platform)
- )
-
designList = list(product(headerList, invList))
if not os.path.isdir(simDir + "run/"):
os.mkdir(simDir + "run/")
- # Update simulation testbench, depending on platform config and if running modeling
- with open(simDir + "templates/tempsenseInst_%s.sp" % (simTool), "r") as rf:
- simTestbench = rf.read()
- simTestbench = re.sub("@model_file", model_file, simTestbench)
- simTestbench = re.sub(
- "@model_corner", platformConfig["model_corner"], simTestbench
- )
- simTestbench = re.sub(
- "@voltage", str(platformConfig["nominal_voltage"]), simTestbench
- )
-
- if not modeling:
- simTestbench = re.sub("\*@verification", "", simTestbench)
- if jsonConfig["simMode"] == "full":
- simTestbench = re.sub("\*@full", "", simTestbench)
- elif jsonConfig["simMode"] == "partial":
- simTestbench = re.sub("\*@partial", "", simTestbench)
- else:
- print("simulation mode - " + jsonConfig["simMode"] + "is not supported")
- sys.exit(1)
- else:
- simTestbench = re.sub("\*@modeling", "", simTestbench)
- simTestbench = re.sub("\*@partial", "", simTestbench)
-
# Iterate over runs
for design in designList:
header = design[0]
inv = design[1]
- if prePEX:
- runDir = simDir + "run/prePEX_inv{:d}_header{:d}/".format(inv, header)
- else:
- runDir = simDir + "run/PEX_inv{:d}_header{:d}/".format(inv, header)
-
- if os.path.isdir(runDir):
- shutil.rmtree(runDir, ignore_errors=True)
- os.mkdir(runDir)
- shutil.copyfile(genDir + "tools/result.py", runDir + "result.py")
- shutil.copyfile(genDir + "tools/result_error.py", runDir + "result_error.py")
-
- if modeling:
- srcNetlist = genDir + "tools/TEMP_sensor_template.sp"
- dstNetlist = runDir + "TEMP_sensor_inv%d_header%d.spice" % (inv, header)
- simTestbench = re.sub(
- "@netlist",
- os.path.abspath(
- runDir + "TEMP_sensor_inv%d_header%d.spice" % (inv, header)
- ),
- simTestbench,
- )
- TEMP_netlist.gen_modeling_netlist(srcNetlist, dstNetlist, inv, header)
- with open(dstNetlist, "r") as rf:
- filedata = rf.read()
- for spice in platformSpice:
- filedata = ".INCLUDE '%s'\n" % os.path.abspath(spice) + filedata
- with open(dstNetlist, "w") as wf:
- filedata = wf.write(filedata)
- else:
- if prePEX:
- srcNetlist = spiceDir + "/" + designName + ".spice"
- else:
- srcNetlist = spiceDir + "/" + designName + "_pex.spice"
- dstNetlist = runDir + designName + ".spice"
- simTestbench = re.sub(
- "@netlist",
- os.path.abspath(runDir + designName + ".spice"),
- simTestbench,
- )
- update_netlist(srcNetlist, dstNetlist, jsonConfig["simMode"])
-
- for temp in tempList:
- w_file = open(runDir + "/%s_sim_%d.sp" % (designName, temp), "w")
- wfdata = re.sub("@temp", str(temp), simTestbench)
- wfdata = re.sub("@design_nickname", designName, wfdata)
- if jsonConfig["simTool"] == "xyce":
- sim_end = round(math.pow(10, -3) * 800 / math.exp(0.04 * temp), 4)
- wfdata = re.sub("@sim_end", str(sim_end), wfdata)
- w_file.write(wfdata)
- w_file.close()
+ runDir = "run/{:s}_inv{:d}_header{:d}/".format("prePEX" if prePEX else "PEX", inv, header)
+ runDirPath = os.path.join(simDir, runDir)
+
+ if os.path.isdir(runDirPath):
+ shutil.rmtree(runDirPath, ignore_errors=True)
+ os.mkdir(runDirPath)
+
+ srcNetlist = spiceDir + "/" + designName + (".spice" if prePEX else "_pex.spice")
+ dstNetlist = os.path.join(runDirPath, designName + ".spice")
+
+ update_netlist(srcNetlist, dstNetlist, jsonConfig["simMode"])
+
+ num_simulations = run_simulations(
+ parameters={
+ 'temp': {'start': tempStart, 'end': tempStop, 'step': tempStep},
+ 'model_file': model_file,
+ 'model_corner': platformConfig['model_corner'],
+ 'nominal_voltage': platformConfig['nominal_voltage'],
+ 'design_name': designName
+ },
+ platform=platform,
+ simulation_dir=simDir,
+ template_path=os.path.join("templates", f"tempsenseInst_{simTool}.sp"),
+ runs_dir=runDir,
+ sim_tool=simTool,
+ netlist_path=dstNetlist
+ )
- # runs simulation only if mode is set to "full"
- if mode == "full":
- print(
- "#----------------------------------------------------------------------"
- )
- print("# Running {0} Simulations".format("prePEX" if prePEX else "PEX"))
- print(
- "#----------------------------------------------------------------------"
- )
+ # Calculating simulation results and error
+ with open(os.path.join(runDirPath, 'sim_output'), 'w') as sim_output_file:
+ for i in range(num_simulations):
+ log_file_path = os.path.join(runDirPath, f"{i + 1}", f"sim_{i + 1}.log")
+ sim_results = get_sim_results(open(log_file_path, "r").read())
- run_simulations(
- runDir,
- designName,
- tempList,
- jsonConfig["simTool"],
- jsonConfig["simMode"],
- )
- else:
- print(
- "spice netlists created for different temperatures to run the {0} simulations".format(
- "prePEX" if prePEX else "PEX"
- )
- )
+ sim_output_file.write(f"{sim_results['temp']} {sim_results['period']} {sim_results['power']}\n")
- return runDir
+ with open(os.path.join(runDirPath, 'sim_output'), 'r') as sim_output_file:
+ with open(os.path.join(runDirPath, 'all_result'), 'w') as all_result_file:
+ error_data = calculate_sim_error(sim_output_lines=sim_output_file.readlines())
+ all_result_file.write("\n".join(error_data))
+ return runDirPath
def matchNetlistCell(cell_instantiation):
"""returns true if the input contains as a pin (as a substring) one of the identified cells to remove for partial simulations"""
@@ -231,175 +169,4 @@ def update_netlist(srcNetlist, dstNetlist, simMode):
"""
netlist = netlist.replace(toplevel_pinout.split(" ", 2)[2], standardized_pinout)
with open(dstNetlist, "w") as wf:
- wf.write(netlist)
-
-
-def run_simulations(runDir, designName, temp_list, simTool, simMode) -> None:
- if simTool == "finesim":
- with open(runDir + "run_sim", "w") as wf:
- for temp in temp_list:
- wf.write(
- "finesim -spice %s_sim_%d.sp -o %s_sim_%d &\n"
- % (designName, temp, designName, temp)
- )
-
- with open(runDir + "cal_result", "w") as wf:
- for temp in temp_list:
- wf.write(
- "python result.py --tool finesim --inputFile %s_sim_%d.log\n"
- % (designName, temp)
- )
- wf.write("python result_error.py --mode %s\n" % (simMode))
-
- processes = []
- for temp in temp_list:
- p = sp.Popen(
- [
- "finesim",
- "-spice",
- "%s_sim_%d.sp" % (designName, temp),
- "-o",
- "%s_sim_%d" % (designName, temp),
- ],
- cwd=runDir,
- )
- processes.append(p)
-
- for p in processes:
- p.wait()
-
- for temp in temp_list:
- if not os.path.isfile(runDir + "%s_sim_%d.log" % (designName, temp)):
- print(
- "simulation output: %s_sim_%d.log is not generated"
- % (designName, temp)
- )
- sys.exit(1)
- p = sp.Popen(
- [
- "python",
- "result.py",
- "--tool",
- "finesim",
- "--inputFile",
- "%s_sim_%d.log" % (designName, temp),
- ],
- cwd=runDir,
- )
- p.wait()
-
- p = sp.Popen(["python", "result_error.py", "--mode", simMode], cwd=runDir)
- p.wait()
-
- elif simTool == "ngspice":
- with open(runDir + "run_sim", "w") as wf:
- for temp in temp_list:
- wf.write(
- "ngspice -b -o %s_sim_%d.log %s_sim_%d.sp\n"
- % (designName, temp, designName, temp)
- )
-
- with open(runDir + "cal_result", "w") as wf:
- for temp in temp_list:
- wf.write(
- "python result.py --tool ngspice --inputFile %s_sim_%d.log\n"
- % (designName, temp)
- )
- wf.write("python result_error.py --mode %s\n" % (simMode))
-
- processes = []
- for temp in temp_list:
- p = sp.Popen(
- [
- "ngspice",
- "-b",
- "-o",
- "%s_sim_%d.log" % (designName, temp),
- "%s_sim_%d.sp" % (designName, temp),
- ],
- cwd=runDir,
- )
- processes.append(p)
-
- for p in processes:
- p.wait()
-
- for temp in temp_list:
- if not os.path.isfile(runDir + "%s_sim_%d.log" % (designName, temp)):
- print(
- "simulation output: %s_sim_%d.log is not generated"
- % (designName, temp)
- )
- sys.exit(1)
- p = sp.Popen(
- [
- "python",
- "result.py",
- "--tool",
- "ngspice",
- "--inputFile",
- "%s_sim_%d.log" % (designName, temp),
- ],
- cwd=runDir,
- )
- p.wait()
-
- p = sp.Popen(["python", "result_error.py", "--mode", simMode], cwd=runDir)
- p.wait()
-
- elif simTool == "xyce":
- with open(runDir + "run_sim", "w") as wf:
- for temp in temp_list:
- wf.write(
- "xyce -l %s_sim_%d.log -o %s_sim_%d %s_sim_%d.sp\n"
- % (designName, temp, designName, temp, designName, temp)
- )
-
- with open(runDir + "cal_result", "w") as wf:
- for temp in temp_list:
- wf.write(
- "python result.py --tool xyce --inputFile %s_sim_%d.mt0\n"
- % (designName, temp)
- )
- wf.write("python result_error.py --mode %s\n" % (simMode))
-
- processes = []
- for temp in temp_list:
- p = sp.Popen(
- [
- "/opt/xyce/xyce_serial/bin/Xyce",
- "-l",
- "%s_sim_%d.log" % (designName, temp),
- "-o",
- "%s_sim_%d" % (designName, temp),
- "%s_sim_%d.sp" % (designName, temp),
- ],
- cwd=runDir,
- )
- processes.append(p)
-
- for p in processes:
- p.wait()
-
- for temp in temp_list:
- if not os.path.isfile(runDir + "%s_sim_%d.mt0" % (designName, temp)):
- print(
- "simulation output: %s_sim_%d.mt0 is not generated"
- % (designName, temp)
- )
- sys.exit(1)
- p = sp.Popen(
- [
- "python",
- "result.py",
- "--tool",
- "xyce",
- "--inputFile",
- "%s_sim_%d.mt0" % (designName, temp),
- ],
- cwd=runDir,
- )
- p.wait()
-
- p = sp.Popen(["python", "result_error.py", "--mode", simMode], cwd=runDir)
- p.wait()
+ wf.write(netlist)
\ No newline at end of file
diff --git a/openfasoc/generators/temp-sense-gen/tools/simulation_result.py b/openfasoc/generators/temp-sense-gen/tools/simulation_result.py
new file mode 100644
index 000000000..cc169a62e
--- /dev/null
+++ b/openfasoc/generators/temp-sense-gen/tools/simulation_result.py
@@ -0,0 +1,93 @@
+import re
+import sys
+import math
+
+def get_value(log_file_text, key):
+ """Finds a value in the simulation log file.
+
+ Finds and returns the value from a statement of the format key = value.
+ """
+ pattern = f"{key}\s*=\s*([0-9\.e-]+)"
+ pattern_re = re.search(pattern, log_file_text, flags=re.IGNORECASE)
+ value = "failed"
+
+ if pattern_re:
+ value = pattern_re.group(1)
+
+ return value
+
+def get_sim_results(log_file_text):
+ temp_value = get_value(log_file_text=log_file_text, key="temp")
+ period_value = get_value(log_file_text=log_file_text, key="period")
+ power_value = get_value(log_file_text=log_file_text, key="power")
+
+ return {
+ 'temp': temp_value,
+ 'period': period_value,
+ 'power': power_value
+ }
+
+def calculate_sim_error(sim_output_lines: list[str]):
+ temp_list = []
+ period_list = []
+ power_list = []
+
+ for line in sim_output_lines:
+ data = line.split()
+ temp_list.append(float(data[0]))
+ period_list.append(float(data[1]))
+ power_list.append(float(data[2]))
+
+ frequency_list = []
+ data1 = []
+
+ for i, x in enumerate(period_list):
+ if x == "failed":
+ x = "failed"
+ frequency = "failed"
+ else:
+ frequency = 1 / float(x)
+ print("temp: %f, \tfrequency: %f" % (temp_list[i], frequency))
+
+ x = math.log(1 / float(x)) * (temp_list[i] + 273.15) * 0.01
+
+ frequency_list.append(frequency)
+ data1.append(x)
+
+ try:
+ slope_f = (temp_list[-2] - temp_list[1]) / (data1[-2] - data1[1])
+ except:
+ print("Error calculation failed")
+ sys.exit(1)
+
+ data2 = []
+ for data in data1:
+ if data == "failed":
+ x = "failed"
+ else:
+ x = data * slope_f
+ data2.append(x)
+
+ data3 = []
+ offset = data2[0] - temp_list[0]
+ for val in data2:
+ if val == "failed":
+ val = "failed"
+ else:
+ val = val - offset
+ data3.append(val)
+
+ error_list = []
+ for i, x in enumerate(data3):
+ if x == "failed":
+ error = "failed"
+ else:
+ error = temp_list[i] - x
+ error_list.append(error)
+
+ error_data: list[str] = []
+ error_data.append("Temp Frequency Power Error")
+ for idx, temp in enumerate(temp_list):
+ error_data.append(f"{temp} {frequency_list[idx]} {power_list[idx]} {error_list[idx]}")
+
+ return error_data
diff --git a/openfasoc/generators/temp-sense-gen/tools/temp-sense-gen.py b/openfasoc/generators/temp-sense-gen/tools/temp-sense-gen.py
index ab31b61e0..7d5d48aa8 100755
--- a/openfasoc/generators/temp-sense-gen/tools/temp-sense-gen.py
+++ b/openfasoc/generators/temp-sense-gen/tools/temp-sense-gen.py
@@ -7,7 +7,7 @@
import sys
import re
-from readparamgen import args, check_search_done, designName
+from readparamgen import args, check_search_done, designName, Tmin, Tmax, Tstep
from simulation import generate_runs
# TODO: Find a better way to import modules from parent directory
@@ -192,12 +192,6 @@
os.mkdir(args.outputDir)
outputDir = args.outputDir
-# print("genDir + args.outputDir: {}".format(genDir + args.outputDir))
-# print("flowDir: {}".format(flowDir))
-# print("args.platform: {}".format(args.platform))
-# print("designName: {}".format(designName))
-# subprocess.run(["ls", "-l", flowDir, "results/", args.platform, "/tempsense"])
-
shutil.copyfile(
flowDir + "results/" + args.platform + "/tempsense/6_final.gds",
outputDir + "/" + designName + ".gds",
@@ -231,83 +225,41 @@
outputDir + "/6_final_lvs.rpt",
)
-
print("#----------------------------------------------------------------------")
print("# Macro Generated")
print("#----------------------------------------------------------------------")
print()
-
-print("#----------------------------------------------------------------------")
-print("# Generating spice netlists for the macro")
-print("#----------------------------------------------------------------------")
-
-stage_var = [int(ninv)]
-header_var = [int(nhead)]
-
-# make a temp list, TODO: get from JSON config
-temp_start = -20
-temp_stop = 100
-temp_step = 20
-temp_points = int((temp_stop - temp_start) / temp_step)
-temp_list = []
-for i in range(0, temp_points + 1):
- temp_list.append(temp_start + i * temp_step)
-
-# run PEX and/or prePEX simulations based on the command line flags
-if args.prepex:
- prepexDir = generate_runs(
- genDir,
- designName,
- header_var,
- stage_var,
- temp_list,
- jsonConfig,
- args.platform,
- args.mode,
- pdk,
- spiceDir=args.outputDir,
- prePEX=True,
- )
- if args.mode == "full":
- if os.path.isfile(prepexDir + "all_result"):
- shutil.copyfile(
- prepexDir + "all_result", genDir + args.outputDir + "/prePEX_sim_result"
- )
- else:
- print(prepexDir + "prePEX all_result file is not generated successfully")
- sys.exit(1)
-
-if args.pex:
- pexDir = generate_runs(
- genDir,
- designName,
- header_var,
- stage_var,
- temp_list,
- jsonConfig,
- args.platform,
- args.mode,
- pdk,
- spiceDir=args.outputDir,
- prePEX=False,
- )
-
- if args.mode == "full":
- if os.path.isfile(pexDir + "all_result"):
- shutil.copyfile(
- pexDir + "all_result", genDir + args.outputDir + "/PEX_sim_result"
- )
- else:
- print(pexDir + "PEX all_result file is not generated successfully")
- sys.exit(1)
-
-
if args.mode == "full":
print("#----------------------------------------------------------------------")
- print("# Simulation output Generated")
+ print("# Running simulations.")
print("#----------------------------------------------------------------------")
+ stage_var = [int(ninv)]
+ header_var = [int(nhead)]
+
+ # run PEX and/or prePEX simulations based on the command line flags
+ sim_output_dir = generate_runs(
+ genDir=genDir,
+ designName=designName,
+ headerList=header_var,
+ invList=stage_var,
+ tempStart=Tmin,
+ tempStop=Tmax,
+ tempStep=Tstep,
+ jsonConfig=jsonConfig,
+ platform=args.platform,
+ pdk=pdk,
+ spiceDir=args.outputDir,
+ prePEX=args.prepex,
+ )
+ if os.path.isfile(sim_output_dir + "all_result"):
+ shutil.copyfile(
+ sim_output_dir + "all_result", genDir + args.outputDir + f"/{'prePEX' if args.prepex else 'PEX'}_sim_result"
+ )
+ else:
+ print(sim_output_dir + f"{'prePEX' if args.prepex else 'PEX'} all_result file is not generated successfully")
+ sys.exit(1)
print("Exiting tool....")
exit()
diff --git a/tests/common_api/test-simulation/expected/1/sim_1.sp b/tests/common_api/test-simulation/expected/1/sim_1.sp
new file mode 100644
index 000000000..b82cb6986
--- /dev/null
+++ b/tests/common_api/test-simulation/expected/1/sim_1.sp
@@ -0,0 +1,19 @@
+
+
+.OPTION temp = -5
+
+.subckt TEST_NETLIST V1 V2
+C1 V1 V0 C=5
+R1 V0 V2 R=5k
+.ends
+
+X0 V1 0 TEST_NETLIST
+
+VPOWER V1 0 AC SIN(0 1 1k 0 0 0)
+
+.TRAN 0.01 1
+
+.MEAS tran vrms RMS par('V(V1)')
+.MEAS tran irms RMS par('I(VPOWER)')
+
+.END
diff --git a/tests/common_api/test-simulation/expected/2/sim_2.sp b/tests/common_api/test-simulation/expected/2/sim_2.sp
new file mode 100644
index 000000000..4c0802904
--- /dev/null
+++ b/tests/common_api/test-simulation/expected/2/sim_2.sp
@@ -0,0 +1,19 @@
+
+
+.OPTION temp = 5
+
+.subckt TEST_NETLIST V1 V2
+C1 V1 V0 C=5
+R1 V0 V2 R=5k
+.ends
+
+X0 V1 0 TEST_NETLIST
+
+VPOWER V1 0 AC SIN(0 1 1k 0 0 0)
+
+.TRAN 0.01 1
+
+.MEAS tran vrms RMS par('V(V1)')
+.MEAS tran irms RMS par('I(VPOWER)')
+
+.END
diff --git a/tests/common_api/test-simulation/expected/3/sim_3.sp b/tests/common_api/test-simulation/expected/3/sim_3.sp
new file mode 100644
index 000000000..9e4d6f067
--- /dev/null
+++ b/tests/common_api/test-simulation/expected/3/sim_3.sp
@@ -0,0 +1,19 @@
+
+
+.OPTION temp = -5
+
+.subckt TEST_NETLIST V1 V2
+C1 V1 V0 C=5
+R1 V0 V2 R=5k
+.ends
+
+X0 V1 0 TEST_NETLIST
+
+VPOWER V1 0 AC SIN(0 10 1k 0 0 0)
+
+.TRAN 0.01 1
+
+.MEAS tran vrms RMS par('V(V1)')
+.MEAS tran irms RMS par('I(VPOWER)')
+
+.END
diff --git a/tests/common_api/test-simulation/expected/4/sim_4.sp b/tests/common_api/test-simulation/expected/4/sim_4.sp
new file mode 100644
index 000000000..4280cc6f0
--- /dev/null
+++ b/tests/common_api/test-simulation/expected/4/sim_4.sp
@@ -0,0 +1,19 @@
+
+
+.OPTION temp = 5
+
+.subckt TEST_NETLIST V1 V2
+C1 V1 V0 C=5
+R1 V0 V2 R=5k
+.ends
+
+X0 V1 0 TEST_NETLIST
+
+VPOWER V1 0 AC SIN(0 10 1k 0 0 0)
+
+.TRAN 0.01 1
+
+.MEAS tran vrms RMS par('V(V1)')
+.MEAS tran irms RMS par('I(VPOWER)')
+
+.END
diff --git a/tests/common_api/test-simulation/expected/5/sim_5.sp b/tests/common_api/test-simulation/expected/5/sim_5.sp
new file mode 100644
index 000000000..1fe61045f
--- /dev/null
+++ b/tests/common_api/test-simulation/expected/5/sim_5.sp
@@ -0,0 +1,19 @@
+
+
+.OPTION temp = -5
+
+.subckt TEST_NETLIST V1 V2
+C1 V1 V0 C=5
+R1 V0 V2 R=5k
+.ends
+
+X0 V1 0 TEST_NETLIST
+
+VPOWER V1 0 AC SIN(0 1 10k 0 0 0)
+
+.TRAN 0.01 1
+
+.MEAS tran vrms RMS par('V(V1)')
+.MEAS tran irms RMS par('I(VPOWER)')
+
+.END
diff --git a/tests/common_api/test-simulation/expected/6/sim_6.sp b/tests/common_api/test-simulation/expected/6/sim_6.sp
new file mode 100644
index 000000000..2507e0dda
--- /dev/null
+++ b/tests/common_api/test-simulation/expected/6/sim_6.sp
@@ -0,0 +1,19 @@
+
+
+.OPTION temp = 5
+
+.subckt TEST_NETLIST V1 V2
+C1 V1 V0 C=5
+R1 V0 V2 R=5k
+.ends
+
+X0 V1 0 TEST_NETLIST
+
+VPOWER V1 0 AC SIN(0 1 10k 0 0 0)
+
+.TRAN 0.01 1
+
+.MEAS tran vrms RMS par('V(V1)')
+.MEAS tran irms RMS par('I(VPOWER)')
+
+.END
diff --git a/tests/common_api/test-simulation/expected/7/sim_7.sp b/tests/common_api/test-simulation/expected/7/sim_7.sp
new file mode 100644
index 000000000..bc9e2c3c6
--- /dev/null
+++ b/tests/common_api/test-simulation/expected/7/sim_7.sp
@@ -0,0 +1,19 @@
+
+
+.OPTION temp = -5
+
+.subckt TEST_NETLIST V1 V2
+C1 V1 V0 C=5
+R1 V0 V2 R=5k
+.ends
+
+X0 V1 0 TEST_NETLIST
+
+VPOWER V1 0 AC SIN(0 10 10k 0 0 0)
+
+.TRAN 0.01 1
+
+.MEAS tran vrms RMS par('V(V1)')
+.MEAS tran irms RMS par('I(VPOWER)')
+
+.END
diff --git a/tests/common_api/test-simulation/expected/8/sim_8.sp b/tests/common_api/test-simulation/expected/8/sim_8.sp
new file mode 100644
index 000000000..0f7bbc084
--- /dev/null
+++ b/tests/common_api/test-simulation/expected/8/sim_8.sp
@@ -0,0 +1,19 @@
+
+
+.OPTION temp = 5
+
+.subckt TEST_NETLIST V1 V2
+C1 V1 V0 C=5
+R1 V0 V2 R=5k
+.ends
+
+X0 V1 0 TEST_NETLIST
+
+VPOWER V1 0 AC SIN(0 10 10k 0 0 0)
+
+.TRAN 0.01 1
+
+.MEAS tran vrms RMS par('V(V1)')
+.MEAS tran irms RMS par('I(VPOWER)')
+
+.END
diff --git a/tests/common_api/test-simulation/src/template.sp b/tests/common_api/test-simulation/src/template.sp
new file mode 100644
index 000000000..5b1d5b006
--- /dev/null
+++ b/tests/common_api/test-simulation/src/template.sp
@@ -0,0 +1,17 @@
+.OPTION temp = ${temp}
+
+.subckt TEST_NETLIST V1 V2
+C1 V1 V0 C=5
+R1 V0 V2 R=5k
+.ends
+
+X0 V1 0 ${netlist_subckt_name}
+
+VPOWER V1 0 AC SIN(0 ${Vhigh} ${freq} 0 0 0)
+
+.TRAN 0.01 ${end_time}
+
+.MEAS tran vrms RMS par('V(V1)')
+.MEAS tran irms RMS par('I(VPOWER)')
+
+.END
diff --git a/tests/common_api/test_simulation.py b/tests/common_api/test_simulation.py
new file mode 100644
index 000000000..b5947f5d2
--- /dev/null
+++ b/tests/common_api/test_simulation.py
@@ -0,0 +1,102 @@
+import sys
+import os
+import re
+import pytest
+from shutil import rmtree
+
+# Add the common API to the path
+# TODO: Find a better way to import the modules
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'openfasoc', 'generators'))
+
+from common.simulation import run_simulations
+
+TEST_SIMULATION_DIR = os.path.join(os.path.dirname(__file__), 'test-simulation')
+EXPECTED_CONFIGS_DIR = os.path.join(TEST_SIMULATION_DIR, 'expected')
+RUNS_DIR = os.path.join(TEST_SIMULATION_DIR, 'runs')
+
+PARAMS = {
+ 'temp': {'start': -5, 'end': 5, 'step': 10},
+ 'netlist_subckt_name': 'TEST_NETLIST',
+ 'Vhigh': [1, 10],
+ 'end_time': 1,
+ 'freq': ['1k', '10k']
+}
+
+EXPECTED_NUM_CONFIGS = 8
+
+# From the temperature senssor generator.
+def get_value(log_file_text, key):
+ """Finds a value in the simulation log file.
+
+ Finds and returns the value from a statement of the format key = value.
+ """
+ pattern = f"{key}\s*=\s*([0-9\.e-]+)"
+ pattern_re = re.search(pattern, log_file_text, flags=re.IGNORECASE)
+ value = "NOT_FOUND"
+
+ if pattern_re:
+ value = pattern_re.group(1)
+
+ return value
+
+def isfloat(num: str):
+ """Checks if a string represents a float."""
+ try:
+ float(num)
+ return True
+
+ except ValueError:
+ return False
+
+@pytest.fixture(autouse=True)
+def run_before_and_after_tests():
+ """Cleans the runs directory before and after tests."""
+ if os.path.exists(RUNS_DIR):
+ rmtree(RUNS_DIR)
+
+ yield
+
+ if os.path.exists(RUNS_DIR):
+ rmtree(RUNS_DIR)
+
+def test_simulations():
+ num_runs = run_simulations(
+ parameters=PARAMS,
+ platform = "",
+ simulation_dir = TEST_SIMULATION_DIR,
+ template_path = os.path.join(TEST_SIMULATION_DIR, 'src', 'template.sp'),
+ num_concurrent_sims = 8
+ )
+
+ # Check if the correct number of configurations are generated
+ assert num_runs == EXPECTED_NUM_CONFIGS, "The number of runs does not match the expected number."
+ assert len(os.listdir(RUNS_DIR)) == EXPECTED_NUM_CONFIGS, "The number of generated configurations does not match the expected number."
+
+
+ for i in range(1, EXPECTED_NUM_CONFIGS + 1):
+ expected_spice_path = os.path.join(EXPECTED_CONFIGS_DIR, str(i), f"sim_{i}.sp")
+
+ run_dir = os.path.join(RUNS_DIR, str(i))
+ generated_spice_path = os.path.join(run_dir, f"sim_{i}.sp")
+ generated_log_file_path = os.path.join(run_dir, f"sim_{i}.log")
+ parameters_file_path = os.path.join(run_dir, f"parameters.txt")
+
+ # Check if all the correct files are generated
+ assert os.path.exists(generated_spice_path), f"SPICE file is not generated for config #{i}"
+ assert os.path.exists(generated_log_file_path), f"Simulation log file is not generated for config #{i}"
+ assert os.path.exists(parameters_file_path), f"parameters.txt file is not generated for config #{i}"
+
+ # Check if the generated spice matches
+ with open(expected_spice_path) as expected_spice:
+ with open(generated_spice_path) as generated_spice:
+ assert generated_spice.read() == expected_spice.read(), f"Generated SPICE does not match the expected SPICE for config #{i}"
+
+ # Check if the simulations are run correctly
+ with open(generated_log_file_path) as log_file:
+ log_file_text = log_file.read()
+ # 1. Search for vrms and irms values
+ vrms_value = get_value(log_file_text, 'vrms')
+ irms_value = get_value(log_file_text, 'irms')
+
+ assert vrms_value != "NOT_FOUND", f"`vrms` value not found in the simulation output for config #{i}"
+ assert irms_value != "NOT_FOUND", f"`irms` value not found in the simulation output for config #{i}"
\ No newline at end of file