IC 设计 AI 实战:OpenCode 跨服务器协同调用 EDA 工具全攻略
系列说明 :本文是《IC 设计私有化 AI 助手实战》系列第三篇。第一篇介绍了 Docker + OpenCode + Ollama 的本地部署方案;第二篇在第一篇基础上增加了部署细节;本篇进入真正的工程实战,聚焦 AI 如何跨服务器调度 EDA 工具,实现从 RTL 提交到综合报告的全流程自动化。
前置条件:已完成第一篇的环境部署,OpenCode 容器可正常启动,Ollama 服务运行中。
一、为什么需要跨服务器协同?
在真实的 IC 设计公司,工程师的工作站和 EDA License 服务器几乎从不在同一台机器上:

传统工作方式:工程师手动 SSH 登录 EDA 服务器,执行命令,再 SSH 回来取结果。这个过程本身没有问题,但一旦要让 OpenCode 替你完成这套操作------读文件、远程执行综合、取回报告、分析、再修改------就需要一套完整的 SSH 免密信任链。
本文解决的核心问题就是:如何让 OpenCode 容器像操作本地文件一样,透明地调度远端 EDA 服务器。
二、SSH 免密信任链配置
2.1 整体信任关系规划
在开始之前,先把需要建立的信任关系画清楚:
[你的工作站 your_pc]
↓ SSH 免密
[EDA 服务器 eda_server_01] ←→ [NFS 共享存储]
↓ 访问 License
[License 服务器 lic_server]

OpenCode 容器运行在 your_pc 上,需要能够免密 SSH 到 eda_server_01,在那里执行 dc_shell,并将结果写回 NFS 共享目录,容器再从本地挂载点读取结果。
2.2 生成 SSH 密钥对
# 在工作站上生成专用密钥(建议为 OpenCode 单独创建,便于权限管理)
ssh-keygen -t ed25519 -C "opencode-eda-agent" -f ~/.ssh/opencode_eda_key
# 查看公钥(下一步需要复制到 EDA 服务器)
cat ~/.ssh/opencode_eda_key.pub
2.3 将公钥部署到 EDA 服务器
# 方式一:ssh-copy-id(推荐,自动处理权限)
ssh-copy-id -i ~/.ssh/opencode_eda_key.pub your_id@eda_server_01
ssh-copy-id -i ~/.ssh/opencode_eda_key.pub your_id@eda_server_02
# 方式二:手动追加(适用于无 ssh-copy-id 的老系统)
cat ~/.ssh/opencode_eda_key.pub | \
ssh your_id@eda_server_01 \
"mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
2.4 配置 SSH 客户端别名
在工作站的 ~/.ssh/config 中为每台 EDA 服务器创建别名,这是后续所有脚本的基础:
# ~/.ssh/config
# EDA 计算服务器集群
Host eda01
HostName eda_server_01.internal
User your_id
IdentityFile ~/.ssh/opencode_eda_key
ServerAliveInterval 60
ServerAliveCountMax 10
ControlMaster auto
ControlPath ~/.ssh/cm_%h_%p_%r
ControlPersist 10m # 复用连接 10 分钟,避免重复握手开销
Host eda02
HostName eda_server_02.internal
User your_id
IdentityFile ~/.ssh/opencode_eda_key
ControlMaster auto
ControlPath ~/.ssh/cm_%h_%p_%r
ControlPersist 10m
Host lic_server
HostName license_server.internal
User your_id
IdentityFile ~/.ssh/opencode_eda_key
ControlMaster复用连接是关键优化:同一个 Host 的多次 SSH 调用复用同一个 TCP 连接,避免每次综合命令都重新 TLS 握手,在高频调用场景下节省 2-5 秒延迟。
2.5 验证免密连通性
# 验证免密登录(不应提示输入密码)
ssh eda01 hostname
ssh eda01 which dc_shell
# 验证 License 环境变量可正确加载
ssh eda01 "source ~/.bashrc && lmstat -a -c $LM_LICENSE_FILE | grep dc_shell"
# 验证 NFS 挂载可读写
ssh eda01 "ls /nfs/projects/ && touch /nfs/projects/.write_test && rm /nfs/projects/.write_test"
echo "NFS 读写正常"
2.6 将 SSH 密钥挂载进 OpenCode 容器
容器默认不携带宿主机的 SSH 密钥,需要在 alias 中显式挂载:
# 更新 /etc/profile.d/opencode.sh 中的 alias
alias opencode='docker run -it --rm \
--name opencode_$(whoami)_$(date +%s) \
--network host \
--user $(id -u):$(id -g) \
-v $HOME/.opencode:/root/.opencode \
-v $HOME/.opencode/cache:/root/.cache \
-v $HOME/.ssh:/root/.ssh:ro \ # 挂载 SSH 密钥(只读)
-v $HOME/.ssh/known_hosts:/root/.ssh/known_hosts \ # 挂载已知主机
-v $(pwd):/app \
-w /app \
opencode-v1.2.22'
:ro以只读模式挂载密钥目录,容器无法修改宿主机的 SSH 配置,安全性更高。
三、NFS 共享目录:数据流通的高速公路
3.1 统一项目目录结构
所有服务器通过 NFS 挂载相同的项目目录,工作站写入 RTL,EDA 服务器读取并写入报告,两端看到的是同一个文件系统视图:

