File size: 4,136 Bytes
4344b33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from __future__ import annotations

import os
import re
import subprocess
import tempfile
from pathlib import Path
from typing import List, Optional

from src.simulation.base import CoverageBin, SimResult, Simulator


class IcarusSimulator(Simulator):
    def __init__(self, work_dir: str = "sim_output", iverilog_path: str = "iverilog",
                 vvp_path: str = "vvp"):
        super().__init__(work_dir)
        self.iverilog_path = iverilog_path
        self.vvp_path = vvp_path

    def _check_available(self) -> bool:
        try:
            subprocess.run([self.iverilog_path, "-V"], capture_output=True, timeout=5)
            return True
        except (FileNotFoundError, subprocess.TimeoutExpired):
            return False

    def run(self, files: List[str], top: str = "testbench",
            plusargs: Optional[List[str]] = None) -> SimResult:
        available = self._check_available()
        if not available:
            return SimResult(
                passed=False,
                errors=["iverilog not found — install Icarus Verilog or use stub simulator"],
                log_output=""
            )

        Path(self.work_dir).mkdir(parents=True, exist_ok=True)
        vvp_out = os.path.join(self.work_dir, "sim.vvp")

        plusargs_list = plusargs or []
        plusargs_str = " ".join(f"-P{top}.{a}" for a in plusargs_list)

        compile_cmd = (
            f"{self.iverilog_path} -g2012 -o {vvp_out} "
            f"{' '.join(files)} "
            f"{plusargs_str}"
        )
        try:
            result = subprocess.run(compile_cmd, shell=True, capture_output=True,
                                    text=True, timeout=120)
            if result.returncode != 0:
                return SimResult(
                    passed=False,
                    errors=[f"iverilog compilation failed:\n{result.stderr}"],
                    log_output=result.stdout + "\n" + result.stderr
                )

            run_cmd = f"{self.vvp_path} {vvp_out} +UVM_NO_RELNOTES"
            sim_result = subprocess.run(run_cmd, shell=True, capture_output=True,
                                         text=True, timeout=300)
            log = sim_result.stdout + "\n" + sim_result.stderr
            return self.parse_coverage(log)

        except subprocess.TimeoutExpired:
            return SimResult(
                passed=False,
                errors=["Simulation timed out (>300s)"],
                log_output=""
            )
        except Exception as e:
            return SimResult(
                passed=False,
                errors=[f"Simulation error: {e}"],
                log_output=""
            )

    def parse_coverage(self, log: str) -> SimResult:
        bins = []
        errors = []
        passed = True

        # Parse UVM coverage output: "COVERAGE: <name> <hit_count>/<goal>"
        cov_pattern = re.compile(
            r"COVERAGE:\s+(\S+)\s+(\d+)/(\d+)", re.MULTILINE
        )
        for match in cov_pattern.finditer(log):
            name, hits, goal = match.group(1), int(match.group(2)), int(match.group(3))
            bins.append(CoverageBin(name=name, hit_count=hits, goal=goal))

        # Parse UVM errors
        err_pattern = re.compile(r"UVM_(ERROR|FATAL)\s*:\s*(.*)")
        for match in err_pattern.finditer(log):
            errors.append(match.group(2).strip())
            passed = False

        # Parse scoreboard result
        if "SCOREBOARD: PASS" in log:
            pass
        elif "SCOREBOARD: FAIL" in log:
            passed = False
            errors.append("Scoreboard mismatch detected")

        # Test pass/fail from UVM summary
        if "UVM Report Summary" in log or "--- UVM Summary ---" in log:
            fail_match = re.search(r"Errors\s*:\s*(\d+)", log)
            if fail_match and int(fail_match.group(1)) > 0:
                passed = False

        covered = sum(1 for b in bins if b.covered)
        return SimResult(
            passed=passed,
            total_bins=len(bins),
            covered_bins=covered,
            bins=bins,
            errors=errors,
            log_output=log
        )