diff --git a/e2e/Makefile b/e2e/Makefile index 00c370bac..2d9adb0c5 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -3,9 +3,9 @@ vlsi_dir=$(abspath .) # minimal flow configuration variables -design ?= pass +design ?= level_shifter pdk ?= sky130 -tools ?= nop +tools ?= cm env ?= bwrc extra ?= # extra configs @@ -22,9 +22,9 @@ TOOLS_CONF ?= configs-tool/$(tools).yml # design-specific overrides of default configs DESIGN_CONF ?= configs-design/$(design)/common.yml DESIGN_PDK_CONF ?= configs-design/$(design)/$(pdk).yml -SIM_CONF ?= $(if $(findstring -rtl,$(MAKECMDGOALS)), configs-design/$(design)/sim-rtl.yml, \ - $(if $(findstring -syn,$(MAKECMDGOALS)), configs-design/$(design)/sim-syn.yml, \ - $(if $(findstring -par,$(MAKECMDGOALS)), configs-design/$(design)/sim-par.yml, ))) +SIM_CONF ?= $(if $(findstring rtl,$(MAKECMDGOALS)), configs-design/$(design)/sim-rtl.yml, \ + $(if $(findstring syn,$(MAKECMDGOALS)), configs-design/$(design)/sim-syn.yml, \ + $(if $(findstring par,$(MAKECMDGOALS)), configs-design/$(design)/sim-par.yml, ))) POWER_CONF ?= $(if $(findstring power-rtl,$(MAKECMDGOALS)), configs-design/$(design)/power-rtl-$(pdk).yml, \ $(if $(findstring power-syn,$(MAKECMDGOALS)), configs-design/$(design)/power-syn-$(pdk).yml, \ $(if $(findstring power-par,$(MAKECMDGOALS)), configs-design/$(design)/power-par-$(pdk).yml, ))) @@ -43,6 +43,3 @@ $(HAMMER_D_MK): hammer-vlsi --obj_dir $(OBJ_DIR) -e $(ENV_YML) $(HAMMER_EXTRA_ARGS) build -include $(HAMMER_D_MK) - -clean: - rm -rf $(OBJ_DIR) hammer-vlsi-*.log diff --git a/e2e/configs-tool/cm.yml b/e2e/configs-tool/cm.yml index df7785586..3474639f4 100644 --- a/e2e/configs-tool/cm.yml +++ b/e2e/configs-tool/cm.yml @@ -4,7 +4,7 @@ vlsi.core.build_system: make # Select tools vlsi.core.synthesis_tool: "hammer.synthesis.genus" vlsi.core.par_tool: "hammer.par.innovus" -vlsi.core.sim_tool: "hammer.sim.vcs" +vlsi.core.sim_tool: "hammer.sim.xcelium" vlsi.core.timing_tool: "hammer.timing.tempus" vlsi.core.formal_tool: "hammer.formal.conformal" diff --git a/e2e/mxhammersetupguide.md b/e2e/mxhammersetupguide.md new file mode 100644 index 000000000..b0831e27a --- /dev/null +++ b/e2e/mxhammersetupguide.md @@ -0,0 +1,208 @@ +# Provisional MXHammer Setup Guide (E2E) + +## 1. Hammer Setup +### 1.1 Setup the Hammer environment according to the steps under 1.2.2. Developer Setup in the Hammer documentation, found [here](https://hammer-vlsi.readthedocs.io/en/stable/Hammer-Basics/Hammer-Setup.html#developer-setup) or below. + +#### 1.2.2. Developer Setup +##### 1. Clone Hammer with `git` +``` +git clone https://github.com/ucb-bar/hammer.git +cd hammer +``` +##### 2. [Install poetry](https://python-poetry.org/docs/master/) to manage the development virtualenv and dependencies +``` +curl -sSL https://install.python-poetry.org | python3 - +``` +##### 3. Create a poetry-managed virtualenv using the dependencies from `pyproject.toml` +``` +# create the virtualenv inside the project folder (in .venv) +poetry config virtualenvs.in-project true +poetry install +``` +##### 4. Activate the virtualenv. Within the virtualenv, Hammer is installed and you can access its scripts defined in `pyproject.toml` (in `[tool.poetry.scripts]`) +``` +poetry shell +hammer-vlsi -h +``` +### 1.2 Set simulator to Xcelium +Under `hammer/e2e/configs-tool/cm.yml`, find the simulator tool key: `vlsi.core.sim_tool` and change the value from VCS to Xcelium. This should like the following when done: `vlsi.core.sim_tool: "hammer.sim.xcelium"`. + +### 1.3 Setup design configuration file +Create a folder under `hammer/e2e/configs-design` to hold the configuration files for your design. For the AMS simulation, at minimum, this folder should contain a `common.yml`, `sim-rtl.yml`, and a process configuration file such as `asap7.yml` or `sky130.yml`. + + +## 2. MXHammer Setup + +### 2.1 Replace standard Xcelium plugin files with their MXHammer variants +The files for the MXHammer variant of Hammer can be found in the `ams_experimental` branch of [Hammer](https://github.com/ucb-bar/hammer.git). If you have the correct files, the following comment should appear within the `__init__.py`: `# MXHammer version` . + +### 2.2 Simulator Setup +#### 2.2.1 General Simulator Settings +The first portion of your `sim-rtl.yml` should contain general simulator settings, such as the top module, testbench name/DUT, etc. **The level setting should be set to "rtl".** It should look something like the following when filled: +``` +sim.inputs: + top_module: "modulename" + tb_name: "testbench" + tb_dut: "module0" + level: "rtl" + input_files: ["src/digital/inv.v", "src/digital/testbench.vams"] + timescale: "1ns/100ps" + waveform.type: null + waveform.dump_name: "wave" + waveform.compression: False + waveform.probe_paths: "-all" + waveform.tcl_opts: null +``` +All Verilog/VHDL and Verilog-AMS files should be included within `input_files`, except for connect module library files, and AMS connection rule files. These are explained later below. + +#### 2.2.2 Xcelium Simulator Settings +The second portion of your `sim-rtl.yml` should contain Xcelium-specific settings for digital and optionally AMS simulation. In order to see what each key expects as a value, please see `defaults.yml` within `hammer/hammer/sim/xcelium`. + +However, particular elements may be unfamiliar, such as `disciplines`, `connectlibs`, and `connrules`. + +##### Disciplines +These are a set of definitions and properties for a specific type of system. It is a combination of an analog **potential** (ex. voltage) and ***flow* nature** (ex. current). **Natures** are declarations that define a collection of **attributes**. The defined nature is then used to define disciplines and other natures. + +##### Disciplines for specific scopes +We can use `-setdiscipline` or `-setd` option to specify to the elaborator which disciplines to apply to domain-less (think voltage for analog, or high/low for digital) nets in a specified design scope. + +The following is the syntax for specifying disciplines: +``` +-setd "LIB-lib_name- discipline_name" +-setd "CELL-lib_name.cell_name:view_name- discipline_name" +-setd "CELLTERM-cell_name.port_name- discipline_name" +-setd "INST-hierarchical_instance_name- discipline_name" +-setd "INSTTERM-hierarchical_instance_name- discipline_name" +``` +Example disciplines for a 3.3V Buffer: +``` +-SETD "INSTTERM-testbench.vlog_buf.I1.in- logic33V" +-SETD "INSTTERM-testbench.vlog_buf.in_33v- logic33V" +-SETD "INSTTERM-testbench.vlog_buf.out_33v- logic33V" +-SETD "INSTTERM-testbench.vlog_buf.I2.out- logic33V" +``` + +`-setdisciple` \ `-setd` can also be used to allow default default disciplines to be applied to different blocks. +``` +-discipline logic 18V + -setd "inst-top.I1.I2- logic_33V" + -setd "inst-top.I1.I3- logic_5V" + -setd "inst-top.I1.I4- electrical" + -setd "net-top.I1.out- logic_33V" +``` +##### Discipline specification for MXHammer +Place all disciplines, formatted in the manner shown above, within a .txt file and provide the path to the file relative to the working directory to the `sim.xcelium.disciplines` key. + +##### Connect Modules (CMs) / Interface Elements (IEs) +Connect modules or interface elements slot between signal domains like electrical, logic, and real. They are inserted *automatically* or *manually* by using connect or **ie** statements, which contain the code required to translate and propagate signals between **nets** that have different **disciplines** connected through a port. + +##### Connect Libraries +The connect libraries are a set of files which define how signal domains should be translated between various **disciplines**. As of now they are not included within the public-facing version of MXHammer. + +##### Connect Rules +Defines a set of **connect modules** to be used in a design and notifies the simulator about which CMs to use and which is chosen from the built-in set provided in the software installation or from a user-defined set + +### 2.3 A/MS Control Files +For an AMS simulation, we need to specify the analog signals, models used in the design, which analog simulation we wish to perform, the analog design itself, and how we want to connect that design to external blocks through and **AMSD** block. + +#### 2.3.1 AMSD Control Files +The AMS control file (**AMSCF**) specifies: + - The simulator language (should be spectre in most cases) + - Global signals (such as `0`/`vss!` and `vdd!`) + - The models to be used in the simulation of the design, an **analog control file** (if the information within the **ACF** is not included within an **AMSD** block within the **AMSCF**). ***ALL*** model files used within the design **must** be included (section of the model the particular block resides in is usually fine). + - **NOTE:** When generating the netlist for your analog design, do so as **CDL**. This can be done from **ICADV** GUI as File -> Export -> CDL. Alternatively you can use the Virtuoso CDL out interface. + - The design(s), whose file(s) must be included, like the model files. + - **AMSD** Block(s) + +##### AMSD Blocks +These 'AMS Designer' blocks can contain any design partitioning, disciplines, and connect modules. Where 'partitioning' includes setting the use of spice, portmaps for subcircuits, bus delimiters, and setting `autobus` to `yes` or `no`. Disciplines include connecting the power supply voltage when connecting from an analog voltage to logic. + +Ex 1: `ie vsup=1.8 discipline=logic18V` tells the elaborator that 1.8V is a logic 1 for that the analog real output + +Ex 2: A generic, mostly unfilled AMSD block might look like the following: +``` +amsd{ + +// set the connect rule power supply + ie vsup=? discipline=logic?V + +// mix analog and digital +portmap subckt=ckt_name autobus=yes busdelim="[] <>" +config cell=ckt_name use=spice +} +``` + +##### AMSCF Example for a skywater 130nm level-shifter +In the following example, notice `autobus` is set to `yes`, this specifies that Xcelium will automatically generate CMs/IEs between nets with different domains. +``` +simulator lang=spectre + +// global signals +global 0 vdd! + +//model deck - Skywater +include "/tools/C/miguel4141/mxhammer/mx-project/bag-sky130/bag3_skywater130_workspace/skywater130/workspace_setup/PDK/MODELS/SPECTRE/s8phirs_10r/Models/design_wrapper.lib.scs" section=tt_fet +include "/tools/C/miguel4141/mxhammer/mx-project/bag-sky130/bag3_skywater130_workspace/skywater130/workspace_setup/PDK/MODELS/SPECTRE/s8phirs_10r/Models/design_wrapper.lib.scs" section=tt_cell +include "/tools/C/miguel4141/mxhammer/mx-project/bag-sky130/bag3_skywater130_workspace/skywater130/workspace_setup/PDK/MODELS/SPECTRE/s8phirs_10r/Models/design_wrapper.lib.scs" section=tt_parRC +include "/tools/C/miguel4141/mxhammer/mx-project/bag-sky130/bag3_skywater130_workspace/skywater130/workspace_setup/PDK/MODELS/SPECTRE/s8phirs_10r/Models/design_wrapper.lib.scs" section=tt_rc +include "/tools/C/miguel4141/mxhammer/mx-project/bag-sky130/bag3_skywater130_workspace/skywater130/workspace_setup/PDK/MODELS/SPECTRE/s8phirs_10r/Models/design_wrapper.lib.scs" section=npn_t + +//Analog Control File +include "acf.scs" + +//Cadence Level Shifter +//include "src/analog/level_shifter.sp" + +//Skywater Level Shifter +include "src/analog/sky130_level_shifter.sp" + +// ams configuration options +amsd{ + +// set the connect rule power supply + ie vsup=1.8 discipline=logic18V + +// mix analog and digital +portmap subckt=sky130_level_shifter autobus=yes busdelim="[] <>" +config cell=sky130_level_shifter use=spice +} + +``` + +#### 2.3.2 Analog Control Files +The analog control file (**ACF**) contains the analog simulation type and settings which the AMS simulation will use. They are specified in the spectre language. + +Ex: +``` +*********************************** +simulator lang=spectre +*********************************** + +*--------------------------------------------------------* +* Spectre Fast APS Analysis Options/Spectre X options +*--------------------------------------------------------* +tran1 tran start=1ns stop=1us method=trap + +*--------------------------------------------------------* +* Spectre Fast APS Simulator Options options +*--------------------------------------------------------* +save * sigtype=node depth=4 +``` + +## 2.4 Running MXHammer +To run the AMS simulation, ensure that the `ams` key in `sim-rtl.yml` is set to `True` and you have all other relevant files and values to the keys set. For any keys whose value is a path to a file or directory which is not needed for your simulation (such as `connectlibs` or `connectrules`), set that value to `None` in `sim-rtl.yml`. Additionally, some additional options are currently hardcoded, so please be sure to use a `.tcl` file to specify which signals you would like to save from the AMS simulation, name that file `probe.tcl`, and put it in `e2e/src/`. If you are unfamiliar with how to put together a `.tcl` probe, a guide can be found [here](https://community.cadence.com/cadence_blogs_8/b/cic/posts/start-your-engines-use-tcl-commands-to-save-signals-more-efficiently-in-mixed-signal-simulations). + +### 2.4.1 Xcelium + MXHammer Mechanics +Xcelium works in one of two primary methodologies, **three-step** or **single-step**. In the three-step methodology, the user (or Hammer) steps through the compilation, elaboration, and simulation stages. In the single-step methodology, the user provides all necessary information up front and Xcelium internally steps through all three stages. The base Xcelium plugin operates off of the three-step methodology. + +MXHammer operates on a pseudo-three-step methodology which takes in the arguments generated by the three-step process of the base digital-only Xcelium plugin and the specified AMS arguments to create a shell script for a single run. + +### 2.4.2 Starting the MXHammer run +To begin the MXHammer, simply go to your hammer folder, initiate the poetry environment, then go to the `e2e` folder. Once there, type `make build` into the command line to generate the `hammer.d` file and then type `make sim-rtl` to begin the run. + + +### 2.4.3 Initiating the AMS Simulation +Currently, there are issues with the step of initiating the run automatically, so you will likely need to do so yourself. All you need to do so is to go to the build directory under `e2e`, under which you fill find the build for your design, under which you can find `sim-rtl-rundir` (the full path will look something like `hammer/e2e/build-node-env/design_name/sim-rtl-rundir`). Once you are there, ensure the `run_mxh` shell script has the correct permissions and then simply type `./run_mxh` into the command line. + + +# For any questions, email Miguel at miguel4141@berkeley.edu diff --git a/e2e/src/amscf_template.scs b/e2e/src/amscf_template.scs new file mode 100644 index 000000000..dfa74f351 --- /dev/null +++ b/e2e/src/amscf_template.scs @@ -0,0 +1,19 @@ +simulator lang=spectre + +// global signals + +// model deck + +// analog control file +include "acf.scs" + +// schematic deck + +// ams configuration options +amsd { + // set the connect rule power supply + + // mix analog and digital + // portmap subckt=cell_name + // config cell=cell_name use=spice +} diff --git a/e2e/src/disciplines.txt b/e2e/src/disciplines.txt new file mode 100644 index 000000000..e69de29bb diff --git a/hammer/sim/xcelium/__init__.py b/hammer/sim/xcelium/__init__.py index d2f035298..afe9751a3 100644 --- a/hammer/sim/xcelium/__init__.py +++ b/hammer/sim/xcelium/__init__.py @@ -15,6 +15,8 @@ import json import datetime import io +import re +import logging # Remove later to use hammer logging from typing import Dict, List, Optional, Tuple, Any import hammer.tech as hammer_tech @@ -22,7 +24,9 @@ from hammer.vlsi import HammerSimTool, HammerToolStep, HammerLSFSubmitCommand, HammerLSFSettings from hammer.logging import HammerVLSILogging from hammer.common.cadence import CadenceTool +from hammer.utils import retrieve_files, sift_exts +# MXHammer version class xcelium(HammerSimTool, CadenceTool): @@ -30,17 +34,20 @@ class xcelium(HammerSimTool, CadenceTool): def xcelium_ext(self) -> List[str]: verilog_ext = [".v", ".V", ".VS", ".vp", ".VP"] sverilog_ext = [".sv",".SV",".svp",".SVP",".svi",".svh",".vlib",".VLIB"] + verilogams_ext = [".vams", ".VAMS", ".Vams", ".vAMS"] + vhdl_ext = [".vhdl", ".VHDL"] + scs_ext = [".scs", ".SCS", ".sp", ".SP"] c_cxx_ext = [".c",".cc",".cpp"] - gz_ext = [ext + ".gz" for ext in verilog_ext + sverilog_ext] - z_ext = [ext + ".z" for ext in verilog_ext + sverilog_ext] - return (verilog_ext + sverilog_ext + c_cxx_ext + gz_ext + z_ext) + gz_ext = [ext + ".gz" for ext in verilog_ext + sverilog_ext + verilogams_ext + scs_ext] + z_ext = [ext + ".z" for ext in verilog_ext + sverilog_ext + verilogams_ext + scs_ext] + return (verilog_ext + sverilog_ext + verilogams_ext + vhdl_ext + scs_ext + c_cxx_ext + gz_ext + z_ext) @property def steps(self) -> List[HammerToolStep]: return self.make_steps_from_methods([self.compile_xrun, self.elaborate_xrun, self.sim_xrun]) - + def tool_config_prefix(self) -> str: return "sim.xcelium" @@ -55,6 +62,10 @@ def sim_waveform_prefix(self) -> str: @property def xcelium_bin(self) -> str: return self.get_setting("sim.xcelium.xcelium_bin") + + @property + def spectre_bin(self) -> str: + return self.get_setting("sim.xcelium.spectre_bin") @property def sim_tcl_file(self) -> str: @@ -107,7 +118,7 @@ def extract_xrun_opts(self) -> Tuple[Dict[str, str], Dict[str, str]]: xrun_opts = self.get_settings_from_dict(xrun_opts_def ,key_prefix=self.tool_config_prefix()) xrun_opts_proc = xrun_opts.copy() - bool_list = ["global_access", "enhanced_recompile", "mce"] + bool_list = ["global_access", "enhanced_recompile", "mce", "ams"] if xrun_opts_proc ["global_access"]: xrun_opts_proc ["global_access"] = "+access+rcw" @@ -123,7 +134,7 @@ def extract_xrun_opts(self) -> Tuple[Dict[str, str], Dict[str, str]]: xrun_opts_proc ["mce"] = "-mce" else: xrun_opts_proc ["mce"] = "" - + for opt, setting in xrun_opts_proc.items(): if opt not in bool_list and setting is not None: xrun_opts_proc [opt] = f"-{opt} {setting}" @@ -247,7 +258,9 @@ def generate_arg_file(self, [f.write(elem + "\n") for elem in opt_list[1]] f.close() - return arg_path + return arg_path + + # Convenience function invoked when multicore options are needed. def generate_mc_cmd(self) -> str: @@ -377,6 +390,80 @@ def generate_sim_tcl(self) -> bool: f.close() return True + """def generate_amscf(self) -> bool: + # Open AMS control file template for read. + # Hardcoded path for now + t = open("amscf_template.scs", "r") + + # Create AMS control file (or overwrite if one already exists) for read/write. + # Hardcoded path for now. + f = open(f"./src/amscf.scs", "w+") + + # Get absolute paths for analog models from PDK and schematics from extralibs, respectively. + model_path = self.get_setting("sim.xcelium.anamodels") + models = [modelfile for modelfile in os.scandir(model_path)] + schematic_path = self.get_setting("sim.xcelium.schematics") + schematics = [schematic for schematic in os.scandir(schematic_path)] + + # Get list of paths to individual files within the PDK models (?) and schematic directories, respectively. + #models = [] + #schematics = [] + + # Warnings for missing files. + if (len(schematics) > 0 and len(models) == 0): + self.logger.warning(f"No models found in {model_path} to support analog schematics.") + else: + if (len(models) == 0): + self.logger.warning(f"No models found in {model_path}.") + if (len(schematicpath) == 0): + self.logger.warning(f"No analog schematics found {schematics}.") + + # Get string representation of AMS control file template. + template = t.read() + + # Format modelpaths list as a single string with include statements. + formatted_models = "" + for modelpath in models: + formatted_models += f"include {modelpath}" + + # Format schematicpaths list as a single string with include statements. + formatted_schematics = "" + for schematicpath in schematics: + formatted_schematics += f"include {schematicpath}" + + # Replace empty model deck with formatted string of model paths. + template = re.sub("// model deck\n", "// model deck\n" + formatted_models) + + # Replace empty schematic deck with formatted string of schematic paths. + template = re.sub("// schematic deck\n", "// schematic deck\n" + formatted_schematics) + + # Write filled template to AMS control file. + f.write(template) + + # Close files properly. + t.close() + f.close() + return True""" + + def attach_opts(self, filepath, attachment): + f = open(filepath, "a+") + f.write(attachment) + f.close + return + + def get_disciplines(self) -> str: + ### Read in disciplines file, if it exists. + disciplines = self.get_setting("sim.xcelium.disciplines") + cwd = os.getcwd() + dpath = os.path.join(cwd, disciplines) + if disciplines: + df = open(dpath, "r") + discipline_opts = df.read() + "\n" + df.close() + return discipline_opts + else: + return "" + def compile_xrun(self) -> bool: if not os.path.isfile(self.xcelium_bin): @@ -388,7 +475,7 @@ def compile_xrun(self) -> bool: # Gather complation-only options xrun_opts = self.extract_xrun_opts()[1] - compile_opts = self.get_setting(f"{self.tool_config_prefix}.compile_opts", []) + compile_opts = self.get_setting(f"{self.tool_config_prefix()}.compile_opts", []) compile_opts.append("-logfile xrun_compile.log") if xrun_opts["mce"]: compile_opts.append(self.generate_mc_cmd()) compile_opts = ('COMPILE', compile_opts) @@ -398,6 +485,11 @@ def compile_xrun(self) -> bool: args.append(f"-compile -f {arg_file_path}") self.update_submit_options() + + ### If AMS enabled, submit options but do not run compile sub-step. + if self.get_setting(f"{self.tool_config_prefix()}.ams"): + return True + self.run_executable(args, cwd=self.run_dir) HammerVLSILogging.enable_colour = True HammerVLSILogging.enable_tag = True @@ -406,7 +498,7 @@ def compile_xrun(self) -> bool: def elaborate_xrun(self) -> bool: xrun_opts = self.extract_xrun_opts()[1] sim_opts = self.extract_sim_opts()[1] - elab_opts = self.get_setting(f"{self.tool_config_prefix}.elab_opts", []) + elab_opts = self.get_setting(f"{self.tool_config_prefix()}.elab_opts", []) elab_opts.append("-logfile xrun_elab.log") elab_opts.append("-glsperf") elab_opts.append("-genafile access.txt") @@ -427,12 +519,18 @@ def elaborate_xrun(self) -> bool: if xrun_opts["mce"]: elab_opts.append(self.generate_mc_cmd()) elab_opts = ('ELABORATION', elab_opts) + + arg_file_path = self.generate_arg_file("xrun_elab.arg", "HAMMER-GEN XRUN ELAB ARG FILE", [elab_opts]) args =[self.xcelium_bin] args.append(f"-elaborate -f {arg_file_path}") - + self.update_submit_options() + ### If AMS enabled, submit options but do not run elaborate sub-step. + if self.get_setting(f"{self.tool_config_prefix()}.ams"): + return True + self.run_executable(args, cwd=self.run_dir) return True @@ -458,4 +556,337 @@ def sim_xrun(self) -> bool: self.run_executable(args, cwd=self.run_dir) return True + def retrieve_file_list(self, path, exts=[], relative=True) -> list: + file_list = [] + extslower = [extension.lower() for extension in exts] + exts_proc = [f".{ext}" if ("." not in ext) else ext for ext in extslower] + + for (root, directories, filenames) in os.walk(path): + for filename in filenames: + file_ext = (os.path.splitext(filename)[1]).lower() + rel_root = os.path.relpath(root) + if (relative): + filepath = os.path.join(rel_root, filename) + else: + filepath = f"{os.path.join(root, filename)}" + + if (not exts): + file_list.append(filepath) + elif (file_ext in exts_proc): + file_list.append(filepath) + + return file_list + + + + + def vlog_preparer(self, collect=False, sourcelist=[], sourcedir="", blacklist=[]) -> str: + """ + Returns a formatted string of all verilog/VHDL files in the source + """ + vlog = "" + if (collect): + sourcepath = os.path.join(os.getcwd(), sourcedir) + vlog_list = self.retrieve_file_list(sourcepath, [".v", ".vhdl"]) + else: + vlog_list = sift_exts(sourcelist, [".v"]) + + if (blacklist and vlog_list): + for pathname in blacklist: + if pathname in vlog_list: + vlog_list.remove(pathname) + + if vlog_list: + vlog = " \\\n".join(vlog_list) + " \\\n" + + return f"{vlog}" + + def vams_preparer(self, collect=False, sourcelist=[], sourcedir="", blacklist=[]) -> str: + """ + Returns a formatted string of all V-AMS files in the source + """ + vams = "" + if (collect): + sourcepath = os.path.join(os.getcwd(), sourcedir) + vams_list = self.retrieve_file_list(sourcepath, [".vams"]) + else: + vams_list = sift_exts(sourcelist, [".vams"]) + + if (blacklist and vams_list): + for pathname in blacklist: + if pathname in vams_list: + vams_list.remove(pathname) + + if vams_list: + vams = " \\\n".join(vams_list) + " \\\n" + + return f"{vams}" + + def analog_preparer(self, collect=False, sourcelist=[], sourcedir="", blacklist=[]) -> str: + """ + Returns a formatted string of all analog (.scs) files in the source + """ + control = "" + if (collect): + sourcepath = os.path.join(os.getcwd(), sourcedir) + control_list = self.retrieve_file_list(sourcepath, [".scs"]) + else: + control_list = sift_exts(sourcelist, [".scs"]) + + if (blacklist and control_list): + for pathname in blacklist: + if pathname in control_list: + control_list.remove(pathname) + + if control_list: + control = " \\\n".join(control_list) + " \\\n" + + return f"{control}" + + def discipline_collector(self, discipline_filename) -> str: + """ + Returns a formatted string of all disciplines from the disciplines.txt file + """ + ### Read in disciplines file, if it exists. + + + dpath = os.path.join(os.getcwd(), discipline_filename) + + if not os.path.isfile(dpath): + self.logger.error(f"No discipline file found at {dpath}.") + + if dpath: + df = open(dpath, "r") + discipline_opts = df.read() + disciplines_formatted = re.sub("\n", " \\\n", discipline_opts) + " \\" + df.close() + return disciplines_formatted + else: + return "" + + def option_preparer(self, opts, addt_opts, pseudo_step=True) -> str: + """ + Returns a formatted string of all provided AMS options and their arguments + """ + bool_list = ["ams", "disciplines", "gen_amscf"] + opts_proc = opts.copy() + header = "" + + if not opts: + return "" + + if opts ["ams"] is True: + opts_proc ["ams"] = "-ams_flex" + " \\" + else: + opts_proc ["ams"] = "" + + if opts ["gen_amscf"] is True: + self.generate_amscf(self.get_setting("sim.xcelium.amscf_template"), self.get_setting("sim.xcelium.amscf")) # Expect names, not filepaths + opts_proc ["gen_amscf"] = "" + else: + opts_proc ["gen_amscf"] = "" + + if opts ["disciplines"]: + header += self.discipline_collector(opts["disciplines"]) + "\n" + + if opts ["amsconnrules"]: + opts_proc ["amsconnrules"] = opts["amsconnrules"] + + if (pseudo_step): + digital_opts = self.option_extractor(["xrun_compile.arg", "xrun_elab.arg", "xrun_sim.arg"]) + digital_opts.update(opts_proc) # Should any keys match, AMS arguments take precedence + opts_proc = digital_opts + + opts_proc = {opt:setting for (opt, setting) in opts_proc.items() if opt not in bool_list and setting is not None} + + opts_len = len(opts_proc) - 1 + for (n, (opt, setting)) in enumerate(opts_proc.items()): + if (n == opts_len): + if (setting == ""): + opts_proc [opt] = f"-{opt}" + else: + opts_proc [opt] = f"-{opt} {setting}" + else: + if (setting == ""): + opts_proc [opt] = f"-{opt} \\" + else: + opts_proc [opt] = f"-{opt} {setting} \\" + + + opts_rev = {k: v for k, v in opts_proc.items() if v} + + opts_proc_str = "\n".join(opts_rev.values()) + + # Attach user-defined commands, if any are included + if (addt_opts): + opts_proc_str += " \\\n" + + footer = " \\\n".join(addt_opts) + + return header + f"{opts_proc_str}" + footer + + def generate_amscf(self, template_filename, amscontrol_filename) -> bool: + """ + Creates an AMS control file based on templated format with available analog models & schematics + """ + + # Get analog models, schematics from directories specified in extralibs + extralib = self.get_setting("vlsi.technologies.extra_libraries") + extralib_dict = extralib[0] + anamodels_dir = extralib_dict["anamodels"] + schematics_dir = extralib_dict["schematics"] + + + # Open AMS control file template for read. + template_path = os.path.join(os.getcwd(), template_filename) + t = open(template_path, "r") + + # Create AMS control file (or overwrite if one already exists) for read/write. + amscontrol_path = os.path.join(os.getcwd(), amscontrol_filename) + f = open(amscontrol_path, "w+") + + # Get normalized, absolute paths for analog model files + model_path = os.path.join(os.getcwd(), anamodels_dir) + models = [os.path.normpath(modelfile.path) for modelfile in os.scandir(model_path)] + + # Get normalized, absolute paths for analog schematic files + schematic_path = os.path.join(os.getcwd(), schematics_dir) + schematics = [os.path.normpath(schematic.path) for schematic in os.scandir(schematic_path)] + + # Warnings for missing files. + if (len(schematics) > 0 and len(models) == 0): + logging.warning(f"No models found in {model_path} to support analog schematics.") + else: + if (len(models) == 0): + logging.warning(f"No models found in {model_path}.") + if (len(schematic_path) == 0): + logging.warning(f"No analog schematics found {schematics}.") + + # Get string representation of AMS control file template. + template = t.read() + + # Format model_paths list as a single string with include statements. + formatted_models = "" + for modelpath in models: + formatted_models += f"include {modelpath!r}\n" + # Format schematic_paths list as a single string with include statements. + formatted_schematics = "" + for schematicpath in schematics: + formatted_schematics += f"include {schematicpath!r}\n" + + # Replace empty model deck with formatted string of model paths. + model_template = re.sub("// model deck\n", "// model deck\n" + formatted_models, template) + + # Replace empty schematic deck with formatted string of schematic paths. + schematic_template = re.sub("// schematic deck\n", "// schematic deck\n" + formatted_schematics, model_template) + + # Write filled template to AMS control file. + f.write(schematic_template) + + # Close files properly. + t.close() + f.close() + return True + + def option_extractor(self, argfile_names=[]): + if not argfile_names: + return {} + + opts = {} + + # Extract the options from each argfile listed, ignoring duplicate opts and file inclusions + for filename in argfile_names: + path = os.path.join(self.run_dir, filename) + file_opts = {} + f = open(path, "r") + + for line in f: + if (line[0] == "-"): + split_line = line.split(sep=None, maxsplit=2) + if (len(split_line) > 1): + opt_key, opt_arg = split_line[0].strip("- "), split_line[1].lstrip("\n") + else: + opt_key, opt_arg = split_line[0].strip("- "), "" + + file_opts[opt_key] = opt_arg + + opts.update(file_opts) + f.close() + + return opts + + def scriptwriter(self, options, additional_options, collect=False, sourcedir="", blacklist=[], sourcelist=[]): + """ + Writes all prepared files and arguments to the run_mxh shell script + """ + runpath = os.path.join(self.run_dir, "run_mxh") + + f = open(runpath, "w+") + + # Write Shebang + xrun clean + f.write("#!/bin/csh -f\n#\nxrun -clean \\\n") + + # Write Digital Files + f.write(self.vlog_preparer(collect, sourcelist, sourcedir, blacklist)) + f.write(self.vams_preparer(collect, sourcelist, sourcedir, blacklist)) + + # Write Analog Files + f.write(self.analog_preparer(collect, sourcelist, sourcedir, blacklist)) + + # Write Options + f.write(self.option_preparer(options, additional_options)) + + f.close() + return + + def name_finder(self, name, sourcelist): + # Helper function, should probably be moved later + sourcelist_proc = [element.lower() for element in sourcelist if (type(element) == str)] + for element in sourcelist_proc: + if (name in element): + return element + + return "" + + def run_mxh_pseudo_three_step(self) -> bool: + if not os.path.isfile(self.xcelium_bin): + self.logger.error(f"Xcelium (xrun) binary not found at {self.xcelium_bin}.") + return False + + if not self.check_input_files(self.xcelium_ext): + return False + + digital_files = self.get_setting("sim.inputs.input_files") + acf = self.get_setting("sim.xcelium.acf") + amscf = self.get_setting("sim.xcelium.amscf") + + # Extralibs Autorecognition + extralib = self.get_setting("vlsi.technologies.extra_libraries") + extralib_dict = extralib[0] + + #ams_opts = self.get_setting("sim.xcelium.ams_opts") + + #source = digital_files + [acf, amscf, connectlibs] + + ams_opts_dict = { + "ams": self.get_setting("sim.xcelium.ams"), + "disciplines": extralib_dict["disciplines"], + #"disciplines": self.name_finder("disciplines", extralibs), + "amsconnrules": extralib_dict["amsconnrules"], + "gen_amscf": extralib_dict["gen_amscf"] + } + + ams_addt_opts = extralib_dict["ams_addt_opts"] + + filepath_blacklist = extralib_dict["filepath_blacklist"] + [extralib_dict["amscf_template"]] + + self.scriptwriter(options=ams_opts_dict, additional_options=ams_addt_opts, collect=True, sourcedir="src/", blacklist=filepath_blacklist) + + # Extract digital-only options from compile, elab, and sim argfiles + combined_opts = self.option_extractor(["xrun_compile.arg", "xrun_elab.arg", "xrun_sim.arg"]) + + + self.update_submit_options() + self.run_executable([self.xcelium_bin, './run_mxh'], cwd=self.run_dir) + return True tool = xcelium diff --git a/hammer/sim/xcelium/defaults.yml b/hammer/sim/xcelium/defaults.yml index 980bbe29c..8943244e4 100644 --- a/hammer/sim/xcelium/defaults.yml +++ b/hammer/sim/xcelium/defaults.yml @@ -1,11 +1,18 @@ sim.xcelium: - # Tool version (e.g., "XCELIUM2103") + # Tool version (e.g., "XCELIUM2103") version: "XCELIUM2103" + # Spectre version (e.g., "SPECTRE211") + spectreversion: "SPECTRE211" + # Path to xcelium binary. xcelium_bin: "${cadence.cadence_home}/XCELIUM/${sim.xcelium.version}/tools/xcelium/bin/64bit/xrun" xcelium_bin_meta: lazysubst + # Path to spectre binary. + spectre_bin: "${cadence.cadence_home}/SPECTRE/${sim.xcelium.spectreversion}/tools/spectre/bin/64bit/spectre" + spectre_bin_meta: lazysubst + # Path to xmsimrc_def file. xmsimrc_def: "${cadence.cadence_home}/XCELIUM/${sim.xcelium.version}/tools/xcelium/files/xmsimrc" xmsimrc_def_meta: lazysubst @@ -33,4 +40,16 @@ sim.xcelium: compile_opts: null # Opts to access elaboration step in xcelium. elab_opts: null + # If true, run as mixed-signal (AMS) simulation. + # By default it should be FALSE as requirements and inputs are different to digital-only simulation. + ams: False + # Specifies location of analog control file. + acf: null + # Specifies location of ams control file. + amscf: null + # List of strings specifying ams options. + ams_opts: null + compile_opts: null + elab_opts: null + diff --git a/hammer/sim/xcelium/defaults_types.yml b/hammer/sim/xcelium/defaults_types.yml index e793c0b11..3b65c14f0 100644 --- a/hammer/sim/xcelium/defaults_types.yml +++ b/hammer/sim/xcelium/defaults_types.yml @@ -1,6 +1,10 @@ sim.xcelium: # Tool version (e.g., "XCELIUM2103") version: str + # Spectre version (e.g. "SPECTRE211") + spectreversion: Optional[str] + # Path to spectre binary. + spectre_bin: Optional[str] # Path to xcelium binary. xcelium_bin: str # Path to xmsimrc_def file. @@ -19,6 +23,14 @@ sim.xcelium: global_access: bool # If true, enable multicore support. mce: bool + # If true, enable AMS sims. + ams: bool + # Path to analog control file. + acf: Optional[str] + # Path to ams control file. + amscf: Optional[str] + # If true, create a partially populated template of the AMS control file. compile_opts: Optional[list[str]] - elab_opts: Optional[list[str]] \ No newline at end of file + elab_opts: Optional[list[str]] + ams_opts: Optional[list[str]] diff --git a/hammer/utils/__init__.py b/hammer/utils/__init__.py index aea332e88..b3068579b 100644 --- a/hammer/utils/__init__.py +++ b/hammer/utils/__init__.py @@ -368,6 +368,50 @@ def compare_types(a: Any, b: Any) -> bool: return None +def retrieve_files(path: str, exts=[], output_type="str", relative=True): + """ + Returns a list or line-seperated string of all filepaths in a directory and any of its subdirectories + with the specified extensions, returning all filepaths within that directory if no extension is specified. + + :param path: Specifies the filepath to the directory + :param exts: List of strings specifying whitelisted extensions + :param output_type: Specifies whether output will be a list or string, by "list" or "str" + :param relative: If true, return filepaths relative to the directory. Else, return absolute filepaths. + """ + file_list = [] + extslower = [extension.lower() for extension in exts] + exts_proc = [f".{ext}" if ("." not in ext) else ext for ext in extslower] + + for (root, directories, filenames) in os.walk(path): + for filename in filenames: + file_ext = (os.path.splitext(filename)[1]).lower() + rel_root = os.path.relpath(root) + if (relative): + filepath = os.path.join(rel_root, filename) + else: + filepath = f"{os.path.join(root, filename)}" + + if (not exts): + file_list.append(filepath) + elif (file_ext in exts_proc): + file_list.append(filepath) + + if (output_type is "str"): + return "".join(file_list) + "\n" + elif (output_type is "list"): + return file_list + else: + return "".join(file_list) + "\n" + +def sift_exts(filelist, exts=[]): + """ + Returns a list of filepaths whose filenames contain any of the extensions in the specified extension list + """ + exts_lower = list(map(str.lower, exts)) + filelist_lower = list(map(str.lower, filelist)) + sifted = list(filter(lambda x: os.path.splitext(x)[1] in exts_lower, filelist_lower)) + + return sifted # Contributors: Be sure to add to this list if you need to call get_filetype @unique