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_Manifest.sv",
44 """Add a single RTL file to the source list."""
48 raise FileNotFoundError(f
"File {file} does not exist")
51 """Add all the RTL files in a directory to the source list."""
52 for file
in sorted(dir.iterdir()):
53 if file.is_file()
and (file.suffix ==
".sv" or file.suffix ==
".v"):
59 """Return a list of all the DPI shared object files."""
61 def find_so(name: str) -> Path:
62 for path
in Simulator.get_env().get(
"LD_LIBRARY_PATH",
"").split(
":"):
64 so = Path(path) / f
"{name}.dll"
66 so = Path(path) / f
"lib{name}.so"
69 raise FileNotFoundError(f
"Could not find {name}.so in LD_LIBRARY_PATH")
71 return [find_so(name)
for name
in self.dpi_so]
75 """Return a list of all the RTL source files."""
76 return self.dpi_sv + self.user
82 proc: subprocess.Popen,
84 threads: Optional[List[threading.Thread]] =
None,
88 self.threads: List[threading.Thread] = threads
or []
92 """Make sure to stop the simulation no matter what."""
94 os.killpg(os.getpgid(self.
proc.pid), signal.SIGINT)
97 self.
proc.wait(timeout=1.0)
98 except subprocess.TimeoutExpired:
103 for t
in self.threads:
115 sources: SourceFiles,
118 run_stdout_callback: Optional[Callable[[str],
None]] =
None,
119 run_stderr_callback: Optional[Callable[[str],
None]] =
None,
120 compile_stdout_callback: Optional[Callable[[str],
None]] =
None,
121 compile_stderr_callback: Optional[Callable[[str],
None]] =
None,
122 make_default_logs: bool =
True,
123 macro_definitions: Optional[Dict[str, str]] =
None):
124 """Simulator base class.
126 Optional sinks can be provided for capturing output. If not provided,
127 the simulator will write to log files in `run_dir`.
130 sources: SourceFiles describing RTL/DPI inputs.
131 run_dir: Directory where build/run artifacts are placed.
132 debug: Enable cosim debug mode.
133 run_stdout_callback: Line-based callback for runtime stdout.
134 run_stderr_callback: Line-based callback for runtime stderr.
135 compile_stdout_callback: Line-based callback for compile stdout.
136 compile_stderr_callback: Line-based callback for compile stderr.
137 make_default_logs: If True and corresponding callback is not supplied,
138 create log file and emit via internally-created callback.
139 macro_definitions: Optional dictionary of macro definitions to be defined
148 self._default_files: List[IO[str]] = []
150 def _ensure_default(cb: Optional[Callable[[str],
None]], filename: str):
151 """Return (callback, file_handle_or_None) with optional file creation.
154 * If a callback is provided, return it unchanged with no file.
155 * If no callback and make_default_logs is False, return (None, None).
156 * If no callback and make_default_logs is True, create a log file and
157 return a writer callback plus the opened file handle.
161 if not make_default_logs:
164 p.parent.mkdir(parents=
True, exist_ok=
True)
166 self._default_files.
append(logf)
168 def _writer(line: str, _lf=logf):
169 _lf.write(line +
"\n")
176 compile_stdout_callback,
'compile_stdout.log')
178 compile_stderr_callback,
'compile_stderr.log')
180 run_stdout_callback,
'sim_stdout.log')
182 run_stderr_callback,
'sim_stderr.log')
186 """Get the environment variables to locate shared objects."""
188 env = os.environ.copy()
189 env[
"LIBRARY_PATH"] = env.get(
"LIBRARY_PATH",
"") +
":" + str(
190 _thisdir.parent /
"lib")
191 env[
"LD_LIBRARY_PATH"] = env.get(
"LD_LIBRARY_PATH",
"") +
":" + str(
192 _thisdir.parent /
"lib")
196 """Compile the sources. Returns the exit code of the simulation compiler."""
197 assert False,
"Must be implemented by subclass"
201 self.
run_dir.mkdir(parents=
True, exist_ok=
True)
205 env=Simulator.get_env(),
210 if isinstance(ret, int)
and ret != 0:
211 print(
"====== Compilation failure")
228 """Return the command to run the simulation."""
229 assert False,
"Must be implemented by subclass"
231 def run_proc(self, gui: bool =
False) -> SimProcess:
232 """Run the simulation process. Returns the Popen object and the port which
233 the simulation is listening on.
235 If user-provided stdout/stderr sinks were supplied in the constructor,
236 they are used. Otherwise, log files are created in `run_dir`.
238 self.
run_dir.mkdir(parents=
True, exist_ok=
True)
240 env_gui = os.environ.get(
"COSIM_GUI",
"0")
246 portFileName = self.
run_dir /
"cosim.cfg"
247 if os.path.exists(portFileName):
248 os.remove(portFileName)
251 simEnv = Simulator.get_env()
253 simEnv[
"COSIM_DEBUG_FILE"] =
"cosim_debug.log"
254 if "DEBUG_PERIOD" not in simEnv:
256 simEnv[
"DEBUG_PERIOD"] =
"1"
269 while (
not os.path.exists(portFileName))
and \
273 if checkCount > 500
and not gui:
274 raise Exception(f
"Cosim never wrote cfg file: {portFileName}")
277 portFile = open(portFileName,
"r")
278 for line
in portFile.readlines():
279 m = re.match(
"port: (\\d+)", line)
281 port = int(m.group(1))
289 raise Exception(f
"Cosim RPC port ({port}) never opened")
290 if proc.poll()
is not None:
291 raise Exception(
"Simulation exited early")
293 return SimProcess(proc=proc, port=port, threads=threads, gui=gui)
296 self, cmd: List[str], env: Optional[Dict[str, str]], cwd: Optional[Path],
297 stdout_cb: Optional[Callable[[str],
298 None]], stderr_cb: Optional[Callable[[str],
300 wait: bool) -> int | tuple[subprocess.Popen, List[threading.Thread]]:
301 """Start a subprocess and stream its stdout/stderr to callbacks.
303 If wait is True, blocks until process completes and returns its exit code.
304 If wait is False, returns the Popen object (threads keep streaming).
306 if os.name ==
"posix":
307 proc = subprocess.Popen(cmd,
308 stdout=subprocess.PIPE,
309 stderr=subprocess.PIPE,
313 preexec_fn=os.setsid)
315 proc = subprocess.Popen(cmd,
316 stdout=subprocess.PIPE,
317 stderr=subprocess.PIPE,
321 creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
323 def _reader(pipe, cb):
327 if raw.endswith(
'\n'):
332 except Exception
as e:
333 print(f
"Exception in simulator output callback: {e}")
335 threads: List[threading.Thread] = [
336 threading.Thread(target=_reader,
337 args=(proc.stdout, stdout_cb),
339 threading.Thread(target=_reader,
340 args=(proc.stderr, stderr_cb),
354 server_only: bool =
False) -> int:
355 """Start the simulation then run the command specified. Kill the simulation
356 when the command exits."""
366 f
"Running in server-only mode on port {simProc.port} - Press anything to kill the server..."
371 testEnv = os.environ.copy()
372 testEnv[
"ESI_COSIM_PORT"] = str(simProc.port)
373 testEnv[
"ESI_COSIM_HOST"] =
"localhost"
374 ret = subprocess.run(inner_command, cwd=os.getcwd(),
375 env=testEnv).returncode
377 print(
"GUI mode - waiting for simulator to exit...")
381 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)