- 添加后端工具调用控制前端的详细说明 - 新增 mode 参数(batch/gui)支持批处理和图形界面模式 - 补充参数询问流程和验证规则 - 添加完整实现示例:生成比特流和布局布线 - 更新所有调用示例包含必需参数
25 KiB
25 KiB
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 配置结构
/**
* 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 请求和响应结构
/**
* 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 配置读取
// 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 脚本生成器
// 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 实现
// 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 文件导入实现
// 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 集成
// src/utils/messageHandler.ts (新增部分)
import { runVivado } from './vivadoRunner';
// 在 handleUserMessage 中添加 Vivado 工具处理
export async function handleVivadoTool(
panel: vscode.WebviewPanel,
toolCall: any
): Promise<VivadoToolResponse> {
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 参数验证和处理
// 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 进度显示组件
// 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 结果展示组件
// 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 设置页面扩展
// 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 单元测试
// 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 集成测试
// 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 配置文件更新
// 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 许可证问题
问题:执行时提示许可证错误
解决方案:
- 检查环境变量
XILINX_VIVADO是否设置 - 确认许可证服务器可访问
- 在配置中添加许可证路径
8.2 路径问题
问题:Windows 路径包含空格导致执行失败
解决方案:
function escapeWindowsPath(p: string): string {
return p.includes(' ') ? `"${p}"` : p;
}
8.3 执行超时
问题:大型项目综合时间过长
解决方案:
- 增加超时时间配置
- 添加取消执行功能
- 显示详细进度信息
9. 性能优化
9.1 日志缓冲
限制日志输出大小,避免内存溢出:
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. 后续优化方向
- 并行执行:支持多个设计同时综合
- 缓存机制:缓存未修改模块的综合结果
- 云端集成:支持云端 Vivado 服务
- 可视化报告:图形化展示资源使用和时序
- 自动约束生成:根据设计自动生成 XDC 约束文件