# 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('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 } = 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 `
${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 配置文件更新 ```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 约束文件