NFS 挂载点:/nfs/projects/my_design_syn/(全服务器一致)
│
├── rtl/ ← 工作站写入(OpenCode 修改后自动同步)
├── scripts/ ← TCL / SDC 脚本
├── constraints/
├── utils/
├── reports/ ← EDA 服务器写入(综合后自动可见)
├── results/ ← EDA 服务器写入(网表输出)
└── logs/ ← 远程执行日志
└── run_syn_20240315_143022.log
3.2 挂载配置(EDA 服务器侧)
# 在 EDA 服务器 /etc/fstab 中配置永久挂载
nfs_server:/export/projects /nfs/projects nfs \
rw,hard,intr,rsize=65536,wsize=65536,timeo=600 0 0
# 验证挂载
mount | grep nfs
df -h /nfs/projects
四、核心实战:OpenCode 远程调度综合
这是本文的核心部分。通过 OpenCode 的 ! 命令执行器,让 AI 在本地分析、远端执行、本地取回结果,形成完整的闭环。
4.1 基础远程执行模式
在 OpenCode 对话框中,用 ! 前缀直接执行 shell 命令:
# OpenCode 中输入(! 前缀表示执行终端命令)
# 检查 EDA 服务器状态
! ssh eda01 "hostname && nproc && free -h"
# 检查 License 是否可用
! ssh eda01 "lmstat -a -c $LM_LICENSE_FILE 2>&1 | grep -E 'dc_shell|Users'"
# 确认项目目录可见
! ssh eda01 "ls /nfs/projects/my_design_syn/rtl/"
OpenCode 会把命令输出自动带入上下文,可以直接提问:
上面显示 eda01 有 32 核、64GB 内存,License 显示 dc_shell 有 2/4 个 token 在用。
请帮我评估当前服务器负载是否适合启动综合,以及如何设置 compile 的并行度。
4.2 远程启动综合(后台执行)
综合通常需要数十分钟,必须以后台方式运行,避免 SSH 断连导致任务中断:
# OpenCode 中输入:
! ssh eda01 "cd /nfs/projects/my_design_syn && \
nohup dc_shell -f scripts/dc_synthesis_template.tcl \
> logs/run_syn_$(date +%Y%m%d_%H%M%S).log 2>&1 & \
echo \$! > /tmp/dc_pid_$(whoami).txt && \
echo 'DC Shell 已在后台启动,PID:' \$(cat /tmp/dc_pid_$(whoami).txt)"
关键点解析:
| 技术 | 作用 |
|---|---|
nohup ... & |
后台运行,SSH 断连后进程继续 |
$(date +%Y%m%d_%H%M%S) |
日志文件名带时间戳,多次运行不覆盖 |
echo $! > /tmp/dc_pid.txt |
保存进程 PID,便于后续监控和终止 |
2>&1 |
stderr 合并到 stdout,确保错误信息也进日志 |
4.3 监控综合进度
# 实时查看日志尾部(OpenCode 中执行)
! ssh eda01 "tail -30 \$(ls -t /nfs/projects/my_design_syn/logs/*.log | head -1)"
# 检查进程是否仍在运行
! ssh eda01 "ps aux | grep dc_shell | grep -v grep"
# 查看当前综合阶段(从日志中提取关键行)
! ssh eda01 "grep -E '(===>|compile|Timing|Area|ERROR)' \
\$(ls -t /nfs/projects/my_design_syn/logs/*.log | head -1) | tail -20"
把监控输出粘贴给 OpenCode:
以下是综合日志的最新 20 行:
[粘贴输出]
请告诉我:
1. 当前跑到哪个阶段了
2. 有没有 ERROR 或 WARNING 需要关注
3. 估计还需要多久完成
4.4 综合完成后自动拉取报告并分析
# 检测综合是否完成(qor.rpt 存在则说明完成)
! ssh eda01 "ls -la /nfs/projects/my_design_syn/reports/qor.rpt 2>/dev/null \
&& echo '综合完成' || echo '尚未完成'"
# 由于 NFS 共享,报告实际上本地已可见,直接分析
! python3 utils/analyze_timing.py \
-i reports/timing_setup.rpt \
--top 20 --csv --rpt
然后把分析结果直接喂给 OpenCode:
@ reports/timing_analysis.rpt
综合已完成,以上是 timing 分析报告。
请按热点模块排序,给出优先级最高的3个修复建议,
每个建议注明是改 RTL 还是改 SDC,风险等级如何。
五、多服务器负载均衡:智能调度不同 EDA 任务
真实项目中,综合、仿真、形式验证往往需要同时进行,合理分配到不同服务器是提升整体效率的关键。

