为 netlist 的执行设置单独的线程
This commit is contained in:
parent
0ce43f6628
commit
048fa9d2ea
@ -1,244 +1,34 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as fspath from 'path';
|
import * as fspath from 'path';
|
||||||
import * as fs from 'fs';
|
import { Worker } from 'worker_threads';
|
||||||
import * as os from 'os';
|
import { hdlFile, hdlPath } from '../../hdlFs';
|
||||||
|
import { t } from '../../i18n';
|
||||||
import { WASI } from 'wasi';
|
import { gotoDefinition, saveAsPdf, saveAsSvg } from './api';
|
||||||
|
import { getIconConfig } from '../../hdlFs/icons';
|
||||||
import { AbsPath, opeParam, ReportType, YosysOutput } from '../../global';
|
import { AbsPath, opeParam, ReportType, YosysOutput } from '../../global';
|
||||||
|
import { PathSet } from '../../global/util';
|
||||||
|
|
||||||
import { hdlParam } from '../../hdlParser';
|
import { hdlParam } from '../../hdlParser';
|
||||||
import { hdlDir, hdlFile, hdlPath } from '../../hdlFs';
|
|
||||||
import { defaultMacro, doFastApi } from '../../hdlParser/util';
|
import { defaultMacro, doFastApi } from '../../hdlParser/util';
|
||||||
import { HdlFile } from '../../hdlParser/core';
|
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';
|
|
||||||
import { Worker } from 'worker_threads';
|
|
||||||
|
|
||||||
type SynthMode = 'before' | 'after' | 'RTL';
|
type SynthMode = 'before' | 'after' | 'RTL';
|
||||||
|
|
||||||
|
interface SimpleOpe {
|
||||||
|
workspacePath: string,
|
||||||
|
libCommonPath: string,
|
||||||
|
prjPath: string,
|
||||||
|
extensionPath: string
|
||||||
|
}
|
||||||
|
|
||||||
class Netlist {
|
const workerScriptPath = hdlPath.join(__dirname, 'worker.js');
|
||||||
context: vscode.ExtensionContext;
|
|
||||||
wsName: string;
|
class NetlistRender {
|
||||||
libName: string;
|
|
||||||
panel?: vscode.WebviewPanel;
|
panel?: vscode.WebviewPanel;
|
||||||
wasm?: WebAssembly.Module;
|
constructor() {
|
||||||
|
|
||||||
constructor(context: vscode.ExtensionContext) {
|
|
||||||
this.context = context;
|
|
||||||
this.wsName = '{workspace}';
|
|
||||||
this.libName = '{library}';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async open(uri: vscode.Uri, moduleName: string, option = {}) {
|
public create(moduleName: string) {
|
||||||
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 (_, token) => {
|
|
||||||
token.onCancellationRequested(() => {
|
|
||||||
console.log('cancel');
|
|
||||||
fs.closeSync(fd);
|
|
||||||
});
|
|
||||||
|
|
||||||
const instance = WebAssembly.instantiate(wasm, {
|
|
||||||
wasi_snapshot_preview1: wasi.wasiImport
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const exitCode = wasi.start(instance);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
// Create panel
|
||||||
this.panel = vscode.window.createWebviewPanel(
|
this.panel = vscode.window.createWebviewPanel(
|
||||||
'Netlist',
|
'Netlist',
|
||||||
@ -252,7 +42,6 @@ class Netlist {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.panel.onDidDispose(() => {
|
this.panel.onDidDispose(() => {
|
||||||
fs.closeSync(fd);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const previewHtml = this.getWebviewContent();
|
const previewHtml = this.getWebviewContent();
|
||||||
@ -281,10 +70,6 @@ class Netlist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public send() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public getWebviewContent() {
|
public getWebviewContent() {
|
||||||
const netlistPath = hdlPath.join(opeParam.extensionPath, 'resources', 'dide-netlist', 'view');
|
const netlistPath = hdlPath.join(opeParam.extensionPath, 'resources', 'dide-netlist', 'view');
|
||||||
const htmlIndexPath = hdlPath.join(netlistPath, 'index.html');
|
const htmlIndexPath = hdlPath.join(netlistPath, 'index.html');
|
||||||
@ -297,80 +82,61 @@ class Netlist {
|
|||||||
});
|
});
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public getJsonPathFromYs(path: AbsPath): AbsPath | undefined {
|
async function generateFilelist(path: AbsPath): Promise<AbsPath[]> {
|
||||||
for (const line of fs.readFileSync(path, { encoding: 'utf-8' }).split('\n')) {
|
const pathset = new PathSet();
|
||||||
if (line.trim().startsWith('write_json')) {
|
path = hdlPath.toSlash(path);
|
||||||
const path = line.split(/\s+/).at(1);
|
|
||||||
if (path) {
|
let moduleFile = hdlParam.getHdlFile(path);
|
||||||
const realPath = path
|
// 没有说明是单文件模式,直接打开解析
|
||||||
.replace(this.wsName, opeParam.workspacePath)
|
if (!moduleFile) {
|
||||||
.replace(this.libName, opeParam.prjInfo.libCommonPath);
|
const standardPath = hdlPath.toSlash(path);
|
||||||
return hdlPath.toSlash(realPath);
|
const response = await doFastApi(standardPath, 'common');
|
||||||
}
|
const langID = hdlFile.getLanguageId(standardPath);
|
||||||
}
|
const projectType = hdlParam.getHdlFileProjectType(standardPath, 'common');
|
||||||
}
|
moduleFile = new HdlFile(
|
||||||
return undefined;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async runYs(uri: vscode.Uri) {
|
for (const hdlModule of moduleFile.getAllHdlModules()) {
|
||||||
const ysPath = hdlPath.toSlash(uri.fsPath);
|
const hdlDependence = hdlParam.getAllDependences(path, hdlModule.name);
|
||||||
const targetJson = this.getJsonPathFromYs(ysPath);
|
if (hdlDependence) {
|
||||||
const name = ysPath.split('/').at(-1) as string;
|
// include 宏在后续会被正确处理,所以只需要处理 others 即可
|
||||||
const wasiResult = await this.makeWasi(ysPath, name);
|
hdlDependence.others.forEach(path => pathset.add(path));
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pathset.add(path);
|
||||||
|
|
||||||
|
const filelist = [...pathset.files];
|
||||||
|
|
||||||
|
console.log(filelist);
|
||||||
|
console.log(opeParam.prjInfo.prjPath);
|
||||||
|
|
||||||
|
return filelist;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openNetlistViewer(context: vscode.ExtensionContext, uri: vscode.Uri, moduleName: string) {
|
function generateOpe(): SimpleOpe {
|
||||||
const viewer = new Netlist(context);
|
return {
|
||||||
viewer.open(uri, moduleName);
|
workspacePath: opeParam.workspacePath,
|
||||||
|
extensionPath: opeParam.extensionPath,
|
||||||
|
libCommonPath: opeParam.prjInfo.libCommonPath,
|
||||||
|
prjPath: opeParam.prjInfo.prjPath
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runYsScript(context: vscode.ExtensionContext, uri: vscode.Uri) {
|
|
||||||
const viewer = new Netlist(context);
|
|
||||||
viewer.runYs(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function registerMessageEvent(panel: vscode.WebviewPanel) {
|
function registerMessageEvent(panel: vscode.WebviewPanel) {
|
||||||
panel.webview.onDidReceiveMessage(message => {
|
panel.webview.onDidReceiveMessage(message => {
|
||||||
const { command, data } = message;
|
const { command, data } = message;
|
||||||
@ -390,3 +156,125 @@ function registerMessageEvent(panel: vscode.WebviewPanel) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function waitForFinish(worker: Worker): Promise<void> {
|
||||||
|
return new Promise<void>(resolve => {
|
||||||
|
worker.on('message', message => {
|
||||||
|
if (message.command === 'finish') {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkResource() {
|
||||||
|
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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openNetlistViewer(context: vscode.ExtensionContext, uri: vscode.Uri, moduleName: string) {
|
||||||
|
checkResource();
|
||||||
|
const worker = new Worker(workerScriptPath);
|
||||||
|
|
||||||
|
worker.on('message', message => {
|
||||||
|
const command = message.command;
|
||||||
|
const data = message.data;
|
||||||
|
switch (command) {
|
||||||
|
case 'error-log-file':
|
||||||
|
showErrorLogFile(data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const configuration = vscode.workspace.getConfiguration();
|
||||||
|
const mode = configuration.get<SynthMode>('digital-ide.function.netlist.schema-mode') || 'before';
|
||||||
|
const filelist = await generateFilelist(uri.fsPath);
|
||||||
|
const ope = generateOpe();
|
||||||
|
|
||||||
|
worker.postMessage({
|
||||||
|
command: 'open',
|
||||||
|
data: {
|
||||||
|
path: uri.fsPath,
|
||||||
|
moduleName, mode,
|
||||||
|
filelist,
|
||||||
|
ope
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await vscode.window.withProgress({
|
||||||
|
location: vscode.ProgressLocation.Notification,
|
||||||
|
title: t('info.netlist.generate-network'),
|
||||||
|
cancellable: true
|
||||||
|
}, async (_, token) => {
|
||||||
|
token.onCancellationRequested(() => {
|
||||||
|
worker.terminate();
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForFinish(worker);
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.terminate();
|
||||||
|
|
||||||
|
const render = new NetlistRender();
|
||||||
|
render.create(moduleName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showErrorLogFile(data: any) {
|
||||||
|
const { logFilePath } = data;
|
||||||
|
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 runYsScript(context: vscode.ExtensionContext, uri: vscode.Uri) {
|
||||||
|
checkResource();
|
||||||
|
const worker = new Worker(workerScriptPath);
|
||||||
|
|
||||||
|
worker.on('message', message => {
|
||||||
|
const command = message.command;
|
||||||
|
const data = message.data;
|
||||||
|
switch (command) {
|
||||||
|
case 'error-log-file':
|
||||||
|
showErrorLogFile(data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ope = generateOpe();
|
||||||
|
|
||||||
|
worker.postMessage({
|
||||||
|
command: 'run',
|
||||||
|
data: {
|
||||||
|
path: uri.fsPath,
|
||||||
|
ope
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await vscode.window.withProgress({
|
||||||
|
location: vscode.ProgressLocation.Notification,
|
||||||
|
title: t('info.netlist.generate-network'),
|
||||||
|
cancellable: true
|
||||||
|
}, async (_, token) => {
|
||||||
|
token.onCancellationRequested(() => {
|
||||||
|
worker.terminate();
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForFinish(worker);
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.terminate();
|
||||||
|
}
|
||||||
|
@ -1,4 +1,25 @@
|
|||||||
import { isMainThread, Worker, parentPort } from 'worker_threads';
|
import { parentPort } from 'worker_threads';
|
||||||
|
import * as childProcess from 'child_process';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as os from 'os';
|
||||||
|
|
||||||
|
import { WASI } from 'wasi';
|
||||||
|
|
||||||
|
type SynthMode = 'before' | 'after' | 'RTL';
|
||||||
|
type AbsPath = string;
|
||||||
|
enum HdlLangID {
|
||||||
|
Verilog = 'verilog',
|
||||||
|
SystemVerilog = 'systemverilog',
|
||||||
|
Vhdl = 'vhdl',
|
||||||
|
Unknown = 'Unknown'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SimpleOpe {
|
||||||
|
workspacePath: string,
|
||||||
|
libCommonPath: string,
|
||||||
|
prjPath: string,
|
||||||
|
extensionPath: string
|
||||||
|
}
|
||||||
|
|
||||||
if (parentPort) {
|
if (parentPort) {
|
||||||
parentPort.on('message', message => {
|
parentPort.on('message', message => {
|
||||||
@ -6,13 +27,323 @@ if (parentPort) {
|
|||||||
const data = message.data;
|
const data = message.data;
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'ys':
|
case 'open':
|
||||||
|
open(data);
|
||||||
|
break;
|
||||||
|
case 'run':
|
||||||
|
run(data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function open(data: any) {
|
||||||
|
const { path, moduleName, mode, filelist, ope } = data;
|
||||||
|
const viewer = new Netlist(ope);
|
||||||
|
viewer.open(path, moduleName, filelist, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run(data: any) {
|
||||||
|
const { path, ope } = data;
|
||||||
|
const viewer = new Netlist(ope);
|
||||||
|
viewer.runYs(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDiskLetters(): Promise<string[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// 调用 wmic 命令获取磁盘信息
|
||||||
|
childProcess.exec('wmic logicaldisk get name', (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(`Error: ${error.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stderr) {
|
||||||
|
reject(`Stderr: ${stderr}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析命令输出
|
||||||
|
const disks = stdout
|
||||||
|
.split('\n') // 按行分割
|
||||||
|
.map(line => line.trim()) // 去除每行的空白字符
|
||||||
|
.filter(line => /^[A-Z]:$/.test(line)); // 过滤出盘符(如 C:, D:)
|
||||||
|
|
||||||
|
resolve(disks);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mkdir(path: AbsPath): boolean {
|
||||||
|
if (!path) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 如果存在则直接退出
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path, {recursive:true});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
fs.mkdirSync(path, {recursive:true});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function join(...paths: string[]): AbsPath {
|
||||||
|
return paths.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVlog(file: AbsPath): boolean {
|
||||||
|
const exts = ['.v', '.vh', '.vl', '.sv'];
|
||||||
|
for (const ext of exts) {
|
||||||
|
if (file.toLowerCase().endsWith(ext)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Netlist {
|
||||||
|
wsName: string;
|
||||||
|
libName: string;
|
||||||
|
ope: SimpleOpe;
|
||||||
|
wasm?: WebAssembly.Module;
|
||||||
|
|
||||||
|
constructor(ope: SimpleOpe) {
|
||||||
|
this.wsName = '{workspace}';
|
||||||
|
this.libName = '{library}';
|
||||||
|
this.ope = ope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async open(path: string, moduleName: string, filelist: AbsPath[], mode: SynthMode) {
|
||||||
|
|
||||||
|
if (!this.wasm) {
|
||||||
|
const wasm = await this.loadWasm();
|
||||||
|
this.wasm = wasm;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetYs = this.makeYs(filelist, moduleName, mode);
|
||||||
|
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 = join(this.ope.prjPath, 'netlist');
|
||||||
|
const targetJson = join(netlistPayloadFolder, moduleName + '.json');
|
||||||
|
if (fs.existsSync(targetJson)) {
|
||||||
|
fs.rmSync(targetJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = await WebAssembly.instantiate(wasm, {
|
||||||
|
wasi_snapshot_preview1: wasi.wasiImport
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const exitCode = wasi.start(instance);
|
||||||
|
} catch (error) {
|
||||||
|
if (parentPort) {
|
||||||
|
parentPort.postMessage({
|
||||||
|
command: 'finish',
|
||||||
|
data: { error }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(targetJson)) {
|
||||||
|
fs.closeSync(fd);
|
||||||
|
const logFilePath = join(this.ope.prjPath, 'netlist', moduleName + '.log');
|
||||||
|
if (parentPort) {
|
||||||
|
parentPort.postMessage({
|
||||||
|
command: 'error-log-file',
|
||||||
|
data: { logFilePath }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentPort) {
|
||||||
|
parentPort.postMessage({
|
||||||
|
command: 'finish',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private makeYs(files: AbsPath[], topModule: string, mode: SynthMode) {
|
||||||
|
const netlistPayloadFolder = join(this.ope.prjPath, 'netlist');
|
||||||
|
mkdir(netlistPayloadFolder);
|
||||||
|
const target = join(netlistPayloadFolder, topModule + '.ys');
|
||||||
|
const targetJson = join(netlistPayloadFolder, topModule + '.json').replace(this.ope.workspacePath, this.wsName);
|
||||||
|
|
||||||
|
const scripts: string[] = [];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (!isVlog(file)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.startsWith(this.ope.workspacePath)) {
|
||||||
|
const constraintPath = file.replace(this.ope.workspacePath, this.wsName);
|
||||||
|
scripts.push(`read_verilog -sv -formal -overwrite ${constraintPath}`);
|
||||||
|
} else if (file.startsWith(this.ope.libCommonPath)) {
|
||||||
|
const constraintPath = file.replace(this.ope.libCommonPath, this.libName);
|
||||||
|
scripts.push(`read_verilog -sv -formal -overwrite ${constraintPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
fs.writeFileSync(target, ysCode, { encoding: 'utf-8' });
|
||||||
|
return target.replace(this.ope.workspacePath, this.wsName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getPreopens() {
|
||||||
|
const basepreopens = {
|
||||||
|
'/share': join(this.ope.extensionPath, 'resources', 'dide-netlist', 'static', 'share'),
|
||||||
|
[this.wsName ]: this.ope.workspacePath,
|
||||||
|
[this.libName]: this.ope.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 = join(this.ope.prjPath, 'netlist', logName + '.log');
|
||||||
|
if (fs.existsSync(logFilePath)) {
|
||||||
|
fs.rmSync(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 = join(this.ope.extensionPath, 'resources', 'dide-netlist', 'static', 'yosys.wasm');
|
||||||
|
const binary = fs.readFileSync(netlistWasmPath);
|
||||||
|
const wasm = await WebAssembly.compile(binary);
|
||||||
|
return wasm;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, this.ope.workspacePath)
|
||||||
|
.replace(this.libName, this.ope.libCommonPath);
|
||||||
|
return realPath.replace(/\\/g,"\/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async runYs(path: string) {
|
||||||
|
const ysPath = path.replace(/\\/g,"\/");
|
||||||
|
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) {
|
||||||
|
fs.rmSync(targetJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.wasm) {
|
||||||
|
const wasm = await this.loadWasm();
|
||||||
|
this.wasm = wasm;
|
||||||
|
}
|
||||||
|
const wasm = this.wasm;
|
||||||
|
|
||||||
|
const instance = await WebAssembly.instantiate(wasm, {
|
||||||
|
wasi_snapshot_preview1: wasi.wasiImport
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const exitCode = wasi.start(instance);
|
||||||
|
} catch (error) {
|
||||||
|
if (parentPort) {
|
||||||
|
parentPort.postMessage({
|
||||||
|
command: 'finish',
|
||||||
|
data: { error }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetJson && !fs.existsSync(targetJson)) {
|
||||||
|
fs.closeSync(fd);
|
||||||
|
const logFilePath = join(this.ope.prjPath, 'netlist', name + '.log');
|
||||||
|
if (parentPort) {
|
||||||
|
parentPort.postMessage({
|
||||||
|
command: 'error-log-file',
|
||||||
|
data: { logFilePath }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentPort) {
|
||||||
|
parentPort.postMessage({
|
||||||
|
command: 'finish',
|
||||||
|
data: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -263,28 +263,3 @@ export function getUserHomeDir(): AbsPath {
|
|||||||
const path = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
const path = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
||||||
return hdlPath.toSlash(path);
|
return hdlPath.toSlash(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDiskLetters(): Promise<string[]> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// 调用 wmic 命令获取磁盘信息
|
|
||||||
childProcess.exec('wmic logicaldisk get name', (error, stdout, stderr) => {
|
|
||||||
if (error) {
|
|
||||||
reject(`Error: ${error.message}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stderr) {
|
|
||||||
reject(`Stderr: ${stderr}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析命令输出
|
|
||||||
const disks = stdout
|
|
||||||
.split('\n') // 按行分割
|
|
||||||
.map(line => line.trim()) // 去除每行的空白字符
|
|
||||||
.filter(line => /^[A-Z]:$/.test(line)); // 过滤出盘符(如 C:, D:)
|
|
||||||
|
|
||||||
resolve(disks);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -5,8 +5,6 @@ import { AbsPath, RelPath } from '../global';
|
|||||||
import { HdlLangID } from '../global/enum';
|
import { HdlLangID } from '../global/enum';
|
||||||
import { verilogExts, vhdlExts, systemVerilogExts, hdlExts } from '../global/lang';
|
import { verilogExts, vhdlExts, systemVerilogExts, hdlExts } from '../global/lang';
|
||||||
import * as hdlPath from './path';
|
import * as hdlPath from './path';
|
||||||
import { HdlFileProjectType } from '../hdlParser/common';
|
|
||||||
import { opeParam } from '../global';
|
|
||||||
import { hdlIgnore } from '../manager/ignore';
|
import { hdlIgnore } from '../manager/ignore';
|
||||||
import { hdlDir } from '.';
|
import { hdlDir } from '.';
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import * as vscode from 'vscode';
|
|
||||||
import { AbsPath, opeParam } from '../global';
|
import { AbsPath, opeParam } from '../global';
|
||||||
import { hdlFile, hdlPath } from '../hdlFs';
|
import { hdlPath } from '../hdlFs';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as fspath from 'path';
|
import * as fspath from 'path';
|
||||||
import { minimatch } from 'minimatch';
|
import { minimatch } from 'minimatch';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user