11from pathlib
import Path
12from typing
import Dict, List, Optional, Callable, IO
15_thisdir = Path(__file__).parent
16CosimCollateralDir = _thisdir
20 """Check if a TCP port is open locally."""
21 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
22 result = sock.connect_ex((
'127.0.0.1', port))
24 return True if result == 0
else False
31 self.user: List[Path] = []
33 self.dpi_so: List[str] = [
"EsiCosimDpiServer"]
35 self.dpi_sv: List[Path] = [
36 CosimCollateralDir /
"Cosim_DpiPkg.sv",
37 CosimCollateralDir /
"Cosim_Endpoint.sv",
38 CosimCollateralDir /
"Cosim_CycleCount.sv",
39 CosimCollateralDir /
"Cosim_Manifest.sv",
45 """Add a single RTL file to the source list."""
49 raise FileNotFoundError(f
"File {file} does not exist")
52 """Add all the RTL files in a directory to the source list."""
53 for file
in sorted(dir.iterdir()):
54 if file.is_file()
and (file.suffix ==
".sv" or file.suffix ==
".v"):
60 """Return a list of all the DPI shared object files."""
62 def find_so(name: str) -> Path:
63 for path
in Simulator.get_env().get(
"LD_LIBRARY_PATH",
"").split(
":"):
65 so = Path(path) / f
"{name}.dll"
67 so = Path(path) / f
"lib{name}.so"
70 raise FileNotFoundError(f
"Could not find {name}.so in LD_LIBRARY_PATH")
72 return [find_so(name)
for name
in self.dpi_so]
76 """Return a list of all the RTL source files."""
77 return self.dpi_sv + self.user
83 proc: subprocess.Popen,
85 threads: Optional[List[threading.Thread]] =
None,
89 self.threads: List[threading.Thread] = threads
or []
93 """Make sure to stop the simulation no matter what."""
95 os.killpg(os.getpgid(self.
proc.pid), signal.SIGINT)
98 self.
proc.wait(timeout=1.0)
99 except subprocess.TimeoutExpired:
104 for t
in self.threads:
116 sources: SourceFiles,
119 run_stdout_callback: Optional[Callable[[str],
None]] =
None,
120 run_stderr_callback: Optional[Callable[[str],
None]] =
None,
121 compile_stdout_callback: Optional[Callable[[str],
None]] =
None,
122 compile_stderr_callback: Optional[Callable[[str],
None]] =
None,
123 make_default_logs: bool =
True,
124 macro_definitions: Optional[Dict[str, str]] =
None):
125 """Simulator base class.
127 Optional sinks can be provided for capturing output. If not provided,
128 the simulator will write to log files in `run_dir`.
131 sources: SourceFiles describing RTL/DPI inputs.
132 run_dir: Directory where build/run artifacts are placed.
133 debug: Enable cosim debug mode.
134 run_stdout_callback: Line-based callback for runtime stdout.
135 run_stderr_callback: Line-based callback for runtime stderr.
136 compile_stdout_callback: Line-based callback for compile stdout.
137 compile_stderr_callback: Line-based callback for compile stderr.
138 make_default_logs: If True and corresponding callback is not supplied,
139 create log file and emit via internally-created callback.
140 macro_definitions: Optional dictionary of macro definitions to be defined
149 self._default_files: List[IO[str]] = []
151 def _ensure_default(cb: Optional[Callable[[str],
None]], filename: str):
152 """Return (callback, file_handle_or_None) with optional file creation.
155 * If a callback is provided, return it unchanged with no file.
156 * If no callback and make_default_logs is False, return (None, None).
157 * If no callback and make_default_logs is True, create a log file and
158 return a writer callback plus the opened file handle.
162 if not make_default_logs:
165 p.parent.mkdir(parents=
True, exist_ok=
True)
167 self._default_files.
append(logf)
169 def _writer(line: str, _lf=logf):
170 _lf.write(line +
"\n")
177 compile_stdout_callback,
'compile_stdout.log')
179 compile_stderr_callback,
'compile_stderr.log')
181 run_stdout_callback,
'sim_stdout.log')
183 run_stderr_callback,
'sim_stderr.log')
187 """Get the environment variables to locate shared objects."""
189 env = os.environ.copy()
190 env[
"LIBRARY_PATH"] = env.get(
"LIBRARY_PATH",
"") +
":" + str(
191 _thisdir.parent /
"lib")
192 env[
"LD_LIBRARY_PATH"] = env.get(
"LD_LIBRARY_PATH",
"") +
":" + str(
193 _thisdir.parent /
"lib")
197 """Compile the sources. Returns the exit code of the simulation compiler."""
198 assert False,
"Must be implemented by subclass"
202 self.
run_dir.mkdir(parents=
True, exist_ok=
True)
206 env=Simulator.get_env(),
211 if isinstance(ret, int)
and ret != 0:
212 print(
"====== Compilation failure")
229 """Return the command to run the simulation."""
230 assert False,
"Must be implemented by subclass"
232 def run_proc(self, gui: bool =
False) -> SimProcess:
233 """Run the simulation process. Returns the Popen object and the port which
234 the simulation is listening on.
236 If user-provided stdout/stderr sinks were supplied in the constructor,
237 they are used. Otherwise, log files are created in `run_dir`.
239 self.
run_dir.mkdir(parents=
True, exist_ok=
True)
241 env_gui = os.environ.get(
"COSIM_GUI",
"0")
247 portFileName = self.
run_dir /
"cosim.cfg"
248 if os.path.exists(portFileName):
249 os.remove(portFileName)
252 simEnv = Simulator.get_env()
254 simEnv[
"COSIM_DEBUG_FILE"] =
"cosim_debug.log"
255 if "DEBUG_PERIOD" not in simEnv:
257 simEnv[
"DEBUG_PERIOD"] =
"1"
270 while (
not os.path.exists(portFileName))
and \
274 if checkCount > 500
and not gui:
275 raise Exception(f
"Cosim never wrote cfg file: {portFileName}")
278 portFile = open(portFileName,
"r")
279 for line
in portFile.readlines():
280 m = re.match(
"port: (\\d+)", line)
282 port = int(m.group(1))
290 raise Exception(f
"Cosim RPC port ({port}) never opened")
291 if proc.poll()
is not None:
292 raise Exception(
"Simulation exited early")
294 return SimProcess(proc=proc, port=port, threads=threads, gui=gui)
297 self, cmd: List[str], env: Optional[Dict[str, str]], cwd: Optional[Path],
298 stdout_cb: Optional[Callable[[str],
299 None]], stderr_cb: Optional[Callable[[str],
301 wait: bool) -> int | tuple[subprocess.Popen, List[threading.Thread]]:
302 """Start a subprocess and stream its stdout/stderr to callbacks.
304 If wait is True, blocks until process completes and returns its exit code.
305 If wait is False, returns the Popen object (threads keep streaming).
307 if os.name ==
"posix":
308 proc = subprocess.Popen(cmd,
309 stdout=subprocess.PIPE,
310 stderr=subprocess.PIPE,
314 preexec_fn=os.setsid)
316 proc = subprocess.Popen(cmd,
317 stdout=subprocess.PIPE,
318 stderr=subprocess.PIPE,
322 creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
324 def _reader(pipe, cb):
328 if raw.endswith(
'\n'):
333 except Exception
as e:
334 print(f
"Exception in simulator output callback: {e}")
336 threads: List[threading.Thread] = [
337 threading.Thread(target=_reader,
338 args=(proc.stdout, stdout_cb),
340 threading.Thread(target=_reader,
341 args=(proc.stderr, stderr_cb),
355 server_only: bool =
False) -> int:
356 """Start the simulation then run the command specified. Kill the simulation
357 when the command exits."""
367 f
"Running in server-only mode on port {simProc.port} - Press anything to kill the server..."
372 testEnv = os.environ.copy()
373 testEnv[
"ESI_COSIM_PORT"] = str(simProc.port)
374 testEnv[
"ESI_COSIM_HOST"] =
"localhost"
375 ret = subprocess.run(inner_command, cwd=os.getcwd(),
376 env=testEnv).returncode
378 print(
"GUI mode - waiting for simulator to exit...")
382 if simProc
and simProc.proc.poll()
is None:
static void print(TypedAttr val, llvm::raw_ostream &os)
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
__init__(self, subprocess.Popen proc, int port, Optional[List[threading.Thread]] threads=None, bool gui=False)
int|tuple[subprocess.Popen, List[threading.Thread]] _start_process_with_callbacks(self, List[str] cmd, Optional[Dict[str, str]] env, Optional[Path] cwd, Optional[Callable[[str], None]] stdout_cb, Optional[Callable[[str], None]] stderr_cb, bool wait)
int run(self, str inner_command, bool gui=False, bool server_only=False)
__init__(self, SourceFiles sources, Path run_dir, bool debug, Optional[Callable[[str], None]] run_stdout_callback=None, Optional[Callable[[str], None]] run_stderr_callback=None, Optional[Callable[[str], None]] compile_stdout_callback=None, Optional[Callable[[str], None]] compile_stderr_callback=None, bool make_default_logs=True, Optional[Dict[str, str]] macro_definitions=None)
List[List[str]] compile_commands(self)
List[str] run_command(self, bool gui)
SimProcess run_proc(self, bool gui=False)
List[Path] rtl_sources(self)
add_file(self, Path file)
None __init__(self, str top)
List[Path] dpi_so_paths(self)