superrtl 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- superrtl/__init__.py +6 -0
- superrtl/cli.py +203 -0
- superrtl/resources/__init__.py +13 -0
- superrtl/resources/skills.py +56 -0
- superrtl/resources/templates.py +54 -0
- superrtl/runtime.py +117 -0
- superrtl/server.py +246 -0
- superrtl/setup.py +217 -0
- superrtl/shared/skills/verilog_cdc.md +213 -0
- superrtl/shared/skills/verilog_combinational_logic.md +102 -0
- superrtl/shared/skills/verilog_fifo.md +58 -0
- superrtl/shared/skills/verilog_fifo_async.md +228 -0
- superrtl/shared/skills/verilog_fifo_sync.md +150 -0
- superrtl/shared/skills/verilog_fir_filter.md +146 -0
- superrtl/shared/skills/verilog_fsm.md +54 -0
- superrtl/shared/skills/verilog_fsm_design.md +189 -0
- superrtl/shared/skills/verilog_sequential_logic.md +143 -0
- superrtl/shared/templates/counter.v +18 -0
- superrtl/shared/templates/register.v +19 -0
- superrtl/tools/__init__.py +19 -0
- superrtl/tools/compile.py +112 -0
- superrtl/tools/lint.py +88 -0
- superrtl/tools/simulate.py +113 -0
- superrtl/tools/synthesize.py +112 -0
- superrtl/tools/testbench.py +157 -0
- superrtl/tools/waveform.py +158 -0
- superrtl/utils/__init__.py +59 -0
- superrtl/utils/verilog.py +29 -0
- superrtl-0.2.0.dist-info/METADATA +360 -0
- superrtl-0.2.0.dist-info/RECORD +33 -0
- superrtl-0.2.0.dist-info/WHEEL +4 -0
- superrtl-0.2.0.dist-info/entry_points.txt +2 -0
- superrtl-0.2.0.dist-info/licenses/LICENSE +21 -0
superrtl/__init__.py
ADDED
superrtl/cli.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperRTL CLI 命令行工具
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.syntax import Syntax
|
|
11
|
+
|
|
12
|
+
from . import __version__
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group()
|
|
18
|
+
@click.version_option(version=__version__, prog_name="superrtl")
|
|
19
|
+
def main():
|
|
20
|
+
"""SuperRTL - Verilog EDA 工具的 MCP/CLI 客户端"""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@main.command()
|
|
25
|
+
@click.argument("file", type=click.Path(exists=True))
|
|
26
|
+
@click.option("--top", "-t", default="", help="顶层模块名")
|
|
27
|
+
def compile(file: str, top: str):
|
|
28
|
+
"""编译 Verilog 代码"""
|
|
29
|
+
from .tools import compile_verilog
|
|
30
|
+
|
|
31
|
+
code = Path(file).read_text()
|
|
32
|
+
result = asyncio.run(compile_verilog(code, top))
|
|
33
|
+
|
|
34
|
+
if result.get("success"):
|
|
35
|
+
console.print(f"[OK] [green]编译成功[/green]: {result.get('top_module')}")
|
|
36
|
+
console.print(f" 耗时: {result.get('duration')}s")
|
|
37
|
+
else:
|
|
38
|
+
console.print("[FAIL] [red]编译失败[/red]")
|
|
39
|
+
for error in result.get("errors", []):
|
|
40
|
+
console.print(f" {error}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@main.command()
|
|
44
|
+
@click.argument("design", type=click.Path(exists=True))
|
|
45
|
+
@click.argument("testbench", type=click.Path(exists=True))
|
|
46
|
+
@click.option("--timeout", "-t", default=30, help="超时时间 (秒)")
|
|
47
|
+
def simulate(design: str, testbench: str, timeout: int):
|
|
48
|
+
"""运行 Verilog 仿真"""
|
|
49
|
+
from .tools import simulate_verilog
|
|
50
|
+
|
|
51
|
+
design_code = Path(design).read_text()
|
|
52
|
+
tb_code = Path(testbench).read_text()
|
|
53
|
+
result = asyncio.run(simulate_verilog(design_code, tb_code, timeout))
|
|
54
|
+
|
|
55
|
+
if result.get("success"):
|
|
56
|
+
if result.get("passed"):
|
|
57
|
+
console.print("[OK] [green]仿真通过[/green]")
|
|
58
|
+
else:
|
|
59
|
+
console.print("[FAIL] [red]仿真失败[/red]")
|
|
60
|
+
console.print(f" 耗时: {result.get('duration')}s")
|
|
61
|
+
if result.get("output"):
|
|
62
|
+
console.print(f" 输出: {result['output'][:200]}")
|
|
63
|
+
else:
|
|
64
|
+
console.print(f"[FAIL] [red]仿真错误[/red]: {result.get('error')}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@main.command()
|
|
68
|
+
@click.argument("file", type=click.Path(exists=True))
|
|
69
|
+
@click.option(
|
|
70
|
+
"--style", "-s", default="default",
|
|
71
|
+
type=click.Choice(["default", "strict", "relaxed"])
|
|
72
|
+
)
|
|
73
|
+
def lint(file: str, style: str):
|
|
74
|
+
"""Lint 检查"""
|
|
75
|
+
from .tools import lint_verilog
|
|
76
|
+
|
|
77
|
+
code = Path(file).read_text()
|
|
78
|
+
result = asyncio.run(lint_verilog(code, style))
|
|
79
|
+
|
|
80
|
+
if result.get("success"):
|
|
81
|
+
console.print("[OK] [green]Lint 通过[/green]")
|
|
82
|
+
else:
|
|
83
|
+
console.print("[FAIL] [red]Lint 失败[/red]")
|
|
84
|
+
|
|
85
|
+
if result.get("warnings"):
|
|
86
|
+
console.print(f" 警告: {len(result['warnings'])}")
|
|
87
|
+
for w in result["warnings"][:5]:
|
|
88
|
+
console.print(f" - {w}")
|
|
89
|
+
|
|
90
|
+
if result.get("errors"):
|
|
91
|
+
console.print(f" 错误: {len(result['errors'])}")
|
|
92
|
+
for e in result["errors"][:5]:
|
|
93
|
+
console.print(f" - {e}")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@main.command()
|
|
97
|
+
@click.argument("file", type=click.Path(exists=True))
|
|
98
|
+
@click.option("--top", "-t", default="", help="顶层模块名")
|
|
99
|
+
@click.option("--target", default="generic", type=click.Choice(["generic", "xilinx", "ice40"]))
|
|
100
|
+
def synthesize(file: str, top: str, target: str):
|
|
101
|
+
"""综合检查"""
|
|
102
|
+
from .tools import synthesize_verilog
|
|
103
|
+
|
|
104
|
+
code = Path(file).read_text()
|
|
105
|
+
result = asyncio.run(synthesize_verilog(code, top, target))
|
|
106
|
+
|
|
107
|
+
if result.get("success"):
|
|
108
|
+
console.print(f"[OK] [green]综合通过[/green]: {result.get('top_module')}")
|
|
109
|
+
if result.get("resources"):
|
|
110
|
+
console.print(" 资源:")
|
|
111
|
+
for k, v in result["resources"].items():
|
|
112
|
+
console.print(f" {k}: {v}")
|
|
113
|
+
else:
|
|
114
|
+
console.print("[FAIL] [red]综合失败[/red]")
|
|
115
|
+
for error in result.get("errors", []):
|
|
116
|
+
console.print(f" {error}")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@main.command()
|
|
120
|
+
@click.argument("file", type=click.Path(exists=True))
|
|
121
|
+
@click.option("--style", "-s", default="basic", type=click.Choice(["basic", "comprehensive"]))
|
|
122
|
+
@click.option("--cases", "-c", default=3, help="测试用例数量")
|
|
123
|
+
@click.option("--output", "-o", default="", help="输出文件路径")
|
|
124
|
+
def testbench(file: str, style: str, cases: int, output: str):
|
|
125
|
+
"""生成 Testbench"""
|
|
126
|
+
from .tools import generate_testbench
|
|
127
|
+
|
|
128
|
+
code = Path(file).read_text()
|
|
129
|
+
result = asyncio.run(generate_testbench(code, style, cases))
|
|
130
|
+
|
|
131
|
+
if result.get("success"):
|
|
132
|
+
tb_code = result["testbench"]
|
|
133
|
+
|
|
134
|
+
if output:
|
|
135
|
+
Path(output).write_text(tb_code)
|
|
136
|
+
console.print(f"[OK] [green]Testbench 已保存[/green]: {output}")
|
|
137
|
+
else:
|
|
138
|
+
console.print(Syntax(tb_code, "verilog"))
|
|
139
|
+
else:
|
|
140
|
+
console.print("[FAIL] [red]生成失败[/red]")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@main.command()
|
|
144
|
+
@click.argument("file", type=click.Path(exists=True))
|
|
145
|
+
@click.option("--signals", "-s", multiple=True, help="要分析的信号")
|
|
146
|
+
def waveform(file: str, signals: tuple):
|
|
147
|
+
"""分析波形"""
|
|
148
|
+
from .tools import analyze_waveform
|
|
149
|
+
|
|
150
|
+
result = asyncio.run(
|
|
151
|
+
analyze_waveform(vcd_file=file, signals=list(signals) if signals else None)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if result.get("success"):
|
|
155
|
+
console.print("[INFO] [blue]波形分析[/blue]")
|
|
156
|
+
console.print(f" 信号数: {len(result.get('signals', {}))}")
|
|
157
|
+
if result.get("ascii_waveform"):
|
|
158
|
+
console.print("\n" + result["ascii_waveform"])
|
|
159
|
+
else:
|
|
160
|
+
console.print(f"[FAIL] [red]分析失败[/red]: {result.get('error')}")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@main.command("check-tools")
|
|
164
|
+
def check_tools_cmd():
|
|
165
|
+
"""检查 EDA 工具安装状态"""
|
|
166
|
+
from .setup import check_tools
|
|
167
|
+
check_tools()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@main.command()
|
|
171
|
+
@click.option("--force", "-f", is_flag=True, help="强制重新安装")
|
|
172
|
+
def setup(force: bool):
|
|
173
|
+
"""安装 EDA 工具(首次运行时自动下载)"""
|
|
174
|
+
from .setup import install_tools
|
|
175
|
+
install_tools(force=force)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@main.command()
|
|
179
|
+
def uninstall():
|
|
180
|
+
"""卸载 EDA 工具"""
|
|
181
|
+
from .setup import uninstall_tools
|
|
182
|
+
|
|
183
|
+
if click.confirm("确定要卸载 EDA 工具吗?"):
|
|
184
|
+
uninstall_tools()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@main.command()
|
|
188
|
+
@click.option("--transport", "-t", default="stdio", type=click.Choice(["stdio", "sse"]))
|
|
189
|
+
@click.option("--port", "-p", default=8080, help="SSE 端口")
|
|
190
|
+
def mcp(transport: str, port: int):
|
|
191
|
+
"""启动 MCP Server"""
|
|
192
|
+
from .server import main as server_main
|
|
193
|
+
|
|
194
|
+
if transport == "stdio":
|
|
195
|
+
console.print("[START] [green]启动 SuperRTL MCP Server (stdio 模式)[/green]")
|
|
196
|
+
server_main()
|
|
197
|
+
else:
|
|
198
|
+
console.print(f"[START] [green]启动 SuperRTL MCP Server (SSE 模式, 端口: {port})[/green]")
|
|
199
|
+
console.print("[yellow]SSE 模式暂未实现,请使用 stdio 模式[/yellow]")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
main()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Skills 资源管理
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# Skills 目录
|
|
9
|
+
# 优先使用包内的 shared 目录(安装后)
|
|
10
|
+
# 回退到项目根目录的 shared 目录(开发模式)
|
|
11
|
+
_PACKAGE_DIR = Path(__file__).parent.parent
|
|
12
|
+
SKILLS_DIR = _PACKAGE_DIR / "shared" / "skills"
|
|
13
|
+
|
|
14
|
+
# 如果包内没有,尝试项目根目录(开发模式)
|
|
15
|
+
if not SKILLS_DIR.exists():
|
|
16
|
+
SKILLS_DIR = _PACKAGE_DIR.parent.parent / "shared" / "skills"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def list_skills() -> str:
|
|
20
|
+
"""列出所有可用的 Skills"""
|
|
21
|
+
skills = []
|
|
22
|
+
|
|
23
|
+
if SKILLS_DIR.exists():
|
|
24
|
+
for skill_file in SKILLS_DIR.glob("*.md"):
|
|
25
|
+
skill_name = skill_file.stem.replace("verilog_", "")
|
|
26
|
+
skills.append(skill_name)
|
|
27
|
+
|
|
28
|
+
return json.dumps({
|
|
29
|
+
"skills": skills,
|
|
30
|
+
"count": len(skills)
|
|
31
|
+
}, ensure_ascii=False)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def get_skill(skill_name: str) -> str:
|
|
35
|
+
"""获取指定 Skill 的内容"""
|
|
36
|
+
# 尝试不同的文件名格式
|
|
37
|
+
possible_names = [
|
|
38
|
+
f"verilog_{skill_name}.md",
|
|
39
|
+
f"{skill_name}.md",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
for name in possible_names:
|
|
43
|
+
skill_file = SKILLS_DIR / name
|
|
44
|
+
if skill_file.exists():
|
|
45
|
+
return skill_file.read_text(encoding="utf-8")
|
|
46
|
+
|
|
47
|
+
# 如果找不到,返回错误
|
|
48
|
+
available = []
|
|
49
|
+
if SKILLS_DIR.exists():
|
|
50
|
+
for f in SKILLS_DIR.glob("*.md"):
|
|
51
|
+
available.append(f.stem.replace("verilog_", ""))
|
|
52
|
+
|
|
53
|
+
return json.dumps({
|
|
54
|
+
"error": f"Skill 未找到: {skill_name}",
|
|
55
|
+
"available": available
|
|
56
|
+
}, ensure_ascii=False)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
模板资源管理
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# 模板目录
|
|
9
|
+
# 优先使用包内的 shared 目录(安装后)
|
|
10
|
+
# 回退到项目根目录的 shared 目录(开发模式)
|
|
11
|
+
_PACKAGE_DIR = Path(__file__).parent.parent
|
|
12
|
+
TEMPLATES_DIR = _PACKAGE_DIR / "shared" / "templates"
|
|
13
|
+
|
|
14
|
+
# 如果包内没有,尝试项目根目录(开发模式)
|
|
15
|
+
if not TEMPLATES_DIR.exists():
|
|
16
|
+
TEMPLATES_DIR = _PACKAGE_DIR.parent.parent / "shared" / "templates"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def list_templates() -> str:
|
|
20
|
+
"""列出所有可用的模板"""
|
|
21
|
+
templates = []
|
|
22
|
+
|
|
23
|
+
if TEMPLATES_DIR.exists():
|
|
24
|
+
for template_file in TEMPLATES_DIR.glob("*.v"):
|
|
25
|
+
templates.append(template_file.stem)
|
|
26
|
+
|
|
27
|
+
return json.dumps({
|
|
28
|
+
"templates": templates,
|
|
29
|
+
"count": len(templates)
|
|
30
|
+
}, ensure_ascii=False)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def get_template(template_name: str) -> str:
|
|
34
|
+
"""获取指定模板的内容"""
|
|
35
|
+
possible_names = [
|
|
36
|
+
f"{template_name}.v",
|
|
37
|
+
f"{template_name}.sv",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
for name in possible_names:
|
|
41
|
+
template_file = TEMPLATES_DIR / name
|
|
42
|
+
if template_file.exists():
|
|
43
|
+
return template_file.read_text(encoding="utf-8")
|
|
44
|
+
|
|
45
|
+
# 如果找不到,返回错误
|
|
46
|
+
available = []
|
|
47
|
+
if TEMPLATES_DIR.exists():
|
|
48
|
+
for f in TEMPLATES_DIR.glob("*.v"):
|
|
49
|
+
available.append(f.stem)
|
|
50
|
+
|
|
51
|
+
return json.dumps({
|
|
52
|
+
"error": f"模板未找到: {template_name}",
|
|
53
|
+
"available": available
|
|
54
|
+
}, ensure_ascii=False)
|
superrtl/runtime.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
运行时环境管理
|
|
3
|
+
|
|
4
|
+
管理 EDA 工具的路径和环境变量
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_project_root() -> Path:
|
|
13
|
+
"""获取项目根目录(当前工作目录)"""
|
|
14
|
+
return Path.cwd()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_tools_dir() -> Path:
|
|
18
|
+
"""获取工具安装目录"""
|
|
19
|
+
return get_project_root() / ".superrtl"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_oss_cad_suite_dir() -> Path:
|
|
23
|
+
"""获取 OSS CAD Suite 目录"""
|
|
24
|
+
return get_tools_dir() / "oss-cad-suite"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_tools_bin_dir() -> Path:
|
|
28
|
+
"""获取工具 bin 目录"""
|
|
29
|
+
return get_oss_cad_suite_dir() / "bin"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_tool_path(tool_name: str) -> str:
|
|
33
|
+
"""
|
|
34
|
+
获取工具的完整路径
|
|
35
|
+
|
|
36
|
+
优先使用本地安装的工具,回退到系统 PATH
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
tool_name: 工具名称 (如 iverilog, yosys, verilator)
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
工具的完整路径或工具名(回退到系统 PATH)
|
|
43
|
+
"""
|
|
44
|
+
bin_dir = get_tools_bin_dir()
|
|
45
|
+
|
|
46
|
+
if sys.platform == "win32":
|
|
47
|
+
tool_path = bin_dir / f"{tool_name}.exe"
|
|
48
|
+
else:
|
|
49
|
+
tool_path = bin_dir / tool_name
|
|
50
|
+
|
|
51
|
+
if tool_path.exists():
|
|
52
|
+
return str(tool_path)
|
|
53
|
+
|
|
54
|
+
# 回退到系统 PATH
|
|
55
|
+
return tool_name
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_environ() -> dict:
|
|
59
|
+
"""
|
|
60
|
+
获取包含工具路径的环境变量
|
|
61
|
+
|
|
62
|
+
将本地工具目录添加到 PATH 最前面
|
|
63
|
+
"""
|
|
64
|
+
env = os.environ.copy()
|
|
65
|
+
|
|
66
|
+
# 添加 bin 和 lib 目录到 PATH
|
|
67
|
+
oss_dir = get_oss_cad_suite_dir()
|
|
68
|
+
dirs_to_add = [
|
|
69
|
+
str(oss_dir / "bin"),
|
|
70
|
+
str(oss_dir / "lib"),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
current_path = env.get("PATH", "")
|
|
74
|
+
for d in dirs_to_add:
|
|
75
|
+
if d not in current_path:
|
|
76
|
+
current_path = d + os.pathsep + current_path
|
|
77
|
+
|
|
78
|
+
env["PATH"] = current_path
|
|
79
|
+
|
|
80
|
+
return env
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def tools_installed() -> bool:
|
|
84
|
+
"""检查工具是否已安装"""
|
|
85
|
+
bin_dir = get_tools_bin_dir()
|
|
86
|
+
|
|
87
|
+
if sys.platform == "win32":
|
|
88
|
+
return (bin_dir / "iverilog.exe").exists()
|
|
89
|
+
else:
|
|
90
|
+
return (bin_dir / "iverilog").exists()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_tools_status() -> dict:
|
|
94
|
+
"""获取工具安装状态"""
|
|
95
|
+
tools = {
|
|
96
|
+
"iverilog": "Icarus Verilog (编译仿真)",
|
|
97
|
+
"vvp": "Icarus Verilog (仿真器)",
|
|
98
|
+
"yosys": "Yosys (综合检查)",
|
|
99
|
+
"verilator": "Verilator (Lint)",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
status = {}
|
|
103
|
+
bin_dir = get_tools_bin_dir()
|
|
104
|
+
|
|
105
|
+
for tool, desc in tools.items():
|
|
106
|
+
if sys.platform == "win32":
|
|
107
|
+
tool_path = bin_dir / f"{tool}.exe"
|
|
108
|
+
else:
|
|
109
|
+
tool_path = bin_dir / tool
|
|
110
|
+
|
|
111
|
+
status[tool] = {
|
|
112
|
+
"name": desc,
|
|
113
|
+
"installed": tool_path.exists(),
|
|
114
|
+
"path": str(tool_path) if tool_path.exists() else None,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return status
|
superrtl/server.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SuperRTL MCP Server
|
|
3
|
+
|
|
4
|
+
提供 Verilog EDA 工具的 MCP 接口
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
from mcp import types
|
|
11
|
+
from mcp.server import Server
|
|
12
|
+
from mcp.server.stdio import stdio_server
|
|
13
|
+
|
|
14
|
+
from .resources import get_skill, get_template, list_skills, list_templates
|
|
15
|
+
from .tools import (
|
|
16
|
+
analyze_waveform,
|
|
17
|
+
compile_verilog,
|
|
18
|
+
generate_testbench,
|
|
19
|
+
lint_verilog,
|
|
20
|
+
simulate_verilog,
|
|
21
|
+
synthesize_verilog,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# 创建 MCP Server 实例
|
|
25
|
+
app = Server("superrtl")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ============ Tools 定义 ============
|
|
29
|
+
|
|
30
|
+
TOOLS = [
|
|
31
|
+
types.Tool(
|
|
32
|
+
name="compile_verilog",
|
|
33
|
+
description="使用 Icarus Verilog 编译 Verilog 代码",
|
|
34
|
+
inputSchema={
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"code": {"type": "string", "description": "Verilog 源代码"},
|
|
38
|
+
"top_module": {"type": "string", "description": "顶层模块名 (可选)", "default": ""},
|
|
39
|
+
},
|
|
40
|
+
"required": ["code"],
|
|
41
|
+
},
|
|
42
|
+
),
|
|
43
|
+
types.Tool(
|
|
44
|
+
name="simulate_verilog",
|
|
45
|
+
description="使用 Icarus Verilog 运行仿真",
|
|
46
|
+
inputSchema={
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"code": {"type": "string", "description": "Verilog 源代码"},
|
|
50
|
+
"testbench": {"type": "string", "description": "测试平台代码"},
|
|
51
|
+
"timeout": {"type": "integer", "description": "仿真超时时间 (秒)", "default": 30},
|
|
52
|
+
},
|
|
53
|
+
"required": ["code", "testbench"],
|
|
54
|
+
},
|
|
55
|
+
),
|
|
56
|
+
types.Tool(
|
|
57
|
+
name="lint_verilog",
|
|
58
|
+
description="使用 Verilator 进行 Lint 检查",
|
|
59
|
+
inputSchema={
|
|
60
|
+
"type": "object",
|
|
61
|
+
"properties": {
|
|
62
|
+
"code": {"type": "string", "description": "Verilog 源代码"},
|
|
63
|
+
"style": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"description": "检查风格",
|
|
66
|
+
"enum": ["default", "strict", "relaxed"],
|
|
67
|
+
"default": "default",
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
"required": ["code"],
|
|
71
|
+
},
|
|
72
|
+
),
|
|
73
|
+
types.Tool(
|
|
74
|
+
name="synthesize_verilog",
|
|
75
|
+
description="使用 Yosys 进行综合检查",
|
|
76
|
+
inputSchema={
|
|
77
|
+
"type": "object",
|
|
78
|
+
"properties": {
|
|
79
|
+
"code": {"type": "string", "description": "Verilog 源代码"},
|
|
80
|
+
"top_module": {"type": "string", "description": "顶层模块名", "default": ""},
|
|
81
|
+
"target": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": "目标工艺库",
|
|
84
|
+
"enum": ["generic", "xilinx", "ice40"],
|
|
85
|
+
"default": "generic",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
"required": ["code"],
|
|
89
|
+
},
|
|
90
|
+
),
|
|
91
|
+
types.Tool(
|
|
92
|
+
name="generate_testbench",
|
|
93
|
+
description="自动生成 Testbench",
|
|
94
|
+
inputSchema={
|
|
95
|
+
"type": "object",
|
|
96
|
+
"properties": {
|
|
97
|
+
"code": {"type": "string", "description": "Verilog 源代码"},
|
|
98
|
+
"style": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "测试风格",
|
|
101
|
+
"enum": ["basic", "comprehensive"],
|
|
102
|
+
"default": "basic",
|
|
103
|
+
},
|
|
104
|
+
"test_cases": {"type": "integer", "description": "测试用例数量", "default": 3},
|
|
105
|
+
},
|
|
106
|
+
"required": ["code"],
|
|
107
|
+
},
|
|
108
|
+
),
|
|
109
|
+
types.Tool(
|
|
110
|
+
name="analyze_waveform",
|
|
111
|
+
description="分析 VCD 波形文件",
|
|
112
|
+
inputSchema={
|
|
113
|
+
"type": "object",
|
|
114
|
+
"properties": {
|
|
115
|
+
"vcd_file": {"type": "string", "description": "VCD 文件路径"},
|
|
116
|
+
"vcd_content": {"type": "string", "description": "VCD 内容 (直接传入)"},
|
|
117
|
+
"signals": {
|
|
118
|
+
"type": "array",
|
|
119
|
+
"items": {"type": "string"},
|
|
120
|
+
"description": "要分析的信号列表",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
),
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# ============ 注册处理器 ============
|
|
129
|
+
|
|
130
|
+
@app.list_tools()
|
|
131
|
+
async def list_tools() -> list[types.Tool]:
|
|
132
|
+
"""列出所有可用的工具"""
|
|
133
|
+
return TOOLS
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@app.call_tool()
|
|
137
|
+
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
|
|
138
|
+
"""处理工具调用"""
|
|
139
|
+
try:
|
|
140
|
+
if name == "compile_verilog":
|
|
141
|
+
result = await compile_verilog(
|
|
142
|
+
arguments["code"],
|
|
143
|
+
arguments.get("top_module", "")
|
|
144
|
+
)
|
|
145
|
+
elif name == "simulate_verilog":
|
|
146
|
+
result = await simulate_verilog(
|
|
147
|
+
arguments["code"],
|
|
148
|
+
arguments["testbench"],
|
|
149
|
+
arguments.get("timeout", 30)
|
|
150
|
+
)
|
|
151
|
+
elif name == "lint_verilog":
|
|
152
|
+
result = await lint_verilog(
|
|
153
|
+
arguments["code"],
|
|
154
|
+
arguments.get("style", "default")
|
|
155
|
+
)
|
|
156
|
+
elif name == "synthesize_verilog":
|
|
157
|
+
result = await synthesize_verilog(
|
|
158
|
+
arguments["code"],
|
|
159
|
+
arguments.get("top_module", ""),
|
|
160
|
+
arguments.get("target", "generic")
|
|
161
|
+
)
|
|
162
|
+
elif name == "generate_testbench":
|
|
163
|
+
result = await generate_testbench(
|
|
164
|
+
arguments["code"],
|
|
165
|
+
arguments.get("style", "basic"),
|
|
166
|
+
arguments.get("test_cases", 3)
|
|
167
|
+
)
|
|
168
|
+
elif name == "analyze_waveform":
|
|
169
|
+
result = await analyze_waveform(
|
|
170
|
+
arguments.get("vcd_file"),
|
|
171
|
+
arguments.get("vcd_content"),
|
|
172
|
+
arguments.get("signals")
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
result = {"success": False, "error": f"未知工具: {name}"}
|
|
176
|
+
|
|
177
|
+
return [types.TextContent(type="text", text=json.dumps(result, ensure_ascii=False))]
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
return [types.TextContent(
|
|
181
|
+
type="text",
|
|
182
|
+
text=json.dumps({"success": False, "error": str(e)}, ensure_ascii=False)
|
|
183
|
+
)]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ============ Resources 定义 ============
|
|
187
|
+
|
|
188
|
+
@app.list_resources()
|
|
189
|
+
async def list_resources() -> list[types.Resource]:
|
|
190
|
+
"""列出所有可用的资源"""
|
|
191
|
+
resources = []
|
|
192
|
+
|
|
193
|
+
# Skills
|
|
194
|
+
skills_data = json.loads(await list_skills())
|
|
195
|
+
for skill in skills_data.get("skills", []):
|
|
196
|
+
resources.append(types.Resource(
|
|
197
|
+
uri=f"skills://{skill}",
|
|
198
|
+
name=f"Skill: {skill}",
|
|
199
|
+
description=f"Verilog 设计模式: {skill}",
|
|
200
|
+
mimeType="text/markdown",
|
|
201
|
+
))
|
|
202
|
+
|
|
203
|
+
# Templates
|
|
204
|
+
templates_data = json.loads(await list_templates())
|
|
205
|
+
for template in templates_data.get("templates", []):
|
|
206
|
+
resources.append(types.Resource(
|
|
207
|
+
uri=f"templates://{template}",
|
|
208
|
+
name=f"Template: {template}",
|
|
209
|
+
description=f"Verilog 代码模板: {template}",
|
|
210
|
+
mimeType="text/plain",
|
|
211
|
+
))
|
|
212
|
+
|
|
213
|
+
return resources
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@app.read_resource()
|
|
217
|
+
async def read_resource(uri: str) -> str:
|
|
218
|
+
"""读取资源内容"""
|
|
219
|
+
try:
|
|
220
|
+
if uri.startswith("skills://"):
|
|
221
|
+
skill_name = uri.replace("skills://", "")
|
|
222
|
+
return await get_skill(skill_name)
|
|
223
|
+
elif uri.startswith("templates://"):
|
|
224
|
+
template_name = uri.replace("templates://", "")
|
|
225
|
+
return await get_template(template_name)
|
|
226
|
+
else:
|
|
227
|
+
return json.dumps({"error": f"未知资源 URI: {uri}"}, ensure_ascii=False)
|
|
228
|
+
except Exception as e:
|
|
229
|
+
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# ============ 启动函数 ============
|
|
233
|
+
|
|
234
|
+
async def run_stdio():
|
|
235
|
+
"""以 stdio 模式运行 (用于 MCP Host 连接)"""
|
|
236
|
+
async with stdio_server() as (read, write):
|
|
237
|
+
await app.run(read, write)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def main():
|
|
241
|
+
"""主入口"""
|
|
242
|
+
asyncio.run(run_stdio())
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
if __name__ == "__main__":
|
|
246
|
+
main()
|