From 50e3f9fafffce414f9882c3452333ec4e898a6d5 Mon Sep 17 00:00:00 2001 From: John Demme Date: Tue, 17 Dec 2024 18:14:27 +0000 Subject: [PATCH] [circt-cocotb] Refactor slightly and add Questa support --- .../circt-cocotb-driver.py.in | 92 ++++++++++++++----- 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/tools/circt-cocotb-driver/circt-cocotb-driver.py.in b/tools/circt-cocotb-driver/circt-cocotb-driver.py.in index 844f8fbdd276..0a579f9a1f7d 100755 --- a/tools/circt-cocotb-driver/circt-cocotb-driver.py.in +++ b/tools/circt-cocotb-driver/circt-cocotb-driver.py.in @@ -5,7 +5,7 @@ import os import subprocess import sys import re -from cocotb_test.simulator import run +import cocotb_test.simulator as csim def parseArgs(args): @@ -22,7 +22,7 @@ def parseArgs(args): help="Name of the top level verilog module.") argparser.add_argument("--simulator", - choices=['icarus'], + choices=['icarus', 'questa'], default="icarus", help="Name of the simulator to use.") @@ -31,10 +31,16 @@ def parseArgs(args): required=True, help="Name of the python module.") - argparser.add_argument("--pythonFolders", - type=str, - default="", - help="The folders where cocotb should include from, separated by commas.") + argparser.add_argument( + "--pythonFolders", + type=str, + default="", + help="The folders where cocotb should include from, separated by commas.") + + argparser.add_argument( + "--gui", + action="store_true", + help="Run the simulator with a GUI. Only supported by Questa.") argparser.add_argument( "sources", @@ -44,10 +50,12 @@ def parseArgs(args): return argparser.parse_args(args[1:]) -class _IVerilogHandler: +class Icarus(csim.Icarus): """ Class for handling icarus-verilog specific commands and patching.""" - def __init__(self): + def __init__(self, **kwargs): + super().__init__(**kwargs) + # Ensure that iverilog is available in path and it is at least iverilog v11 try: out = subprocess.check_output(["iverilog", "-V"]) @@ -64,7 +72,42 @@ class _IVerilogHandler: if float(ver) < 11: raise Exception(f"Icarus Verilog version must be >= 11, got {ver}") - def extra_compile_args(self, objDir): + def run(self, objDir, gui=False): + if gui: + raise Exception("GUI is not supported by Icarus Verilog") + + # If no timescale is defined in the source code, icarus assumes a + # timescale of '1'. This prevents cocotb from creating small timescale clocks. + # Since a timescale is not emitted by default from export-verilog, make our + # lives easier and create a minimum timescale through the command-line. + cmd_file = os.path.join(objDir, "cmds.f") + with open(cmd_file, "w+") as f: + f.write("+timescale+1ns/1ps") + + self.compile_args.append(f"-f{cmd_file}") + return super().run() + + +class Questa(csim.Questa): + """ Class for handling icarus-verilog specific commands and patching.""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + # Ensure that iverilog is available in path and it is at least iverilog v11 + try: + out = subprocess.check_output(["vlog", "-version"]) + except subprocess.CalledProcessError: + raise Exception("vlog not found in path") + + # find the 'Icarus Verilog version #' string and extract the version number + # using a regex + ver_re = r"QuestaSim-64 vlog (\d+\.\d+)" + ver_match = re.search(ver_re, out.decode("utf-8")) + if ver_match is None: + raise Exception("Could not find Questa version") + + def run(self, objDir, gui=False): # If no timescale is defined in the source code, icarus assumes a # timescale of '1'. This prevents cocotb from creating small timescale clocks. # Since a timescale is not emitted by default from export-verilog, make our @@ -73,7 +116,9 @@ class _IVerilogHandler: with open(cmd_file, "w+") as f: f.write("+timescale+1ns/1ps") - return [f"-f{cmd_file}"] + self.gui = gui + self.extra_compile_args = ["-f", f"{cmd_file}"] + return super().run() def main(): @@ -96,28 +141,25 @@ def main(): except subprocess.CalledProcessError: raise Exception( "'make' is not available, and is required to run cocotb tests.") - + kwargs = { + "module": args.pythonModule, + "toplevel": args.topLevel, + "toplevel_lang": "verilog", + "verilog_sources": sources, + "python_search": [f.strip() for f in args.pythonFolders.split(",")], + "work_dir": objDir, + } try: if args.simulator == "icarus": - simhandler = _IVerilogHandler() + sim = Icarus(**kwargs) + elif args.simulator == "questa": + sim = Questa(**kwargs) else: raise Exception(f"Unknown simulator: {args.simulator}") except Exception as e: raise Exception(f"Failed to initialize simulator handler: {e}") - # Simulator-specific extra compile args. - compileArgs = [] - if simhandler: - compileArgs += simhandler.extra_compile_args(objDir) - - run(simulator=args.simulator, - module=args.pythonModule, - toplevel=args.topLevel, - toplevel_lang="verilog", - verilog_sources=sources, - python_search=[f.strip() for f in args.pythonFolders.split(",")], - work_dir=objDir, - compile_args=compileArgs) + sim.run(objDir, gui=args.gui) if __name__ == "__main__":