feat: 实现 Vivado 综合功能并修正文档
- 实现 runVivadoSynthesis 工具,支持工程模式和无工程模式 - 新增 generateSynthesisTcl 生成综合 TCL 脚本 - 修正文档:明确约束文件依赖(实现阶段必需,综合阶段可选) - 补充生成比特流的核心依赖说明
This commit is contained in:
@ -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. 后续扩展
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<VivadoToolResponse> {
|
||||
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<VivadoToolResponse> {
|
||||
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 命令
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user