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 save_waveform: bool =
False,
120 run_stdout_callback: Optional[Callable[[str],
None]] =
None,
121 run_stderr_callback: Optional[Callable[[str],
None]] =
None,
122 compile_stdout_callback: Optional[Callable[[str],
None]] =
None,
123 compile_stderr_callback: Optional[Callable[[str],
None]] =
None,
124 make_default_logs: bool =
True,
125 macro_definitions: Optional[Dict[str, str]] =
None):
126 """Simulator base class.
128 Optional sinks can be provided for capturing output. If not provided,
129 the simulator will write to log files in `run_dir`.
132 sources: SourceFiles describing RTL/DPI inputs.
133 run_dir: Directory where build/run artifacts are placed.
134 debug: Enable cosim debug mode.
135 save_waveform: When True and debug=True, dump simulator waveforms to a
136 waveform file. The exact format depends on the backend (e.g. FST for
137 Verilator, VCD for Questa). Requires debug to be enabled.
138 run_stdout_callback: Line-based callback for runtime stdout.
139 run_stderr_callback: Line-based callback for runtime stderr.
140 compile_stdout_callback: Line-based callback for compile stdout.
141 compile_stderr_callback: Line-based callback for compile stderr.
142 make_default_logs: If True and corresponding callback is not supplied,
143 create log file and emit via internally-created callback.
144 macro_definitions: Optional dictionary of macro definitions to be defined
154 self._default_files: List[IO[str]] = []
156 def _ensure_default(cb: Optional[Callable[[str],
None]], filename: str):
157 """Return (callback, file_handle_or_None) with optional file creation.
160 * If a callback is provided, return it unchanged with no file.
161 * If no callback and make_default_logs is False, return (None, None).
162 * If no callback and make_default_logs is True, create a log file and
163 return a writer callback plus the opened file handle.
167 if not make_default_logs:
170 p.parent.mkdir(parents=
True, exist_ok=
True)
172 self._default_files.
append(logf)
174 def _writer(line: str, _lf=logf):
175 _lf.write(line +
"\n")
182 compile_stdout_callback,
'compile_stdout.log')
184 compile_stderr_callback,
'compile_stderr.log')
186 run_stdout_callback,
'sim_stdout.log')
188 run_stderr_callback,
'sim_stderr.log')
192 """Get the environment variables to locate shared objects."""
194 env = os.environ.copy()
195 env[
"LIBRARY_PATH"] = env.get(
"LIBRARY_PATH",
"") +
":" + str(
196 _thisdir.parent /
"lib")
197 env[
"LD_LIBRARY_PATH"] = env.get(
"LD_LIBRARY_PATH",
"") +
":" + str(
198 _thisdir.parent /
"lib")
202 """Compile the sources. Returns the exit code of the simulation compiler."""
203 assert False,
"Must be implemented by subclass"
207 self.
run_dir.mkdir(parents=
True, exist_ok=
True)
211 env=Simulator.get_env(),
216 if isinstance(ret, int)
and ret != 0:
217 print(
"====== Compilation failure")
234 """Return the command to run the simulation."""
235 assert False,
"Must be implemented by subclass"
239 """File extension for waveform dumps.
241 Subclasses should override if their format differs. The Verilator C++
242 driver writes FST (via ``VerilatedFstC``); the generic SV driver uses
243 ``$dumpfile/$dumpvars`` which produces VCD.
247 def run_proc(self, gui: bool =
False) -> SimProcess:
248 """Run the simulation process. Returns the Popen object and the port which
249 the simulation is listening on.
251 If user-provided stdout/stderr sinks were supplied in the constructor,
252 they are used. Otherwise, log files are created in `run_dir`.
254 self.
run_dir.mkdir(parents=
True, exist_ok=
True)
256 env_gui = os.environ.get(
"COSIM_GUI",
"0")
262 portFileName = self.
run_dir /
"cosim.cfg"
263 if os.path.exists(portFileName):
264 os.remove(portFileName)
267 simEnv = Simulator.get_env()
270 simEnv[
"COSIM_DEBUG_FILE"] = str(debug_file)
271 if "DEBUG_PERIOD" not in simEnv:
273 simEnv[
"DEBUG_PERIOD"] =
"1"
275 waveform_file = (self.
run_dir /
276 f
"cosim_waveform{self.waveform_extension}").
resolve()
277 simEnv[
"SAVE_WAVE"] = str(waveform_file)
290 while (
not os.path.exists(portFileName))
and \
294 if checkCount > 500
and not gui:
295 raise Exception(f
"Cosim never wrote cfg file: {portFileName}")
298 portFile = open(portFileName,
"r")
299 for line
in portFile.readlines():
300 m = re.match(
"port: (\\d+)", line)
302 port = int(m.group(1))
310 raise Exception(f
"Cosim RPC port ({port}) never opened")
311 if proc.poll()
is not None:
312 raise Exception(
"Simulation exited early")
314 return SimProcess(proc=proc, port=port, threads=threads, gui=gui)
317 self, cmd: List[str], env: Optional[Dict[str, str]], cwd: Optional[Path],
318 stdout_cb: Optional[Callable[[str],
319 None]], stderr_cb: Optional[Callable[[str],
321 wait: bool) -> int | tuple[subprocess.Popen, List[threading.Thread]]:
322 """Start a subprocess and stream its stdout/stderr to callbacks.
324 If wait is True, blocks until process completes and returns its exit code.
325 If wait is False, returns the Popen object (threads keep streaming).
327 if os.name ==
"posix":
328 proc = subprocess.Popen(cmd,
329 stdout=subprocess.PIPE,
330 stderr=subprocess.PIPE,
334 preexec_fn=os.setsid)
336 proc = subprocess.Popen(cmd,
337 stdout=subprocess.PIPE,
338 stderr=subprocess.PIPE,
342 creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
344 def _reader(pipe, cb):
348 if raw.endswith(
'\n'):
353 except Exception
as e:
354 print(f
"Exception in simulator output callback: {e}")
356 threads: List[threading.Thread] = [
357 threading.Thread(target=_reader,
358 args=(proc.stdout, stdout_cb),
360 threading.Thread(target=_reader,
361 args=(proc.stderr, stderr_cb),
375 server_only: bool =
False) -> int:
376 """Start the simulation then run the command specified. Kill the simulation
377 when the command exits."""
387 f
"Running in server-only mode on port {simProc.port} - Press anything to kill the server..."
392 testEnv = os.environ.copy()
393 testEnv[
"ESI_COSIM_PORT"] = str(simProc.port)
394 testEnv[
"ESI_COSIM_HOST"] =
"localhost"
395 ret = subprocess.run(inner_command, cwd=os.getcwd(),
396 env=testEnv).returncode
398 print(
"GUI mode - waiting for simulator to exit...")
402 if simProc
and simProc.proc.poll()
is None:
406def get_simulator(name: str,
407 sources: SourceFiles,
410 save_waveform: bool =
False) -> Simulator:
412 if name ==
"verilator":
413 from .verilator
import Verilator
414 return Verilator(sources, rundir, debug, save_waveform=save_waveform)
415 elif name ==
"questa":
416 from .questa
import Questa
417 return Questa(sources, rundir, debug, save_waveform=save_waveform)
419 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)
str waveform_extension(self)
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)