docs: 添加 Vivado 联动功能文档
- 添加 EDA 联动功能需求文档 - 添加 Vivado 联动前后端对接文档 - 添加 Vivado 联动功能技术设计文档
This commit is contained in:
368
docs/EDA联动功能需求文档.md
Normal file
368
docs/EDA联动功能需求文档.md
Normal file
@ -0,0 +1,368 @@
|
||||
# Vivado 联动功能需求文档
|
||||
|
||||
## 1. 项目背景
|
||||
|
||||
### 1.1 当前状态
|
||||
|
||||
IC Coder Plugin 目前支持:
|
||||
|
||||
- iverilog 仿真(内置 Windows 版本)
|
||||
- VCD 波形查看
|
||||
- Verilog 代码生成和文件操作
|
||||
|
||||
### 1.2 需求来源
|
||||
|
||||
用户需要在 VS Code 中直接调用本地 Vivado 工具,并将产出文件自动导入到项目中,完成从仿真到 FPGA 部署的完整流程。
|
||||
|
||||
### 1.3 Vivado 是什么?
|
||||
|
||||
**Vivado** 是 Xilinx(现 AMD)的 FPGA 开发工具,用于将 Verilog 代码部署到 FPGA 硬件:
|
||||
|
||||
- **综合(Synthesis)**:将 RTL 代码转换为门级网表
|
||||
- **实现(Implementation)**:布局布线,映射到具体 FPGA 芯片
|
||||
- **生成比特流(Bitstream)**:生成 .bit 配置文件用于烧录
|
||||
|
||||
**与 iverilog 的区别**:
|
||||
|
||||
- iverilog:只做**仿真验证**(软件层面验证逻辑)
|
||||
- Vivado:做**综合+实现+生成配置文件**(真正部署到硬件)
|
||||
|
||||
**典型开发流程**:
|
||||
|
||||
```
|
||||
编写 Verilog → iverilog 仿真验证 → Vivado 综合 → Vivado 实现 → 生成 .bit 文件 → 烧录到 FPGA
|
||||
```
|
||||
|
||||
## 2. 功能目标
|
||||
|
||||
### 2.1 核心目标
|
||||
|
||||
- **前端工具封装**:在插件前端实现 Vivado 调用的完整逻辑
|
||||
- **后端简化调用**:后端只需调用一个工具接口
|
||||
- **文件自动导入**:Vivado 执行完成后,自动将产出文件导入到项目
|
||||
- **流程可视化**:执行进度、日志实时显示
|
||||
|
||||
### 2.2 非功能目标
|
||||
|
||||
- 配置简单,用户友好
|
||||
- 执行过程可视化(进度、日志)
|
||||
- 错误处理完善,提示清晰
|
||||
|
||||
## 3. 功能详细需求
|
||||
|
||||
### 3.1 Vivado 支持的操作
|
||||
|
||||
#### 3.1.1 综合(Synthesis)
|
||||
|
||||
- **输入**:Verilog/VHDL 源文件、约束文件(.xdc)
|
||||
- **输出**:设计检查点(.dcp)、综合报告(.rpt)
|
||||
- **用途**:将 RTL 代码转换为门级网表,检查资源使用情况
|
||||
|
||||
#### 3.1.2 实现(Implementation)
|
||||
|
||||
- **输入**:综合后的 .dcp 文件
|
||||
- **输出**:实现后的 .dcp 文件、时序报告、布局布线报告
|
||||
- **用途**:完成布局布线,检查时序是否满足要求
|
||||
|
||||
#### 3.1.3 生成比特流(Generate Bitstream)
|
||||
|
||||
- **输入**:实现后的 .dcp 文件
|
||||
- **输出**:比特流文件(.bit)
|
||||
- **用途**:生成可烧录到 FPGA 的配置文件
|
||||
|
||||
### 3.2 配置管理
|
||||
|
||||
#### 3.2.1 配置项
|
||||
|
||||
```json
|
||||
{
|
||||
"vivado": {
|
||||
"enabled": true,
|
||||
"executablePath": "C:/Xilinx/Vivado/2023.1/bin/vivado.bat",
|
||||
"workingDir": "${workspaceFolder}/vivado_project",
|
||||
"part": "xc7a35tcpg236-1", // FPGA 型号
|
||||
"commands": {
|
||||
"synthesis": "vivado -mode batch -source synth.tcl",
|
||||
"implementation": "vivado -mode batch -source impl.tcl",
|
||||
"bitstream": "vivado -mode batch -source bitstream.tcl"
|
||||
},
|
||||
"outputFiles": {
|
||||
"synthesis": ["*.dcp", "*_synth.rpt"],
|
||||
"implementation": ["*.dcp", "*_timing.rpt", "*_utilization.rpt"],
|
||||
"bitstream": ["*.bit"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.2 存储位置
|
||||
|
||||
- 全局配置:VS Code Settings(`settings.json`)
|
||||
- 项目配置:`.vscode/ic-coder-vivado.json`(优先级更高)
|
||||
|
||||
### 3.3 工具调用接口
|
||||
|
||||
#### 3.3.1 接口定义
|
||||
|
||||
```typescript
|
||||
interface VivadoToolRequest {
|
||||
command: string; // 命令类型:synthesis | implementation | bitstream
|
||||
parameters?: {
|
||||
topModule?: string; // 顶层模块名
|
||||
files?: string[]; // 输入文件列表
|
||||
part?: string; // FPGA 型号(可选,使用配置中的默认值)
|
||||
constraints?: string; // 约束文件路径(.xdc)
|
||||
outputDir?: string; // 输出目录
|
||||
};
|
||||
importOutput?: {
|
||||
enabled: boolean; // 是否自动导入
|
||||
targetDir: string; // 目标目录
|
||||
};
|
||||
}
|
||||
|
||||
interface VivadoToolResponse {
|
||||
success: boolean;
|
||||
command: string;
|
||||
executionTime: number; // 执行时间(毫秒)
|
||||
output: string; // 标准输出
|
||||
error?: string; // 错误信息
|
||||
importedFiles?: string[]; // 已导入的文件列表
|
||||
reports?: {
|
||||
// 报告摘要
|
||||
resources?: string; // 资源使用情况
|
||||
timing?: string; // 时序信息
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 执行流程
|
||||
|
||||
#### 3.4.1 参数验证
|
||||
|
||||
- 检查 Vivado 是否已配置
|
||||
- 检查可执行文件是否存在
|
||||
- 检查输入文件是否存在
|
||||
- 检查工作目录是否存在
|
||||
|
||||
#### 3.4.2 TCL 脚本生成
|
||||
|
||||
根据命令类型自动生成 TCL 脚本:
|
||||
|
||||
**综合脚本示例(synth.tcl)**:
|
||||
|
||||
```tcl
|
||||
# 读取源文件
|
||||
read_verilog counter.v
|
||||
read_xdc constraints.xdc
|
||||
|
||||
# 设置顶层模块
|
||||
set_property top counter [current_fileset]
|
||||
|
||||
# 综合
|
||||
synth_design -part xc7a35tcpg236-1 -top counter
|
||||
|
||||
# 生成报告
|
||||
report_utilization -file utilization_synth.rpt
|
||||
report_timing -file timing_synth.rpt
|
||||
|
||||
# 保存检查点
|
||||
write_checkpoint -force counter_synth.dcp
|
||||
```
|
||||
|
||||
#### 3.4.3 命令执行
|
||||
|
||||
- 启动子进程执行 Vivado 命令
|
||||
- 实时捕获标准输出和错误输出
|
||||
- 向前端推送进度信息(解析日志中的进度标记)
|
||||
|
||||
#### 3.4.4 结果处理
|
||||
|
||||
- 检查执行结果(退出码)
|
||||
- 解析报告文件,提取关键信息(资源使用、时序)
|
||||
- 查找产出文件
|
||||
|
||||
#### 3.4.5 文件导入
|
||||
|
||||
- 根据配置的文件模式查找产出文件
|
||||
- 复制文件到目标目录
|
||||
- 通知用户导入结果
|
||||
|
||||
### 3.5 UI 交互
|
||||
|
||||
#### 3.5.1 配置界面
|
||||
|
||||
- 在设置页面添加 "Vivado 配置" 选项
|
||||
- 支持配置 Vivado 路径、FPGA 型号
|
||||
- 支持测试 Vivado 可用性(点击按钮测试)
|
||||
|
||||
#### 3.5.2 调用界面
|
||||
|
||||
- 在聊天面板中,AI 可以建议使用 Vivado
|
||||
- 用户确认后,显示执行进度对话框
|
||||
- 实时显示日志输出(可折叠)
|
||||
- 显示执行状态:准备中 → 执行中 → 完成/失败
|
||||
|
||||
#### 3.5.3 结果展示
|
||||
|
||||
- 执行成功:显示执行时间、资源使用、时序信息
|
||||
- 执行失败:显示错误信息、建议解决方案
|
||||
- 导入文件:高亮显示已导入的文件,支持点击打开报告
|
||||
|
||||
### 3.6 后端集成
|
||||
|
||||
#### 3.6.1 工具定义
|
||||
|
||||
后端在工具列表中添加 Vivado 工具:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "runVivado",
|
||||
"description": "调用 Vivado 执行综合、实现或生成比特流",
|
||||
"parameters": {
|
||||
"command": "命令类型(synthesis/implementation/bitstream)",
|
||||
"topModule": "顶层模块名",
|
||||
"files": "输入文件列表",
|
||||
"constraints": "约束文件路径(可选)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.6.2 调用示例
|
||||
|
||||
```
|
||||
用户:帮我用 Vivado 综合一下 counter.v
|
||||
AI:好的,我将使用 Vivado 进行综合。
|
||||
[调用工具] runVivado
|
||||
参数:
|
||||
- command: synthesis
|
||||
- topModule: counter
|
||||
- files: ["counter.v"]
|
||||
- constraints: "constraints.xdc"
|
||||
|
||||
[执行中...]
|
||||
Vivado 综合完成!
|
||||
- 执行时间:45 秒
|
||||
- 资源使用:LUT: 32/20800 (0.15%), FF: 8/41600 (0.02%)
|
||||
- 产出文件:counter_synth.dcp, utilization_synth.rpt
|
||||
- 已自动导入到:vivado_project/output/
|
||||
```
|
||||
|
||||
## 4. 用户场景
|
||||
|
||||
### 4.1 场景一:单步综合
|
||||
|
||||
1. 用户编写完 Verilog 代码
|
||||
2. 在聊天中输入:"用 Vivado 综合一下 counter.v"
|
||||
3. AI 调用 `runVivado` 工具
|
||||
4. 插件执行 Vivado 综合
|
||||
5. 综合完成后,显示资源使用情况,自动导入报告文件
|
||||
|
||||
### 4.2 场景二:完整流程
|
||||
|
||||
1. 用户输入:"用 Vivado 跑完整个流程"
|
||||
2. AI 依次调用:
|
||||
- 综合(Synthesis)
|
||||
- 实现(Implementation)
|
||||
- 生成比特流(Bitstream)
|
||||
3. 每个步骤完成后显示结果
|
||||
4. 最终生成 .bit 文件,用户可以烧录到 FPGA
|
||||
|
||||
### 4.3 场景三:查看报告
|
||||
|
||||
1. Vivado 执行完成后
|
||||
2. 用户点击导入的报告文件
|
||||
3. 在编辑器中查看资源使用、时序分析等信息
|
||||
|
||||
## 5. 技术约束
|
||||
|
||||
### 5.1 平台兼容性
|
||||
|
||||
- Windows:支持 `.bat` 可执行文件
|
||||
- Linux:支持 shell 脚本
|
||||
- 路径分隔符自动适配
|
||||
|
||||
### 5.2 性能要求
|
||||
|
||||
- 命令执行不阻塞 UI
|
||||
- 综合时间可能较长(分钟级),需要进度提示
|
||||
- 日志输出实时更新,限制缓冲区大小
|
||||
|
||||
### 5.3 安全性
|
||||
|
||||
- 工作目录限制在项目范围内
|
||||
- 许可证路径不记录到日志
|
||||
|
||||
## 6. 验收标准
|
||||
|
||||
### 6.1 功能验收
|
||||
|
||||
- [ ] 用户可以配置 Vivado 路径和 FPGA 型号
|
||||
- [ ] AI 可以通过工具调用成功执行 Vivado 综合
|
||||
- [ ] 产出文件自动导入到指定目录
|
||||
- [ ] 执行过程有清晰的进度提示
|
||||
- [ ] 报告文件可以正常打开查看
|
||||
|
||||
### 6.2 性能验收
|
||||
|
||||
- [ ] 小型项目综合时间 < 1 分钟
|
||||
- [ ] UI 响应流畅,不卡顿
|
||||
- [ ] 日志输出实时更新(延迟 < 500ms)
|
||||
|
||||
### 6.3 用户体验验收
|
||||
|
||||
- [ ] 配置界面直观易用
|
||||
- [ ] 首次使用有引导提示
|
||||
- [ ] 错误提示清晰,有解决建议
|
||||
- [ ] 导入的文件可以直接打开查看
|
||||
|
||||
## 7. 风险和依赖
|
||||
|
||||
### 7.1 风险
|
||||
|
||||
- **Vivado 版本差异**:不同版本的命令行参数可能不同
|
||||
- **许可证问题**:Vivado 需要许可证才能运行
|
||||
- **路径问题**:Windows 路径中的空格和特殊字符
|
||||
- **执行时间长**:大型项目可能需要数十分钟
|
||||
|
||||
### 7.2 依赖
|
||||
|
||||
- 用户需要自行安装 Vivado
|
||||
- 用户需要配置正确的 Vivado 路径
|
||||
- 需要设置环境变量(如 `XILINX_VIVADO`)
|
||||
- 需要有效的 Vivado 许可证
|
||||
|
||||
## 8. 后续扩展
|
||||
|
||||
### 8.1 短期扩展
|
||||
|
||||
- 支持自定义 TCL 脚本模板
|
||||
- 支持批量处理多个设计
|
||||
- 支持时序约束编辑器
|
||||
|
||||
### 8.2 长期扩展
|
||||
|
||||
- 支持其他 FPGA 工具(Quartus)
|
||||
- 云端 Vivado 服务集成
|
||||
- 结果对比和版本管理
|
||||
- 性能分析和优化建议
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### A. Vivado 命令行参考
|
||||
|
||||
- 官方文档:https://docs.xilinx.com/
|
||||
- TCL 命令参考:UG835
|
||||
- 设计流程参考:UG892
|
||||
|
||||
### B. 术语表
|
||||
|
||||
- **RTL**:Register Transfer Level,寄存器传输级
|
||||
- **综合**:Synthesis,将 RTL 代码转换为门级网表
|
||||
- **实现**:Implementation,布局布线
|
||||
- **比特流**:Bitstream,FPGA 配置文件
|
||||
- **DCP**:Design Checkpoint,Vivado 设计检查点文件
|
||||
- **XDC**:Xilinx Design Constraints,约束文件
|
||||
- **LUT**:Look-Up Table,查找表(FPGA 基本逻辑单元)
|
||||
- **FF**:Flip-Flop,触发器
|
||||
413
docs/Vivado联动前后端对接文档.md
Normal file
413
docs/Vivado联动前后端对接文档.md
Normal file
@ -0,0 +1,413 @@
|
||||
# Vivado 联动前后端对接文档
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档描述后端 AI 服务如何调用前端的 Vivado 工具,以及前端如何响应和返回结果。
|
||||
|
||||
### 1.1 调用流程
|
||||
|
||||
```
|
||||
后端 AI 服务
|
||||
↓ (1) 发送工具调用请求
|
||||
前端 Extension (MessageHandler)
|
||||
↓ (2) 解析请求,调用 VivadoRunner
|
||||
VivadoRunner
|
||||
↓ (3) 执行 Vivado,实时推送进度
|
||||
前端 Webview
|
||||
↓ (4) 显示进度和结果
|
||||
前端 Extension
|
||||
↓ (5) 返回执行结果给后端
|
||||
后端 AI 服务
|
||||
```
|
||||
|
||||
## 2. 工具定义(后端)
|
||||
|
||||
### 2.1 工具注册
|
||||
|
||||
后端需要在工具列表中注册 `runVivado` 工具:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "runVivado",
|
||||
"description": "调用本地 Vivado 工具执行 FPGA 综合、实现或生成比特流。用于将 Verilog 代码部署到 FPGA 硬件。",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"enum": ["synthesis", "implementation", "bitstream"],
|
||||
"description": "要执行的命令类型:synthesis(综合)、implementation(实现)、bitstream(生成比特流)"
|
||||
},
|
||||
"topModule": {
|
||||
"type": "string",
|
||||
"description": "顶层模块名称"
|
||||
},
|
||||
"files": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "输入的 Verilog 文件路径列表"
|
||||
},
|
||||
"constraints": {
|
||||
"type": "string",
|
||||
"description": "约束文件路径(.xdc 文件),可选"
|
||||
},
|
||||
"part": {
|
||||
"type": "string",
|
||||
"description": "FPGA 型号,可选,默认使用配置中的型号"
|
||||
}
|
||||
},
|
||||
"required": ["command", "topModule", "files"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 调用示例
|
||||
|
||||
#### 示例 1:综合单个文件
|
||||
|
||||
```json
|
||||
{
|
||||
"tool": "runVivado",
|
||||
"parameters": {
|
||||
"command": "synthesis",
|
||||
"topModule": "counter",
|
||||
"files": ["counter.v"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 示例 2:综合带约束文件
|
||||
|
||||
```json
|
||||
{
|
||||
"tool": "runVivado",
|
||||
"parameters": {
|
||||
"command": "synthesis",
|
||||
"topModule": "uart_top",
|
||||
"files": ["uart_tx.v", "uart_rx.v", "uart_top.v"],
|
||||
"constraints": "constraints.xdc"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 示例 3:实现(需要先综合)
|
||||
|
||||
```json
|
||||
{
|
||||
"tool": "runVivado",
|
||||
"parameters": {
|
||||
"command": "implementation",
|
||||
"topModule": "counter"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 示例 4:生成比特流(需要先实现)
|
||||
|
||||
```json
|
||||
{
|
||||
"tool": "runVivado",
|
||||
"parameters": {
|
||||
"command": "bitstream",
|
||||
"topModule": "counter"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 前端接收和处理
|
||||
|
||||
### 3.1 MessageHandler 处理逻辑
|
||||
|
||||
前端在 `messageHandler.ts` 中添加工具处理:
|
||||
|
||||
```typescript
|
||||
// src/utils/messageHandler.ts
|
||||
|
||||
export async function handleToolExecution(
|
||||
panel: vscode.WebviewPanel,
|
||||
toolName: string,
|
||||
parameters: any
|
||||
): Promise<any> {
|
||||
|
||||
if (toolName === 'runVivado') {
|
||||
return await handleVivadoTool(panel, parameters);
|
||||
}
|
||||
|
||||
// 其他工具处理...
|
||||
}
|
||||
|
||||
async function handleVivadoTool(
|
||||
panel: vscode.WebviewPanel,
|
||||
parameters: any
|
||||
): Promise<VivadoToolResponse> {
|
||||
|
||||
const { command, topModule, files, constraints, part } = parameters;
|
||||
|
||||
// 构建请求
|
||||
const request: VivadoToolRequest = {
|
||||
command,
|
||||
parameters: {
|
||||
topModule,
|
||||
files,
|
||||
constraints,
|
||||
part
|
||||
},
|
||||
importOutput: {
|
||||
enabled: true,
|
||||
targetDir: path.join(
|
||||
vscode.workspace.workspaceFolders![0].uri.fsPath,
|
||||
'vivado_output'
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// 向前端发送开始消息
|
||||
panel.webview.postMessage({
|
||||
type: 'vivado-start',
|
||||
command
|
||||
});
|
||||
|
||||
// 执行 Vivado
|
||||
const response = await runVivado(request, (progress) => {
|
||||
// 实时推送进度到前端
|
||||
panel.webview.postMessage({
|
||||
type: 'vivado-progress',
|
||||
progress
|
||||
});
|
||||
});
|
||||
|
||||
// 向前端发送完成消息
|
||||
panel.webview.postMessage({
|
||||
type: 'vivado-complete',
|
||||
response
|
||||
});
|
||||
|
||||
// 返回结果给后端
|
||||
return response;
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 响应格式
|
||||
|
||||
### 4.1 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"command": "synthesis",
|
||||
"executionTime": 45230,
|
||||
"output": "Vivado 执行日志...",
|
||||
"importedFiles": [
|
||||
"/path/to/vivado_output/counter_synth.dcp",
|
||||
"/path/to/vivado_output/counter_utilization_synth.rpt"
|
||||
],
|
||||
"reports": {
|
||||
"resources": "LUT: 32/20800 (0.15%)\nFF: 8/41600 (0.02%)",
|
||||
"timing": "WNS: 5.234ns, TNS: 0.000ns"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 失败响应
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"command": "synthesis",
|
||||
"executionTime": 1250,
|
||||
"output": "部分执行日志...",
|
||||
"error": "ERROR: [Synth 8-439] module 'counter' not found"
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 后端使用指南
|
||||
|
||||
### 5.1 AI 对话流程
|
||||
|
||||
```
|
||||
用户:帮我用 Vivado 综合一下 counter.v
|
||||
|
||||
AI 分析:
|
||||
1. 用户想要综合 Verilog 文件
|
||||
2. 需要调用 runVivado 工具
|
||||
3. 命令类型是 synthesis
|
||||
4. 顶层模块名从文件名推断为 counter
|
||||
5. 输入文件是 counter.v
|
||||
|
||||
AI 调用工具:
|
||||
{
|
||||
"tool": "runVivado",
|
||||
"parameters": {
|
||||
"command": "synthesis",
|
||||
"topModule": "counter",
|
||||
"files": ["counter.v"]
|
||||
}
|
||||
}
|
||||
|
||||
前端执行并返回结果
|
||||
|
||||
AI 回复用户:
|
||||
"Vivado 综合完成!
|
||||
- 执行时间:45.2 秒
|
||||
- 资源使用:LUT: 32/20800 (0.15%), FF: 8/41600 (0.02%)
|
||||
- 产出文件已导入到 vivado_output 目录"
|
||||
```
|
||||
|
||||
### 5.2 完整流程示例
|
||||
|
||||
```
|
||||
用户:用 Vivado 跑完整个流程
|
||||
|
||||
AI:好的,我将依次执行综合、实现和生成比特流。
|
||||
|
||||
步骤 1:综合
|
||||
[调用] runVivado { command: "synthesis", ... }
|
||||
[结果] 综合成功,耗时 45s
|
||||
|
||||
步骤 2:实现
|
||||
[调用] runVivado { command: "implementation", ... }
|
||||
[结果] 实现成功,耗时 120s,时序满足要求
|
||||
|
||||
步骤 3:生成比特流
|
||||
[调用] runVivado { command: "bitstream", ... }
|
||||
[结果] 比特流生成成功,文件:counter.bit
|
||||
|
||||
完成!所有文件已导入到 vivado_output 目录。
|
||||
```
|
||||
|
||||
## 6. 错误处理
|
||||
|
||||
### 6.1 常见错误
|
||||
|
||||
#### 错误 1:Vivado 未配置
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Vivado 未配置,请在设置中配置 Vivado 路径"
|
||||
}
|
||||
```
|
||||
|
||||
**AI 应该回复**:
|
||||
"Vivado 尚未配置,请先在插件设置中配置 Vivado 的安装路径。"
|
||||
|
||||
#### 错误 2:文件不存在
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "输入文件不存在: counter.v"
|
||||
}
|
||||
```
|
||||
|
||||
**AI 应该回复**:
|
||||
"找不到文件 counter.v,请确认文件路径是否正确。"
|
||||
|
||||
#### 错误 3:综合失败
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "ERROR: [Synth 8-439] module 'counter' not found",
|
||||
"output": "详细日志..."
|
||||
}
|
||||
```
|
||||
|
||||
**AI 应该回复**:
|
||||
"综合失败,错误信息:找不到模块 'counter'。请检查:
|
||||
1. 模块名是否正确
|
||||
2. 文件中是否定义了该模块
|
||||
3. 是否有语法错误"
|
||||
|
||||
### 6.2 错误处理建议
|
||||
|
||||
后端收到 `success: false` 时:
|
||||
1. 提取 `error` 字段中的错误信息
|
||||
2. 分析错误类型(配置问题、文件问题、语法问题等)
|
||||
3. 给用户提供具体的解决建议
|
||||
4. 必要时可以查看 `output` 字段获取详细日志
|
||||
|
||||
## 7. 进度推送(可选)
|
||||
|
||||
前端会实时推送进度信息到 Webview,后端无需处理,但可以了解进度格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vivado-progress",
|
||||
"progress": {
|
||||
"stage": "synthesis",
|
||||
"percentage": 45,
|
||||
"message": "正在综合模块 counter..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 测试建议
|
||||
|
||||
### 8.1 后端测试用例
|
||||
|
||||
```javascript
|
||||
// 测试用例 1:基本综合
|
||||
test('综合单个文件', async () => {
|
||||
const result = await callTool('runVivado', {
|
||||
command: 'synthesis',
|
||||
topModule: 'counter',
|
||||
files: ['counter.v']
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.importedFiles.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// 测试用例 2:错误处理
|
||||
test('文件不存在', async () => {
|
||||
const result = await callTool('runVivado', {
|
||||
command: 'synthesis',
|
||||
topModule: 'test',
|
||||
files: ['not_exist.v']
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('不存在');
|
||||
});
|
||||
```
|
||||
|
||||
## 9. 注意事项
|
||||
|
||||
### 9.1 执行时间
|
||||
- 综合:小型设计 30s-2min,大型设计 5-30min
|
||||
- 实现:通常是综合时间的 2-3 倍
|
||||
- 生成比特流:通常 10-30s
|
||||
|
||||
后端应该设置合理的超时时间(建议 10 分钟)。
|
||||
|
||||
### 9.2 依赖关系
|
||||
- `implementation` 需要先执行 `synthesis`
|
||||
- `bitstream` 需要先执行 `implementation`
|
||||
|
||||
后端 AI 应该理解这个依赖关系,按顺序调用。
|
||||
|
||||
### 9.3 文件路径
|
||||
- 所有文件路径都是相对于工作区根目录
|
||||
- 前端会自动解析为绝对路径
|
||||
- 支持相对路径和绝对路径
|
||||
|
||||
## 10. 快速集成清单
|
||||
|
||||
后端开发者需要做的事情:
|
||||
|
||||
- [ ] 在工具列表中注册 `runVivado` 工具
|
||||
- [ ] 实现工具调用逻辑(发送请求到前端)
|
||||
- [ ] 处理返回结果(success/error)
|
||||
- [ ] 实现错误处理和用户提示
|
||||
- [ ] 理解三个命令的依赖关系
|
||||
- [ ] 设置合理的超时时间
|
||||
- [ ] 编写测试用例
|
||||
|
||||
前端开发者需要做的事情:
|
||||
|
||||
- [ ] 实现 `handleVivadoTool` 函数
|
||||
- [ ] 集成 VivadoRunner
|
||||
- [ ] 实现进度推送
|
||||
- [ ] 实现结果展示
|
||||
- [ ] 处理各种错误情况
|
||||
864
docs/Vivado联动功能技术设计文档.md
Normal file
864
docs/Vivado联动功能技术设计文档.md
Normal file
@ -0,0 +1,864 @@
|
||||
# Vivado 联动功能技术设计文档
|
||||
|
||||
## 1. 架构设计
|
||||
|
||||
### 1.1 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 后端 AI 服务 │
|
||||
│ (调用 runVivado 工具) │
|
||||
└────────────────────────────┬────────────────────────────────┘
|
||||
│ 工具调用请求
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ VS Code Extension │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ ICHelperPanel (Webview) │ │
|
||||
│ │ - 接收后端工具调用 │ │
|
||||
│ │ - 显示执行进度和日志 │ │
|
||||
│ │ - 展示执行结果 │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ VivadoRunner (utils/vivadoRunner.ts) │ │
|
||||
│ │ - 配置管理 │ │
|
||||
│ │ - TCL 脚本生成 │ │
|
||||
│ │ - 命令执行 │ │
|
||||
│ │ - 进度监控 │ │
|
||||
│ │ - 结果解析 │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ FileImporter (utils/fileImporter.ts) │ │
|
||||
│ │ - 查找产出文件 │ │
|
||||
│ │ - 复制文件到目标目录 │ │
|
||||
│ │ - 通知文件变更 │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 本地 Vivado 工具 │
|
||||
│ (通过子进程执行 TCL 脚本) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.2 模块职责
|
||||
|
||||
#### 1.2.1 VivadoRunner
|
||||
- 读取和验证 Vivado 配置
|
||||
- 根据命令类型生成 TCL 脚本
|
||||
- 启动子进程执行 Vivado
|
||||
- 实时捕获输出并解析进度
|
||||
- 返回执行结果
|
||||
|
||||
#### 1.2.2 FileImporter
|
||||
- 根据文件模式查找产出文件
|
||||
- 复制文件到指定目录
|
||||
- 返回已导入的文件列表
|
||||
|
||||
#### 1.2.3 MessageHandler
|
||||
- 接收后端的 `runVivado` 工具调用
|
||||
- 调用 VivadoRunner 执行
|
||||
- 向 Webview 推送进度和结果
|
||||
|
||||
## 2. 数据结构设计
|
||||
|
||||
### 2.1 配置结构
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Vivado 配置
|
||||
*/
|
||||
interface VivadoConfig {
|
||||
enabled: boolean;
|
||||
executablePath: string;
|
||||
workingDir: string;
|
||||
part: string;
|
||||
commands: {
|
||||
synthesis: string;
|
||||
implementation: string;
|
||||
bitstream: string;
|
||||
};
|
||||
outputFiles: {
|
||||
synthesis: string[];
|
||||
implementation: string[];
|
||||
bitstream: string[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 请求和响应结构
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Vivado 工具请求
|
||||
*/
|
||||
interface VivadoToolRequest {
|
||||
command: 'synthesis' | 'implementation' | 'bitstream';
|
||||
parameters?: {
|
||||
topModule?: string;
|
||||
files?: string[];
|
||||
part?: string;
|
||||
constraints?: string;
|
||||
outputDir?: string;
|
||||
};
|
||||
importOutput?: {
|
||||
enabled: boolean;
|
||||
targetDir: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Vivado 工具响应
|
||||
*/
|
||||
interface VivadoToolResponse {
|
||||
success: boolean;
|
||||
command: string;
|
||||
executionTime: number;
|
||||
output: string;
|
||||
error?: string;
|
||||
importedFiles?: string[];
|
||||
reports?: {
|
||||
resources?: string;
|
||||
timing?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行进度
|
||||
*/
|
||||
interface VivadoProgress {
|
||||
stage: string;
|
||||
percentage: number;
|
||||
message: string;
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 核心模块实现
|
||||
|
||||
### 3.1 配置管理
|
||||
|
||||
#### 3.1.1 配置读取
|
||||
```typescript
|
||||
// src/utils/vivadoConfig.ts
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export function getVivadoConfig(): VivadoConfig | null {
|
||||
// 优先读取项目配置
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
if (workspaceFolder) {
|
||||
const projectConfigPath = path.join(
|
||||
workspaceFolder.uri.fsPath,
|
||||
'.vscode',
|
||||
'ic-coder-vivado.json'
|
||||
);
|
||||
if (fs.existsSync(projectConfigPath)) {
|
||||
const content = fs.readFileSync(projectConfigPath, 'utf-8');
|
||||
return JSON.parse(content).vivado;
|
||||
}
|
||||
}
|
||||
|
||||
// 读取全局配置
|
||||
const config = vscode.workspace.getConfiguration('ic-coder');
|
||||
return config.get<VivadoConfig>('vivado') || null;
|
||||
}
|
||||
|
||||
export function validateConfig(config: VivadoConfig): string | null {
|
||||
if (!config.enabled) {
|
||||
return 'Vivado 未启用';
|
||||
}
|
||||
if (!fs.existsSync(config.executablePath)) {
|
||||
return `Vivado 可执行文件不存在: ${config.executablePath}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 TCL 脚本生成
|
||||
|
||||
#### 3.2.1 脚本生成器
|
||||
```typescript
|
||||
// src/utils/tclGenerator.ts
|
||||
|
||||
export function generateSynthesisTcl(
|
||||
topModule: string,
|
||||
files: string[],
|
||||
part: string,
|
||||
constraints?: string,
|
||||
outputDir?: string
|
||||
): string {
|
||||
const output = outputDir || '.';
|
||||
let tcl = `# Vivado 综合脚本\n\n`;
|
||||
|
||||
// 读取源文件
|
||||
files.forEach(file => {
|
||||
tcl += `read_verilog ${file}\n`;
|
||||
});
|
||||
|
||||
// 读取约束文件
|
||||
if (constraints) {
|
||||
tcl += `read_xdc ${constraints}\n`;
|
||||
}
|
||||
|
||||
tcl += `\n# 综合\n`;
|
||||
tcl += `synth_design -part ${part} -top ${topModule}\n\n`;
|
||||
|
||||
// 生成报告
|
||||
tcl += `# 生成报告\n`;
|
||||
tcl += `report_utilization -file ${output}/${topModule}_utilization_synth.rpt\n`;
|
||||
tcl += `report_timing -file ${output}/${topModule}_timing_synth.rpt\n\n`;
|
||||
|
||||
// 保存检查点
|
||||
tcl += `# 保存检查点\n`;
|
||||
tcl += `write_checkpoint -force ${output}/${topModule}_synth.dcp\n`;
|
||||
|
||||
return tcl;
|
||||
}
|
||||
|
||||
export function generateImplementationTcl(
|
||||
dcpFile: string,
|
||||
outputDir?: string
|
||||
): string {
|
||||
const output = outputDir || '.';
|
||||
const baseName = path.basename(dcpFile, '.dcp').replace('_synth', '');
|
||||
|
||||
let tcl = `# Vivado 实现脚本\n\n`;
|
||||
tcl += `open_checkpoint ${dcpFile}\n\n`;
|
||||
|
||||
tcl += `# 优化\n`;
|
||||
tcl += `opt_design\n`;
|
||||
tcl += `place_design\n`;
|
||||
tcl += `route_design\n\n`;
|
||||
|
||||
tcl += `# 生成报告\n`;
|
||||
tcl += `report_utilization -file ${output}/${baseName}_utilization_impl.rpt\n`;
|
||||
tcl += `report_timing_summary -file ${output}/${baseName}_timing_impl.rpt\n\n`;
|
||||
|
||||
tcl += `# 保存检查点\n`;
|
||||
tcl += `write_checkpoint -force ${output}/${baseName}_impl.dcp\n`;
|
||||
|
||||
return tcl;
|
||||
}
|
||||
|
||||
export function generateBitstreamTcl(
|
||||
dcpFile: string,
|
||||
outputDir?: string
|
||||
): string {
|
||||
const output = outputDir || '.';
|
||||
const baseName = path.basename(dcpFile, '.dcp').replace('_impl', '');
|
||||
|
||||
let tcl = `# Vivado 比特流生成脚本\n\n`;
|
||||
tcl += `open_checkpoint ${dcpFile}\n\n`;
|
||||
|
||||
tcl += `# 生成比特流\n`;
|
||||
tcl += `write_bitstream -force ${output}/${baseName}.bit\n`;
|
||||
|
||||
return tcl;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 VivadoRunner 实现
|
||||
|
||||
```typescript
|
||||
// src/utils/vivadoRunner.ts
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { spawn } from 'child_process';
|
||||
import { getVivadoConfig, validateConfig } from './vivadoConfig';
|
||||
import { generateSynthesisTcl, generateImplementationTcl, generateBitstreamTcl } from './tclGenerator';
|
||||
|
||||
export async function runVivado(
|
||||
request: VivadoToolRequest,
|
||||
progressCallback?: (progress: VivadoProgress) => void
|
||||
): Promise<VivadoToolResponse> {
|
||||
const startTime = Date.now();
|
||||
|
||||
// 读取配置
|
||||
const config = getVivadoConfig();
|
||||
if (!config) {
|
||||
return {
|
||||
success: false,
|
||||
command: request.command,
|
||||
executionTime: 0,
|
||||
output: '',
|
||||
error: 'Vivado 未配置'
|
||||
};
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
const configError = validateConfig(config);
|
||||
if (configError) {
|
||||
return {
|
||||
success: false,
|
||||
command: request.command,
|
||||
executionTime: 0,
|
||||
output: '',
|
||||
error: configError
|
||||
};
|
||||
}
|
||||
|
||||
// 准备工作目录
|
||||
const workingDir = resolveWorkingDir(config.workingDir);
|
||||
if (!fs.existsSync(workingDir)) {
|
||||
fs.mkdirSync(workingDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 生成 TCL 脚本
|
||||
const tclScript = generateTclScript(request, config, workingDir);
|
||||
const tclPath = path.join(workingDir, `${request.command}.tcl`);
|
||||
fs.writeFileSync(tclPath, tclScript);
|
||||
|
||||
// 执行 Vivado
|
||||
const result = await executeVivado(
|
||||
config.executablePath,
|
||||
tclPath,
|
||||
workingDir,
|
||||
progressCallback
|
||||
);
|
||||
|
||||
const executionTime = Date.now() - startTime;
|
||||
|
||||
// 解析报告
|
||||
const reports = parseReports(request.command, workingDir, request.parameters?.topModule);
|
||||
|
||||
// 导入文件
|
||||
let importedFiles: string[] = [];
|
||||
if (request.importOutput?.enabled && result.success) {
|
||||
importedFiles = await importOutputFiles(
|
||||
request.command,
|
||||
config,
|
||||
workingDir,
|
||||
request.importOutput.targetDir
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
success: result.success,
|
||||
command: request.command,
|
||||
executionTime,
|
||||
output: result.output,
|
||||
error: result.error,
|
||||
importedFiles,
|
||||
reports
|
||||
};
|
||||
}
|
||||
|
||||
function generateTclScript(
|
||||
request: VivadoToolRequest,
|
||||
config: VivadoConfig,
|
||||
workingDir: string
|
||||
): string {
|
||||
const { command, parameters } = request;
|
||||
const part = parameters?.part || config.part;
|
||||
|
||||
switch (command) {
|
||||
case 'synthesis':
|
||||
return generateSynthesisTcl(
|
||||
parameters?.topModule || 'top',
|
||||
parameters?.files || [],
|
||||
part,
|
||||
parameters?.constraints,
|
||||
parameters?.outputDir
|
||||
);
|
||||
case 'implementation':
|
||||
const synthDcp = path.join(workingDir, `${parameters?.topModule}_synth.dcp`);
|
||||
return generateImplementationTcl(synthDcp, parameters?.outputDir);
|
||||
case 'bitstream':
|
||||
const implDcp = path.join(workingDir, `${parameters?.topModule}_impl.dcp`);
|
||||
return generateBitstreamTcl(implDcp, parameters?.outputDir);
|
||||
default:
|
||||
throw new Error(`未知命令: ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function executeVivado(
|
||||
executablePath: string,
|
||||
tclPath: string,
|
||||
workingDir: string,
|
||||
progressCallback?: (progress: VivadoProgress) => void
|
||||
): Promise<{ success: boolean; output: string; error?: string }> {
|
||||
return new Promise((resolve) => {
|
||||
let output = '';
|
||||
let errorOutput = '';
|
||||
|
||||
const process = spawn(executablePath, ['-mode', 'batch', '-source', tclPath], {
|
||||
cwd: workingDir,
|
||||
shell: true
|
||||
});
|
||||
|
||||
process.stdout.on('data', (data) => {
|
||||
const text = data.toString();
|
||||
output += text;
|
||||
|
||||
// 解析进度
|
||||
if (progressCallback) {
|
||||
const progress = parseProgress(text);
|
||||
if (progress) {
|
||||
progressCallback(progress);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
process.stderr.on('data', (data) => {
|
||||
errorOutput += data.toString();
|
||||
});
|
||||
|
||||
process.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve({ success: true, output });
|
||||
} else {
|
||||
resolve({ success: false, output, error: errorOutput || '执行失败' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function parseProgress(logText: string): VivadoProgress | null {
|
||||
// 解析 Vivado 日志中的进度信息
|
||||
if (logText.includes('Starting synthesis')) {
|
||||
return { stage: 'synthesis', percentage: 10, message: '开始综合' };
|
||||
}
|
||||
if (logText.includes('Finished synthesis')) {
|
||||
return { stage: 'synthesis', percentage: 100, message: '综合完成' };
|
||||
}
|
||||
// 更多进度解析...
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseReports(
|
||||
command: string,
|
||||
workingDir: string,
|
||||
topModule?: string
|
||||
): { resources?: string; timing?: string } {
|
||||
const reports: { resources?: string; timing?: string } = {};
|
||||
|
||||
if (command === 'synthesis' || command === 'implementation') {
|
||||
const utilizationFile = path.join(
|
||||
workingDir,
|
||||
`${topModule}_utilization_${command === 'synthesis' ? 'synth' : 'impl'}.rpt`
|
||||
);
|
||||
if (fs.existsSync(utilizationFile)) {
|
||||
const content = fs.readFileSync(utilizationFile, 'utf-8');
|
||||
reports.resources = extractResourceSummary(content);
|
||||
}
|
||||
|
||||
const timingFile = path.join(
|
||||
workingDir,
|
||||
`${topModule}_timing_${command === 'synthesis' ? 'synth' : 'impl'}.rpt`
|
||||
);
|
||||
if (fs.existsSync(timingFile)) {
|
||||
const content = fs.readFileSync(timingFile, 'utf-8');
|
||||
reports.timing = extractTimingSummary(content);
|
||||
}
|
||||
}
|
||||
|
||||
return reports;
|
||||
}
|
||||
|
||||
function extractResourceSummary(reportContent: string): string {
|
||||
// 提取资源使用摘要
|
||||
const lines = reportContent.split('\n');
|
||||
const summary: string[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.includes('LUT') || line.includes('FF') || line.includes('BRAM')) {
|
||||
summary.push(line.trim());
|
||||
}
|
||||
}
|
||||
|
||||
return summary.join('\n');
|
||||
}
|
||||
|
||||
function extractTimingSummary(reportContent: string): string {
|
||||
// 提取时序摘要
|
||||
const lines = reportContent.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.includes('WNS') || line.includes('TNS')) {
|
||||
return line.trim();
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function resolveWorkingDir(workingDir: string): string {
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
if (workspaceFolder) {
|
||||
return workingDir.replace('${workspaceFolder}', workspaceFolder.uri.fsPath);
|
||||
}
|
||||
return workingDir;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 文件导入实现
|
||||
|
||||
```typescript
|
||||
// src/utils/fileImporter.ts
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as glob from 'glob';
|
||||
|
||||
export async function importOutputFiles(
|
||||
command: string,
|
||||
config: VivadoConfig,
|
||||
sourceDir: string,
|
||||
targetDir: string
|
||||
): Promise<string[]> {
|
||||
const patterns = config.outputFiles[command] || [];
|
||||
const importedFiles: string[] = [];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const files = glob.sync(pattern, { cwd: sourceDir });
|
||||
|
||||
for (const file of files) {
|
||||
const sourcePath = path.join(sourceDir, file);
|
||||
const targetPath = path.join(targetDir, file);
|
||||
|
||||
// 确保目标目录存在
|
||||
const targetDirPath = path.dirname(targetPath);
|
||||
if (!fs.existsSync(targetDirPath)) {
|
||||
fs.mkdirSync(targetDirPath, { recursive: true });
|
||||
}
|
||||
|
||||
// 复制文件
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
importedFiles.push(targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
return importedFiles;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 MessageHandler 集成
|
||||
|
||||
```typescript
|
||||
// src/utils/messageHandler.ts (新增部分)
|
||||
|
||||
import { runVivado } from './vivadoRunner';
|
||||
|
||||
// 在 handleUserMessage 中添加 Vivado 工具处理
|
||||
export async function handleVivadoTool(
|
||||
panel: vscode.WebviewPanel,
|
||||
toolCall: any
|
||||
): Promise<void> {
|
||||
const { command, topModule, files, constraints } = toolCall.parameters;
|
||||
|
||||
// 构建请求
|
||||
const request: VivadoToolRequest = {
|
||||
command,
|
||||
parameters: {
|
||||
topModule,
|
||||
files,
|
||||
constraints
|
||||
},
|
||||
importOutput: {
|
||||
enabled: true,
|
||||
targetDir: path.join(vscode.workspace.workspaceFolders![0].uri.fsPath, 'vivado_output')
|
||||
}
|
||||
};
|
||||
|
||||
// 向前端发送开始消息
|
||||
panel.webview.postMessage({
|
||||
type: 'vivado-start',
|
||||
command
|
||||
});
|
||||
|
||||
// 执行 Vivado
|
||||
const response = await runVivado(request, (progress) => {
|
||||
// 推送进度
|
||||
panel.webview.postMessage({
|
||||
type: 'vivado-progress',
|
||||
progress
|
||||
});
|
||||
});
|
||||
|
||||
// 向前端发送结果
|
||||
panel.webview.postMessage({
|
||||
type: 'vivado-complete',
|
||||
response
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 前端 UI 实现
|
||||
|
||||
### 4.1 进度显示组件
|
||||
|
||||
```typescript
|
||||
// src/views/vivadoProgress.ts
|
||||
|
||||
export function renderVivadoProgress(progress: VivadoProgress): string {
|
||||
return `
|
||||
<div class="vivado-progress">
|
||||
<div class="progress-header">
|
||||
<span class="stage">${progress.stage}</span>
|
||||
<span class="percentage">${progress.percentage}%</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: ${progress.percentage}%"></div>
|
||||
</div>
|
||||
<div class="progress-message">${progress.message}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 结果展示组件
|
||||
|
||||
```typescript
|
||||
// src/views/vivadoResult.ts
|
||||
|
||||
export function renderVivadoResult(response: VivadoToolResponse): string {
|
||||
if (!response.success) {
|
||||
return `
|
||||
<div class="vivado-result error">
|
||||
<h4>❌ 执行失败</h4>
|
||||
<pre>${response.error}</pre>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="vivado-result success">
|
||||
<h4>✅ 执行成功</h4>
|
||||
<div class="result-info">
|
||||
<p>命令: ${response.command}</p>
|
||||
<p>执行时间: ${(response.executionTime / 1000).toFixed(2)}s</p>
|
||||
</div>
|
||||
|
||||
${response.reports?.resources ? `
|
||||
<div class="report-section">
|
||||
<h5>资源使用</h5>
|
||||
<pre>${response.reports.resources}</pre>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${response.reports?.timing ? `
|
||||
<div class="report-section">
|
||||
<h5>时序信息</h5>
|
||||
<pre>${response.reports.timing}</pre>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${response.importedFiles && response.importedFiles.length > 0 ? `
|
||||
<div class="imported-files">
|
||||
<h5>已导入文件</h5>
|
||||
<ul>
|
||||
${response.importedFiles.map(f => `<li>${f}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 配置界面实现
|
||||
|
||||
### 5.1 设置页面扩展
|
||||
|
||||
```typescript
|
||||
// src/views/vivadoSettings.ts
|
||||
|
||||
export function renderVivadoSettings(config: VivadoConfig | null): string {
|
||||
return `
|
||||
<div class="vivado-settings">
|
||||
<h3>Vivado 配置</h3>
|
||||
|
||||
<div class="setting-item">
|
||||
<label>启用 Vivado</label>
|
||||
<input type="checkbox" id="vivado-enabled" ${config?.enabled ? 'checked' : ''}>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<label>可执行文件路径</label>
|
||||
<input type="text" id="vivado-path" value="${config?.executablePath || ''}"
|
||||
placeholder="C:/Xilinx/Vivado/2023.1/bin/vivado.bat">
|
||||
<button onclick="testVivado()">测试</button>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<label>工作目录</label>
|
||||
<input type="text" id="vivado-workdir" value="${config?.workingDir || ''}"
|
||||
placeholder="\${workspaceFolder}/vivado_project">
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<label>FPGA 型号</label>
|
||||
<input type="text" id="vivado-part" value="${config?.part || ''}"
|
||||
placeholder="xc7a35tcpg236-1">
|
||||
</div>
|
||||
|
||||
<button onclick="saveVivadoConfig()">保存配置</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 测试方案
|
||||
|
||||
### 6.1 单元测试
|
||||
|
||||
```typescript
|
||||
// src/test/vivadoRunner.test.ts
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { generateSynthesisTcl } from '../utils/tclGenerator';
|
||||
|
||||
suite('Vivado TCL Generator', () => {
|
||||
test('生成综合脚本', () => {
|
||||
const tcl = generateSynthesisTcl(
|
||||
'counter',
|
||||
['counter.v'],
|
||||
'xc7a35tcpg236-1'
|
||||
);
|
||||
|
||||
assert.ok(tcl.includes('read_verilog counter.v'));
|
||||
assert.ok(tcl.includes('synth_design'));
|
||||
assert.ok(tcl.includes('write_checkpoint'));
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 6.2 集成测试
|
||||
|
||||
```typescript
|
||||
// src/test/vivadoIntegration.test.ts
|
||||
|
||||
suite('Vivado Integration', () => {
|
||||
test('完整综合流程', async () => {
|
||||
const request: VivadoToolRequest = {
|
||||
command: 'synthesis',
|
||||
parameters: {
|
||||
topModule: 'counter',
|
||||
files: ['test/fixtures/counter.v']
|
||||
}
|
||||
};
|
||||
|
||||
const response = await runVivado(request);
|
||||
assert.ok(response.success);
|
||||
assert.ok(response.executionTime > 0);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 7. 部署和发布
|
||||
|
||||
### 7.1 文件清单
|
||||
|
||||
新增文件:
|
||||
- `src/utils/vivadoConfig.ts` - 配置管理
|
||||
- `src/utils/tclGenerator.ts` - TCL 脚本生成
|
||||
- `src/utils/vivadoRunner.ts` - Vivado 执行器
|
||||
- `src/utils/fileImporter.ts` - 文件导入
|
||||
- `src/views/vivadoProgress.ts` - 进度显示
|
||||
- `src/views/vivadoResult.ts` - 结果展示
|
||||
- `src/views/vivadoSettings.ts` - 设置界面
|
||||
|
||||
修改文件:
|
||||
- `src/utils/messageHandler.ts` - 添加 Vivado 工具处理
|
||||
- `src/views/settingsComponent.ts` - 添加 Vivado 设置页面
|
||||
|
||||
### 7.2 配置文件更新
|
||||
|
||||
```json
|
||||
// package.json (新增配置项)
|
||||
{
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"properties": {
|
||||
"ic-coder.vivado.enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "启用 Vivado 集成"
|
||||
},
|
||||
"ic-coder.vivado.executablePath": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Vivado 可执行文件路径"
|
||||
},
|
||||
"ic-coder.vivado.workingDir": {
|
||||
"type": "string",
|
||||
"default": "${workspaceFolder}/vivado_project",
|
||||
"description": "Vivado 工作目录"
|
||||
},
|
||||
"ic-coder.vivado.part": {
|
||||
"type": "string",
|
||||
"default": "xc7a35tcpg236-1",
|
||||
"description": "默认 FPGA 型号"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 常见问题和解决方案
|
||||
|
||||
### 8.1 Vivado 许可证问题
|
||||
|
||||
**问题**:执行时提示许可证错误
|
||||
|
||||
**解决方案**:
|
||||
1. 检查环境变量 `XILINX_VIVADO` 是否设置
|
||||
2. 确认许可证服务器可访问
|
||||
3. 在配置中添加许可证路径
|
||||
|
||||
### 8.2 路径问题
|
||||
|
||||
**问题**:Windows 路径包含空格导致执行失败
|
||||
|
||||
**解决方案**:
|
||||
```typescript
|
||||
function escapeWindowsPath(p: string): string {
|
||||
return p.includes(' ') ? `"${p}"` : p;
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 执行超时
|
||||
|
||||
**问题**:大型项目综合时间过长
|
||||
|
||||
**解决方案**:
|
||||
- 增加超时时间配置
|
||||
- 添加取消执行功能
|
||||
- 显示详细进度信息
|
||||
|
||||
## 9. 性能优化
|
||||
|
||||
### 9.1 日志缓冲
|
||||
|
||||
限制日志输出大小,避免内存溢出:
|
||||
|
||||
```typescript
|
||||
const MAX_LOG_SIZE = 1024 * 1024; // 1MB
|
||||
let logBuffer = '';
|
||||
|
||||
process.stdout.on('data', (data) => {
|
||||
logBuffer += data.toString();
|
||||
if (logBuffer.length > MAX_LOG_SIZE) {
|
||||
logBuffer = logBuffer.slice(-MAX_LOG_SIZE / 2);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 9.2 增量构建
|
||||
|
||||
支持增量综合,只重新综合修改的模块。
|
||||
|
||||
## 10. 后续优化方向
|
||||
|
||||
1. **并行执行**:支持多个设计同时综合
|
||||
2. **缓存机制**:缓存未修改模块的综合结果
|
||||
3. **云端集成**:支持云端 Vivado 服务
|
||||
4. **可视化报告**:图形化展示资源使用和时序
|
||||
5. **自动约束生成**:根据设计自动生成 XDC 约束文件
|
||||
|
||||
Reference in New Issue
Block a user