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"
161 simEnv[
"DEBUG_PERIOD"] =
"1"
162 simProc = subprocess.Popen(self.
run_commandrun_command(gui),
167 preexec_fn=os.setsid)
173 while (
not os.path.exists(portFileName))
and \
174 simProc.poll()
is None:
177 if checkCount > 200
and not gui:
178 raise Exception(f
"Cosim never wrote cfg file: {portFileName}")
181 portFile = open(portFileName,
"r")
182 for line
in portFile.readlines():
183 m = re.match(
"port: (\\d+)", line)
185 port = int(m.group(1))
193 raise Exception(f
"Cosim RPC port ({port}) never opened")
194 if simProc.poll()
is not None:
195 raise Exception(
"Simulation exited early")
199 testEnv = os.environ.copy()
200 testEnv[
"ESI_COSIM_PORT"] = str(port)
201 testEnv[
"ESI_COSIM_HOST"] =
"localhost"
202 return subprocess.run(inner_command, cwd=os.getcwd(),
203 env=testEnv).returncode
207 os.killpg(os.getpgid(simProc.pid), signal.SIGINT)
210 simProc.wait(timeout=1.0)
211 except subprocess.TimeoutExpired:
217 """Run and compile funcs for Verilator."""
219 DefaultDriver = CosimCollateralDir /
"driver.cpp"
221 def __init__(self, sources: SourceFiles, run_dir: Path, debug: bool):
222 super().
__init__(sources, run_dir, debug)
225 if "VERILATOR_PATH" in os.environ:
226 self.
verilatorverilator = os.environ[
"VERILATOR_PATH"]
239 str(Verilator.DefaultDriver),
242 "-DTOP_MODULE=" + self.
sourcessources.top,
245 cmd += [
"--trace",
"--trace-params",
"--trace-structs"]
246 cflags.append(
"-DTRACE")
248 cmd += [
"-CFLAGS",
" ".join(cflags)]
249 if len(self.
sourcessources.dpi_so) > 0:
250 cmd += [
"-LDFLAGS",
" ".join([
"-l" + so
for so
in self.
sourcessources.dpi_so])]
251 cmd += [str(p)
for p
in self.
sourcessources.rtl_sources]
256 raise RuntimeError(
"Verilator does not support GUI mode.")
257 exe = Path.cwd() /
"obj_dir" / (
"V" + self.
sourcessources.top)
262 """Run and compile funcs for Questasim."""
264 DefaultDriver = CosimCollateralDir /
"driver.sv"
271 "onerror { quit -f -code 1 }",
273 sources = self.
sourcessources.rtl_sources
274 sources.append(Questa.DefaultDriver)
276 cmds.append(f
"vlog -incr +acc -sv +define+TOP_MODULE={self.sources.top}"
277 f
" +define+SIMULATION {str(src)}")
278 cmds.append(f
"vopt -incr driver -o driver_opt +acc")
282 with open(
"compile.do",
"w")
as f:
288 [
"vsim",
"-batch",
"-do",
"compile.do"],
308 for lib
in self.
sourcessources.dpi_so_paths():
309 svLib = os.path.splitext(lib)[0]
310 cmd.append(
"-sv_lib")
314 def run(self, inner_command: str, gui: bool =
False) -> int:
315 """Override the Simulator.run() to add a soft link in the run directory (to
316 the work directory) before running vsim the usual way."""
319 workDir = self.
run_dirrun_dir /
"work"
320 if not workDir.exists():
321 os.symlink(Path(os.getcwd()) /
"work", workDir)
324 return super().
run(inner_command, gui)
328 argparser = argparse.ArgumentParser(
329 description=
"Wrap a 'inner_cmd' in an ESI cosimulation environment.",
330 formatter_class=argparse.RawDescriptionHelpFormatter,
331 epilog=textwrap.dedent(
"""
333 - For Verilator, libEsiCosimDpiServer.so must be in the dynamic
334 library runtime search path (LD_LIBRARY_PATH) and link time path
335 (LIBRARY_PATH). If it is installed to a standard location (e.g.
336 /usr/lib), this should be handled automatically.
337 - This script needs to sit in the same directory as the ESI support
338 SystemVerilog (e.g. Cosim_DpiPkg.sv, Cosim_MMIO.sv, etc.). It can,
339 however, be soft linked to a different location.
340 - The simulator executable(s) must be in your PATH.
343 argparser.add_argument(
347 help=
"Name of the RTL simulator to use or path to an executable.")
348 argparser.add_argument(
"--rundir",
350 help=
"Directory in which simulation should be run.")
351 argparser.add_argument(
353 default=
"ESI_Cosim_Top",
354 help=
"Name of the 'top' module to use in the simulation.")
355 argparser.add_argument(
"--no-compile",
357 help=
"Do not run the compile.")
358 argparser.add_argument(
"--debug",
360 help=
"Enable debug output.")
361 argparser.add_argument(
"--gui",
363 help=
"Run the simulator in GUI mode (if supported).")
364 argparser.add_argument(
"--source",
365 help=
"Directories containing the source files.",
368 argparser.add_argument(
"inner_cmd",
369 nargs=argparse.REMAINDER,
370 help=
"Command to run in the simulation environment.")
373 argparser.print_help()
375 args = argparser.parse_args(args[1:])
378 sources.add_dir(Path(args.source))
380 if args.sim ==
"verilator":
381 sim =
Verilator(sources, Path(args.rundir), args.debug)
382 elif args.sim ==
"questa":
383 sim =
Questa(sources, Path(args.rundir), args.debug)
385 print(
"Unknown simulator: " + args.sim)
386 print(
"Supported simulators: ")
387 print(
" - verilator")
391 if not args.no_compile:
395 return sim.run(args.inner_cmd[1:], gui=args.gui)
398 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.