18 from pathlib
import Path
26 from typing
import Dict, List
28 _thisdir = Path(__file__).parent
29 CosimCollateralDir = _thisdir.parent /
"cosim"
33 """Check if a TCP port is open locally."""
34 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
35 result = sock.connect_ex((
'127.0.0.1', port))
37 return True if result == 0
else False
44 self.user: List[Path] = []
46 self.dpi_so: List[str] = [
"EsiCosimDpiServer"]
48 self.dpi_sv: List[Path] = [
49 CosimCollateralDir /
"Cosim_DpiPkg.sv",
50 CosimCollateralDir /
"Cosim_Endpoint.sv",
51 CosimCollateralDir /
"Cosim_Manifest.sv",
57 """Add all the RTL files in a directory to the source list."""
58 for file
in sorted(dir.iterdir()):
59 if file.is_file()
and (file.suffix ==
".sv" or file.suffix ==
".v"):
65 """Return a list of all the DPI shared object files."""
67 def find_so(name: str) -> Path:
68 for path
in Simulator.get_env().
get(
"LD_LIBRARY_PATH",
"").split(
":"):
70 so = Path(path) / f
"{name}.dll"
72 so = Path(path) / f
"lib{name}.so"
75 raise FileNotFoundError(f
"Could not find {name}.so in LD_LIBRARY_PATH")
77 return [find_so(name)
for name
in self.dpi_so]
81 """Return a list of all the RTL source files."""
82 return self.dpi_sv + self.user
92 def __init__(self, sources: SourceFiles, run_dir: Path, debug: bool):
99 """Get the environment variables to locate shared objects."""
101 env = os.environ.copy()
102 env[
"LIBRARY_PATH"] = env.get(
"LIBRARY_PATH",
"") +
":" + str(
103 _thisdir.parent /
"lib")
104 env[
"LD_LIBRARY_PATH"] = env.get(
"LD_LIBRARY_PATH",
"") +
":" + str(
105 _thisdir.parent /
"lib")
109 """Compile the sources. Returns the exit code of the simulation compiler."""
110 assert False,
"Must be implemented by subclass"
114 self.
run_dirrun_dir.mkdir(parents=
True, exist_ok=
True)
115 with (self.
run_dirrun_dir /
"compile_stdout.log").open(
"w")
as stdout, (
116 self.
run_dirrun_dir /
"compile_stderr.log").open(
"w")
as stderr:
118 cp = subprocess.run(cmd,
119 env=Simulator.get_env(),
122 stdout.write(cp.stdout)
123 stderr.write(cp.stderr)
124 if cp.returncode != 0:
125 print(
"====== Compilation failure:")
134 """Return the command to run the simulation."""
135 assert False,
"Must be implemented by subclass"
137 def run(self, inner_command: str, gui: bool =
False) -> int:
138 """Start the simulation then run the command specified. Kill the simulation
139 when the command exits."""
146 self.
run_dirrun_dir.mkdir(parents=
True, exist_ok=
True)
147 simStdout = open(self.
run_dirrun_dir /
"sim_stdout.log",
"w")
148 simStderr = open(self.
run_dirrun_dir /
"sim_stderr.log",
"w")
152 portFileName = self.
run_dirrun_dir /
"cosim.cfg"
153 if os.path.exists(portFileName):
154 os.remove(portFileName)
157 simEnv = Simulator.get_env()
159 simEnv[
"COSIM_DEBUG_FILE"] =
"cosim_debug.log"
160 simProc = subprocess.Popen(self.
run_commandrun_command(gui),
165 preexec_fn=os.setsid)
171 while (
not os.path.exists(portFileName))
and \
172 simProc.poll()
is None:
175 if checkCount > 200
and not gui:
176 raise Exception(f
"Cosim never wrote cfg file: {portFileName}")
179 portFile = open(portFileName,
"r")
180 for line
in portFile.readlines():
181 m = re.match(
"port: (\\d+)", line)
183 port = int(m.group(1))
191 raise Exception(f
"Cosim RPC port ({port}) never opened")
192 if simProc.poll()
is not None:
193 raise Exception(
"Simulation exited early")
197 testEnv = os.environ.copy()
198 testEnv[
"ESI_COSIM_PORT"] = str(port)
199 testEnv[
"ESI_COSIM_HOST"] =
"localhost"
200 return subprocess.run(inner_command, cwd=os.getcwd(),
201 env=testEnv).returncode
205 os.killpg(os.getpgid(simProc.pid), signal.SIGINT)
208 simProc.wait(timeout=1.0)
209 except subprocess.TimeoutExpired:
215 """Run and compile funcs for Verilator."""
217 DefaultDriver = CosimCollateralDir /
"driver.cpp"
219 def __init__(self, sources: SourceFiles, run_dir: Path, debug: bool):
220 super().
__init__(sources, run_dir, debug)
223 if "VERILATOR_PATH" in os.environ:
224 self.
verilatorverilator = os.environ[
"VERILATOR_PATH"]
237 str(Verilator.DefaultDriver),
240 "-DTOP_MODULE=" + self.
sourcessources.top,
243 cmd += [
"--trace",
"--trace-params",
"--trace-structs"]
244 cflags.append(
"-DTRACE")
246 cmd += [
"-CFLAGS",
" ".join(cflags)]
247 if len(self.
sourcessources.dpi_so) > 0:
248 cmd += [
"-LDFLAGS",
" ".join([
"-l" + so
for so
in self.
sourcessources.dpi_so])]
249 cmd += [str(p)
for p
in self.
sourcessources.rtl_sources]
254 raise RuntimeError(
"Verilator does not support GUI mode.")
255 exe = Path.cwd() /
"obj_dir" / (
"V" + self.
sourcessources.top)
260 """Run and compile funcs for Questasim."""
262 DefaultDriver = CosimCollateralDir /
"driver.sv"
269 "onerror { quit -f -code 1 }",
271 sources = self.
sourcessources.rtl_sources
272 sources.append(Questa.DefaultDriver)
274 cmds.append(f
"vlog -incr +acc -sv +define+TOP_MODULE={self.sources.top}"
275 f
" +define+SIMULATION {str(src)}")
276 cmds.append(f
"vopt -incr driver -o driver_opt +acc")
280 with open(
"compile.do",
"w")
as f:
286 [
"vsim",
"-batch",
"-do",
"compile.do"],
306 for lib
in self.
sourcessources.dpi_so_paths():
307 svLib = os.path.splitext(lib)[0]
308 cmd.append(
"-sv_lib")
312 def run(self, inner_command: str, gui: bool =
False) -> int:
313 """Override the Simulator.run() to add a soft link in the run directory (to
314 the work directory) before running vsim the usual way."""
317 workDir = self.
run_dirrun_dir /
"work"
318 if not workDir.exists():
319 os.symlink(Path(os.getcwd()) /
"work", workDir)
322 return super().
run(inner_command, gui)
326 argparser = argparse.ArgumentParser(
327 description=
"Wrap a 'inner_cmd' in an ESI cosimulation environment.",
328 formatter_class=argparse.RawDescriptionHelpFormatter,
329 epilog=textwrap.dedent(
"""
331 - For Verilator, libEsiCosimDpiServer.so must be in the dynamic
332 library runtime search path (LD_LIBRARY_PATH) and link time path
333 (LIBRARY_PATH). If it is installed to a standard location (e.g.
334 /usr/lib), this should be handled automatically.
335 - This script needs to sit in the same directory as the ESI support
336 SystemVerilog (e.g. Cosim_DpiPkg.sv, Cosim_MMIO.sv, etc.). It can,
337 however, be soft linked to a different location.
338 - The simulator executable(s) must be in your PATH.
341 argparser.add_argument(
345 help=
"Name of the RTL simulator to use or path to an executable.")
346 argparser.add_argument(
"--rundir",
348 help=
"Directory in which simulation should be run.")
349 argparser.add_argument(
351 default=
"ESI_Cosim_Top",
352 help=
"Name of the 'top' module to use in the simulation.")
353 argparser.add_argument(
"--no-compile",
355 help=
"Do not run the compile.")
356 argparser.add_argument(
"--debug",
358 help=
"Enable debug output.")
359 argparser.add_argument(
"--gui",
361 help=
"Run the simulator in GUI mode (if supported).")
362 argparser.add_argument(
"--source",
363 help=
"Directories containing the source files.",
366 argparser.add_argument(
"inner_cmd",
367 nargs=argparse.REMAINDER,
368 help=
"Command to run in the simulation environment.")
371 argparser.print_help()
373 args = argparser.parse_args(args[1:])
376 sources.add_dir(Path(args.source))
378 if args.sim ==
"verilator":
379 sim =
Verilator(sources, Path(args.rundir), args.debug)
380 elif args.sim ==
"questa":
381 sim =
Questa(sources, Path(args.rundir), args.debug)
383 print(
"Unknown simulator: " + args.sim)
384 print(
"Supported simulators: ")
385 print(
" - verilator")
389 if not args.no_compile:
393 return sim.run(args.inner_cmd[1:], gui=args.gui)
396 if __name__ ==
'__main__':
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
List[List[str]] compile_commands(self)
int run(self, str inner_command, bool gui=False)
List[str] internal_compile_commands(self)
List[str] run_command(self, bool gui)
List[str] run_command(self, bool gui)
int run(self, str inner_command, bool gui=False)
def __init__(self, SourceFiles sources, Path run_dir, bool debug)
List[List[str]] compile_commands(self)
List[Path] rtl_sources(self)
List[Path] dpi_so_paths(self)
None __init__(self, str top)
def add_dir(self, Path dir)
def __init__(self, SourceFiles sources, Path run_dir, bool debug)
def run_command(self, bool gui)
List[List[str]] compile_commands(self)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.