File size: 8,024 Bytes
8c7dd56
a1e6013
2e4ea50
8c7dd56
bb78364
8c7dd56
2654ab0
 
 
 
 
 
2e4ea50
3266076
 
 
8c7dd56
 
 
ee5fb69
ccd6515
47f79ef
a1e6013
47f79ef
 
2315207
 
 
 
 
 
 
 
ec345ab
 
 
 
 
 
51debf3
a1e6013
 
0ed3642
a1e6013
51debf3
a1e6013
51debf3
 
358e630
 
 
 
 
 
a1e6013
ee5fb69
a1e6013
 
51debf3
 
 
8c7dd56
ec345ab
 
 
7ffa76d
 
 
 
 
ec345ab
 
7ffa76d
 
 
 
 
 
 
 
 
ec345ab
 
 
 
 
 
7ffa76d
 
 
 
ec345ab
7ffa76d
ec345ab
 
7ffa76d
 
ec345ab
7ffa76d
 
 
2315207
ec345ab
 
 
 
2315207
 
 
7ffa76d
ec345ab
 
a1e6013
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8c7dd56
a1e6013
 
8c7dd56
6104d33
8a637a8
 
 
 
 
 
 
 
 
 
 
 
 
a1e6013
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import os
from typing import Dict, Any, Optional
from dotenv import load_dotenv

# Project Paths
WORKSPACE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Load .env file ONLY if it exists, and NEVER override environment variables
# already set by the platform (e.g. HuggingFace Spaces secrets).
_dotenv_path = os.path.join(WORKSPACE_ROOT, ".env")
if os.path.isfile(_dotenv_path):
    load_dotenv(_dotenv_path, override=False)

# For end-users, we want designs to generate exactly where they run the command.
# Developers can still override OPENLANE_ROOT with an environment variable.
OPENLANE_ROOT = os.environ.get("OPENLANE_ROOT", os.getcwd())
DESIGNS_DIR = os.path.join(OPENLANE_ROOT, "designs")
SCRIPTS_DIR = os.path.join(WORKSPACE_ROOT, "scripts")

CLOUD_CONFIG = {
    "model": os.environ.get("NVIDIA_MODEL", "openai/meta/llama-3.3-70b-instruct"),
    "base_url": os.environ.get("NVIDIA_BASE_URL", "https://integrate.api.nvidia.com/v1"),
    "api_key": os.environ.get("NVIDIA_API_KEY", ""),
}

DEEPSEEK_CONFIG = {
    "model": os.environ.get("DEEPSEEK_MODEL", "openai/deepseek-ai/deepseek-v3.2"),
    "base_url": os.environ.get("DEEPSEEK_BASE_URL", "https://integrate.api.nvidia.com/v1"),
    "api_key": os.environ.get("DEEPSEEK_API_KEY", ""),
    "extra_body": {"chat_template_kwargs": {"thinking": True}}
}


GLM_CONFIG = {
    "model": os.environ.get("GLM_MODEL", "glm-4-plus"),
    "base_url": os.environ.get("GLM_BASE_URL", "https://open.bigmodel.cn/api/paas/v4/"),
    "api_key": os.environ.get("GLM_API_KEY", ""),
}

LOCAL_CONFIG = {
    "model": os.environ.get(
        "LLM_MODEL",
        "ollama/qwen2.5-coder:7b",
    ),
    "base_url": os.environ.get("LLM_BASE_URL", "http://localhost:11434"),
    "api_key": os.environ.get("LLM_API_KEY", "NA"),
}

GROQ_CONFIG = {
    "model": os.environ.get("GROQ_MODEL", "groq/llama-3.3-70b-versatile"),
    "base_url": "",  # litellm resolves groq routing from the model prefix
    "api_key": os.environ.get("GROQ_API_KEY", ""),
}

# Backward-compat alias used by parts of the codebase/docs
NVIDIA_CONFIG = CLOUD_CONFIG

# Expose active defaults (CLI chooses concrete backend)
LLM_MODEL = LOCAL_CONFIG["model"]
LLM_BASE_URL = LOCAL_CONFIG["base_url"]
LLM_API_KEY = LOCAL_CONFIG["api_key"]

def get_role_llm_config(role: str) -> Dict[str, str]:
    """
    Resolve the LLM config for a specific multi-agent role.
    Intelligently maps agent roles to preferred backend engines:
    - Defaulting Heavy Logic (Architect, Designer, Fixer, etc.) to GLM-4-Plus
    - Physical -> Groq (Blazing fast iterative speed)
    - Documenter/Reporter -> NVIDIA (Excellent prose generation, lower precision required)
    - Reasoning parts (Architect, Fixer, Debugger) -> DeepSeek via NVIDIA
    """
    role = role.lower()
    
    # 1. Select the preferred engine
    preferred_engine = GLM_CONFIG
    if role in ("fixer", "debugger", "reasoner"):
        preferred_engine = DEEPSEEK_CONFIG
    elif role in ("architect", "designer", "testbench_designer", "verifier", "manager", "physical"):
        preferred_engine = GLM_CONFIG
    elif role in ("documenter", "reporter", "doc_gen"):
        preferred_engine = GROQ_CONFIG

    # Helper to check if an engine has a valid API key
    def is_valid(cfg):
        k = cfg.get("api_key", "")
        return bool(k and k not in ("mock-key", "NA", ""))

    # 2. Fallback execution order (Preferred -> DeepSeek -> GLM -> NVIDIA -> Groq -> Local)
    # If the user's setup lacks a key for the preferred engine, shift to the next best
    engines = [preferred_engine, DEEPSEEK_CONFIG, GLM_CONFIG, CLOUD_CONFIG, GROQ_CONFIG, LOCAL_CONFIG]
    
    for cfg in engines:
        # LOCAL_CONFIG is always considered a valid fallback if we reach the end
        if cfg is LOCAL_CONFIG or is_valid(cfg):
            model = cfg.get("model", "")
            # Ensure proper prefixing: use 'openai/' for custom OpenAI-compatible endpoints like Zhipu
            if cfg is GLM_CONFIG and not model.startswith("openai/"):
                model = f"openai/{model}"
            if cfg is DEEPSEEK_CONFIG and not model.startswith("openai/"):
                model = f"openai/{model}"
            
            result = {
                "model": model,
                "api_key": cfg.get("api_key", ""),
                "base_url": cfg.get("base_url", "")
            }
            if "extra_body" in cfg:
                result["extra_body"] = cfg["extra_body"]
            return result
            
    return LOCAL_CONFIG.copy()

