feat: init mlkem project with Verilator test framework
- sync_rtl/common/: skid_buffer, pipeline_reg, defines (valid/ready) - sync_rtl/mod_add/: modular adder example with Verilator C++ TB - test_framework/: Python-driven Verilator compile/sim/compare pipeline - test_framework/modules/mod_add/: 50-vector test plan, full鏈路 PASS - .trellis/spec/: RTL and test_framework conventions documented
This commit is contained in:
134
test_framework/lib/sim_controller.py
Normal file
134
test_framework/lib/sim_controller.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""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 <work_dir>/V<top_module>
|
||||
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.'
|
||||
}
|
||||
Reference in New Issue
Block a user