为 netlist 的执行设置单独的线程
This commit is contained in:
parent
0ce43f6628
commit
048fa9d2ea
@ -1,41 +1,92 @@
|
||||
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 { Worker } from 'worker_threads';
|
||||
import { hdlFile, hdlPath } from '../../hdlFs';
|
||||
import { t } from '../../i18n';
|
||||
import { gotoDefinition, saveAsPdf, saveAsSvg } from './api';
|
||||
import { getIconConfig } from '../../hdlFs/icons';
|
||||
import { AbsPath, opeParam, ReportType, YosysOutput } from '../../global';
|
||||
import { PathSet } from '../../global/util';
|
||||
|
||||
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';
|
||||
import { Worker } from 'worker_threads';
|
||||
|
||||
type SynthMode = 'before' | 'after' | 'RTL';
|
||||
|
||||
interface SimpleOpe {
|
||||
workspacePath: string,
|
||||
libCommonPath: string,
|
||||
prjPath: string,
|
||||
extensionPath: string
|
||||
}
|
||||
|
||||
class Netlist {
|
||||
context: vscode.ExtensionContext;
|
||||
wsName: string;
|
||||
libName: string;
|
||||
const workerScriptPath = hdlPath.join(__dirname, 'worker.js');
|
||||
|
||||
class NetlistRender {
|
||||
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) {
|
||||
// Create panel
|
||||
this.panel = vscode.window.createWebviewPanel(
|
||||
'Netlist',
|
||||
'Netlist',
|
||||
vscode.ViewColumn.One,
|
||||
{
|
||||
enableScripts: true,
|
||||
enableForms: true,
|
||||
retainContextWhenHidden: true
|
||||
}
|
||||
);
|
||||
|
||||
this.panel.onDidDispose(() => {
|
||||
});
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
async function generateFilelist(path: AbsPath): Promise<AbsPath[]> {
|
||||
const pathset = new PathSet();
|
||||
const path = hdlPath.toSlash(uri.fsPath);
|
||||
path = hdlPath.toSlash(path);
|
||||
|
||||
let moduleFile = hdlParam.getHdlFile(path);
|
||||
// 没有说明是单文件模式,直接打开解析
|
||||
@ -69,308 +120,23 @@ class Netlist {
|
||||
}
|
||||
pathset.add(path);
|
||||
|
||||
const prjFiles = [...pathset.files];
|
||||
const filelist = [...pathset.files];
|
||||
|
||||
console.log(prjFiles);
|
||||
console.log(filelist);
|
||||
console.log(opeParam.prjInfo.prjPath);
|
||||
|
||||
if (!this.wasm) {
|
||||
const wasm = await this.loadWasm();
|
||||
this.wasm = wasm;
|
||||
}
|
||||
return filelist;
|
||||
}
|
||||
|
||||
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
|
||||
function generateOpe(): SimpleOpe {
|
||||
return {
|
||||
workspacePath: opeParam.workspacePath,
|
||||
extensionPath: opeParam.extensionPath,
|
||||
libCommonPath: opeParam.prjInfo.libCommonPath,
|
||||
prjPath: opeParam.prjInfo.prjPath
|
||||
};
|
||||
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;
|
||||
@ -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) {
|
||||
parentPort.on('message', message => {
|
||||
@ -6,13 +27,323 @@ if (parentPort) {
|
||||
const data = message.data;
|
||||
|
||||
switch (command) {
|
||||
case 'ys':
|
||||
|
||||
case 'open':
|
||||
open(data);
|
||||
break;
|
||||
case 'run':
|
||||
run(data);
|
||||
break;
|
||||
|
||||
default:
|
||||
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();
|
||||
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 { verilogExts, vhdlExts, systemVerilogExts, hdlExts } from '../global/lang';
|
||||
import * as hdlPath from './path';
|
||||
import { HdlFileProjectType } from '../hdlParser/common';
|
||||
import { opeParam } from '../global';
|
||||
import { hdlIgnore } from '../manager/ignore';
|
||||
import { hdlDir } from '.';
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { AbsPath, opeParam } from '../global';
|
||||
import { hdlFile, hdlPath } from '../hdlFs';
|
||||
import { hdlPath } from '../hdlFs';
|
||||
import * as fs from 'fs';
|
||||
import * as fspath from 'path';
|
||||
import { minimatch } from 'minimatch';
|
||||
|
Loading…
x
Reference in New Issue
Block a user