Files
IC-Coder-Plugin/docs/Vivado联动功能技术设计文档.md
Roe-xin 81717dc84f docs: 添加 Vivado 联动功能文档
- 添加 EDA 联动功能需求文档
   - 添加 Vivado 联动前后端对接文档
   - 添加 Vivado 联动功能技术设计文档
2026-03-12 18:01:31 +08:00

865 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Vivado 联动功能技术设计文档
## 1. 架构设计
### 1.1 整体架构
```
┌─────────────────────────────────────────────────────────────┐
│ 后端 AI 服务 │
│ (调用 runVivado 工具) │
└────────────────────────────┬────────────────────────────────┘
│ 工具调用请求
┌─────────────────────────────────────────────────────────────┐
│ VS Code Extension │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ ICHelperPanel (Webview) │ │
│ │ - 接收后端工具调用 │ │
│ │ - 显示执行进度和日志 │ │
│ │ - 展示执行结果 │ │
│ └──────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ VivadoRunner (utils/vivadoRunner.ts) │ │
│ │ - 配置管理 │ │
│ │ - TCL 脚本生成 │ │
│ │ - 命令执行 │ │
│ │ - 进度监控 │ │
│ │ - 结果解析 │ │
│ └──────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ FileImporter (utils/fileImporter.ts) │ │
│ │ - 查找产出文件 │ │
│ │ - 复制文件到目标目录 │ │
│ │ - 通知文件变更 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 本地 Vivado 工具 │
│ (通过子进程执行 TCL 脚本) │
└─────────────────────────────────────────────────────────────┘
```
### 1.2 模块职责
#### 1.2.1 VivadoRunner
- 读取和验证 Vivado 配置
- 根据命令类型生成 TCL 脚本
- 启动子进程执行 Vivado
- 实时捕获输出并解析进度
- 返回执行结果
#### 1.2.2 FileImporter
- 根据文件模式查找产出文件
- 复制文件到指定目录
- 返回已导入的文件列表
#### 1.2.3 MessageHandler
- 接收后端的 `runVivado` 工具调用
- 调用 VivadoRunner 执行
- 向 Webview 推送进度和结果
## 2. 数据结构设计
### 2.1 配置结构
```typescript
/**
* Vivado 配置
*/
interface VivadoConfig {
enabled: boolean;
executablePath: string;
workingDir: string;
part: string;
commands: {
synthesis: string;
implementation: string;
bitstream: string;
};
outputFiles: {
synthesis: string[];
implementation: string[];
bitstream: string[];
};
}
```
### 2.2 请求和响应结构
```typescript
/**
* Vivado 工具请求
*/
interface VivadoToolRequest {
command: 'synthesis' | 'implementation' | 'bitstream';
parameters?: {
topModule?: string;
files?: string[];
part?: string;
constraints?: string;
outputDir?: string;
};
importOutput?: {
enabled: boolean;
targetDir: string;
};
}
/**
* Vivado 工具响应
*/
interface VivadoToolResponse {
success: boolean;
command: string;
executionTime: number;
output: string;
error?: string;
importedFiles?: string[];
reports?: {
resources?: string;
timing?: string;
};
}
/**
* 执行进度
*/
interface VivadoProgress {
stage: string;
percentage: number;
message: string;
}
```
## 3. 核心模块实现
### 3.1 配置管理
#### 3.1.1 配置读取
```typescript
// src/utils/vivadoConfig.ts
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
export function getVivadoConfig(): VivadoConfig | null {
// 优先读取项目配置
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
const projectConfigPath = path.join(
workspaceFolder.uri.fsPath,
'.vscode',
'ic-coder-vivado.json'
);
if (fs.existsSync(projectConfigPath)) {
const content = fs.readFileSync(projectConfigPath, 'utf-8');
return JSON.parse(content).vivado;
}
}
// 读取全局配置
const config = vscode.workspace.getConfiguration('ic-coder');
return config.get<VivadoConfig>('vivado') || null;
}
export function validateConfig(config: VivadoConfig): string | null {
if (!config.enabled) {
return 'Vivado 未启用';
}
if (!fs.existsSync(config.executablePath)) {
return `Vivado 可执行文件不存在: ${config.executablePath}`;
}
return null;
}
```
### 3.2 TCL 脚本生成
#### 3.2.1 脚本生成器
```typescript
// src/utils/tclGenerator.ts
export function generateSynthesisTcl(
topModule: string,
files: string[],
part: string,
constraints?: string,
outputDir?: string
): string {
const output = outputDir || '.';
let tcl = `# Vivado 综合脚本\n\n`;
// 读取源文件
files.forEach(file => {
tcl += `read_verilog ${file}\n`;
});
// 读取约束文件
if (constraints) {
tcl += `read_xdc ${constraints}\n`;
}
tcl += `\n# 综合\n`;
tcl += `synth_design -part ${part} -top ${topModule}\n\n`;
// 生成报告
tcl += `# 生成报告\n`;
tcl += `report_utilization -file ${output}/${topModule}_utilization_synth.rpt\n`;
tcl += `report_timing -file ${output}/${topModule}_timing_synth.rpt\n\n`;
// 保存检查点
tcl += `# 保存检查点\n`;
tcl += `write_checkpoint -force ${output}/${topModule}_synth.dcp\n`;
return tcl;
}
export function generateImplementationTcl(
dcpFile: string,
outputDir?: string
): string {
const output = outputDir || '.';
const baseName = path.basename(dcpFile, '.dcp').replace('_synth', '');
let tcl = `# Vivado 实现脚本\n\n`;
tcl += `open_checkpoint ${dcpFile}\n\n`;
tcl += `# 优化\n`;
tcl += `opt_design\n`;
tcl += `place_design\n`;
tcl += `route_design\n\n`;
tcl += `# 生成报告\n`;
tcl += `report_utilization -file ${output}/${baseName}_utilization_impl.rpt\n`;
tcl += `report_timing_summary -file ${output}/${baseName}_timing_impl.rpt\n\n`;
tcl += `# 保存检查点\n`;
tcl += `write_checkpoint -force ${output}/${baseName}_impl.dcp\n`;
return tcl;
}
export function generateBitstreamTcl(
dcpFile: string,
outputDir?: string
): string {
const output = outputDir || '.';
const baseName = path.basename(dcpFile, '.dcp').replace('_impl', '');
let tcl = `# Vivado 比特流生成脚本\n\n`;
tcl += `open_checkpoint ${dcpFile}\n\n`;
tcl += `# 生成比特流\n`;
tcl += `write_bitstream -force ${output}/${baseName}.bit\n`;
return tcl;
}
```
### 3.3 VivadoRunner 实现
```typescript
// src/utils/vivadoRunner.ts
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { spawn } from 'child_process';
import { getVivadoConfig, validateConfig } from './vivadoConfig';
import { generateSynthesisTcl, generateImplementationTcl, generateBitstreamTcl } from './tclGenerator';
export async function runVivado(
request: VivadoToolRequest,
progressCallback?: (progress: VivadoProgress) => void
): Promise<VivadoToolResponse> {
const startTime = Date.now();
// 读取配置
const config = getVivadoConfig();
if (!config) {
return {
success: false,
command: request.command,
executionTime: 0,
output: '',
error: 'Vivado 未配置'
};
}
// 验证配置
const configError = validateConfig(config);
if (configError) {
return {
success: false,
command: request.command,
executionTime: 0,
output: '',
error: configError
};
}
// 准备工作目录
const workingDir = resolveWorkingDir(config.workingDir);
if (!fs.existsSync(workingDir)) {
fs.mkdirSync(workingDir, { recursive: true });
}
// 生成 TCL 脚本
const tclScript = generateTclScript(request, config, workingDir);
const tclPath = path.join(workingDir, `${request.command}.tcl`);
fs.writeFileSync(tclPath, tclScript);
// 执行 Vivado
const result = await executeVivado(
config.executablePath,
tclPath,
workingDir,
progressCallback
);
const executionTime = Date.now() - startTime;
// 解析报告
const reports = parseReports(request.command, workingDir, request.parameters?.topModule);
// 导入文件
let importedFiles: string[] = [];
if (request.importOutput?.enabled && result.success) {
importedFiles = await importOutputFiles(
request.command,
config,
workingDir,
request.importOutput.targetDir
);
}
return {
success: result.success,
command: request.command,
executionTime,
output: result.output,
error: result.error,
importedFiles,
reports
};
}
function generateTclScript(
request: VivadoToolRequest,
config: VivadoConfig,
workingDir: string
): string {
const { command, parameters } = request;
const part = parameters?.part || config.part;
switch (command) {
case 'synthesis':
return generateSynthesisTcl(
parameters?.topModule || 'top',
parameters?.files || [],
part,
parameters?.constraints,
parameters?.outputDir
);
case 'implementation':
const synthDcp = path.join(workingDir, `${parameters?.topModule}_synth.dcp`);
return generateImplementationTcl(synthDcp, parameters?.outputDir);
case 'bitstream':
const implDcp = path.join(workingDir, `${parameters?.topModule}_impl.dcp`);
return generateBitstreamTcl(implDcp, parameters?.outputDir);
default:
throw new Error(`未知命令: ${command}`);
}
}
async function executeVivado(
executablePath: string,
tclPath: string,
workingDir: string,
progressCallback?: (progress: VivadoProgress) => void
): Promise<{ success: boolean; output: string; error?: string }> {
return new Promise((resolve) => {
let output = '';
let errorOutput = '';
const process = spawn(executablePath, ['-mode', 'batch', '-source', tclPath], {
cwd: workingDir,
shell: true
});
process.stdout.on('data', (data) => {
const text = data.toString();
output += text;
// 解析进度
if (progressCallback) {
const progress = parseProgress(text);
if (progress) {
progressCallback(progress);
}
}
});
process.stderr.on('data', (data) => {
errorOutput += data.toString();
});
process.on('close', (code) => {
if (code === 0) {
resolve({ success: true, output });
} else {
resolve({ success: false, output, error: errorOutput || '执行失败' });
}
});
});
}
function parseProgress(logText: string): VivadoProgress | null {
// 解析 Vivado 日志中的进度信息
if (logText.includes('Starting synthesis')) {
return { stage: 'synthesis', percentage: 10, message: '开始综合' };
}
if (logText.includes('Finished synthesis')) {
return { stage: 'synthesis', percentage: 100, message: '综合完成' };
}
// 更多进度解析...
return null;
}
function parseReports(
command: string,
workingDir: string,
topModule?: string
): { resources?: string; timing?: string } {
const reports: { resources?: string; timing?: string } = {};
if (command === 'synthesis' || command === 'implementation') {
const utilizationFile = path.join(
workingDir,
`${topModule}_utilization_${command === 'synthesis' ? 'synth' : 'impl'}.rpt`
);
if (fs.existsSync(utilizationFile)) {
const content = fs.readFileSync(utilizationFile, 'utf-8');
reports.resources = extractResourceSummary(content);
}
const timingFile = path.join(
workingDir,
`${topModule}_timing_${command === 'synthesis' ? 'synth' : 'impl'}.rpt`
);
if (fs.existsSync(timingFile)) {
const content = fs.readFileSync(timingFile, 'utf-8');
reports.timing = extractTimingSummary(content);
}
}
return reports;
}
function extractResourceSummary(reportContent: string): string {
// 提取资源使用摘要
const lines = reportContent.split('\n');
const summary: string[] = [];
for (const line of lines) {
if (line.includes('LUT') || line.includes('FF') || line.includes('BRAM')) {
summary.push(line.trim());
}
}
return summary.join('\n');
}
function extractTimingSummary(reportContent: string): string {
// 提取时序摘要
const lines = reportContent.split('\n');
for (const line of lines) {
if (line.includes('WNS') || line.includes('TNS')) {
return line.trim();
}
}
return '';
}
function resolveWorkingDir(workingDir: string): string {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
return workingDir.replace('${workspaceFolder}', workspaceFolder.uri.fsPath);
}
return workingDir;
}
```
### 3.4 文件导入实现
```typescript
// src/utils/fileImporter.ts
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import * as glob from 'glob';
export async function importOutputFiles(
command: string,
config: VivadoConfig,
sourceDir: string,
targetDir: string
): Promise<string[]> {
const patterns = config.outputFiles[command] || [];
const importedFiles: string[] = [];
for (const pattern of patterns) {
const files = glob.sync(pattern, { cwd: sourceDir });
for (const file of files) {
const sourcePath = path.join(sourceDir, file);
const targetPath = path.join(targetDir, file);
// 确保目标目录存在
const targetDirPath = path.dirname(targetPath);
if (!fs.existsSync(targetDirPath)) {
fs.mkdirSync(targetDirPath, { recursive: true });
}
// 复制文件
fs.copyFileSync(sourcePath, targetPath);
importedFiles.push(targetPath);
}
}
return importedFiles;
}
```
### 3.5 MessageHandler 集成
```typescript
// src/utils/messageHandler.ts (新增部分)
import { runVivado } from './vivadoRunner';
// 在 handleUserMessage 中添加 Vivado 工具处理
export async function handleVivadoTool(
panel: vscode.WebviewPanel,
toolCall: any
): Promise<void> {
const { command, topModule, files, constraints } = toolCall.parameters;
// 构建请求
const request: VivadoToolRequest = {
command,
parameters: {
topModule,
files,
constraints
},
importOutput: {
enabled: true,
targetDir: path.join(vscode.workspace.workspaceFolders![0].uri.fsPath, 'vivado_output')
}
};
// 向前端发送开始消息
panel.webview.postMessage({
type: 'vivado-start',
command
});
// 执行 Vivado
const response = await runVivado(request, (progress) => {
// 推送进度
panel.webview.postMessage({
type: 'vivado-progress',
progress
});
});
// 向前端发送结果
panel.webview.postMessage({
type: 'vivado-complete',
response
});
}
```
## 4. 前端 UI 实现
### 4.1 进度显示组件
```typescript
// src/views/vivadoProgress.ts
export function renderVivadoProgress(progress: VivadoProgress): string {
return `
<div class="vivado-progress">
<div class="progress-header">
<span class="stage">${progress.stage}</span>
<span class="percentage">${progress.percentage}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${progress.percentage}%"></div>
</div>
<div class="progress-message">${progress.message}</div>
</div>
`;
}
```
### 4.2 结果展示组件
```typescript
// src/views/vivadoResult.ts
export function renderVivadoResult(response: VivadoToolResponse): string {
if (!response.success) {
return `
<div class="vivado-result error">
<h4>❌ 执行失败</h4>
<pre>${response.error}</pre>
</div>
`;
}
return `
<div class="vivado-result success">
<h4>✅ 执行成功</h4>
<div class="result-info">
<p>命令: ${response.command}</p>
<p>执行时间: ${(response.executionTime / 1000).toFixed(2)}s</p>
</div>
${response.reports?.resources ? `
<div class="report-section">
<h5>资源使用</h5>
<pre>${response.reports.resources}</pre>
</div>
` : ''}
${response.reports?.timing ? `
<div class="report-section">
<h5>时序信息</h5>
<pre>${response.reports.timing}</pre>
</div>
` : ''}
${response.importedFiles && response.importedFiles.length > 0 ? `
<div class="imported-files">
<h5>已导入文件</h5>
<ul>
${response.importedFiles.map(f => `<li>${f}</li>`).join('')}
</ul>
</div>
` : ''}
</div>
`;
}
```
## 5. 配置界面实现
### 5.1 设置页面扩展
```typescript
// src/views/vivadoSettings.ts
export function renderVivadoSettings(config: VivadoConfig | null): string {
return `
<div class="vivado-settings">
<h3>Vivado 配置</h3>
<div class="setting-item">
<label>启用 Vivado</label>
<input type="checkbox" id="vivado-enabled" ${config?.enabled ? 'checked' : ''}>
</div>
<div class="setting-item">
<label>可执行文件路径</label>
<input type="text" id="vivado-path" value="${config?.executablePath || ''}"
placeholder="C:/Xilinx/Vivado/2023.1/bin/vivado.bat">
<button onclick="testVivado()">测试</button>
</div>
<div class="setting-item">
<label>工作目录</label>
<input type="text" id="vivado-workdir" value="${config?.workingDir || ''}"
placeholder="\${workspaceFolder}/vivado_project">
</div>
<div class="setting-item">
<label>FPGA 型号</label>
<input type="text" id="vivado-part" value="${config?.part || ''}"
placeholder="xc7a35tcpg236-1">
</div>
<button onclick="saveVivadoConfig()">保存配置</button>
</div>
`;
}
```
## 6. 测试方案
### 6.1 单元测试
```typescript
// src/test/vivadoRunner.test.ts
import * as assert from 'assert';
import { generateSynthesisTcl } from '../utils/tclGenerator';
suite('Vivado TCL Generator', () => {
test('生成综合脚本', () => {
const tcl = generateSynthesisTcl(
'counter',
['counter.v'],
'xc7a35tcpg236-1'
);
assert.ok(tcl.includes('read_verilog counter.v'));
assert.ok(tcl.includes('synth_design'));
assert.ok(tcl.includes('write_checkpoint'));
});
});
```
### 6.2 集成测试
```typescript
// src/test/vivadoIntegration.test.ts
suite('Vivado Integration', () => {
test('完整综合流程', async () => {
const request: VivadoToolRequest = {
command: 'synthesis',
parameters: {
topModule: 'counter',
files: ['test/fixtures/counter.v']
}
};
const response = await runVivado(request);
assert.ok(response.success);
assert.ok(response.executionTime > 0);
});
});
```
## 7. 部署和发布
### 7.1 文件清单
新增文件:
- `src/utils/vivadoConfig.ts` - 配置管理
- `src/utils/tclGenerator.ts` - TCL 脚本生成
- `src/utils/vivadoRunner.ts` - Vivado 执行器
- `src/utils/fileImporter.ts` - 文件导入
- `src/views/vivadoProgress.ts` - 进度显示
- `src/views/vivadoResult.ts` - 结果展示
- `src/views/vivadoSettings.ts` - 设置界面
修改文件:
- `src/utils/messageHandler.ts` - 添加 Vivado 工具处理
- `src/views/settingsComponent.ts` - 添加 Vivado 设置页面
### 7.2 配置文件更新
```json
// package.json (新增配置项)
{
"contributes": {
"configuration": {
"properties": {
"ic-coder.vivado.enabled": {
"type": "boolean",
"default": false,
"description": "启用 Vivado 集成"
},
"ic-coder.vivado.executablePath": {
"type": "string",
"default": "",
"description": "Vivado 可执行文件路径"
},
"ic-coder.vivado.workingDir": {
"type": "string",
"default": "${workspaceFolder}/vivado_project",
"description": "Vivado 工作目录"
},
"ic-coder.vivado.part": {
"type": "string",
"default": "xc7a35tcpg236-1",
"description": "默认 FPGA 型号"
}
}
}
}
}
```
## 8. 常见问题和解决方案
### 8.1 Vivado 许可证问题
**问题**:执行时提示许可证错误
**解决方案**
1. 检查环境变量 `XILINX_VIVADO` 是否设置
2. 确认许可证服务器可访问
3. 在配置中添加许可证路径
### 8.2 路径问题
**问题**Windows 路径包含空格导致执行失败
**解决方案**
```typescript
function escapeWindowsPath(p: string): string {
return p.includes(' ') ? `"${p}"` : p;
}
```
### 8.3 执行超时
**问题**:大型项目综合时间过长
**解决方案**
- 增加超时时间配置
- 添加取消执行功能
- 显示详细进度信息
## 9. 性能优化
### 9.1 日志缓冲
限制日志输出大小,避免内存溢出:
```typescript
const MAX_LOG_SIZE = 1024 * 1024; // 1MB
let logBuffer = '';
process.stdout.on('data', (data) => {
logBuffer += data.toString();
if (logBuffer.length > MAX_LOG_SIZE) {
logBuffer = logBuffer.slice(-MAX_LOG_SIZE / 2);
}
});
```
### 9.2 增量构建
支持增量综合,只重新综合修改的模块。
## 10. 后续优化方向
1. **并行执行**:支持多个设计同时综合
2. **缓存机制**:缓存未修改模块的综合结果
3. **云端集成**:支持云端 Vivado 服务
4. **可视化报告**:图形化展示资源使用和时序
5. **自动约束生成**:根据设计自动生成 XDC 约束文件