5.1 服务器健康检查脚本
在 OpenCode 中执行这个一键健康检查,让 AI 帮你决策调度:
# 批量检查所有 EDA 服务器状态
! for host in eda01 eda02 eda03; do
echo "=== $host ===";
ssh -o ConnectTimeout=3 $host \
"echo CPU: \$(nproc) cores && \
echo Load: \$(uptime | awk -F'load average:' '{print \$2}') && \
echo MEM: \$(free -h | awk 'NR==2{print \$4}') free && \
echo DC_Jobs: \$(pgrep -c dc_shell || echo 0)" 2>/dev/null \
|| echo "$host 不可达";
done
将输出交给 OpenCode 分析:
以上是三台 EDA 服务器的当前状态。
我需要同时启动:
- 1个综合任务(需要 8 核,约 30 分钟)
- 1个 VCS 仿真任务(需要 16 核,约 2 小时)
请推荐分配方案,并给我对应的启动命令。
5.2 不同 EDA 工具分配到不同服务器
# 综合任务 → eda01(License 在此服务器附近,延迟最低)
! ssh eda01 "cd /nfs/projects/my_design_syn && \
nohup dc_shell -f scripts/dc_synthesis_template.tcl \
> logs/syn_$(date +%Y%m%d_%H%M%S).log 2>&1 &"
# 仿真任务 → eda02(16核大内存服务器)
! ssh eda02 "cd /nfs/projects/my_design_syn && \
nohup vcs -full64 -sverilog rtl/my_top.v tb/tb_my_top.v \
-o simv > logs/vcs_$(date +%Y%m%d_%H%M%S).log 2>&1 &"
# 形式验证 → eda03
! ssh eda03 "cd /nfs/projects/my_design_syn && \
nohup vc_static -f scripts/vc_static.tcl \
> logs/fv_$(date +%Y%m%d_%H%M%S).log 2>&1 &"
echo "三个任务已分别提交到 eda01/02/03,通过 NFS 目录查看进度"
5.3 并行等待与结果汇总
# 等待所有任务完成的轮询脚本
! while true; do
syn_done=$(ssh eda01 "ls /nfs/projects/my_design_syn/results/my_top_netlist.v 2>/dev/null && echo yes || echo no")
vcs_done=$(ssh eda02 "ls /nfs/projects/my_design_syn/simv 2>/dev/null && echo yes || echo no")
echo "综合: $syn_done | 仿真: $vcs_done | 时间: $(date +%H:%M:%S)"
[ "$syn_done" = "yes" ] && [ "$vcs_done" = "yes" ] && break
sleep 60
done
echo "所有任务完成!"
六、实战场景一:SDC 约束生成与远程验证闭环
这是本篇最核心的工程场景,展示从 RTL 到 SDC 再到综合验证的完整 AI 辅助闭环。
Step 1:AI 读取 RTL 生成初版 SDC
@ rtl/my_top.v
@ spec/design_spec.md
请分析 RTL 端口和规格书,为这个设计生成初版 SDC 约束。
要求:
1. 识别所有时钟域,按规格书频率设置 create_clock
2. 分析异步跨时钟域,自动添加 set_false_path
3. 对所有数据输入端口按 40% 周期设置 input_delay
4. 对 i_real_0~7、i_imag_0~7 这类宽总线,单独使用 BUFX4 作为 driving_cell
5. 不确定的参数加 # [REVIEW] 注释
Step 2:工程师 Review + 修改
OpenCode 生成 SDC 后,工程师检查 # [REVIEW] 标注处,根据实际 PDK 单元名修改 driving cell 等参数。
Step 3:推送到 NFS 并远程启动综合
# SDC 已在 NFS 共享目录,EDA 服务器直接可见
# 启动综合
! ssh eda01 "cd /nfs/projects/my_design_syn && \
nohup dc_shell -f scripts/dc_synthesis_template.tcl \
> logs/syn_round1_$(date +%Y%m%d_%H%M%S).log 2>&1 & \
echo PID: \$!"
Step 4:AI 监控日志,实时预警
# 每隔 5 分钟检查一次日志中的 WARNING/ERROR
! ssh eda01 "grep -c 'ERROR' \$(ls -t /nfs/projects/my_design_syn/logs/syn_*.log | head -1)"
日志中出现了 3 个 ERROR,内容如下:
[粘贴 ERROR 内容]
请判断这些 ERROR 是否会影响综合结果,哪些需要立即中止修复,哪些可以忽略继续跑。
Step 5:综合完成,AI 诊断 timing
# NFS 共享,报告本地直接可读
! python3 utils/analyze_timing.py -i reports/timing_setup.rpt --top 20 --csv
@ reports/timing_violations.csv
@ rtl/my_top.v
@ constraints/constraints_template.sdc
综合结果 WNS=-0.22ns,请根据以上三个文件,给出:
1. 违规根因(RTL 问题还是 SDC 约束不合理)
2. 具体修改建议(给出 diff 格式)
3. 预计修改后 WNS 能改善到多少
Step 6:AI 修改文件,自动触发下一轮综合
OpenCode 直接修改 constraints_template.sdc 后,执行:
! ssh eda01 "cd /nfs/projects/my_design_syn && \
nohup dc_shell -f scripts/dc_synthesis_template.tcl \
> logs/syn_round2_$(date +%Y%m%d_%H%M%S).log 2>&1 & \
echo Round 2 已启动,PID: \$!"
七、实战场景二:多文件 Agent 跨服务器 RTL-报告联合诊断
当 timing 违例较复杂时,需要 OpenCode 同时持有 RTL、TCL 脚本、timing 报告三个文件进行联合分析,并将修改结果直接触发远端重跑。
# 一次性加载三个文件
@ rtl/my_top.v
@ scripts/dc_synthesis_template.tcl
@ reports/timing_setup.rpt
timing 报告显示 u_twiddle_mult 路径 slack=-0.22ns。
请同时分析三个文件,给出:
1. RTL 中 twiddle_mult 的逻辑级数是否超过 1GHz 单周期预算
2. TCL 中是否有可以帮助该路径的 compile 选项未启用
3. SDC 中对该路径的约束是否合理
最后给出一个综合修改方案(同时修改 RTL 和 TCL),并在修改完成后自动启动新一轮综合。
OpenCode 修改文件后,执行远端重综合:
! ssh eda01 "cd /nfs/projects/my_design_syn && \
nohup dc_shell -f scripts/dc_synthesis_template.tcl \
> logs/syn_rtl_fix_$(date +%Y%m%d_%H%M%S).log 2>&1 & echo PID: \$!"
八、实战场景三:Timing 收敛自动化迭代
对于时序紧张的设计,反复"修改---综合---分析"是最耗时的环节。以下方案让 OpenCode 驱动这个循环自动运转。

