diff --git a/docs/EDA联动功能需求文档.md b/docs/EDA联动功能需求文档.md index e7cfd69..018abae 100644 --- a/docs/EDA联动功能需求文档.md +++ b/docs/EDA联动功能需求文档.md @@ -37,38 +37,47 @@ IC Coder Plugin 目前支持: ### 2.1 核心目标 -- **前端工具封装**:在插件前端实现 Vivado 调用的完整逻辑 -- **后端简化调用**:后端只需调用一个工具接口 -- **文件自动导入**:Vivado 执行完成后,自动将产出文件导入到项目 -- **流程可视化**:执行进度、日志实时显示 +- **前端提供原子工具**:前端只提供独立的 Vivado 命令工具,不控制流程 +- **后端AI控制流程**:所有执行顺序、依赖检查由后端AI决策 +- **工具职责单一**:每个工具只负责执行一个具体命令 +- **结果透明返回**:执行结果完整返回给后端,由后端决定下一步 -### 2.2 非功能目标 +### 2.2 设计原则 -- 配置简单,用户友好 -- 执行过程可视化(进度、日志) -- 错误处理完善,提示清晰 +- 前端不做流程判断,只执行命令 +- 前端不检查依赖关系,由后端保证顺序 +- 前端返回详细的执行结果,包括成功/失败、输出、报告等 +- 后端AI根据结果智能决策是否继续 ## 3. 功能详细需求 -### 3.1 Vivado 支持的操作 +### 3.1 前端提供的工具 -#### 3.1.1 综合(Synthesis) +前端提供 4 个独立的工具,每个工具只负责执行一个命令: -- **输入**:Verilog/VHDL 源文件、约束文件(.xdc) -- **输出**:设计检查点(.dcp)、综合报告(.rpt) -- **用途**:将 RTL 代码转换为门级网表,检查资源使用情况 +#### 3.1.1 createVivadoProject - 创建工程 -#### 3.1.2 实现(Implementation) +- **输入**:项目名称、芯片型号、源文件列表、约束文件(可选) +- **输出**:工程文件(.xpr) +- **说明**:创建 Vivado 工程,不执行任何构建操作 -- **输入**:综合后的 .dcp 文件 -- **输出**:实现后的 .dcp 文件、时序报告、布局布线报告 -- **用途**:完成布局布线,检查时序是否满足要求 +#### 3.1.2 runVivadoSynthesis - 综合 -#### 3.1.3 生成比特流(Generate Bitstream) +- **输入**:工程路径或源文件、芯片型号、顶层模块 +- **输出**:.dcp 文件、综合报告 +- **说明**:执行综合,前端不检查工程是否存在 -- **输入**:实现后的 .dcp 文件 -- **输出**:比特流文件(.bit) -- **用途**:生成可烧录到 FPGA 的配置文件 +#### 3.1.3 runVivadoImplementation - 实现 + +- **输入**:综合后的 .dcp 文件路径 +- **输出**:实现后的 .dcp 文件、时序报告 +- **说明**:执行实现,前端不检查 .dcp 是否存在 + +#### 3.1.4 runVivadoBitstream - 生成比特流 + +- **输入**:实现后的 .dcp 文件路径 +- **输出**:.bit 文件 +- **说明**:生成比特流,前端不检查 .dcp 是否存在 ### 3.2 配置管理 @@ -102,90 +111,110 @@ IC Coder Plugin 目前支持: ### 3.3 工具调用接口 -#### 3.3.1 接口定义 +#### 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[]; // 已导入的文件列表 + success: boolean; // 是否成功 + command: string; // 执行的命令 + executionTime: number; // 执行时间(毫秒) + output: string; // 完整输出日志 + error?: string; // 错误信息(如果失败) + outputFiles?: string[]; // 产出文件路径列表 reports?: { - // 报告摘要 - resources?: string; // 资源使用情况 - timing?: string; // 时序信息 + resources?: string; // 资源使用摘要 + timing?: string; // 时序信息摘要 }; } ``` -### 3.4 执行流程 +#### 3.3.2 各工具的参数定义 -#### 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 +**createVivadoProject** +```typescript +{ + projectName: string; // 项目名称 + part: string; // 芯片型号 + topModule: string; // 顶层模块 + files: string[]; // 源文件列表 + constraints?: string; // 约束文件(可选) + mode: 'gui' | 'batch'; // 执行模式 +} ``` -#### 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 交互 @@ -212,90 +241,186 @@ write_checkpoint -force counter_synth.dcp #### 3.6.1 工具定义 -后端在工具列表中添加 Vivado 工具: +后端注册 4 个独立工具: ```json { - "name": "runVivado", - "description": "调用 Vivado 执行综合、实现或生成比特流。使用前必须先询问用户芯片型号等必要参数。", + "name": "createVivadoProject", + "description": "创建 Vivado 工程。需要先询问用户芯片型号和执行模式。", "parameters": { - "command": "命令类型(synthesis/implementation/bitstream)", + "projectName": "项目名称", + "part": "芯片型号(必须从用户获取)", "topModule": "顶层模块名", - "files": "输入文件列表", - "part": "FPGA 芯片型号(必须从用户获取)", - "constraints": "约束文件路径(可选)" + "files": "源文件列表", + "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 进行综合。请提供以下信息: - 1. FPGA 芯片型号(例如:xc7a35tcpg236-1) - 2. 是否有约束文件(.xdc)? +AI: 请提供芯片型号和执行模式 +用户: xc7a35tcpg236-1,后端执行 -用户:xc7a35tcpg236-1,没有约束文件 +AI 执行: +1. [调用] createVivadoProject({ projectName: "counter", part: "xc7a35tcpg236-1", ... }) + [结果] { success: true, outputFiles: ["counter.xpr"] } -AI:收到,开始综合... - [调用工具] runVivado - 参数: - - command: synthesis - - topModule: counter - - files: ["counter.v"] - - part: "xc7a35tcpg236-1" +2. [调用] runVivadoSynthesis({ projectPath: "counter.xpr", ... }) + [结果] { success: true, outputFiles: ["counter_synth.dcp"], reports: {...} } - [执行中...] - Vivado 综合完成! - - 芯片型号:xc7a35tcpg236-1 - - 执行时间:45 秒 - - 资源使用:LUT: 32/20800 (0.15%), FF: 8/41600 (0.02%) - - 产出文件:counter_synth.dcp, utilization_synth.rpt - - 已自动导入到:vivado_output/ +3. [调用] runVivadoImplementation({ dcpFile: "counter_synth.dcp", ... }) + [结果] { success: true, outputFiles: ["counter_impl.dcp"], reports: {...} } + +4. [调用] runVivadoBitstream({ dcpFile: "counter_impl.dcp", ... }) + [结果] { success: true, outputFiles: ["counter.bit"] } + +AI 回复: 完成!已生成 counter.bit 文件 ``` ## 4. 用户场景 -### 4.1 场景一:单步综合 +### 4.1 场景一:完整流程执行 -1. 用户编写完 Verilog 代码 -2. 在聊天中输入:"用 Vivado 综合一下 counter.v" -3. AI 调用 `runVivado` 工具 -4. 插件执行 Vivado 综合 -5. 综合完成后,显示资源使用情况,自动导入报告文件 +**用户输入**:"用 Vivado 跑完整个 FPGA 流程" -### 4.2 场景二:完整流程 +**AI 交互**: +``` +AI: 好的,我将使用 Vivado 执行完整的 FPGA 开发流程。请提供以下信息: -1. 用户输入:"用 Vivado 跑完整个流程" -2. AI 依次调用: +1. FPGA 芯片型号(例如:xc7a35tcpg236-1) +2. 请选择要执行的步骤(可多选): - 综合(Synthesis) - 实现(Implementation) - 生成比特流(Bitstream) -3. 每个步骤完成后显示结果 -4. 最终生成 .bit 文件,用户可以烧录到 FPGA +3. 执行模式: + - 图形化模式:打开 Vivado GUI + - 后端执行:自动在后台执行 +4. 是否有约束文件(.xdc)? -### 4.3 场景三:查看报告 +用户: xc7a35tcpg236-1,全部步骤,后端执行,没有约束文件 -1. Vivado 执行完成后 -2. 用户点击导入的报告文件 -3. 在编辑器中查看资源使用、时序分析等信息 +AI: 收到!执行计划: +1. 创建工程 +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. 技术约束 diff --git a/docs/Vivado联动前后端对接文档.md b/docs/Vivado联动前后端对接文档.md index 3281be4..a260c50 100644 --- a/docs/Vivado联动前后端对接文档.md +++ b/docs/Vivado联动前后端对接文档.md @@ -1,637 +1,166 @@ # Vivado 联动前后端对接文档 -## 1. 概述 +## 1. 前端提供的工具 -本文档描述后端 AI 服务如何调用前端的 Vivado 工具,以及前端如何响应和返回结果。 +前端提供 4 个独立工具,每个工具执行一个 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 芯片型号(如 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` 中添加工具处理: +### 1.1 createVivadoProject - 创建工程 +**参数**: ```typescript -// src/utils/messageHandler.ts - -export async function handleToolExecution( - panel: vscode.WebviewPanel, - toolName: string, - parameters: any -): Promise { - - if (toolName === 'runVivado') { - return await handleVivadoTool(panel, parameters); - } - - // 其他工具处理... +{ + projectName: string; // 项目名称 + part: string; // 芯片型号(如 xc7a35tcpg236-1) + topModule: string; // 顶层模块名 + files: string[]; // 源文件路径列表 + constraints?: string; // 约束文件路径(可选) + mode: 'gui' | 'batch'; // gui=打开图形界面,batch=后台执行 } +``` -async function handleVivadoTool( - panel: vscode.WebviewPanel, - parameters: any -): Promise { +**返回**: +```typescript +{ + success: boolean; // 是否成功 + command: "create_project"; + executionTime: number; // 执行时间(毫秒) + output: string; // 完整日志 + error?: string; // 失败原因(如果失败) + outputFiles?: string[]; // 产出的工程文件路径 +} +``` - const { command, topModule, files, constraints, part, mode } = parameters; +### 1.2 runVivadoSynthesis - 综合 - // 验证必需参数 - if (!part) { - return { - success: false, - command, - executionTime: 0, - output: '', - error: '缺少必需参数:芯片型号(part)。后端应该先询问用户。' - }; - } +**参数**: +```typescript +{ + projectPath?: string; // 工程路径(可选) + part: string; // 芯片型号 + topModule: string; // 顶层模块 + files?: string[]; // 源文件(如果没有工程) + constraints?: string; // 约束文件(可选) + mode: 'gui' | 'batch'; +} +``` - 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' - ) - } +**返回**: +```typescript +{ + success: boolean; + command: "synthesis"; + executionTime: number; + output: string; + error?: string; // 失败原因 + outputFiles?: string[]; // .dcp 文件等 + reports?: { + resources?: string; // 资源使用摘要 + timing?: string; // 时序摘要 }; - - // 向前端发送开始消息 - 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.3 runVivadoImplementation - 实现 -### 4.1 成功响应 - -```json +**参数**: +```typescript { - "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" - } + dcpFile: string; // 综合后的 .dcp 文件路径 + mode: 'gui' | 'batch'; } ``` -### 4.2 失败响应 - -```json +**返回**: +```typescript { - "success": false, - "command": "synthesis", - "executionTime": 1250, - "output": "部分执行日志...", - "error": "ERROR: [Synth 8-439] module 'counter' not found" + success: boolean; + command: "implementation"; + executionTime: number; + output: string; + error?: string; // 失败原因 + outputFiles?: string[]; // 实现后的 .dcp 文件等 + reports?: { + resources?: string; + timing?: string; + }; } ``` -## 5. 后端使用指南 +### 1.4 runVivadoBitstream - 生成比特流 -### 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 调用工具: +**参数**: +```typescript { - "tool": "runVivado", - "parameters": { - "command": "synthesis", - "topModule": "counter", - "files": ["counter.v"], - "part": "xc7a35tcpg236-1" - } + dcpFile: string; // 实现后的 .dcp 文件路径 + mode: 'gui' | 'batch'; } - -前端执行并返回结果 - -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 常见错误 - -#### 错误 1:Vivado 未配置 - -```json +**返回**: +```typescript { - "success": false, - "error": "Vivado 未配置,请在设置中配置 Vivado 路径" + success: boolean; + command: "bitstream"; + executionTime: number; + output: string; + error?: string; // 失败原因 + outputFiles?: string[]; // .bit 文件路径 } ``` -**AI 应该回复**: -"Vivado 尚未配置,请先在插件设置中配置 Vivado 的安装路径。" +## 2. 前端职责 -#### 错误 2:文件不存在 +- 接收后端工具调用 +- 生成对应的 TCL 脚本 +- 执行 Vivado 命令 +- 捕获输出日志 +- 解析报告文件(提取资源和时序摘要) +- 返回执行结果 -```json +**前端不做**: +- 不检查依赖关系 +- 不验证执行顺序 +- 不控制流程 + +## 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, - "error": "输入文件不存在: counter.v" + success: false, + error: "ERROR: [Synth 8-439] module 'counter' not found", + output: "详细日志..." } ``` -**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 -- [ ] 实现进度推送 -- [ ] 实现结果展示 -- [ ] 处理各种错误情况 -- [ ] 验证传入的参数完整性 +后端应该: +1. 停止后续步骤 +2. 提取 `error` 字段 +3. 给用户提示和建议 diff --git a/docs/Vivado联动功能技术设计文档.md b/docs/Vivado联动功能技术设计文档.md index 093ce99..784732e 100644 --- a/docs/Vivado联动功能技术设计文档.md +++ b/docs/Vivado联动功能技术设计文档.md @@ -2,922 +2,152 @@ ## 1. 架构设计 -### 1.1 整体架构 - ``` -┌─────────────────────────────────────────────────────────────┐ -│ 后端 AI 服务 │ -│ (调用 runVivado 工具) │ -└────────────────────────────┬────────────────────────────────┘ - │ 工具调用请求 - ↓ -┌─────────────────────────────────────────────────────────────┐ -│ VS Code Extension │ -│ ┌──────────────────────────────────────────────────────┐ │ -│ │ ICHelperPanel (Webview) │ │ -│ │ - 接收后端工具调用 │ │ -│ │ - 显示执行进度和日志 │ │ -│ │ - 展示执行结果 │ │ -│ └──────────────────────────────────────────────────────┘ │ -│ ↓ │ -│ ┌──────────────────────────────────────────────────────┐ │ -│ │ VivadoRunner (utils/vivadoRunner.ts) │ │ -│ │ - 配置管理 │ │ -│ │ - TCL 脚本生成 │ │ -│ │ - 命令执行 │ │ -│ │ - 进度监控 │ │ -│ │ - 结果解析 │ │ -│ └──────────────────────────────────────────────────────┘ │ -│ ↓ │ -│ ┌──────────────────────────────────────────────────────┐ │ -│ │ FileImporter (utils/fileImporter.ts) │ │ -│ │ - 查找产出文件 │ │ -│ │ - 复制文件到目标目录 │ │ -│ │ - 通知文件变更 │ │ -│ └──────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────┐ -│ 本地 Vivado 工具 │ -│ (通过子进程执行 TCL 脚本) │ -└─────────────────────────────────────────────────────────────┘ +后端 AI + ↓ 调用工具 +前端 Extension (messageHandler.ts) + ↓ 调用 +VivadoRunner (utils/vivadoRunner.ts) + ↓ 生成 TCL 脚本并执行 +本地 Vivado ``` -### 1.2 模块职责 +## 2. 核心模块 -#### 1.2.1 VivadoRunner -- 读取和验证 Vivado 配置 -- 根据命令类型生成 TCL 脚本 +### 2.1 VivadoRunner + +**职责**:执行单个 Vivado 命令 + +**主要方法**: +- `createProject()` - 创建工程 +- `runSynthesis()` - 执行综合 +- `runImplementation()` - 执行实现 +- `runBitstream()` - 生成比特流 + +**实现要点**: +- 根据参数生成 TCL 脚本 - 启动子进程执行 Vivado -- 实时捕获输出并解析进度 -- 返回执行结果 +- 捕获输出日志 +- 解析报告文件 +- 返回结果 -#### 1.2.2 FileImporter -- 根据文件模式查找产出文件 -- 复制文件到指定目录 -- 返回已导入的文件列表 +### 2.2 TCL 脚本生成 -#### 1.2.3 MessageHandler -- 接收后端的 `runVivado` 工具调用 -- 调用 VivadoRunner 执行 -- 向 Webview 推送进度和结果 +**创建工程**: +```tcl +create_project {projectName} {workDir} -part {part} -force +add_files -norecurse {files} +set_property top {topModule} [current_fileset] +``` -## 2. 数据结构设计 +**综合**: +```tcl +synth_design -part {part} -top {topModule} +report_utilization -file utilization.rpt +write_checkpoint -force synth.dcp +``` -### 2.1 配置结构 +**实现**: +```tcl +open_checkpoint {dcpFile} +opt_design +place_design +route_design +report_timing_summary -file timing.rpt +write_checkpoint -force impl.dcp +``` + +**生成比特流**: +```tcl +open_checkpoint {dcpFile} +write_bitstream -force output.bit +``` + +### 2.3 MessageHandler 集成 ```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[]; - }; +// 处理工具调用 +async function handleToolCall(toolName: string, params: any) { + switch(toolName) { + case 'createVivadoProject': + return await vivadoRunner.createProject(params); + case 'runVivadoSynthesis': + return await vivadoRunner.runSynthesis(params); + case 'runVivadoImplementation': + return await vivadoRunner.runImplementation(params); + case 'runVivadoBitstream': + return await vivadoRunner.runBitstream(params); + } } ``` -### 2.2 请求和响应结构 +## 3. 配置管理 -```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('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 { - 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 { - 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 { - const { command, topModule, files, constraints, part } = toolCall.parameters; - - // 验证必需参数 - if (!part) { - return { - success: false, - command, - executionTime: 0, - output: '', - error: '缺少必需参数:芯片型号(part)' - }; - } - - // 构建请求 - 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; -} -``` - -### 3.6 参数验证和处理 - -```typescript -// src/utils/vivadoValidator.ts - -export interface ValidationResult { - valid: boolean; - error?: string; -} - -export function validateVivadoRequest(request: VivadoToolRequest): ValidationResult { - const { command, parameters } = request; - - // 验证命令类型 - if (!['synthesis', 'implementation', 'bitstream'].includes(command)) { - return { valid: false, error: `无效的命令类型: ${command}` }; - } - - // 验证必需参数 - if (!parameters?.topModule) { - return { valid: false, error: '缺少顶层模块名(topModule)' }; - } - - if (!parameters?.part) { - return { valid: false, error: '缺少芯片型号(part)' }; - } - - // 验证芯片型号格式 - const partPattern = /^xc[0-9a-z]+$/i; - if (!partPattern.test(parameters.part)) { - return { valid: false, error: `芯片型号格式不正确: ${parameters.part}` }; - } - - // 综合命令需要文件列表 - if (command === 'synthesis') { - if (!parameters?.files || parameters.files.length === 0) { - return { valid: false, error: '综合命令需要提供源文件列表' }; - } - } - - return { valid: true }; -} -``` - -## 4. 前端 UI 实现 - -### 4.1 进度显示组件 - -```typescript -// src/views/vivadoProgress.ts - -export function renderVivadoProgress(progress: VivadoProgress): string { - return ` -
-
- ${progress.stage} - ${progress.percentage}% -
-
-
-
-
${progress.message}
-
- `; -} -``` - -### 4.2 结果展示组件 - -```typescript -// src/views/vivadoResult.ts - -export function renderVivadoResult(response: VivadoToolResponse): string { - if (!response.success) { - return ` -
-

