diff --git a/docs/EDA联动功能需求文档.md b/docs/EDA联动功能需求文档.md index 018abae..6a9b464 100644 --- a/docs/EDA联动功能需求文档.md +++ b/docs/EDA联动功能需求文档.md @@ -63,20 +63,24 @@ IC Coder Plugin 目前支持: #### 3.1.2 runVivadoSynthesis - 综合 -- **输入**:工程路径或源文件、芯片型号、顶层模块 +- **输入**:工程路径或源文件、芯片型号、顶层模块、约束文件(可选) - **输出**:.dcp 文件、综合报告 -- **说明**:执行综合,前端不检查工程是否存在 +- **说明**:执行综合,前端不检查工程是否存在。约束文件在此阶段可选,主要用于时序约束 #### 3.1.3 runVivadoImplementation - 实现 -- **输入**:综合后的 .dcp 文件路径 +- **输入**:综合后的 .dcp 文件路径、约束文件(必需,包含管脚约束) - **输出**:实现后的 .dcp 文件、时序报告 -- **说明**:执行实现,前端不检查 .dcp 是否存在 +- **说明**:执行实现,前端不检查 .dcp 是否存在。**管脚约束是必需的**,否则无法完成布局布线 #### 3.1.4 runVivadoBitstream - 生成比特流 - **输入**:实现后的 .dcp 文件路径 -- **输出**:.bit 文件 +- **输出**:.bit 文件(可下载到 FPGA 的配置文件) +- **核心依赖**: + 1. 实现已完成 + 2. 工程指定目标芯片型号 + 3. 已完成管脚约束(无管脚约束无法生成) - **说明**:生成比特流,前端不检查 .dcp 是否存在 ### 3.2 配置管理 @@ -160,6 +164,7 @@ interface VivadoToolResponse { ```typescript { dcpFile: string; // 综合后的 .dcp 文件路径 + constraints: string; // 约束文件(必需,包含管脚约束) mode: 'gui' | 'batch'; // 执行模式 } ``` @@ -186,7 +191,7 @@ interface VivadoToolResponse { 后端必须询问: - **芯片型号**(必需):"请提供 FPGA 芯片型号(例如:xc7a35tcpg236-1)" - **执行模式**(必需):"选择执行模式:1) 图形化 2) 后端执行" -- **约束文件**(可选):"是否有约束文件(.xdc)?" +- **约束文件**(必需):"请提供约束文件(.xdc),包含管脚约束和时序约束" #### 3.4.2 理解依赖关系 @@ -270,9 +275,10 @@ interface VivadoToolResponse { }, { "name": "runVivadoImplementation", - "description": "执行 Vivado 实现。前端不检查依赖,后端需确保综合已完成。", + "description": "执行 Vivado 实现。前端不检查依赖,后端需确保综合已完成且提供约束文件。", "parameters": { "dcpFile": "综合后的 .dcp 文件路径", + "constraints": "约束文件(必需,包含管脚约束)", "mode": "执行模式(gui/batch)" } }, @@ -303,7 +309,7 @@ AI 执行: 2. [调用] runVivadoSynthesis({ projectPath: "counter.xpr", ... }) [结果] { success: true, outputFiles: ["counter_synth.dcp"], reports: {...} } -3. [调用] runVivadoImplementation({ dcpFile: "counter_synth.dcp", ... }) +3. [调用] runVivadoImplementation({ dcpFile: "counter_synth.dcp", constraints: "counter.xdc", ... }) [结果] { success: true, outputFiles: ["counter_impl.dcp"], reports: {...} } 4. [调用] runVivadoBitstream({ dcpFile: "counter_impl.dcp", ... }) @@ -330,9 +336,9 @@ AI: 好的,我将使用 Vivado 执行完整的 FPGA 开发流程。请提供 3. 执行模式: - 图形化模式:打开 Vivado GUI - 后端执行:自动在后台执行 -4. 是否有约束文件(.xdc)? +4. 约束文件路径(.xdc,必需) -用户: xc7a35tcpg236-1,全部步骤,后端执行,没有约束文件 +用户: xc7a35tcpg236-1,全部步骤,后端执行,约束文件是 counter.xdc AI: 收到!执行计划: 1. 创建工程 @@ -479,6 +485,9 @@ AI: 综合失败,发现以下问题: - 用户需要配置正确的 Vivado 路径 - 需要设置环境变量(如 `XILINX_VIVADO`) - 需要有效的 Vivado 许可证 +- **需要提供 .xdc 约束文件**: + - **管脚约束**(必需):定义信号与 FPGA 引脚的映射关系,实现阶段必须提供 + - **时序约束**(强烈推荐):定义时钟频率和时序要求,确保设计满足性能指标 ## 8. 后续扩展 diff --git a/src/utils/messageHandler.ts b/src/utils/messageHandler.ts index e88a87e..6b136af 100644 --- a/src/utils/messageHandler.ts +++ b/src/utils/messageHandler.ts @@ -14,7 +14,7 @@ import { checkVerilogProject, checkIverilogAvailable, } from "./iverilogRunner"; -import { createVivadoProject } from "./vivadoRunner"; +import { createVivadoProject, runVivadoSynthesis } from "./vivadoRunner"; import { ChatHistoryManager } from "./chatHistoryManager"; import { dialogManager, DialogSession } from "../services/dialogService"; import { userInteractionManager } from "../services/userInteraction"; @@ -1482,6 +1482,9 @@ export async function handleVivadoToolCall( case 'createVivadoProject': return await createVivadoProject(params); + case 'runVivadoSynthesis': + return await runVivadoSynthesis(params); + default: return { success: false, diff --git a/src/utils/tclGenerator.ts b/src/utils/tclGenerator.ts index 7c59f16..c7f80e0 100644 --- a/src/utils/tclGenerator.ts +++ b/src/utils/tclGenerator.ts @@ -14,7 +14,8 @@ export function generateCreateProjectTcl( part: string, topModule: string, files: string[], - constraints?: string + constraints?: string, + runSynthesis?: boolean ): string { // 转换路径为 TCL 格式(正斜杠) const tclPath = (p: string) => p.replace(/\\/g, '/'); @@ -40,5 +41,64 @@ export function generateCreateProjectTcl( tcl += `# 设置顶层模块\n`; tcl += `set_property top ${topModule} [current_fileset]\n\n`; + if (runSynthesis) { + tcl += `# 执行综合\n`; + tcl += `launch_runs synth_1\n`; + tcl += `wait_on_run synth_1\n\n`; + tcl += `# 打开综合结果\n`; + tcl += `open_run synth_1\n\n`; + tcl += `# 生成报告\n`; + tcl += `report_utilization -file {${tclPath(path.join(projectDir, `${projectName}_utilization.rpt`))}}\n`; + tcl += `report_timing_summary -file {${tclPath(path.join(projectDir, `${projectName}_timing.rpt`))}}\n\n`; + } + + return tcl; +} + +/** + * 生成综合的 TCL 脚本 + */ +export function generateSynthesisTcl( + projectPath: string | undefined, + part: string, + topModule: string, + files?: string[], + constraints?: string, + outputDir?: string +): string { + const tclPath = (p: string) => p.replace(/\\/g, '/'); + let tcl = `# Vivado 综合\n\n`; + + if (projectPath) { + // 使用现有工程 + tcl += `open_project {${tclPath(projectPath)}}\n\n`; + } else { + // 无工程模式 + if (!files || files.length === 0) { + throw new Error('无工程模式需要提供源文件'); + } + tcl += `# 读取源文件\n`; + files.forEach(file => { + tcl += `read_verilog {${tclPath(file)}}\n`; + }); + tcl += `\n`; + + if (constraints) { + tcl += `read_xdc {${tclPath(constraints)}}\n\n`; + } + } + + tcl += `# 执行综合\n`; + tcl += `synth_design -top ${topModule} -part ${part}\n\n`; + + if (outputDir) { + const dcpFile = tclPath(path.join(outputDir, `${topModule}_synth.dcp`)); + tcl += `# 保存检查点\n`; + tcl += `write_checkpoint -force {${dcpFile}}\n\n`; + tcl += `# 生成报告\n`; + tcl += `report_utilization -file {${tclPath(path.join(outputDir, `${topModule}_utilization.rpt`))}}\n`; + tcl += `report_timing_summary -file {${tclPath(path.join(outputDir, `${topModule}_timing.rpt`))}}\n`; + } + return tcl; } diff --git a/src/utils/vivadoRunner.ts b/src/utils/vivadoRunner.ts index e2f7285..411a159 100644 --- a/src/utils/vivadoRunner.ts +++ b/src/utils/vivadoRunner.ts @@ -9,7 +9,7 @@ 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'; +import { generateCreateProjectTcl, generateSynthesisTcl } from './tclGenerator'; export interface VivadoToolResponse { success: boolean; @@ -34,6 +34,7 @@ export async function createVivadoProject(params: { files: string[]; constraints?: string; mode: 'gui' | 'batch'; + runSynthesis?: boolean; }): Promise { const startTime = Date.now(); @@ -76,7 +77,8 @@ export async function createVivadoProject(params: { params.part, params.topModule, params.files, - params.constraints + params.constraints, + params.runSynthesis ); const tclPath = path.join(workingDir, 'create_project.tcl'); @@ -106,6 +108,91 @@ export async function createVivadoProject(params: { }; } +/** + * 执行 Vivado 综合 + */ +export async function runVivadoSynthesis(params: { + projectPath?: 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: 'synthesis', + executionTime: 0, + output: '', + error: 'Vivado 未配置' + }; + } + + const configError = validateConfig(config); + if (configError) { + return { + success: false, + command: 'synthesis', + executionTime: 0, + output: '', + error: configError + }; + } + + const workingDir = resolveWorkingDir(config.workingDir); + if (!fs.existsSync(workingDir)) { + fs.mkdirSync(workingDir, { recursive: true }); + } + + const outputDir = path.join(workingDir, 'synth_output'); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + const tclScript = generateSynthesisTcl( + params.projectPath, + params.part, + params.topModule, + params.files, + params.constraints, + outputDir + ); + + const tclPath = path.join(workingDir, 'synthesis.tcl'); + fs.writeFileSync(tclPath, tclScript); + + const result = await executeVivado( + config.executablePath, + tclPath, + workingDir, + params.mode + ); + + const executionTime = Date.now() - startTime; + + const dcpFile = path.join(outputDir, `${params.topModule}_synth.dcp`); + const utilizationRpt = path.join(outputDir, `${params.topModule}_utilization.rpt`); + const timingRpt = path.join(outputDir, `${params.topModule}_timing.rpt`); + + const outputFiles = []; + if (fs.existsSync(dcpFile)) outputFiles.push(dcpFile); + if (fs.existsSync(utilizationRpt)) outputFiles.push(utilizationRpt); + if (fs.existsSync(timingRpt)) outputFiles.push(timingRpt); + + return { + success: result.success, + command: 'synthesis', + executionTime, + output: result.output, + error: result.error, + outputFiles + }; +} + /** * 执行 Vivado 命令 */