feat: 实现 Vivado 创建工程功能

- 添加 createVivadoProject 工具
   - 实现 Vivado 自动检测(支持所有盘符和版本)
   - 添加 TCL 脚本生成器
   - 添加配置管理模块
   - 添加测试命令
This commit is contained in:
Roe-xin
2026-03-16 17:46:25 +08:00
parent aa80088abc
commit fa5c2cdafd
7 changed files with 850 additions and 1625 deletions

View File

@ -37,38 +37,47 @@ IC Coder Plugin 目前支持:
### 2.1 核心目标 ### 2.1 核心目标
- **前端工具封装**:在插件前端实现 Vivado 调用的完整逻辑 - **前端提供原子工具**:前端只提供独立的 Vivado 命令工具,不控制流程
- **后端简化调用**:后端只需调用一个工具接口 - **后端AI控制流程**所有执行顺序、依赖检查由后端AI决策
- **文件自动导入**Vivado 执行完成后,自动将产出文件导入到项目 - **工具职责单一**:每个工具只负责执行一个具体命令
- **流程可视化**:执行进度、日志实时显示 - **结果透明返回**:执行结果完整返回给后端,由后端决定下一步
### 2.2 非功能目标 ### 2.2 设计原则
- 配置简单,用户友好 - 前端不做流程判断,只执行命令
- 执行过程可视化(进度、日志) - 前端不检查依赖关系,由后端保证顺序
- 错误处理完善,提示清晰 - 前端返回详细的执行结果,包括成功/失败、输出、报告等
- 后端AI根据结果智能决策是否继续
## 3. 功能详细需求 ## 3. 功能详细需求
### 3.1 Vivado 支持的操作 ### 3.1 前端提供的工具
#### 3.1.1 综合Synthesis 前端提供 4 个独立的工具,每个工具只负责执行一个命令:
- **输入**Verilog/VHDL 源文件、约束文件(.xdc #### 3.1.1 createVivadoProject - 创建工程
- **输出**:设计检查点(.dcp、综合报告.rpt
- **用途**:将 RTL 代码转换为门级网表,检查资源使用情况
#### 3.1.2 实现Implementation - **输入**:项目名称、芯片型号、源文件列表、约束文件(可选
- **输出**:工程文件(.xpr
- **说明**:创建 Vivado 工程,不执行任何构建操作
- **输入**:综合后的 .dcp 文件 #### 3.1.2 runVivadoSynthesis - 综合
- **输出**:实现后的 .dcp 文件、时序报告、布局布线报告
- **用途**:完成布局布线,检查时序是否满足要求
#### 3.1.3 生成比特流Generate Bitstream - **输入**:工程路径或源文件、芯片型号、顶层模块
- **输出**.dcp 文件、综合报告
- **说明**:执行综合,前端不检查工程是否存在
- **输入**:实现后的 .dcp 文件 #### 3.1.3 runVivadoImplementation - 实现
- **输出**:比特流文件(.bit
- **用途**生成可烧录到 FPGA 的配置文件 - **输入**综合后的 .dcp 文件路径
- **输出**:实现后的 .dcp 文件、时序报告
- **说明**:执行实现,前端不检查 .dcp 是否存在
#### 3.1.4 runVivadoBitstream - 生成比特流
- **输入**:实现后的 .dcp 文件路径
- **输出**.bit 文件
- **说明**:生成比特流,前端不检查 .dcp 是否存在
### 3.2 配置管理 ### 3.2 配置管理
@ -102,90 +111,110 @@ IC Coder Plugin 目前支持:
### 3.3 工具调用接口 ### 3.3 工具调用接口
#### 3.3.1 接口定义 #### 3.3.1 通用响应格式
所有工具返回统一的响应格式:
```typescript ```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 { interface VivadoToolResponse {
success: boolean; success: boolean; // 是否成功
command: string; command: string; // 执行的命令
executionTime: number; // 执行时间(毫秒) executionTime: number; // 执行时间(毫秒)
output: string; // 标准输出 output: string; // 完整输出日志
error?: string; // 错误信息 error?: string; // 错误信息(如果失败)
importedFiles?: string[]; // 已导入的文件列表 outputFiles?: string[]; // 产出文件路径列表
reports?: { reports?: {
// 报告摘要 resources?: string; // 资源使用摘要
resources?: string; // 资源使用情况 timing?: string; // 时序信息摘要
timing?: string; // 时序信息
}; };
} }
``` ```
### 3.4 执行流程 #### 3.3.2 各工具的参数定义
#### 3.4.1 参数验证 **createVivadoProject**
```typescript
- 检查 Vivado 是否已配置 {
- 检查可执行文件是否存在 projectName: string; // 项目名称
- 检查输入文件是否存在 part: string; // 芯片型号
- 检查工作目录是否存在 topModule: string; // 顶层模块
files: string[]; // 源文件列表
#### 3.4.2 TCL 脚本生成 constraints?: string; // 约束文件(可选)
mode: 'gui' | 'batch'; // 执行模式
根据命令类型自动生成 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 命令执行 **runVivadoSynthesis**
```typescript
{
projectPath?: string; // 工程路径(可选,如果有工程)
part: string; // 芯片型号
topModule: string; // 顶层模块
files?: string[]; // 源文件(如果没有工程)
constraints?: string; // 约束文件(可选)
mode: 'gui' | 'batch'; // 执行模式
}
```
- 启动子进程执行 Vivado 命令 **runVivadoImplementation**
- 实时捕获标准输出和错误输出 ```typescript
- 向前端推送进度信息(解析日志中的进度标记) {
dcpFile: string; // 综合后的 .dcp 文件路径
mode: 'gui' | 'batch'; // 执行模式
}
```
#### 3.4.4 结果处理 **runVivadoBitstream**
```typescript
{
dcpFile: string; // 实现后的 .dcp 文件路径
mode: 'gui' | 'batch'; // 执行模式
}
```
- 检查执行结果(退出码) ### 3.4 后端AI的职责
- 解析报告文件,提取关键信息(资源使用、时序)
- 查找产出文件
#### 3.4.5 文件导入 后端AI负责
1. 询问用户必要参数(芯片型号、执行模式等)
2. 理解用户意图,决定调用哪些工具
3. 按正确顺序调用工具(遵循依赖关系)
4. 检查每步执行结果,决定是否继续
5. 汇总结果并展示给用户
- 根据配置的文件模式查找产出文件 #### 3.4.1 询问用户参数
- 复制文件到目标目录
- 通知用户导入结果 后端必须询问:
- **芯片型号**(必需):"请提供 FPGA 芯片型号例如xc7a35tcpg236-1"
- **执行模式**(必需):"选择执行模式1) 图形化 2) 后端执行"
- **约束文件**(可选):"是否有约束文件(.xdc"
#### 3.4.2 理解依赖关系
后端AI需要理解
```
创建工程 → 综合 → 实现 → 生成比特流
```
如果用户说"做实现",后端应该:
1. 先调用 `createVivadoProject` 创建工程
2. 再调用 `runVivadoSynthesis` 执行综合
3. 最后调用 `runVivadoImplementation` 执行实现
#### 3.4.3 逐步调用工具
```
步骤1: 调用 createVivadoProject
检查 response.success
如果失败 → 停止并报错
步骤2: 调用 runVivadoSynthesis
检查 response.success
如果失败 → 停止并报错
步骤3: 调用 runVivadoImplementation
检查 response.success
返回最终结果
```
### 3.5 UI 交互 ### 3.5 UI 交互
@ -212,90 +241,186 @@ write_checkpoint -force counter_synth.dcp
#### 3.6.1 工具定义 #### 3.6.1 工具定义
后端在工具列表中添加 Vivado 工具: 后端注册 4 个独立工具:
```json ```json
{ {
"name": "runVivado", "name": "createVivadoProject",
"description": "调用 Vivado 执行综合、实现或生成比特流。使用前必须先询问用户芯片型号等必要参数。", "description": "创建 Vivado 工程。需要先询问用户芯片型号和执行模式。",
"parameters": { "parameters": {
"command": "命令类型synthesis/implementation/bitstream", "projectName": "项目名称",
"part": "芯片型号(必须从用户获取)",
"topModule": "顶层模块名", "topModule": "顶层模块名",
"files": "输入文件列表", "files": "文件列表",
"part": "FPGA 芯片型号(必须从用户获取", "constraints": "约束文件(可选",
"constraints": "约束文件路径(可选" "mode": "执行模式gui/batch"
}
},
{
"name": "runVivadoSynthesis",
"description": "执行 Vivado 综合。前端不检查依赖,后端需确保工程已创建。",
"parameters": {
"projectPath": "工程路径(可选)",
"part": "芯片型号",
"topModule": "顶层模块",
"files": "源文件(如果没有工程)",
"constraints": "约束文件(可选)",
"mode": "执行模式gui/batch"
}
},
{
"name": "runVivadoImplementation",
"description": "执行 Vivado 实现。前端不检查依赖,后端需确保综合已完成。",
"parameters": {
"dcpFile": "综合后的 .dcp 文件路径",
"mode": "执行模式gui/batch"
}
},
{
"name": "runVivadoBitstream",
"description": "生成比特流。前端不检查依赖,后端需确保实现已完成。",
"parameters": {
"dcpFile": "实现后的 .dcp 文件路径",
"mode": "执行模式gui/batch"
} }
} }
``` ```
#### 3.6.2 后端交互流程 #### 3.6.2 后端调用示例
**关键点**:后端必须先收集必要参数,再调用工具 **场景:用户要求完整流程**
1. **用户发起请求**"打开 Vivado" 或 "用 Vivado 综合"
2. **后端识别意图**:需要调用 runVivado 工具
3. **后端询问参数**
- FPGA 芯片型号(必须)
- 约束文件(可选)
- 确认顶层模块名
4. **用户提供参数**
5. **后端调用工具**:传递完整参数给前端
6. **前端执行**VivadoRunner 执行命令
7. **返回结果**:后端接收结果并展示给用户
#### 3.6.3 调用示例(完整交互)
``` ```
用户:帮我用 Vivado 综合一下 counter.v 用户: 用 Vivado 跑完整流程
AI:好的,我将使用 Vivado 进行综合。请提供以下信息: AI: 请提供芯片型号和执行模式
1. FPGA 芯片型号(例如:xc7a35tcpg236-1 用户: xc7a35tcpg236-1,后端执行
2. 是否有约束文件(.xdc
用户xc7a35tcpg236-1没有约束文件 AI 执行:
1. [调用] createVivadoProject({ projectName: "counter", part: "xc7a35tcpg236-1", ... })
[结果] { success: true, outputFiles: ["counter.xpr"] }
AI收到开始综合... 2. [调用] runVivadoSynthesis({ projectPath: "counter.xpr", ... })
[调用工具] runVivado [结果] { success: true, outputFiles: ["counter_synth.dcp"], reports: {...} }
参数:
- command: synthesis
- topModule: counter
- files: ["counter.v"]
- part: "xc7a35tcpg236-1"
[执行中...] 3. [调用] runVivadoImplementation({ dcpFile: "counter_synth.dcp", ... })
Vivado 综合完成! [结果] { success: true, outputFiles: ["counter_impl.dcp"], reports: {...} }
- 芯片型号xc7a35tcpg236-1
- 执行时间45 秒 4. [调用] runVivadoBitstream({ dcpFile: "counter_impl.dcp", ... })
- 资源使用LUT: 32/20800 (0.15%), FF: 8/41600 (0.02%) [结果] { success: true, outputFiles: ["counter.bit"] }
- 产出文件counter_synth.dcp, utilization_synth.rpt
- 已自动导入到vivado_output/ AI 回复: 完成!已生成 counter.bit 文件
``` ```
## 4. 用户场景 ## 4. 用户场景
### 4.1 场景一:单步综合 ### 4.1 场景一:完整流程执行
1. 用户编写完 Verilog 代码 **用户输入**"用 Vivado 跑完整个 FPGA 流程"
2. 在聊天中输入:"用 Vivado 综合一下 counter.v"
3. AI 调用 `runVivado` 工具
4. 插件执行 Vivado 综合
5. 综合完成后,显示资源使用情况,自动导入报告文件
### 4.2 场景二:完整流程 **AI 交互**
```
AI: 好的,我将使用 Vivado 执行完整的 FPGA 开发流程。请提供以下信息:
1. 用户输入:"用 Vivado 跑完整个流程" 1. FPGA 芯片型号例如xc7a35tcpg236-1
2. AI 依次调用 2. 请选择要执行的步骤(可多选)
- 综合Synthesis - 综合Synthesis
- 实现Implementation - 实现Implementation
- 生成比特流Bitstream - 生成比特流Bitstream
3. 每个步骤完成后显示结果 3. 执行模式:
4. 最终生成 .bit 文件,用户可以烧录到 FPGA - 图形化模式:打开 Vivado GUI
- 后端执行:自动在后台执行
4. 是否有约束文件(.xdc
### 4.3 场景三:查看报告 用户: xc7a35tcpg236-1全部步骤后端执行没有约束文件
1. Vivado 执行完成后 AI: 收到!执行计划:
2. 用户点击导入的报告文件 1. 创建工程
3. 在编辑器中查看资源使用、时序分析等信息 2. 综合
3. 实现
4. 生成比特流
开始执行...
[步骤 1/4] 创建工程...
✓ 工程创建成功(耗时 2s
[步骤 2/4] 综合中...
✓ 综合完成(耗时 45s
资源使用LUT 32/20800 (0.15%), FF 8/41600 (0.02%)
[步骤 3/4] 实现中...
✓ 实现完成(耗时 120s
时序WNS 5.234ns, TNS 0.000ns(满足要求)
[步骤 4/4] 生成比特流...
✓ 比特流生成完成(耗时 15s
所有步骤执行完毕!产出文件已导入到 vivado_output/ 目录。
```
### 4.2 场景二:只执行实现(自动补充依赖)
**用户输入**"用 Vivado 做实现"
**AI 交互**
```
AI: 请提供以下信息:
1. FPGA 芯片型号
2. 执行模式(图形化/后端执行)
用户: xc7a35tcpg236-1后端执行
AI: 注意:实现依赖综合,我将自动执行以下步骤:
1. 创建工程
2. 综合(前置依赖)
3. 实现(您选择的步骤)
是否继续?
用户: 是
AI: 开始执行...
[自动执行创建工程 → 综合 → 实现]
```
### 4.3 场景三:图形化模式
**用户输入**"打开 Vivado 图形界面做综合"
**AI 交互**
```
AI: 请提供芯片型号:
用户: xc7a35tcpg236-1
AI: 正在创建工程并打开 Vivado GUI...
✓ 工程已创建
✓ Vivado 图形界面已启动
您现在可以在 Vivado 中手动操作。完成后,产出文件将保存在工程目录中。
```
### 4.4 场景四:执行中断处理
**执行过程**
```
[步骤 1/3] 创建工程...
✓ 成功
[步骤 2/3] 综合中...
✗ 综合失败
错误ERROR: [Synth 8-439] module 'counter' not found
AI: 综合失败,发现以下问题:
- 找不到模块 'counter'
建议检查:
1. 模块名是否正确
2. 文件中是否定义了该模块
3. 是否有语法错误
执行已停止,请修复错误后重试。
```
## 5. 技术约束 ## 5. 技术约束

View File

@ -1,637 +1,166 @@
# Vivado 联动前后端对接文档 # Vivado 联动前后端对接文档
## 1. 概述 ## 1. 前端提供的工具
本文档描述后端 AI 服务如何调用前端的 Vivado 工具,以及前端如何响应和返回结果 前端提供 4 个独立工具,每个工具执行一个 Vivado 命令
### 1.1 调用流 ### 1.1 createVivadoProject - 创建工
```
后端 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 芯片型号(如 xc7a35tcpg236-1必须从用户处获取"
},
"mode": {
"type": "string",
"enum": ["batch", "gui"],
"description": "执行模式batch(后台批处理)、gui(打开图形界面),必须询问用户"
}
},
"required": ["command", "topModule", "files", "part", "mode"]
}
}
```
### 2.2 后端调用前的准备工作
**重要**:后端在调用 `runVivado` 工具前,必须先向用户询问必要参数:
1. **芯片型号part**:必须询问,例如 "xc7a35tcpg236-1"
2. **执行模式mode**:必须询问用户选择
- `batch`:后台批处理执行,自动完成
- `gui`:打开 Vivado 图形界面,用户手动操作
3. **顶层模块名**:可从文件名推断,但建议确认
4. **约束文件**:询问是否有时序约束文件(.xdc
**询问示例**
```
AI: 我将使用 Vivado 进行综合。请提供以下信息:
1. FPGA 芯片型号例如xc7a35tcpg236-1
2. 执行模式:
- 批处理模式:后台自动执行,完成后返回结果
- 图形界面:打开 Vivado GUI您可以手动操作
3. 是否有约束文件(.xdc
用户: xc7a35tcpg236-1批处理模式没有约束文件
AI: 好的,开始后台综合...
[调用 runVivado 工具]
```
### 2.3 调用示例
#### 示例 1综合单个文件批处理模式
```json
{
"tool": "runVivado",
"parameters": {
"command": "synthesis",
"topModule": "counter",
"files": ["counter.v"],
"part": "xc7a35tcpg236-1",
"mode": "batch"
}
}
```
#### 示例 2综合带约束文件图形界面模式
```json
{
"tool": "runVivado",
"parameters": {
"command": "synthesis",
"topModule": "uart_top",
"files": ["uart_tx.v", "uart_rx.v", "uart_top.v"],
"constraints": "constraints.xdc",
"part": "xc7k325tffg900-2",
"mode": "gui"
}
}
```
#### 示例 3实现批处理模式
```json
{
"tool": "runVivado",
"parameters": {
"command": "implementation",
"topModule": "counter",
"part": "xc7a35tcpg236-1",
"mode": "batch"
}
}
```
#### 示例 4生成比特流图形界面
```json
{
"tool": "runVivado",
"parameters": {
"command": "bitstream",
"topModule": "counter",
"part": "xc7a35tcpg236-1",
"mode": "gui"
}
}
```
## 3. 前端接收和处理
### 3.1 后端如何控制前端
**核心机制**:后端通过调用 `runVivado` 工具来控制前端执行 Vivado 命令。
**控制流程**
1. 后端识别用户意图(如"打开 Vivado"、"开始仿真"
2. 后端向用户询问必要参数(芯片型号等)
3. 后端调用 `runVivado` 工具,传递参数
4. 前端接收工具调用,执行相应操作
5. 前端返回执行结果给后端
6. 后端将结果展示给用户
**示例场景**
```
用户输入:"打开 Vivado 进行综合"
后端处理:
1. 识别意图 → 需要调用 runVivado 工具
2. 检查参数 → 缺少芯片型号
3. 询问用户 → "请提供 FPGA 芯片型号"
4. 用户回复 → "xc7a35tcpg236-1"
5. 调用工具 → runVivado({ command: "synthesis", part: "xc7a35tcpg236-1", ... })
6. 前端执行 → VivadoRunner 启动 Vivado
7. 返回结果 → { success: true, ... }
8. 展示结果 → "综合完成,耗时 45 秒"
```
### 3.2 MessageHandler 处理逻辑
前端在 `messageHandler.ts` 中添加工具处理:
**参数**
```typescript ```typescript
// src/utils/messageHandler.ts {
projectName: string; // 项目名称
export async function handleToolExecution( part: string; // 芯片型号(如 xc7a35tcpg236-1
panel: vscode.WebviewPanel, topModule: string; // 顶层模块名
toolName: string, files: string[]; // 源文件路径列表
parameters: any constraints?: string; // 约束文件路径(可选)
): Promise<any> { mode: 'gui' | 'batch'; // gui=打开图形界面batch=后台执行
if (toolName === 'runVivado') {
return await handleVivadoTool(panel, parameters);
} }
```
// 其他工具处理... **返回**
```typescript
{
success: boolean; // 是否成功
command: "create_project";
executionTime: number; // 执行时间(毫秒)
output: string; // 完整日志
error?: string; // 失败原因(如果失败)
outputFiles?: string[]; // 产出的工程文件路径
} }
```
async function handleVivadoTool( ### 1.2 runVivadoSynthesis - 综合
panel: vscode.WebviewPanel,
parameters: any
): Promise<VivadoToolResponse> {
const { command, topModule, files, constraints, part, mode } = parameters; **参数**
```typescript
{
projectPath?: string; // 工程路径(可选)
part: string; // 芯片型号
topModule: string; // 顶层模块
files?: string[]; // 源文件(如果没有工程)
constraints?: string; // 约束文件(可选)
mode: 'gui' | 'batch';
}
```
// 验证必需参数 **返回**
if (!part) { ```typescript
return { {
success: boolean;
command: "synthesis";
executionTime: number;
output: string;
error?: string; // 失败原因
outputFiles?: string[]; // .dcp 文件等
reports?: {
resources?: string; // 资源使用摘要
timing?: string; // 时序摘要
};
}
```
### 1.3 runVivadoImplementation - 实现
**参数**
```typescript
{
dcpFile: string; // 综合后的 .dcp 文件路径
mode: 'gui' | 'batch';
}
```
**返回**
```typescript
{
success: boolean;
command: "implementation";
executionTime: number;
output: string;
error?: string; // 失败原因
outputFiles?: string[]; // 实现后的 .dcp 文件等
reports?: {
resources?: string;
timing?: string;
};
}
```
### 1.4 runVivadoBitstream - 生成比特流
**参数**
```typescript
{
dcpFile: string; // 实现后的 .dcp 文件路径
mode: 'gui' | 'batch';
}
```
**返回**
```typescript
{
success: boolean;
command: "bitstream";
executionTime: number;
output: string;
error?: string; // 失败原因
outputFiles?: string[]; // .bit 文件路径
}
```
## 2. 前端职责
- 接收后端工具调用
- 生成对应的 TCL 脚本
- 执行 Vivado 命令
- 捕获输出日志
- 解析报告文件(提取资源和时序摘要)
- 返回执行结果
**前端不做**
- 不检查依赖关系
- 不验证执行顺序
- 不控制流程
## 3. 后端职责
- 询问用户参数(芯片型号、执行模式等)
- 理解依赖关系(创建工程 → 综合 → 实现 → 生成比特流)
- 按正确顺序调用工具
- 检查每步的 `success` 字段
- 如果失败,读取 `error` 字段并提示用户
- 汇总结果展示给用户
## 4. 调用示例
```
用户: 用 Vivado 做综合
后端:
1. 询问芯片型号 → xc7a35tcpg236-1
2. 询问执行模式 → batch
3. 调用 createVivadoProject(...)
返回: { success: true, outputFiles: ["counter.xpr"] }
4. 调用 runVivadoSynthesis(...)
返回: { success: true, outputFiles: ["counter_synth.dcp"], reports: {...} }
5. 展示结果给用户
```
## 5. 错误处理
如果某步失败:
```typescript
{
success: false, success: false,
command, error: "ERROR: [Synth 8-439] module 'counter' not found",
executionTime: 0, output: "详细日志..."
output: '',
error: '缺少必需参数芯片型号part。后端应该先询问用户。'
};
}
if (!mode) {
return {
success: false,
command,
executionTime: 0,
output: '',
error: '缺少必需参数执行模式mode。后端应该询问用户选择 batch 或 gui。'
};
}
// 构建请求
const request: VivadoToolRequest = {
command,
parameters: {
topModule,
files,
constraints,
part,
mode
},
importOutput: {
enabled: mode === 'batch', // 只有批处理模式才自动导入
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. 响应格式 后端应该:
1. 停止后续步骤
### 4.1 成功响应 2. 提取 `error` 字段
3. 给用户提示和建议
```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
6. ⚠️ 缺少必要参数:芯片型号
AI 回复用户:
"好的,我将使用 Vivado 进行综合。请提供以下信息:
1. FPGA 芯片型号例如xc7a35tcpg236-1、xc7k325tffg900-2
2. 是否有约束文件(.xdc"
用户xc7a35tcpg236-1没有约束文件
AI 调用工具:
{
"tool": "runVivado",
"parameters": {
"command": "synthesis",
"topModule": "counter",
"files": ["counter.v"],
"part": "xc7a35tcpg236-1"
}
}
前端执行并返回结果
AI 回复用户:
"Vivado 综合完成!
- 执行时间45.2 秒
- 芯片型号xc7a35tcpg236-1
- 资源使用LUT: 32/20800 (0.15%), FF: 8/41600 (0.02%)
- 产出文件已导入到 vivado_output 目录"
```
### 5.2 完整流程示例
```
用户:用 Vivado 跑完整个流程
AI好的我将依次执行综合、实现和生成比特流。请提供
1. FPGA 芯片型号
2. 顶层模块名
3. 是否有约束文件
用户xc7a35tcpg236-1顶层模块是 counter没有约束文件
AI收到开始执行...
步骤 1综合
[调用] runVivado { command: "synthesis", topModule: "counter", files: ["counter.v"], part: "xc7a35tcpg236-1" }
[结果] 综合成功,耗时 45s
步骤 2实现
[调用] runVivado { command: "implementation", topModule: "counter", part: "xc7a35tcpg236-1" }
[结果] 实现成功,耗时 120s时序满足要求
步骤 3生成比特流
[调用] runVivado { command: "bitstream", topModule: "counter", part: "xc7a35tcpg236-1" }
[结果] 比特流生成成功文件counter.bit
完成!所有文件已导入到 vivado_output 目录。
```
## 6. 错误处理
### 6.1 常见错误
#### 错误 1Vivado 未配置
```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 后端必须询问的参数
**关键要求**:后端在调用 `runVivado` 前,必须先向用户收集以下参数:
| 参数 | 是否必须 | 询问方式 | 示例 |
|------|---------|---------|------|
| part芯片型号 | ✅ 必须 | 直接询问 | "请提供 FPGA 芯片型号(如 xc7a35tcpg236-1" |
| mode执行模式 | ✅ 必须 | 提供选项 | "选择执行模式1.批处理 2.图形界面" |
| topModule | ✅ 必须 | 可推断或询问 | 从文件名推断,或询问"顶层模块名是什么?" |
| files | ✅ 必须(综合时) | 可推断或询问 | 从当前文件或工作区推断 |
| constraints | ❌ 可选 | 询问 | "是否有约束文件(.xdc" |
### 9.2 常见用户输入场景
#### 场景 1用户说"打开 Vivado"
```
用户:打开 Vivado
后端分析:用户想使用 Vivado但没有指定具体操作
后端回复:
"好的,请问您想执行什么操作?
1. 综合Synthesis- 将 Verilog 转换为门级网表
2. 实现Implementation- 布局布线
3. 生成比特流Bitstream- 生成 .bit 文件
另外,请提供:
- FPGA 芯片型号例如xc7a35tcpg236-1
- 执行模式:批处理(后台自动)或 图形界面(手动操作)"
```
#### 场景 2用户说"开始仿真"
```
用户:开始仿真
后端分析:用户可能混淆了仿真和综合
后端回复:
"您是想:
1. 使用 iverilog 进行功能仿真(验证逻辑)
2. 使用 Vivado 进行综合(部署到 FPGA
请选择您需要的操作。"
```
#### 场景 3用户提供完整信息
```
用户:用 Vivado 综合 counter.v芯片是 xc7a35tcpg236-1批处理模式
后端分析:信息完整,可以直接调用
后端操作:
[调用] runVivado({
command: "synthesis",
topModule: "counter",
files: ["counter.v"],
part: "xc7a35tcpg236-1",
mode: "batch"
})
```
#### 场景 4用户选择图形界面
```
用户:打开 Vivado 图形界面做综合
后端分析:用户明确要求 GUI 模式
后端询问:
"好的,请提供:
1. FPGA 芯片型号
2. 顶层模块名"
用户xc7a35tcpg236-1, counter
后端操作:
[调用] runVivado({
command: "synthesis",
topModule: "counter",
files: ["counter.v"],
part: "xc7a35tcpg236-1",
mode: "gui"
})
前端执行:
- 生成 TCL 脚本和项目文件
- 执行: vivado counter_project.xpr (打开图形界面)
- 返回: { success: true, message: "Vivado GUI 已启动" }
后端回复:
"Vivado 图形界面已打开,您可以在界面中手动操作。"
```
### 9.3 执行时间
- 综合:小型设计 30s-2min大型设计 5-30min
- 实现:通常是综合时间的 2-3 倍
- 生成比特流:通常 10-30s
后端应该设置合理的超时时间(建议 10 分钟)。
### 9.4 依赖关系
- `implementation` 需要先执行 `synthesis`
- `bitstream` 需要先执行 `implementation`
后端 AI 应该理解这个依赖关系,按顺序调用。
### 9.5 文件路径
- 所有文件路径都是相对于工作区根目录
- 前端会自动解析为绝对路径
- 支持相对路径和绝对路径
## 10. 参数传递详细说明
### 10.1 必需参数
| 参数 | 类型 | 说明 | 获取方式 |
|------|------|------|----------|
| command | string | 命令类型 | 从用户意图推断 |
| topModule | string | 顶层模块名 | 从文件名推断或询问用户 |
| files | string[] | 源文件列表 | 从工作区查找或用户指定 |
| part | string | 芯片型号 | **必须询问用户** |
### 10.2 可选参数
| 参数 | 类型 | 说明 | 默认值 |
|------|------|------|--------|
| constraints | string | 约束文件路径 | 无 |
### 10.3 参数验证规则
后端在调用前应验证:
- `part` 格式正确(如 xc7a35tcpg236-1
- `files` 数组不为空
- `topModule` 不为空
- `command` 在枚举值内
## 11. 快速集成清单
后端开发者需要做的事情:
- [ ] 在工具列表中注册 `runVivado` 工具
- [ ] **实现参数询问逻辑(芯片型号等)**
- [ ] 实现工具调用逻辑(发送请求到前端)
- [ ] 处理返回结果success/error
- [ ] 实现错误处理和用户提示
- [ ] 理解三个命令的依赖关系
- [ ] 设置合理的超时时间(建议 10 分钟)
- [ ] 编写测试用例
前端开发者需要做的事情:
- [ ] 实现 `handleVivadoTool` 函数
- [ ] 集成 VivadoRunner
- [ ] 实现进度推送
- [ ] 实现结果展示
- [ ] 处理各种错误情况
- [ ] 验证传入的参数完整性

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@ import {
checkVerilogProject, checkVerilogProject,
checkIverilogAvailable, checkIverilogAvailable,
} from "./iverilogRunner"; } from "./iverilogRunner";
import { createVivadoProject } from "./vivadoRunner";
import { ChatHistoryManager } from "./chatHistoryManager"; import { ChatHistoryManager } from "./chatHistoryManager";
import { dialogManager, DialogSession } from "../services/dialogService"; import { dialogManager, DialogSession } from "../services/dialogService";
import { userInteractionManager } from "../services/userInteraction"; import { userInteractionManager } from "../services/userInteraction";
@ -1468,3 +1469,35 @@ export async function handleOpenFileDiff(
vscode.window.showErrorMessage(`打开 diff 失败: ${error}`); vscode.window.showErrorMessage(`打开 diff 失败: ${error}`);
} }
} }
/**
* 处理 Vivado 工具调用
*/
export async function handleVivadoToolCall(
toolName: string,
params: any
): Promise<any> {
try {
switch (toolName) {
case 'createVivadoProject':
return await createVivadoProject(params);
default:
return {
success: false,
command: toolName,
executionTime: 0,
output: '',
error: `未知的工具: ${toolName}`
};
}
} catch (error: any) {
return {
success: false,
command: toolName,
executionTime: 0,
output: '',
error: error.message || String(error)
};
}
}

44
src/utils/tclGenerator.ts Normal file
View File

@ -0,0 +1,44 @@
/**
* TCL 脚本生成器
* 功能:生成 Vivado TCL 脚本
*/
import * as path from 'path';
/**
* 生成创建工程的 TCL 脚本
*/
export function generateCreateProjectTcl(
projectName: string,
projectDir: string,
part: string,
topModule: string,
files: string[],
constraints?: string
): string {
// 转换路径为 TCL 格式(正斜杠)
const tclPath = (p: string) => p.replace(/\\/g, '/');
let tcl = `# 创建 Vivado 工程\n\n`;
tcl += `create_project ${projectName} {${tclPath(projectDir)}} -part ${part} -force\n\n`;
// 添加源文件
tcl += `# 添加源文件\n`;
files.forEach(file => {
tcl += `add_files -norecurse {${tclPath(file)}}\n`;
});
tcl += `\n`;
// 添加约束文件
if (constraints) {
tcl += `# 添加约束文件\n`;
tcl += `add_files -fileset constrs_1 -norecurse {${tclPath(constraints)}}\n\n`;
}
// 设置顶层模块
tcl += `# 设置顶层模块\n`;
tcl += `set_property top ${topModule} [current_fileset]\n\n`;
return tcl;
}

105
src/utils/vivadoConfig.ts Normal file
View File

@ -0,0 +1,105 @@
/**
* Vivado 配置管理
* 功能:读取和验证 Vivado 配置
* 依赖vscode
*/
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
export interface VivadoConfig {
enabled: boolean;
executablePath: string;
workingDir: string;
}
/**
* 获取 Vivado 配置
*/
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');
const vivadoConfig = config.get<VivadoConfig>('vivado');
if (vivadoConfig) {
return vivadoConfig;
}
// 自动检测 Vivado
return autoDetectVivado();
}
/**
* 验证配置
*/
export function validateConfig(config: VivadoConfig): string | null {
if (!config.enabled) {
return 'Vivado 未启用';
}
if (!fs.existsSync(config.executablePath)) {
return `Vivado 可执行文件不存在: ${config.executablePath}`;
}
return null;
}
/**
* 解析工作目录
*/
export function resolveWorkingDir(workingDir: string): string {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
return workingDir.replace('${workspaceFolder}', workspaceFolder.uri.fsPath);
}
return workingDir;
}
/**
* 自动检测 Vivado
*/
function autoDetectVivado(): VivadoConfig | null {
const drives = ['C', 'D', 'E', 'F', 'G'];
for (const drive of drives) {
const vivadoDir = `${drive}:\\Xilinx\\Vivado`;
if (!fs.existsSync(vivadoDir)) {
continue;
}
// 读取所有版本目录
const versions = fs.readdirSync(vivadoDir)
.filter(v => fs.statSync(path.join(vivadoDir, v)).isDirectory())
.sort()
.reverse(); // 最新版本在前
for (const version of versions) {
const p = path.join(vivadoDir, version, 'bin', 'vivado.bat');
if (fs.existsSync(p)) {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
return {
enabled: true,
executablePath: p,
workingDir: workspaceFolder
? path.join(workspaceFolder.uri.fsPath, 'vivado_projects')
: `${drive}:\\vivado_projects`
};
}
}
}
return null;
}

159
src/utils/vivadoRunner.ts Normal file
View File

@ -0,0 +1,159 @@
/**
* Vivado 执行器
* 功能:执行 Vivado 命令
* 依赖vivadoConfig, tclGenerator
*/
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { spawn } from 'child_process';
import { getVivadoConfig, validateConfig, resolveWorkingDir } from './vivadoConfig';
import { generateCreateProjectTcl } from './tclGenerator';
export interface VivadoToolResponse {
success: boolean;
command: string;
executionTime: number;
output: string;
error?: string;
outputFiles?: string[];
reports?: {
resources?: string;
timing?: string;
};
}
/**
* 创建 Vivado 工程
*/
export async function createVivadoProject(params: {
projectName: string;
part: string;
topModule: string;
files: string[];
constraints?: string;
mode: 'gui' | 'batch';
}): Promise<VivadoToolResponse> {
const startTime = Date.now();
// 读取配置
const config = getVivadoConfig();
if (!config) {
return {
success: false,
command: 'create_project',
executionTime: 0,
output: '',
error: 'Vivado 未配置'
};
}
// 验证配置
const configError = validateConfig(config);
if (configError) {
return {
success: false,
command: 'create_project',
executionTime: 0,
output: '',
error: configError
};
}
// 准备工作目录
const workingDir = resolveWorkingDir(config.workingDir);
if (!fs.existsSync(workingDir)) {
fs.mkdirSync(workingDir, { recursive: true });
}
const projectDir = path.join(workingDir, params.projectName);
// 生成 TCL 脚本
const tclScript = generateCreateProjectTcl(
params.projectName,
projectDir,
params.part,
params.topModule,
params.files,
params.constraints
);
const tclPath = path.join(workingDir, 'create_project.tcl');
fs.writeFileSync(tclPath, tclScript);
// 执行 Vivado
const result = await executeVivado(
config.executablePath,
tclPath,
workingDir,
params.mode
);
const executionTime = Date.now() - startTime;
// 查找产出文件
const xprFile = path.join(projectDir, `${params.projectName}.xpr`);
const outputFiles = fs.existsSync(xprFile) ? [xprFile] : [];
return {
success: result.success,
command: 'create_project',
executionTime,
output: result.output,
error: result.error,
outputFiles
};
}
/**
* 执行 Vivado 命令
*/
async function executeVivado(
executablePath: string,
tclPath: string,
workingDir: string,
mode: 'gui' | 'batch'
): Promise<{ success: boolean; output: string; error?: string }> {
return new Promise((resolve) => {
let output = '';
let errorOutput = '';
const args = mode === 'gui'
? ['-source', tclPath]
: ['-mode', 'batch', '-source', tclPath];
const process = spawn(executablePath, args, {
cwd: workingDir,
shell: true
});
process.stdout.on('data', (data) => {
output += data.toString();
});
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 || `执行失败,退出码: ${code}`
});
}
});
process.on('error', (err) => {
resolve({
success: false,
output,
error: `启动 Vivado 失败: ${err.message}`
});
});
});
}