"""sim_controller.py - Verilator simulation controller. Handles Verilator compilation and execution of RTL testbenches. """ import subprocess import os import time class SimController: """Controls Verilator compilation and simulation.""" def __init__(self, config: dict): """Initialize with test framework configuration. Args: config: The loaded config.json dict. """ self.config = config self.verilator_path = config.get('verilator', {}).get('path', 'verilator') self.compile_args = config.get('verilator', {}).get('compile_args', []) def compile(self, top_module: str, rtl_files: list[str], tb_cpp: str, work_dir: str, inc_dirs: list[str] = None) -> str: """Compile RTL with Verilator and return path to executable. Args: top_module: Name of the top-level Verilog module. rtl_files: List of Verilog source file paths. tb_cpp: Path to C++ testbench file. work_dir: Working directory for compilation output (-Mdir). inc_dirs: List of include directories for `include directives. Returns: Path to the compiled executable. Raises: RuntimeError: If Verilator is not found or compilation fails. """ os.makedirs(work_dir, exist_ok=True) # Build verilator command cmd = [self.verilator_path] cmd.extend(self.compile_args) cmd.extend(['--top-module', top_module]) # No explicit -std= flag; let Verilator use its default (C++14+) if inc_dirs: for d in inc_dirs: cmd.append(f'+incdir+{d}') cmd.extend(rtl_files) cmd.append(tb_cpp) cmd.extend(['-Mdir', work_dir]) try: result = subprocess.run( cmd, capture_output=True, text=True, timeout=120, cwd=os.getcwd() ) except subprocess.TimeoutExpired: raise RuntimeError("Verilator compilation timed out.") except FileNotFoundError: raise RuntimeError( f"Verilator not found at '{self.verilator_path}'." ) if result.returncode != 0: tail = result.stderr[-800:] if result.stderr else '(no stderr)' raise RuntimeError(f"Verilator compilation failed:\n{tail}") # Executable is at /V executable = os.path.join(work_dir, f'V{top_module}') if not os.path.exists(executable): raise RuntimeError( f"Compilation succeeded but executable not found: {executable}" ) return executable def run(self, executable: str, vector_file: str, timeout_s: int = 30) -> dict: """Run the compiled simulation executable. Args: executable: Path to the compiled executable. vector_file: Path to the hex vector file. timeout_s: Timeout in seconds. Returns: dict with keys: success, results, elapsed, stderr. """ if not os.path.exists(executable): return { 'success': False, 'results': [], 'elapsed': 0.0, 'stderr': f'Executable not found: {executable}' } start_time = time.time() try: result = subprocess.run( [executable, f'+VECTOR_FILE={vector_file}'], capture_output=True, text=True, timeout=timeout_s ) elapsed = time.time() - start_time # Parse stdout for "RESULT: XXX" lines results = [] for line in result.stdout.splitlines(): line = line.strip() if line.startswith('RESULT:'): hex_val = line.split(':', 1)[1].strip() results.append(hex_val) success = result.returncode == 0 and len(results) > 0 return { 'success': success, 'results': results, 'elapsed': elapsed, 'stderr': result.stderr } except subprocess.TimeoutExpired: elapsed = time.time() - start_time return { 'success': False, 'results': [], 'elapsed': elapsed, 'stderr': 'Simulation timed out.' }