Files
IC-Coder-Plugin/docs/Vivado联动功能技术设计文档.md
Roe-xin aa80088abc docs: 完善 Vivado 联动文档
- 添加后端工具调用控制前端的详细说明
   - 新增 mode 参数(batch/gui)支持批处理和图形界面模式
   - 补充参数询问流程和验证规则
   - 添加完整实现示例:生成比特流和布局布线
   - 更新所有调用示例包含必需参数
2026-03-16 14:05:34 +08:00

25 KiB
Raw Blame History

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 许可证问题

问题:执行时提示许可证错误

解决方案

  1. 检查环境变量 XILINX_VIVADO 是否设置
  2. 确认许可证服务器可访问
  3. 在配置中添加许可证路径

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. 后续优化方向

  1. 并行执行:支持多个设计同时综合
  2. 缓存机制:缓存未修改模块的综合结果
  3. 云端集成:支持云端 Vivado 服务
  4. 可视化报告:图形化展示资源使用和时序
  5. 自动约束生成:根据设计自动生成 XDC 约束文件