11from pathlib
import Path
12from typing
import Dict, List, Optional, Callable, IO, Union
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:
64 def check_path(p: Path) -> Optional[Path]:
66 so = p / f
"{name}.dll"
68 so = p / f
"lib{name}.so"
69 return so
if so.exists()
else None
71 for path
in Simulator.get_env().get(
"LD_LIBRARY_PATH",
"").split(
":"):
72 p = check_path(Path(path))
78 search_parent = _thisdir.parent
79 p = check_path(search_parent /
"lib")
82 search_parent = search_parent.parent
83 p = check_path(search_parent /
"lib")
87 raise FileNotFoundError(f
"Could not find {name}.so")
89 return [find_so(name)
for name
in self.dpi_so]
93 """Return a list of all the RTL source files."""
94 return self.dpi_sv + self.user
100 proc: subprocess.Popen,
102 threads: Optional[List[threading.Thread]] =
None,
106 self.threads: List[threading.Thread] = threads
or []
110 """Make sure to stop the simulation no matter what."""
112 os.killpg(os.getpgid(self.
proc.pid), signal.SIGINT)
115 self.
proc.wait(timeout=1.0)
116 except subprocess.TimeoutExpired:
121 for t
in self.threads:
127 CompileCommand = List[str]
128 CompileFunction = Callable[[], Optional[int]]
129 CompileStep = Union[CompileCommand, CompileFunction]
137 sources: SourceFiles,
140 save_waveform: bool =
False,
141 run_stdout_callback: Optional[Callable[[str],
None]] =
None,
142 run_stderr_callback: Optional[Callable[[str],
None]] =
None,
143 compile_stdout_callback: Optional[Callable[[str],
None]] =
None,
144 compile_stderr_callback: Optional[Callable[[str],
None]] =
None,
145 make_default_logs: bool =
True,
146 macro_definitions: Optional[Dict[str, str]] =
None):
147 """Simulator base class.
149 Optional sinks can be provided for capturing output. If not provided,
150 the simulator will write to log files in `run_dir`.
153 sources: SourceFiles describing RTL/DPI inputs.
154 run_dir: Directory where build/run artifacts are placed.
155 debug: Enable cosim debug mode.
156 save_waveform: When True and debug=True, dump simulator waveforms to a
157 waveform file. The exact format depends on the backend (e.g. FST for
158 Verilator, VCD for Questa). Requires debug to be enabled.
159 run_stdout_callback: Line-based callback for runtime stdout.
160 run_stderr_callback: Line-based callback for runtime stderr.
161 compile_stdout_callback: Line-based callback for compile stdout.
162 compile_stderr_callback: Line-based callback for compile stderr.
163 make_default_logs: If True and corresponding callback is not supplied,
164 create log file and emit via internally-created callback.
165 macro_definitions: Optional dictionary of macro definitions to be defined
175 self._default_files: List[IO[str]] = []
177 def _ensure_default(cb: Optional[Callable[[str],
None]], filename: str):
178 """Return (callback, file_handle_or_None) with optional file creation.
181 * If a callback is provided, return it unchanged with no file.
182 * If no callback and make_default_logs is False, return (None, None).
183 * If no callback and make_default_logs is True, create a log file and
184 return a writer callback plus the opened file handle.
188 if not make_default_logs:
191 p.parent.mkdir(parents=
True, exist_ok=
True)
193 self._default_files.
append(logf)
195 def _writer(line: str, _lf=logf):
196 _lf.write(line +
"\n")
203 compile_stdout_callback,
'compile_stdout.log')
205 compile_stderr_callback,
'compile_stderr.log')
207 run_stdout_callback,
'sim_stdout.log')
209 run_stderr_callback,
'sim_stderr.log')
213 """Get the environment variables to locate shared objects."""
215 env = os.environ.copy()
216 env[
"LIBRARY_PATH"] = env.get(
"LIBRARY_PATH",
"") +
":" + str(
217 _thisdir.parent /
"lib") +
":" + str(_thisdir.parent.parent /
"lib")
218 env[
"LD_LIBRARY_PATH"] = env.get(
"LD_LIBRARY_PATH",
"") +
":" + str(
219 _thisdir.parent /
"lib") +
":" + str(_thisdir.parent.parent /
"lib")
223 """Return the compile steps for the simulator.
225 Each step may be either a shell command (`List[str]`) or a Python callback
226 (`Callable[[], Optional[int]]`). Python callbacks should return `0` or
227 `None` on success and a non-zero integer on failure.
229 assert False,
"Must be implemented by subclass"
233 env=Simulator.get_env(),
238 if isinstance(ret, int)
and ret != 0:
239 print(
"====== Compilation failure")
247 print(stdout_content)
252 print(stderr_content)
254 return ret
if isinstance(ret, int)
else 1
261 if not isinstance(ret, int):
262 raise TypeError(
"compile step callback must return int or None")
268 self.
run_dir.mkdir(parents=
True, exist_ok=
True)
276 """Return the command to run the simulation."""
277 assert False,
"Must be implemented by subclass"
281 """File extension for waveform dumps.
283 Subclasses should override if their format differs. The Verilator C++
284 driver writes FST (via ``VerilatedFstC``); the generic SV driver uses
285 ``$dumpfile/$dumpvars`` which produces VCD.
289 def run_proc(self, gui: bool =
False) -> SimProcess:
290 """Run the simulation process. Returns the Popen object and the port which
291 the simulation is listening on.
293 If user-provided stdout/stderr sinks were supplied in the constructor,
294 they are used. Otherwise, log files are created in `run_dir`.
296 self.
run_dir.mkdir(parents=
True, exist_ok=
True)
298 env_gui = os.environ.get(
"COSIM_GUI",
"0")
304 portFileName = self.
run_dir /
"cosim.cfg"
305 if os.path.exists(portFileName):
306 os.remove(portFileName)
309 simEnv = Simulator.get_env()
312 simEnv[
"COSIM_DEBUG_FILE"] = str(debug_file)
313 if "DEBUG_PERIOD" not in simEnv:
315 simEnv[
"DEBUG_PERIOD"] =
"1"
317 waveform_file = (self.
run_dir /
318 f
"cosim_waveform{self.waveform_extension}").
resolve()
319 simEnv[
"SAVE_WAVE"] = str(waveform_file)
332 while (
not os.path.exists(portFileName))
and \
336 if checkCount > 500
and not gui:
337 raise Exception(f
"Cosim never wrote cfg file: {portFileName}")
340 portFile = open(portFileName,
"r")
341 for line
in portFile.readlines():
342 m = re.match(
"port: (\\d+)", line)
344 port = int(m.group(1))
352 raise Exception(f
"Cosim RPC port ({port}) never opened")
353 if proc.poll()
is not None:
354 raise Exception(
"Simulation exited early")
356 return SimProcess(proc=proc, port=port, threads=threads, gui=gui)
359 self, cmd: List[str], env: Optional[Dict[str, str]], cwd: Optional[Path],
360 stdout_cb: Optional[Callable[[str],
361 None]], stderr_cb: Optional[Callable[[str],
363 wait: bool) -> int | tuple[subprocess.Popen, List[threading.Thread]]:
364 """Start a subprocess and stream its stdout/stderr to callbacks.
366 If wait is True, blocks until process completes and returns its exit code.
367 If wait is False, returns the Popen object (threads keep streaming).
369 if os.name ==
"posix":
370 proc = subprocess.Popen(cmd,
371 stdout=subprocess.PIPE,
372 stderr=subprocess.PIPE,
376 preexec_fn=os.setsid)
378 proc = subprocess.Popen(cmd,
379 stdout=subprocess.PIPE,
380 stderr=subprocess.PIPE,
384 creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
386 def _reader(pipe, cb):
390 if raw.endswith(
'\n'):
395 except Exception
as e:
396 print(f
"Exception in simulator output callback: {e}")
398 threads: List[threading.Thread] = [
399 threading.Thread(target=_reader,
400 args=(proc.stdout, stdout_cb),
402 threading.Thread(target=_reader,
403 args=(proc.stderr, stderr_cb),
417 server_only: bool =
False) -> int:
418 """Start the simulation then run the command specified. Kill the simulation
419 when the command exits."""
429 f
"Running in server-only mode on port {simProc.port} - Press anything to kill the server..."
434 testEnv = os.environ.copy()
435 testEnv[
"ESI_COSIM_PORT"] = str(simProc.port)
436 testEnv[
"ESI_COSIM_HOST"] =
"localhost"
437 ret = subprocess.run(inner_command, cwd=os.getcwd(),
438 env=testEnv).returncode
440 print(
"GUI mode - waiting for simulator to exit...")
444 if simProc
and simProc.proc.poll()
is None:
448def get_simulator(name: str,
449 sources: SourceFiles,
452 save_waveform: bool =
False) -> Simulator:
454 if name ==
"verilator":
455 from .verilator
import Verilator
456 return Verilator(sources, rundir, debug, save_waveform=save_waveform)
457 elif name ==
"questa":
458 from .questa
import Questa
459 return Questa(sources, rundir, debug, save_waveform=save_waveform)
461 raise ValueError(f
"Unknown simulator: {name}")
static void print(TypedAttr val, llvm::raw_ostream &os)
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
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)
__init__(self, SourceFiles sources, Path run_dir, bool debug, bool save_waveform=False, 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)
int run(self, str inner_command, bool gui=False, bool server_only=False)
List[CompileStep] compile_commands(self)
int _run_compile_command(self, CompileCommand cmd)
str waveform_extension(self)
int _run_compile_step(self, CompileStep step)
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)