feat: 实现 Vivado 综合功能并修正文档
- 实现 runVivadoSynthesis 工具,支持工程模式和无工程模式 - 新增 generateSynthesisTcl 生成综合 TCL 脚本 - 修正文档:明确约束文件依赖(实现阶段必需,综合阶段可选) - 补充生成比特流的核心依赖说明
This commit is contained in:
@ -63,20 +63,24 @@ IC Coder Plugin 目前支持:
|
|||||||
|
|
||||||
#### 3.1.2 runVivadoSynthesis - 综合
|
#### 3.1.2 runVivadoSynthesis - 综合
|
||||||
|
|
||||||
- **输入**:工程路径或源文件、芯片型号、顶层模块
|
- **输入**:工程路径或源文件、芯片型号、顶层模块、约束文件(可选)
|
||||||
- **输出**:.dcp 文件、综合报告
|
- **输出**:.dcp 文件、综合报告
|
||||||
- **说明**:执行综合,前端不检查工程是否存在
|
- **说明**:执行综合,前端不检查工程是否存在。约束文件在此阶段可选,主要用于时序约束
|
||||||
|
|
||||||
#### 3.1.3 runVivadoImplementation - 实现
|
#### 3.1.3 runVivadoImplementation - 实现
|
||||||
|
|
||||||
- **输入**:综合后的 .dcp 文件路径
|
- **输入**:综合后的 .dcp 文件路径、约束文件(必需,包含管脚约束)
|
||||||
- **输出**:实现后的 .dcp 文件、时序报告
|
- **输出**:实现后的 .dcp 文件、时序报告
|
||||||
- **说明**:执行实现,前端不检查 .dcp 是否存在
|
- **说明**:执行实现,前端不检查 .dcp 是否存在。**管脚约束是必需的**,否则无法完成布局布线
|
||||||
|
|
||||||
#### 3.1.4 runVivadoBitstream - 生成比特流
|
#### 3.1.4 runVivadoBitstream - 生成比特流
|
||||||
|
|
||||||
- **输入**:实现后的 .dcp 文件路径
|
- **输入**:实现后的 .dcp 文件路径
|
||||||
- **输出**:.bit 文件
|
- **输出**:.bit 文件(可下载到 FPGA 的配置文件)
|
||||||
|
- **核心依赖**:
|
||||||
|
1. 实现已完成
|
||||||
|
2. 工程指定目标芯片型号
|
||||||
|
3. 已完成管脚约束(无管脚约束无法生成)
|
||||||
- **说明**:生成比特流,前端不检查 .dcp 是否存在
|
- **说明**:生成比特流,前端不检查 .dcp 是否存在
|
||||||
|
|
||||||
### 3.2 配置管理
|
### 3.2 配置管理
|
||||||
@ -160,6 +164,7 @@ interface VivadoToolResponse {
|
|||||||
```typescript
|
```typescript
|
||||||
{
|
{
|
||||||
dcpFile: string; // 综合后的 .dcp 文件路径
|
dcpFile: string; // 综合后的 .dcp 文件路径
|
||||||
|
constraints: string; // 约束文件(必需,包含管脚约束)
|
||||||
mode: 'gui' | 'batch'; // 执行模式
|
mode: 'gui' | 'batch'; // 执行模式
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -186,7 +191,7 @@ interface VivadoToolResponse {
|
|||||||
后端必须询问:
|
后端必须询问:
|
||||||
- **芯片型号**(必需):"请提供 FPGA 芯片型号(例如:xc7a35tcpg236-1)"
|
- **芯片型号**(必需):"请提供 FPGA 芯片型号(例如:xc7a35tcpg236-1)"
|
||||||
- **执行模式**(必需):"选择执行模式:1) 图形化 2) 后端执行"
|
- **执行模式**(必需):"选择执行模式:1) 图形化 2) 后端执行"
|
||||||
- **约束文件**(可选):"是否有约束文件(.xdc)?"
|
- **约束文件**(必需):"请提供约束文件(.xdc),包含管脚约束和时序约束"
|
||||||
|
|
||||||
#### 3.4.2 理解依赖关系
|
#### 3.4.2 理解依赖关系
|
||||||
|
|
||||||
@ -270,9 +275,10 @@ interface VivadoToolResponse {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "runVivadoImplementation",
|
"name": "runVivadoImplementation",
|
||||||
"description": "执行 Vivado 实现。前端不检查依赖,后端需确保综合已完成。",
|
"description": "执行 Vivado 实现。前端不检查依赖,后端需确保综合已完成且提供约束文件。",
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"dcpFile": "综合后的 .dcp 文件路径",
|
"dcpFile": "综合后的 .dcp 文件路径",
|
||||||
|
"constraints": "约束文件(必需,包含管脚约束)",
|
||||||
"mode": "执行模式(gui/batch)"
|
"mode": "执行模式(gui/batch)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -303,7 +309,7 @@ AI 执行:
|
|||||||
2. [调用] runVivadoSynthesis({ projectPath: "counter.xpr", ... })
|
2. [调用] runVivadoSynthesis({ projectPath: "counter.xpr", ... })
|
||||||
[结果] { success: true, outputFiles: ["counter_synth.dcp"], reports: {...} }
|
[结果] { 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: {...} }
|
[结果] { success: true, outputFiles: ["counter_impl.dcp"], reports: {...} }
|
||||||
|
|
||||||
4. [调用] runVivadoBitstream({ dcpFile: "counter_impl.dcp", ... })
|
4. [调用] runVivadoBitstream({ dcpFile: "counter_impl.dcp", ... })
|
||||||
@ -330,9 +336,9 @@ AI: 好的,我将使用 Vivado 执行完整的 FPGA 开发流程。请提供
|
|||||||
3. 执行模式:
|
3. 执行模式:
|
||||||
- 图形化模式:打开 Vivado GUI
|
- 图形化模式:打开 Vivado GUI
|
||||||
- 后端执行:自动在后台执行
|
- 后端执行:自动在后台执行
|
||||||
4. 是否有约束文件(.xdc)?
|
4. 约束文件路径(.xdc,必需)
|
||||||
|
|
||||||
用户: xc7a35tcpg236-1,全部步骤,后端执行,没有约束文件
|
用户: xc7a35tcpg236-1,全部步骤,后端执行,约束文件是 counter.xdc
|
||||||
|
|
||||||
AI: 收到!执行计划:
|
AI: 收到!执行计划:
|
||||||
1. 创建工程
|
1. 创建工程
|
||||||
@ -479,6 +485,9 @@ AI: 综合失败,发现以下问题:
|
|||||||
- 用户需要配置正确的 Vivado 路径
|
- 用户需要配置正确的 Vivado 路径
|
||||||
- 需要设置环境变量(如 `XILINX_VIVADO`)
|
- 需要设置环境变量(如 `XILINX_VIVADO`)
|
||||||
- 需要有效的 Vivado 许可证
|
- 需要有效的 Vivado 许可证
|
||||||
|
- **需要提供 .xdc 约束文件**:
|
||||||
|
- **管脚约束**(必需):定义信号与 FPGA 引脚的映射关系,实现阶段必须提供
|
||||||
|
- **时序约束**(强烈推荐):定义时钟频率和时序要求,确保设计满足性能指标
|
||||||
|
|
||||||
## 8. 后续扩展
|
## 8. 后续扩展
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
checkVerilogProject,
|
checkVerilogProject,
|
||||||
checkIverilogAvailable,
|
checkIverilogAvailable,
|
||||||
} from "./iverilogRunner";
|
} from "./iverilogRunner";
|
||||||
import { createVivadoProject } from "./vivadoRunner";
|
import { createVivadoProject, runVivadoSynthesis } from "./vivadoRunner";
|
||||||
import { ChatHistoryManager } from "./chatHistoryManager";
|
import { ChatHistoryManager } from "./chatHistoryManager";
|
||||||
import { dialogManager, DialogSession } from "../services/dialogService";
|
import { dialogManager, DialogSession } from "../services/dialogService";
|
||||||
import { userInteractionManager } from "../services/userInteraction";
|
import { userInteractionManager } from "../services/userInteraction";
|
||||||
@ -1482,6 +1482,9 @@ export async function handleVivadoToolCall(
|
|||||||
case 'createVivadoProject':
|
case 'createVivadoProject':
|
||||||
return await createVivadoProject(params);
|
return await createVivadoProject(params);
|
||||||
|
|
||||||
|
case 'runVivadoSynthesis':
|
||||||
|
return await runVivadoSynthesis(params);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@ -14,7 +14,8 @@ export function generateCreateProjectTcl(
|
|||||||
part: string,
|
part: string,
|
||||||
topModule: string,
|
topModule: string,
|
||||||
files: string[],
|
files: string[],
|
||||||
constraints?: string
|
constraints?: string,
|
||||||
|
runSynthesis?: boolean
|
||||||
): string {
|
): string {
|
||||||
// 转换路径为 TCL 格式(正斜杠)
|
// 转换路径为 TCL 格式(正斜杠)
|
||||||
const tclPath = (p: string) => p.replace(/\\/g, '/');
|
const tclPath = (p: string) => p.replace(/\\/g, '/');
|
||||||
@ -40,5 +41,64 @@ export function generateCreateProjectTcl(
|
|||||||
tcl += `# 设置顶层模块\n`;
|
tcl += `# 设置顶层模块\n`;
|
||||||
tcl += `set_property top ${topModule} [current_fileset]\n\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;
|
return tcl;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import * as path from 'path';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { getVivadoConfig, validateConfig, resolveWorkingDir } from './vivadoConfig';
|
import { getVivadoConfig, validateConfig, resolveWorkingDir } from './vivadoConfig';
|
||||||
import { generateCreateProjectTcl } from './tclGenerator';
|
import { generateCreateProjectTcl, generateSynthesisTcl } from './tclGenerator';
|
||||||
|
|
||||||
export interface VivadoToolResponse {
|
export interface VivadoToolResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@ -34,6 +34,7 @@ export async function createVivadoProject(params: {
|
|||||||
files: string[];
|
files: string[];
|
||||||
constraints?: string;
|
constraints?: string;
|
||||||
mode: 'gui' | 'batch';
|
mode: 'gui' | 'batch';
|
||||||
|
runSynthesis?: boolean;
|
||||||
}): Promise<VivadoToolResponse> {
|
}): Promise<VivadoToolResponse> {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
@ -76,7 +77,8 @@ export async function createVivadoProject(params: {
|
|||||||
params.part,
|
params.part,
|
||||||
params.topModule,
|
params.topModule,
|
||||||
params.files,
|
params.files,
|
||||||
params.constraints
|
params.constraints,
|
||||||
|
params.runSynthesis
|
||||||
);
|
);
|
||||||
|
|
||||||
const tclPath = path.join(workingDir, 'create_project.tcl');
|
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 命令
|
* 执行 Vivado 命令
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user