❌ 执行失败

-
${response.error}
-
- `; - } - - return ` -
-

✅ 执行成功

-
-

命令: ${response.command}

-

执行时间: ${(response.executionTime / 1000).toFixed(2)}s

-
- - ${response.reports?.resources ? ` -
-
资源使用
-
${response.reports.resources}
-
- ` : ''} - - ${response.reports?.timing ? ` -
-
时序信息
-
${response.reports.timing}
-
- ` : ''} - - ${response.importedFiles && response.importedFiles.length > 0 ? ` -
-
已导入文件
-
    - ${response.importedFiles.map(f => `
  • ${f}
  • `).join('')} -
-
- ` : ''} -
- `; -} -``` - -## 5. 配置界面实现 - -### 5.1 设置页面扩展 - -```typescript -// src/views/vivadoSettings.ts - -export function renderVivadoSettings(config: VivadoConfig | null): string { - return ` -
-

Vivado 配置

- -
- - -
- -
- - - -
- -
- - -
- -
- - -
- - -
- `; -} -``` - -## 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 配置文件更新 +**配置文件位置**: +- 全局:`settings.json` 中的 `ic-coder.vivado` +- 项目:`.vscode/ic-coder-vivado.json` +**配置项**: ```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 型号" - } - } - } + "vivado": { + "enabled": true, + "executablePath": "C:/Xilinx/Vivado/2023.1/bin/vivado.bat", + "workingDir": "${workspaceFolder}/vivado_project" } } ``` -## 8. 常见问题和解决方案 +## 4. 文件结构 -### 8.1 Vivado 许可证问题 - -**问题**:执行时提示许可证错误 - -**解决方案**: -1. 检查环境变量 `XILINX_VIVADO` 是否设置 -2. 确认许可证服务器可访问 -3. 在配置中添加许可证路径 - -### 8.2 路径问题 - -**问题**:Windows 路径包含空格导致执行失败 - -**解决方案**: -```typescript -function escapeWindowsPath(p: string): string { - return p.includes(' ') ? `"${p}"` : p; -} +``` +src/ +├── utils/ +│ ├── vivadoRunner.ts # Vivado 执行器 +│ ├── vivadoConfig.ts # 配置读取 +│ └── tclGenerator.ts # TCL 脚本生成 +└── utils/ + └── messageHandler.ts # 工具调用处理(新增部分) ``` -### 8.3 执行超时 +## 5. 实现要点 -**问题**:大型项目综合时间过长 - -**解决方案**: -- 增加超时时间配置 -- 添加取消执行功能 -- 显示详细进度信息 - -## 9. 性能优化 - -### 9.1 日志缓冲 - -限制日志输出大小,避免内存溢出: +### 5.1 子进程执行 ```typescript -const MAX_LOG_SIZE = 1024 * 1024; // 1MB -let logBuffer = ''; +const process = spawn(vivadoPath, ['-mode', 'batch', '-source', tclPath]); process.stdout.on('data', (data) => { - logBuffer += data.toString(); - if (logBuffer.length > MAX_LOG_SIZE) { - logBuffer = logBuffer.slice(-MAX_LOG_SIZE / 2); - } + output += data.toString(); +}); + +process.on('close', (code) => { + resolve({ success: code === 0, output }); }); ``` -### 9.2 增量构建 +### 5.2 报告解析 -支持增量综合,只重新综合修改的模块。 +从 `.rpt` 文件中提取关键信息: +- 资源使用:LUT、FF、BRAM 数量 +- 时序信息:WNS、TNS -## 10. 后续优化方向 +### 5.3 错误处理 -1. **并行执行**:支持多个设计同时综合 -2. **缓存机制**:缓存未修改模块的综合结果 -3. **云端集成**:支持云端 Vivado 服务 -4. **可视化报告**:图形化展示资源使用和时序 -5. **自动约束生成**:根据设计自动生成 XDC 约束文件 +捕获常见错误: +- Vivado 未配置 +- 文件不存在 +- 综合/实现失败 +- 时序不满足 +返回清晰的错误信息给后端。 + +## 6. 测试 + +**单元测试**: +- TCL 脚本生成正确性 +- 配置读取 + +**集成测试**: +- 完整流程测试(需要本地 Vivado) +- 错误处理测试 diff --git a/src/utils/messageHandler.ts b/src/utils/messageHandler.ts index 05b9097..e88a87e 100644 --- a/src/utils/messageHandler.ts +++ b/src/utils/messageHandler.ts @@ -14,6 +14,7 @@ import { checkVerilogProject, checkIverilogAvailable, } from "./iverilogRunner"; +import { createVivadoProject } from "./vivadoRunner"; import { ChatHistoryManager } from "./chatHistoryManager"; import { dialogManager, DialogSession } from "../services/dialogService"; import { userInteractionManager } from "../services/userInteraction"; @@ -1468,3 +1469,35 @@ export async function handleOpenFileDiff( vscode.window.showErrorMessage(`打开 diff 失败: ${error}`); } } + +/** + * 处理 Vivado 工具调用 + */ +export async function handleVivadoToolCall( + toolName: string, + params: any +): Promise { + 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) + }; + } +} diff --git a/src/utils/tclGenerator.ts b/src/utils/tclGenerator.ts new file mode 100644 index 0000000..7c59f16 --- /dev/null +++ b/src/utils/tclGenerator.ts @@ -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; +} diff --git a/src/utils/vivadoConfig.ts b/src/utils/vivadoConfig.ts new file mode 100644 index 0000000..9d7b44d --- /dev/null +++ b/src/utils/vivadoConfig.ts @@ -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('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; +} diff --git a/src/utils/vivadoRunner.ts b/src/utils/vivadoRunner.ts new file mode 100644 index 0000000..e2f7285 --- /dev/null +++ b/src/utils/vivadoRunner.ts @@ -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 { + 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}` + }); + }); + }); +}