# Portable OSS-PDK profiles (adapter-style)
PDK_PROFILES: Dict[str, Dict[str, Any]] = {
    "sky130": {
        "pdk": "sky130A",
        "std_cell_library": "sky130_fd_sc_hd",
        "default_clock_period": "10.0",
    },
    "gf180": {
        "pdk": "gf180mcuC",
        "std_cell_library": "gf180mcu_fd_sc_mcu7t5v0",
        "default_clock_period": "15.0",
    },
}

DEFAULT_PDK_PROFILE = os.environ.get("PDK_PROFILE", "sky130").strip().lower()
if DEFAULT_PDK_PROFILE not in PDK_PROFILES:
    DEFAULT_PDK_PROFILE = "sky130"

# Tool Settings
PDK_ROOT = os.environ.get("PDK_ROOT", os.path.expanduser("~/.ciel"))
PDK = os.environ.get("PDK", PDK_PROFILES[DEFAULT_PDK_PROFILE]["pdk"])
OPENLANE_IMAGE = "ghcr.io/the-openroad-project/openlane:ff5509f65b17bfa4068d5336495ab1718987ff69-amd64"

# Simulation/Coverage adapter defaults
SIM_BACKEND_DEFAULT = os.environ.get("SIM_BACKEND_DEFAULT", "auto").strip().lower()
if SIM_BACKEND_DEFAULT not in {"auto", "verilator", "iverilog"}:
    SIM_BACKEND_DEFAULT = "auto"

COVERAGE_FALLBACK_POLICY_DEFAULT = os.environ.get("COVERAGE_FALLBACK_POLICY", "fallback_oss").strip().lower()
if COVERAGE_FALLBACK_POLICY_DEFAULT not in {"fail_closed", "fallback_oss", "skip"}:
    COVERAGE_FALLBACK_POLICY_DEFAULT = "fallback_oss"

COVERAGE_PROFILE_DEFAULT = os.environ.get("COVERAGE_PROFILE", "balanced").strip().lower()
if COVERAGE_PROFILE_DEFAULT not in {"balanced", "aggressive", "relaxed"}:
    COVERAGE_PROFILE_DEFAULT = "balanced"


def _resolve_tool_binary(bin_name: str, env_var: Optional[str] = None) -> str:
    """Resolve tool binary using configured roots before PATH.

    Fallback order:
    1) Explicit env var for that tool (if provided)
    2) OSS_CAD_SUITE_HOME/bin
    3) WORKSPACE_ROOT/oss-cad-suite/bin
    4) /home/vickynishad/oss-cad-suite/bin
    5) bin_name from PATH
    """
    explicit = os.environ.get(env_var, "").strip() if env_var else ""
    if explicit and os.path.exists(explicit):
        return explicit

    roots = []
    oss_home = os.environ.get("OSS_CAD_SUITE_HOME", "").strip()
    if oss_home:
        roots.append(oss_home)
    roots.append(os.path.join(WORKSPACE_ROOT, "oss-cad-suite"))

    for root in roots:
        candidate = os.path.join(root, "bin", bin_name)
        if os.path.exists(candidate):
            return candidate

    return bin_name


OSS_CAD_SUITE_ROOT = os.environ.get("OSS_CAD_SUITE_HOME", os.path.join(WORKSPACE_ROOT, "oss-cad-suite"))
SBY_BIN = _resolve_tool_binary("sby", env_var="SBY_BIN")
YOSYS_BIN = _resolve_tool_binary("yosys", env_var="YOSYS_BIN")
EQY_BIN = _resolve_tool_binary("eqy", env_var="EQY_BIN")


def get_pdk_profile(profile: str) -> Dict[str, Any]:
    key = (profile or DEFAULT_PDK_PROFILE).strip().lower()
    if key not in PDK_PROFILES:
        key = "sky130"
    data = dict(PDK_PROFILES[key])
    data["profile"] = key
    return data


def get_toolchain_diagnostics() -> Dict[str, Any]:
    """Return resolved toolchain paths and existence info for startup checks."""
    bins = {
        "sby": SBY_BIN,
        "yosys": YOSYS_BIN,
        "eqy": EQY_BIN,
    }
    return {
        "workspace_root": WORKSPACE_ROOT,
        "openlane_root": OPENLANE_ROOT,
        "pdk_root": PDK_ROOT,
        "pdk": PDK,
        "oss_cad_suite_home": os.environ.get("OSS_CAD_SUITE_HOME", ""),
        "bins": {
            name: {"path": path, "exists": os.path.exists(path) if os.path.isabs(path) else False}
            for name, path in bins.items()
        },
    }