2025-01-06 23:01:02 +08:00

382 lines
14 KiB
TypeScript

import * as vscode from 'vscode';
import * as fspath from 'path';
import * as fs from 'fs';
import * as os from 'os';
import { WASI } from 'wasi';
import { AbsPath, opeParam, ReportType, YosysOutput } from '../../global';
import { hdlParam } from '../../hdlParser';
import { hdlDir, hdlFile, hdlPath } from '../../hdlFs';
import { defaultMacro, doFastApi } from '../../hdlParser/util';
import { HdlFile } from '../../hdlParser/core';
import { t } from '../../i18n';
import { HdlLangID } from '../../global/enum';
import { getIconConfig } from '../../hdlFs/icons';
import { getDiskLetters, PathSet } from '../../global/util';
import { gotoDefinition, saveAsPdf, saveAsSvg } from './api';
type SynthMode = 'before' | 'after' | 'RTL';
class Netlist {
context: vscode.ExtensionContext;
wsName: string;
libName: string;
panel?: vscode.WebviewPanel;
wasm?: WebAssembly.Module;
constructor(context: vscode.ExtensionContext) {
this.context = context;
this.wsName = '{workspace}';
this.libName = '{library}';
}
public async open(uri: vscode.Uri, moduleName: string, option = {}) {
const pathset = new PathSet();
const path = hdlPath.toSlash(uri.fsPath);
let moduleFile = hdlParam.getHdlFile(path);
// 没有说明是单文件模式,直接打开解析
if (!moduleFile) {
const standardPath = hdlPath.toSlash(path);
const response = await doFastApi(standardPath, 'common');
const langID = hdlFile.getLanguageId(standardPath);
const projectType = hdlParam.getHdlFileProjectType(standardPath, 'common');
moduleFile = new HdlFile(
standardPath, langID,
response?.macro || defaultMacro,
response?.content || [],
projectType,
'common'
);
// 从 hdlParam 中去除,避免干扰全局
hdlParam.removeFromHdlFile(moduleFile);
// const message = t('error.common.not-valid-hdl-file');
// const errorMsg = path + ' ' + message + ' ' + opeParam.prjInfo.hardwareSrcPath + '\n' + opeParam.prjInfo.hardwareSimPath;
// vscode.window.showErrorMessage(errorMsg);
// return undefined;
}
for (const hdlModule of moduleFile.getAllHdlModules()) {
const hdlDependence = hdlParam.getAllDependences(path, hdlModule.name);
if (hdlDependence) {
// include 宏在后续会被正确处理,所以只需要处理 others 即可
hdlDependence.others.forEach(path => pathset.add(path));
}
}
pathset.add(path);
const prjFiles = [...pathset.files];
console.log(prjFiles);
console.log(opeParam.prjInfo.prjPath);
if (!this.wasm) {
const wasm = await this.loadWasm();
this.wasm = wasm;
}
const targetYs = this.makeYs(prjFiles, moduleName);
if (!targetYs || !this.wasm) {
return;
}
const wasm = this.wasm;
const wasiResult = await this.makeWasi(targetYs, moduleName);
if (wasiResult === undefined) {
return;
}
const { wasi, fd } = wasiResult;
const netlistPayloadFolder = hdlPath.join(opeParam.prjInfo.prjPath, 'netlist');
const targetJson = hdlPath.join(netlistPayloadFolder, moduleName + '.json');
hdlFile.rmSync(targetJson);
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: t('info.netlist.generate-network'),
cancellable: true
}, async () => {
const instance = await WebAssembly.instantiate(wasm, {
wasi_snapshot_preview1: wasi.wasiImport
});
const exitCode = wasi.start(instance);
});
if (!fs.existsSync(targetJson)) {
fs.closeSync(fd);
const logFilePath = hdlPath.join(opeParam.prjInfo.prjPath, 'netlist', moduleName + '.log');
const res = await vscode.window.showErrorMessage(
t('error.cannot-gen-netlist'),
{ title: t('error.look-up-log'), value: true }
)
if (res?.value) {
const document = await vscode.workspace.openTextDocument(vscode.Uri.file(logFilePath));
await vscode.window.showTextDocument(document);
}
return;
}
this.create(moduleName, fd);
}
private getSynthMode(): SynthMode {
const configuration = vscode.workspace.getConfiguration();
return configuration.get<SynthMode>('digital-ide.function.netlist.schema-mode') || 'before';
}
private makeYs(files: AbsPath[], topModule: string) {
const netlistPayloadFolder = hdlPath.join(opeParam.prjInfo.prjPath, 'netlist');
hdlDir.mkdir(netlistPayloadFolder);
const target = hdlPath.join(netlistPayloadFolder, topModule + '.ys');
const targetJson = hdlPath.join(netlistPayloadFolder, topModule + '.json').replace(opeParam.workspacePath, this.wsName);
const scripts: string[] = [];
for (const file of files) {
const langID = hdlFile.getLanguageId(file);
if (langID === HdlLangID.Unknown || langID === HdlLangID.Vhdl) {
vscode.window.showErrorMessage(t('info.netlist.not-support-vhdl'));
return undefined;
}
if (file.startsWith(opeParam.workspacePath)) {
const constraintPath = file.replace(opeParam.workspacePath, this.wsName);
scripts.push(`read_verilog -sv -formal -overwrite ${constraintPath}`);
} else if (file.startsWith(opeParam.prjInfo.libCommonPath)) {
const constraintPath = file.replace(opeParam.prjInfo.libCommonPath, this.libName);
scripts.push(`read_verilog -sv -formal -overwrite ${constraintPath}`);
}
}
const mode = this.getSynthMode();
switch (mode) {
case 'before':
scripts.push('design -reset-vlog; proc;');
break;
case 'after':
scripts.push('design -reset-vlog; proc; opt_clean;');
break;
case 'RTL':
scripts.push('synth -run coarse;');
break;
}
scripts.push(`write_json ${targetJson}`);
const ysCode = scripts.join('\n');
hdlFile.writeFile(target, ysCode);
return target.replace(opeParam.workspacePath, this.wsName);
}
public async getPreopens() {
const basepreopens = {
'/share': hdlPath.join(opeParam.extensionPath, 'resources', 'dide-netlist', 'static', 'share'),
[this.wsName ]: opeParam.workspacePath,
[this.libName]: opeParam.prjInfo.libCommonPath
};
if (os.platform() === 'win32') {
const mounts = await getDiskLetters();
for (const mountName of mounts) {
const realMount = mountName + '/';
basepreopens[realMount.toLowerCase()] = realMount;
basepreopens[realMount.toUpperCase()] = realMount;
}
} else {
basepreopens['/'] = '/';
}
return basepreopens;
}
private async makeWasi(target: string, logName: string) {
// 创建日志文件路径
const logFilePath = hdlPath.join(opeParam.prjInfo.prjPath, 'netlist', logName + '.log');
hdlFile.removeFile(logFilePath);
const logFd = fs.openSync(logFilePath, 'a');
try {
const wasiOption = {
version: 'preview1',
args: [
'yosys',
'-s',
target
],
preopens: await this.getPreopens(),
stdin: process.stdin.fd,
stdout: process.stdout.fd,
stderr: logFd,
env: process.env
};
const wasi = new WASI(wasiOption);
return { wasi, fd: logFd };
} catch (error) {
fs.closeSync(logFd);
return undefined;
}
}
private async loadWasm() {
const netlistWasmPath = hdlPath.join(opeParam.extensionPath, 'resources', 'dide-netlist', 'static', 'yosys.wasm');
if (!hdlPath.exist(netlistWasmPath)) {
vscode.window.showErrorMessage(t('info.netlist.not-found-payload'));
throw Error(t('info.netlist.not-found-payload'));
}
const binary = fs.readFileSync(netlistWasmPath);
const wasm = await WebAssembly.compile(binary);
return wasm;
}
private create(moduleName: string, fd: number) {
// Create panel
this.panel = vscode.window.createWebviewPanel(
'Netlist',
'Netlist',
vscode.ViewColumn.One,
{
enableScripts: true,
enableForms: true,
retainContextWhenHidden: true
}
);
this.panel.onDidDispose(() => {
fs.closeSync(fd);
});
const previewHtml = this.getWebviewContent();
if (this.panel && previewHtml) {
const netlistPath = hdlPath.join(opeParam.extensionPath, 'resources', 'dide-netlist', 'view');
const netlistPayloadFolder = hdlPath.join(opeParam.prjInfo.prjPath, 'netlist');
const targetJson = hdlPath.join(netlistPayloadFolder, moduleName + '.json');
const skinPath= hdlPath.join(netlistPath, 'dide.skin');
const graph = this.panel.webview.asWebviewUri(vscode.Uri.file(targetJson)).toString();
const skin = this.panel.webview.asWebviewUri(vscode.Uri.file(skinPath)).toString();
this.panel.iconPath = getIconConfig('view');
let preprocessHtml = previewHtml
.replace('test.json', graph)
.replace('test.module', moduleName)
.replace('dide.skin', skin);
this.panel.webview.html = preprocessHtml;
registerMessageEvent(this.panel);
} else {
YosysOutput.report('preview html in <Netlist.create> is empty', {
level: ReportType.Warn
});
}
}
public send() {
}
public getWebviewContent() {
const netlistPath = hdlPath.join(opeParam.extensionPath, 'resources', 'dide-netlist', 'view');
const htmlIndexPath = hdlPath.join(netlistPath, 'index.html');
const html = hdlFile.readFile(htmlIndexPath)?.replace(/(<link.+?href="|<script.+?src="|<img.+?src=")(.+?)"/g, (m, $1, $2) => {
const absLocalPath = fspath.resolve(netlistPath, $2);
const webviewUri = this.panel?.webview.asWebviewUri(vscode.Uri.file(absLocalPath));
const replaceHref = $1 + webviewUri?.toString() + '"';
return replaceHref;
});
return html;
}
public getJsonPathFromYs(path: AbsPath): AbsPath | undefined {
for (const line of fs.readFileSync(path, { encoding: 'utf-8' }).split('\n')) {
if (line.trim().startsWith('write_json')) {
const path = line.split(/\s+/).at(1);
if (path) {
const realPath = path
.replace(this.wsName, opeParam.workspacePath)
.replace(this.libName, opeParam.prjInfo.libCommonPath);
return hdlPath.toSlash(realPath);
}
}
}
return undefined;
}
public async runYs(uri: vscode.Uri) {
const ysPath = hdlPath.toSlash(uri.fsPath);
const targetJson = this.getJsonPathFromYs(ysPath);
const name = ysPath.split('/').at(-1) as string;
const wasiResult = await this.makeWasi(ysPath, name);
if (wasiResult === undefined) {
return;
}
const { wasi, fd } = wasiResult;
if (targetJson) {
hdlFile.rmSync(targetJson);
}
if (!this.wasm) {
const wasm = await this.loadWasm();
this.wasm = wasm;
}
const wasm = this.wasm;
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: t('info.netlist.generate-network'),
cancellable: true
}, async () => {
const instance = await WebAssembly.instantiate(wasm, {
wasi_snapshot_preview1: wasi.wasiImport
});
const exitCode = wasi.start(instance);
});
if (targetJson && !fs.existsSync(targetJson)) {
fs.closeSync(fd);
const logFilePath = hdlPath.join(opeParam.prjInfo.prjPath, 'netlist', name + '.log');
const res = await vscode.window.showErrorMessage(
t('error.cannot-gen-netlist'),
{ title: t('error.look-up-log'), value: true }
)
if (res?.value) {
const document = await vscode.workspace.openTextDocument(vscode.Uri.file(logFilePath));
await vscode.window.showTextDocument(document);
}
}
}
}
export async function openNetlistViewer(context: vscode.ExtensionContext, uri: vscode.Uri, moduleName: string) {
const viewer = new Netlist(context);
viewer.open(uri, moduleName);
}
export async function runYsScript(context: vscode.ExtensionContext, uri: vscode.Uri) {
const viewer = new Netlist(context);
viewer.runYs(uri);
}
function registerMessageEvent(panel: vscode.WebviewPanel) {
panel.webview.onDidReceiveMessage(message => {
const { command, data } = message;
switch (command) {
case 'save-as-svg':
saveAsSvg(data, panel);
break;
case 'save-as-pdf':
saveAsPdf(data, panel);
break;
case 'goto-definition':
gotoDefinition(data, panel);
break;
default:
break;
}
});
}