@@ -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+
85103class 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
228245class Verilator (Simulator ):
0 commit comments