Skip to content

Commit f048b4c

Browse files
authored
[ESI] Factor out inner execution in Simulator::run (llvm#8964)
* [ESI] Factor out inner execution in Simulator::run When using esi-cosim as a library, it is very inconvenient that the default behavior of the `run` command assumes that `esi-cosim` is executed as a standalone script (i.e. either tries to run a user-provided command or waits for user input). To better serve as a library, factor out the inner simulator process execution to `run_proc` s.t. other python code can call this, and get a handle to the simulator process and port. * factor out finally block
1 parent d786372 commit f048b4c

File tree

1 file changed

+81
-64
lines changed

1 file changed

+81
-64
lines changed

lib/Dialect/ESI/runtime/cosim_dpi_server/esi-cosim.py

Lines changed: 81 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ def rtl_sources(self) -> List[Path]:
8282
return self.dpi_sv + self.user
8383

8484

85+
class SimProcess:
86+
87+
def __init__(self, proc: subprocess.Popen, port: int):
88+
self.proc = proc
89+
self.port = port
90+
91+
def force_stop(self):
92+
"""Make sure to stop the simulation no matter what."""
93+
if self.proc:
94+
os.killpg(os.getpgid(self.proc.pid), signal.SIGINT)
95+
# Allow the simulation time to flush its outputs.
96+
try:
97+
self.proc.wait(timeout=1.0)
98+
except subprocess.TimeoutExpired:
99+
# If the simulation doesn't exit of its own free will, kill it.
100+
self.proc.kill()
101+
102+
85103
class Simulator:
86104

87105
# Some RTL simulators don't use stderr for error messages. Everything goes to
@@ -135,6 +153,65 @@ def run_command(self, gui: bool) -> List[str]:
135153
"""Return the command to run the simulation."""
136154
assert False, "Must be implemented by subclass"
137155

156+
def run_proc(self, gui: bool = False) -> SimProcess:
157+
"""Run the simulation process. Returns the Popen object and the port which
158+
the simulation is listening on."""
159+
# Open log files
160+
self.run_dir.mkdir(parents=True, exist_ok=True)
161+
simStdout = open(self.run_dir / "sim_stdout.log", "w")
162+
simStderr = open(self.run_dir / "sim_stderr.log", "w")
163+
164+
# Erase the config file if it exists. We don't want to read
165+
# an old config.
166+
portFileName = self.run_dir / "cosim.cfg"
167+
if os.path.exists(portFileName):
168+
os.remove(portFileName)
169+
170+
# Run the simulation.
171+
simEnv = Simulator.get_env()
172+
if self.debug:
173+
simEnv["COSIM_DEBUG_FILE"] = "cosim_debug.log"
174+
if "DEBUG_PERIOD" not in simEnv:
175+
# Slow the simulation down to one tick per millisecond.
176+
simEnv["DEBUG_PERIOD"] = "1"
177+
rcmd = self.run_command(gui)
178+
simProc = subprocess.Popen(self.run_command(gui),
179+
stdout=simStdout,
180+
stderr=simStderr,
181+
env=simEnv,
182+
cwd=self.run_dir,
183+
preexec_fn=os.setsid)
184+
simStderr.close()
185+
simStdout.close()
186+
187+
# Get the port which the simulation RPC selected.
188+
checkCount = 0
189+
while (not os.path.exists(portFileName)) and \
190+
simProc.poll() is None:
191+
time.sleep(0.1)
192+
checkCount += 1
193+
if checkCount > 500 and not gui:
194+
raise Exception(f"Cosim never wrote cfg file: {portFileName}")
195+
port = -1
196+
while port < 0:
197+
portFile = open(portFileName, "r")
198+
for line in portFile.readlines():
199+
m = re.match("port: (\\d+)", line)
200+
if m is not None:
201+
port = int(m.group(1))
202+
portFile.close()
203+
204+
# Wait for the simulation to start accepting RPC connections.
205+
checkCount = 0
206+
while not is_port_open(port):
207+
checkCount += 1
208+
if checkCount > 200:
209+
raise Exception(f"Cosim RPC port ({port}) never opened")
210+
if simProc.poll() is not None:
211+
raise Exception("Simulation exited early")
212+
time.sleep(0.05)
213+
return SimProcess(proc=simProc, port=port)
214+
138215
def run(self,
139216
inner_command: str,
140217
gui: bool = False,
@@ -146,83 +223,23 @@ def run(self,
146223
# syntax errors in that block.
147224
simProc = None
148225
try:
149-
# Open log files
150-
self.run_dir.mkdir(parents=True, exist_ok=True)
151-
simStdout = open(self.run_dir / "sim_stdout.log", "w")
152-
simStderr = open(self.run_dir / "sim_stderr.log", "w")
153-
154-
# Erase the config file if it exists. We don't want to read
155-
# an old config.
156-
portFileName = self.run_dir / "cosim.cfg"
157-
if os.path.exists(portFileName):
158-
os.remove(portFileName)
159-
160-
# Run the simulation.
161-
simEnv = Simulator.get_env()
162-
if self.debug:
163-
simEnv["COSIM_DEBUG_FILE"] = "cosim_debug.log"
164-
if "DEBUG_PERIOD" not in simEnv:
165-
# Slow the simulation down to one tick per millisecond.
166-
simEnv["DEBUG_PERIOD"] = "1"
167-
simProc = subprocess.Popen(self.run_command(gui),
168-
stdout=simStdout,
169-
stderr=simStderr,
170-
env=simEnv,
171-
cwd=self.run_dir,
172-
preexec_fn=os.setsid)
173-
simStderr.close()
174-
simStdout.close()
175-
176-
# Get the port which the simulation RPC selected.
177-
checkCount = 0
178-
while (not os.path.exists(portFileName)) and \
179-
simProc.poll() is None:
180-
time.sleep(0.1)
181-
checkCount += 1
182-
if checkCount > 200 and not gui:
183-
raise Exception(f"Cosim never wrote cfg file: {portFileName}")
184-
port = -1
185-
while port < 0:
186-
portFile = open(portFileName, "r")
187-
for line in portFile.readlines():
188-
m = re.match("port: (\\d+)", line)
189-
if m is not None:
190-
port = int(m.group(1))
191-
portFile.close()
192-
193-
# Wait for the simulation to start accepting RPC connections.
194-
checkCount = 0
195-
while not is_port_open(port):
196-
checkCount += 1
197-
if checkCount > 200:
198-
raise Exception(f"Cosim RPC port ({port}) never opened")
199-
if simProc.poll() is not None:
200-
raise Exception("Simulation exited early")
201-
time.sleep(0.05)
202-
226+
simProc = self.run_proc(gui=gui)
203227
if server_only:
204228
# wait for user input to kill the server
205229
input(
206-
f"Running in server-only mode on port {port} - Press anything to kill the server..."
230+
f"Running in server-only mode on port {simProc.port} - Press anything to kill the server..."
207231
)
208232
return 0
209233
else:
210234
# Run the inner command, passing the connection info via environment vars.
211235
testEnv = os.environ.copy()
212-
testEnv["ESI_COSIM_PORT"] = str(port)
236+
testEnv["ESI_COSIM_PORT"] = str(simProc.port)
213237
testEnv["ESI_COSIM_HOST"] = "localhost"
214238
return subprocess.run(inner_command, cwd=os.getcwd(),
215239
env=testEnv).returncode
216240
finally:
217-
# Make sure to stop the simulation no matter what.
218241
if simProc:
219-
os.killpg(os.getpgid(simProc.pid), signal.SIGINT)
220-
# Allow the simulation time to flush its outputs.
221-
try:
222-
simProc.wait(timeout=1.0)
223-
except subprocess.TimeoutExpired:
224-
# If the simulation doesn't exit of its own free will, kill it.
225-
simProc.kill()
242+
simProc.force_stop()
226243

227244

228245
class Verilator(Simulator):

0 commit comments

Comments
 (0)