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",
52 CosimCollateralDir /
"Cosim_MMIO.sv",
58 """Add all the RTL files in a directory to the source list."""
59 for file
in sorted(dir.iterdir()):
60 if file.is_file()
and (file.suffix ==
".sv" or file.suffix ==
".v"):
64 """Return a list of all the DPI shared object files."""
66 def find_so(name: str) -> Path:
67 for path
in Simulator.get_env().
get(
"LD_LIBRARY_PATH",
"").split(
":"):
69 so = Path(path) / f
"{name}.dll"
71 so = Path(path) / f
"lib{name}.so"
74 raise FileNotFoundError(f
"Could not find {name}.so in LD_LIBRARY_PATH")
76 return [find_so(name)
for name
in self.dpi_so]
80 """Return a list of all the RTL source files."""
81 return self.dpi_sv + self.user
91 def __init__(self, sources: SourceFiles, run_dir: Path, debug: bool):
98 """Get the environment variables to locate shared objects."""
100 env = os.environ.copy()
101 env[
"LIBRARY_PATH"] = env.get(
"LIBRARY_PATH",
"") +
":" + str(
102 _thisdir.parent /
"lib")
103 env[
"LD_LIBRARY_PATH"] = env.get(
"LD_LIBRARY_PATH",
"") +
":" + str(
104 _thisdir.parent /
"lib")
108 """Compile the sources. Returns the exit code of the simulation compiler."""
109 assert False,
"Must be implemented by subclass"
113 env=Simulator.get_env(),
116 self.
run_dirrun_dir.mkdir(parents=
True, exist_ok=
True)
117 open(self.
run_dirrun_dir /
"compile_stdout.log",
"w").write(cp.stdout)
118 open(self.
run_dirrun_dir /
"compile_stderr.log",
"w").write(cp.stderr)
119 if cp.returncode != 0:
120 print(
"====== Compilation failure:")
128 """Return the command to run the simulation."""
129 assert False,
"Must be implemented by subclass"
131 def run(self, inner_command: str) -> int:
132 """Start the simulation then run the command specified. Kill the simulation
133 when the command exits."""
140 self.
run_dirrun_dir.mkdir(parents=
True, exist_ok=
True)
141 simStdout = open(self.
run_dirrun_dir /
"sim_stdout.log",
"w")
142 simStderr = open(self.
run_dirrun_dir /
"sim_stderr.log",
"w")
146 portFileName = self.
run_dirrun_dir /
"cosim.cfg"
147 if os.path.exists(portFileName):
148 os.remove(portFileName)
151 simEnv = Simulator.get_env()
153 simEnv[
"COSIM_DEBUG_FILE"] =
"cosim_debug.log"
154 simProc = subprocess.Popen(self.
run_commandrun_command(),
159 preexec_fn=os.setsid)
165 while (
not os.path.exists(portFileName))
and \
166 simProc.poll()
is None:
170 raise Exception(f
"Cosim never wrote cfg file: {portFileName}")
173 portFile = open(portFileName,
"r")
174 for line
in portFile.readlines():
175 m = re.match(
"port: (\\d+)", line)
177 port = int(m.group(1))
185 raise Exception(f
"Cosim RPC port ({port}) never opened")
186 if simProc.poll()
is not None:
187 raise Exception(
"Simulation exited early")
191 testEnv = Simulator.get_env()
192 testEnv[
"ESI_COSIM_PORT"] = str(port)
193 testEnv[
"ESI_COSIM_HOST"] =
"localhost"
194 return subprocess.run(inner_command, cwd=os.getcwd(),
195 env=testEnv).returncode
199 os.killpg(os.getpgid(simProc.pid), signal.SIGINT)
202 simProc.wait(timeout=1.0)
203 except subprocess.TimeoutExpired:
209 """Run and compile funcs for Verilator."""
211 DefaultDriver = CosimCollateralDir /
"driver.cpp"
213 def __init__(self, sources: SourceFiles, run_dir: Path, debug: bool):
214 super().
__init__(sources, run_dir, debug)
217 if "VERILATOR_PATH" in os.environ:
218 self.
verilatorverilator = os.environ[
"VERILATOR_PATH"]
231 str(Verilator.DefaultDriver),
234 "-DTOP_MODULE=" + self.
sourcessources.top,
237 cmd += [
"--trace",
"--trace-params",
"--trace-structs"]
238 cflags.append(
"-DTRACE")
240 cmd += [
"-CFLAGS",
" ".join(cflags)]
241 if len(self.
sourcessources.dpi_so) > 0:
242 cmd += [
"-LDFLAGS",
" ".join([
"-l" + so
for so
in self.
sourcessources.dpi_so])]
243 cmd += [str(p)
for p
in self.
sourcessources.rtl_sources]
247 exe = Path.cwd() /
"obj_dir" / (
"V" + self.
sourcessources.top)
252 """Run and compile funcs for Questasim."""
254 DefaultDriver = CosimCollateralDir /
"driver.sv"
263 "+define+TOP_MODULE=" + self.
sourcessources.top,
264 "+define+SIMULATION",
265 str(Questa.DefaultDriver),
267 cmd += [str(p)
for p
in self.
sourcessources.rtl_sources]
281 for lib
in self.
sourcessources.dpi_so_paths():
282 svLib = os.path.splitext(lib)[0]
283 cmd.append(
"-sv_lib")
285 if len(self.
sourcessources.dpi_so) > 0:
286 cmd.append(
"-cpppath")
287 cmd.append(
"/usr/bin/clang++")
290 def run(self, inner_command: str) -> int:
291 """Override the Simulator.run() to add a soft link in the run directory (to
292 the work directory) before running vsim the usual way."""
295 workDir = self.
run_dirrun_dir /
"work"
296 if not workDir.exists():
297 os.symlink(Path(os.getcwd()) /
"work", workDir)
300 return super().
run(inner_command)
304 argparser = argparse.ArgumentParser(
305 description=
"Wrap a 'inner_cmd' in an ESI cosimulation environment.",
306 formatter_class=argparse.RawDescriptionHelpFormatter,
307 epilog=textwrap.dedent(
"""
309 - For Verilator, libEsiCosimDpiServer.so must be in the dynamic
310 library runtime search path (LD_LIBRARY_PATH) and link time path
311 (LIBRARY_PATH). If it is installed to a standard location (e.g.
312 /usr/lib), this should be handled automatically.
313 - This script needs to sit in the same directory as the ESI support
314 SystemVerilog (e.g. Cosim_DpiPkg.sv, Cosim_MMIO.sv, etc.). It can,
315 however, be soft linked to a different location.
316 - The simulator executable(s) must be in your PATH.
319 argparser.add_argument(
323 help=
"Name of the RTL simulator to use or path to an executable.")
324 argparser.add_argument(
"--rundir",
326 help=
"Directory in which simulation should be run.")
327 argparser.add_argument(
329 default=
"ESI_Cosim_Top",
330 help=
"Name of the 'top' module to use in the simulation.")
331 argparser.add_argument(
"--debug",
333 help=
"Enable debug output.")
334 argparser.add_argument(
"--source",
335 help=
"Directories containing the source files.",
339 argparser.add_argument(
"inner_cmd",
340 nargs=argparse.REMAINDER,
341 help=
"Command to run in the simulation environment.")
344 argparser.print_help()
346 args = argparser.parse_args(args[1:])
349 for src
in args.source:
350 sources.add_dir(Path(src))
352 if args.sim ==
"verilator":
353 sim =
Verilator(sources, Path(args.rundir), args.debug)
354 elif args.sim ==
"questa":
355 sim =
Questa(sources, Path(args.rundir), args.debug)
357 print(
"Unknown simulator: " + args.sim)
358 print(
"Supported simulators: ")
359 print(
" - verilator")
366 return sim.run(args.inner_cmd[1:])
369 if __name__ ==
'__main__':
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
int run(self, str inner_command)
List[str] compile_command(self)
List[str] run_command(self)
List[str] compile_command(self)
int run(self, str inner_command)
List[str] run_command(self)
def __init__(self, SourceFiles sources, Path run_dir, bool debug)
List[Path] rtl_sources(self)
List[Path] dpi_so_paths(self)
None __init__(self, str top)
def add_dir(self, Path dir)
List[str] compile_command(self)
def __init__(self, SourceFiles sources, Path run_dir, bool debug)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.