8.1 自动迭代驱动脚本
在项目根目录创建 auto_converge.sh,让 OpenCode 生成并执行:
#!/bin/bash
# auto_converge.sh --- AI 驱动 timing 收敛自动化迭代
# 用法:bash auto_converge.sh [最大迭代次数]
MAX_ITER=${1:-5}
TARGET_WNS=0.0 # 目标:全部 timing MET
for iter in $(seq 1 $MAX_ITER); do
echo "========== 第 $iter 轮综合 =========="
# 启动远程综合
LOG="logs/syn_iter${iter}_$(date +%Y%m%d_%H%M%S).log"
ssh eda01 "cd /nfs/projects/my_design_syn && \
dc_shell -f scripts/dc_synthesis_template.tcl > $LOG 2>&1"
echo "综合完成,分析 timing..."
# 本地分析报告
WNS=$(python3 utils/analyze_timing.py \
-i reports/timing_setup.rpt 2>/dev/null | \
grep "WNS" | awk '{print $3}' | tr -d 'ns')
echo "第 $iter 轮 WNS = ${WNS}ns"
# 判断是否收敛
if python3 -c "exit(0 if float('${WNS:-"-999"}') >= 0 else 1)" 2>/dev/null; then
echo "✅ Timing MET!迭代完成,共 $iter 轮。"
exit 0
fi
# WNS 太差,退出让工程师介入
if python3 -c "exit(0 if float('${WNS:-"-999"}') < -0.5 else 1)" 2>/dev/null; then
echo "❌ WNS < -0.5ns,超出自动修复范围,请人工介入。"
exit 2
fi
echo "WNS=${WNS}ns,尝试 incremental compile..."
# 自动追加 incremental 优化到 TCL(如果还没有的话)
ssh eda01 "grep -q 'incremental' /nfs/projects/my_design_syn/scripts/dc_synthesis_template.tcl \
|| echo 'compile_ultra -incremental -no_autoungroup' \
>> /nfs/projects/my_design_syn/scripts/dc_synthesis_template.tcl"
done
echo "达到最大迭代次数 $MAX_ITER,最终 WNS=${WNS}ns"
在 OpenCode 中执行:
! bash auto_converge.sh 5
8.2 迭代记录可视化
迭代完成后,让 OpenCode 汇总每轮结果:
! grep "轮 WNS" logs/syn_iter*.log | sort
# 输出示例:
# 第 1 轮 WNS = -0.22ns
# 第 2 轮 WNS = -0.09ns
# 第 3 轮 WNS = +0.02ns ← Timing MET
以上是 3 轮迭代的 WNS 变化:-0.22 → -0.09 → +0.02
请分析:
1. 每轮改善的原因(从对应日志可以推断)
2. 第3轮 WNS 正了,但只有 0.02ns,在 PnR 阶段是否足够
3. 给出 PnR 前的建议裕量检查清单
九、运维进阶:远程任务管理与异常处理
9.1 任务状态统一监控
# 一键查看所有 EDA 服务器上的任务状态
! for host in eda01 eda02 eda03; do
echo "=== $host 当前运行任务 ==="
ssh $host "ps aux | grep -E '(dc_shell|vcs|calibre)' | grep -v grep | \
awk '{print \$2, \$11, \$12}'" 2>/dev/null || echo "无法连接"
done
9.2 远程任务清理
# 终止某台服务器上所有 DC 进程(谨慎使用)
! ssh eda01 "pkill -f dc_shell && echo 'DC 进程已清理'"
# 按 PID 精确终止(更安全)
! PID=$(ssh eda01 "cat /tmp/dc_pid_$(whoami).txt")
! ssh eda01 "kill $PID && echo '进程 $PID 已终止'"
9.3 SSH 连接异常处理
在要求 OpenCode 执行远程命令时,加上超时和错误检测可以避免任务卡死:
# 带超时的远程命令模板
! timeout 10 ssh -o ConnectTimeout=5 eda01 "hostname" \
&& echo "连接正常" \
|| echo "⚠️ eda01 连接异常,请检查网络或切换到 eda02"
9.4 License 超时自动重试
# License 等待脚本(综合常因 License 不够用而排队)
! ssh eda01 "
MAX_WAIT=1800 # 最多等待 30 分钟
INTERVAL=60
WAITED=0
while [ \$WAITED -lt \$MAX_WAIT ]; do
AVAIL=\$(lmstat -c \$LM_LICENSE_FILE -a 2>/dev/null | \
grep 'dc_shell' | awk '{print \$NF}')
[ \"\$AVAIL\" != \"0\" ] && echo 'License 可用,启动综合' && break
echo \"等待 License 中... (\${WAITED}s / \${MAX_WAIT}s)\"
sleep \$INTERVAL
WAITED=\$((WAITED + INTERVAL))
done
"
十、完整工作流总结
至此,我们构建了一套从 AI 生成约束到跨服务器自动综合的完整闭环:
工程师在 OpenCode 中输入需求
↓
AI 读取 RTL + 规格书 → 生成 SDC / TCL
↓
工程师 Review [REVIEW] 标注处(5 分钟)
↓
OpenCode 通过 SSH 免密启动 EDA 服务器综合
↓
后台轮询日志,实时预警 ERROR
↓
综合完成,NFS 共享报告本地直接可读
↓
analyze_timing.py 提取 WNS/TNS
↓
AI 联合 RTL + SDC + 报告三文件诊断
↓
给出三档修复方案 🟢🟡🔴
↓
OpenCode 修改文件 → 触发下一轮综合
↓
auto_converge.sh 驱动循环直到 Timing MET
| 环节 | 传统耗时 | AI 辅助耗时 | 提升倍数 |
|---|---|---|---|
| SDC 初版生成 | 2 小时 | 20 分钟 | 6× |
| 报告人工分析 | 30 分钟 | 3 分钟 | 10× |
| 违例定位与修复方案 | 1-4 小时 | 15 分钟 | 5-15× |
| 迭代轮次(达到收敛) | 3-7 天 | 当天完成 | 显著 |
十一、安全注意事项
实战中有几条红线需要严格遵守:
1. SSH 密钥权限管理
# 密钥文件权限必须严格,否则 SSH 拒绝使用
chmod 600 ~/.ssh/opencode_eda_key
chmod 644 ~/.ssh/opencode_eda_key.pub
chmod 700 ~/.ssh
2. 禁止将 RTL 或 SDC 文件发送到公网 AI 服务 OpenCode 配置使用内网 Ollama,确保 ~/.opencode/config.json 中的 baseURL 指向内网地址,而不是 api.openai.com 等公网端点。
3. NFS 挂载目录访问控制
# /etc/exports 中限制只有授权 IP 段可挂载
/export/projects 192.168.10.0/24(rw,sync,no_root_squash)
4. 容器内 SSH 密钥只读挂载 如第二章所述,使用 :ro 标志,确保容器无法修改宿主机的 SSH 配置。
系列下一篇预告:《IC 设计 AI 实战(三):基于 RAGFlow 的团队 PDK 知识库建设与 OpenCode 接入》 ------ 将全公司 PDF 规范文档转化为可查询的 AI 知识库,让每个工程师都能问到"资深老工程师级别"的答案。
参考资源
- SSH ControlMaster 文档:
man ssh_config- NFS 挂载优化参考:https://nfs.sourceforge.net/nfs-howto
- OpenCode 命令执行文档:https://opencode.ai/docs/commands