From 010a4b6bec798d16767b2e47091ff14c348bc56a Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Wed, 22 Nov 2023 22:45:19 +0800 Subject: [PATCH] add verilator support --- package.json | 10 +- src/function/lsp/linter/base.ts | 3 +- src/function/lsp/linter/default.ts | 7 +- src/function/lsp/linter/modelsim.ts | 44 ++++---- src/function/lsp/linter/verilator.ts | 153 +++++++++++++++++++++++++++ src/function/lsp/linter/vhdl.ts | 87 ++++++++++++++- src/function/lsp/linter/vivado.ts | 19 ++-- src/function/lsp/linter/vlog.ts | 86 ++++++++++++++- 8 files changed, 371 insertions(+), 38 deletions(-) create mode 100644 src/function/lsp/linter/verilator.ts diff --git a/package.json b/package.json index 015182c..7a1b401 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,13 @@ "scope": "window", "type": "string", "default": "", - "description": "set the xilinx install path. \n e.g. : D:/APP/vivado_18_3/Vivado/2018.3/bin \n Default path is C:/Xilinx/Vivado/2018.3/bin \n This applies only to WIN For other systems, add it to environment variables" + "description": "Set the xilinx install path. Ignore this setting if you add relative path to environment variable PATH \n e.g. : D:/APP/vivado_18_3/Vivado/2018.3/bin \n Default path is C:/Xilinx/Vivado/2018.3/bin" + }, + "prj.modelsim.install.path": { + "scope": "window", + "type": "string", + "default": "", + "description": "set the modelsim install path. Ignore this setting if you add relative path to environment variable PATH \n Default path is C:/modeltech64_10.4/win64" }, "prj.xilinx.IP.repo.path": { "scope": "window", @@ -1046,4 +1052,4 @@ "vscode-textmate": "^9.0.0", "wavedrom": "^2.9.1" } -} \ No newline at end of file +} diff --git a/src/function/lsp/linter/base.ts b/src/function/lsp/linter/base.ts index 239889d..3e3771e 100644 --- a/src/function/lsp/linter/base.ts +++ b/src/function/lsp/linter/base.ts @@ -9,7 +9,8 @@ interface BaseManager { initialise(): Promise; } + export { BaseLinter, BaseManager -}; \ No newline at end of file +}; diff --git a/src/function/lsp/linter/default.ts b/src/function/lsp/linter/default.ts index dd6c133..efb9505 100644 --- a/src/function/lsp/linter/default.ts +++ b/src/function/lsp/linter/default.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { All } from '../../../../resources/hdlParser'; import { isVerilogFile, isVhdlFile } from '../../../hdlFs/file'; -import { Position, Range } from '../../../hdlParser/common'; +import { Position } from '../../../hdlParser/common'; import { hdlSymbolStorage } from '../core'; import { BaseLinter } from './base'; import { LspOutput, ReportType } from '../../../global'; @@ -159,7 +159,12 @@ class DefaultVHDLLinter implements BaseLinter { } } +const defaultVlogLinter = new DefaultVlogLinter(); +const defaultVHDLLinter = new DefaultVHDLLinter(); + export { + defaultVlogLinter, + defaultVHDLLinter, DefaultVlogLinter, DefaultVHDLLinter }; diff --git a/src/function/lsp/linter/modelsim.ts b/src/function/lsp/linter/modelsim.ts index 08f6aaf..a4510cf 100644 --- a/src/function/lsp/linter/modelsim.ts +++ b/src/function/lsp/linter/modelsim.ts @@ -28,10 +28,6 @@ class ModelsimLinter implements BaseLinter { this.linterArgsMap.set(HdlLangID.Vhdl, ['-quiet', '-nologo', '-2008']); this.linterArgsMap.set(HdlLangID.SystemVerilog, ['-quiet', '-nolog', '-sv']); this.linterArgsMap.set(HdlLangID.Unknown, []); - - this.initialise(HdlLangID.Verilog); - this.initialise(HdlLangID.Vhdl); - this.initialise(HdlLangID.SystemVerilog); } @@ -40,16 +36,22 @@ class ModelsimLinter implements BaseLinter { const langID = hdlFile.getLanguageId(filePath); // acquire install path - const args = [hdlPath.toSlash(filePath), ...this.linterArgsMap]; + const linterArgs = this.linterArgsMap.get(langID); + + if (linterArgs === undefined) { + return; + } + + const args = [filePath, ...linterArgs]; const executor = this.executableInvokeNameMap.get(langID); if (executor !== undefined) { - const { stdout, stderr } = await easyExec(executor, args); + const { stdout } = await easyExec(executor, args); if (stdout.length > 0) { const diagnostics = this.provideDiagnostics(document, stdout); this.diagnostic.set(document.uri, diagnostics); } } else { - LspOutput.report('linter is not available, please check prj.vivado.install.path in your setting', ReportType.Error); + LspOutput.report('linter is not available, please check prj.modelsim.install.path in your setting', ReportType.Error); } } @@ -63,7 +65,7 @@ class ModelsimLinter implements BaseLinter { for (const line of stdout.split('\n')) { const tokens = line.split(/(Error|Warning).+?(?: *?(?:.+?(?:\\|\/))+.+?\((\d+?)\):|)(?: *?near "(.+?)":|)(?: *?\((.+?)\)|) +?(.+)/gm); - + const headerInfo = tokens[0]; if (headerInfo === 'Error') { const errorLine = parseInt(tokens[2]) - 1; @@ -93,26 +95,26 @@ class ModelsimLinter implements BaseLinter { } private getExecutableFilePath(langID: HdlLangID): string | Path | undefined { - // vivado install path stored in prj.vivado.install.path - const vivadoConfig = vscode.workspace.getConfiguration('prj.vivado'); - const vivadoInstallPath = vivadoConfig.get('install.path', ''); + // modelsim install path stored in prj.modelsim.install.path + const modelsimConfig = vscode.workspace.getConfiguration('prj.modelsim'); + const modelsimInstallPath = modelsimConfig.get('install.path', ''); const executorName = this.executableFileMap.get(langID); if (executorName === undefined) { return undefined; } - // e.g. xvlog.bat in windows, xvlog in linux - const fullExecutorName = opeParam.os === 'win32' ? executorName + '.bat' : executorName; + // e.g. vlog.exe in windows, vlog in linux + const fullExecutorName = opeParam.os === 'win32' ? executorName + '.exe' : executorName; - if (vivadoInstallPath.trim() === '' || !fs.existsSync(vivadoInstallPath)) { - LspOutput.report(`User's Vivado Install Path ${vivadoInstallPath}, which is invalid. Use ${executorName} in default.`, ReportType.Warn); - LspOutput.report('If you have doubts, check prj.vivado.install.path in setting', ReportType.Warn); + if (modelsimInstallPath.trim() === '' || !fs.existsSync(modelsimInstallPath)) { + LspOutput.report(`User's modelsim Install Path ${modelsimInstallPath}, which is invalid. Use ${executorName} in default.`, ReportType.Warn); + LspOutput.report('If you have doubts, check prj.modelsim.install.path in setting', ReportType.Warn); return executorName; } else { - LspOutput.report(`User's Vivado Install Path ${vivadoInstallPath}, which is invalid`); + LspOutput.report(`User's modelsim Install Path ${modelsimInstallPath}, which is invalid`); const executorPath = hdlPath.join( - hdlPath.toSlash(vivadoInstallPath), + hdlPath.toSlash(modelsimInstallPath), fullExecutorName ); // prevent path like C://stupid name/xxx/xxx/bin @@ -127,14 +129,14 @@ class ModelsimLinter implements BaseLinter { if (executorPath === undefined) { return false; } - const { stdout, stderr } = await easyExec(executorPath, []); + const { stderr } = await easyExec(executorPath, []); if (stderr.length === 0) { this.executableInvokeNameMap.set(langID, undefined); LspOutput.report(`fail to execute ${executorPath}! Reason: ${stderr}`, ReportType.Error); return false; } else { this.executableInvokeNameMap.set(langID, executorPath); - LspOutput.report(`success to verify ${executorPath}, linter from vivado is ready to go!`, ReportType.Launch); + LspOutput.report(`success to verify ${executorPath}, linter from modelsim is ready to go!`, ReportType.Launch); return true; } } @@ -145,7 +147,9 @@ class ModelsimLinter implements BaseLinter { } } +const modelsimLinter = new ModelsimLinter() export { + modelsimLinter, ModelsimLinter }; diff --git a/src/function/lsp/linter/verilator.ts b/src/function/lsp/linter/verilator.ts new file mode 100644 index 0000000..b51a953 --- /dev/null +++ b/src/function/lsp/linter/verilator.ts @@ -0,0 +1,153 @@ +import * as vscode from "vscode"; +import * as fs from 'fs'; + +import { LspOutput, ReportType, opeParam } from "../../../global"; +import { Path } from "../../../../resources/hdlParser"; +import { hdlFile, hdlPath } from "../../../hdlFs"; +import { easyExec } from "../../../global/util"; +import { BaseLinter } from "./base"; +import { HdlLangID } from "../../../global/enum"; + +class VerilatorLinter implements BaseLinter { + diagnostic: vscode.DiagnosticCollection; + executableFileMap: Map = new Map(); + executableInvokeNameMap: Map = new Map(); + linterArgsMap: Map = new Map(); + + constructor() { + this.diagnostic = vscode.languages.createDiagnosticCollection(); + + // configure map for executable file name + this.executableFileMap.set(HdlLangID.Verilog, 'verilator'); + this.executableFileMap.set(HdlLangID.SystemVerilog, 'verilator'); + this.executableFileMap.set(HdlLangID.Unknown, undefined); + + // configure map for argruments when lintering + this.linterArgsMap.set(HdlLangID.Verilog, ['--lint-only', '-Wall', '-bbox-sys', '--bbox-unsup', '-DGLBL']); + this.linterArgsMap.set(HdlLangID.SystemVerilog, ['--lint-only', '-sv', '-Wall', '-bbox-sys', '--bbox-unsup', '-DGLBL']); + this.linterArgsMap.set(HdlLangID.Unknown, []); + } + + + async lint(document: vscode.TextDocument) { + const filePath = hdlPath.toSlash(document.fileName); + const langID = hdlFile.getLanguageId(filePath); + + // acquire install path + const linterArgs = this.linterArgsMap.get(langID); + + if (linterArgs === undefined) { + return; + } + + const args = [filePath, ...linterArgs]; + const executor = this.executableInvokeNameMap.get(langID); + if (executor !== undefined) { + const { stdout } = await easyExec(executor, args); + if (stdout.length > 0) { + const diagnostics = this.provideDiagnostics(document, stdout); + this.diagnostic.set(document.uri, diagnostics); + } + } else { + LspOutput.report('linter is not available, please check prj.verilator.install.path in your setting', ReportType.Error); + } + } + + /** + * @param document + * @param stdout stdout from xvlog + * @returns { vscode.Diagnostic[] } linter info + */ + private provideDiagnostics(document: vscode.TextDocument, stdout: string): vscode.Diagnostic[] { + const diagnostics = []; + for (const line of stdout.split('\n')) { + const tokens = line.split(/(Error|Warning).+?(?: *?(?:.+?(?:\\|\/))+.+?\((\d+?)\):|)(?: *?near "(.+?)":|)(?: *?\((.+?)\)|) +?(.+)/gm); + // TODO : make parser of output info from verilator + + const headerInfo = tokens[0]; + if (headerInfo === 'Error') { + const errorLine = parseInt(tokens[2]) - 1; + const syntaxInfo = tokens[5]; + const range = this.makeCorrectRange(document, errorLine); + const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Error); + diagnostics.push(diag); + } else if (headerInfo == 'Warning') { + const errorLine = parseInt(tokens[2]) - 1; + const syntaxInfo = tokens[5]; + const range = this.makeCorrectRange(document, errorLine); + const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Warning); + diagnostics.push(diag); + } + } + return diagnostics; + } + + private makeCorrectRange(document: vscode.TextDocument, line: number): vscode.Range { + const startPosition = new vscode.Position(line, 0); + const wordRange = document.getWordRangeAtPosition(startPosition, /[`_0-9a-zA-Z]+/); + if (wordRange) { + return wordRange; + } else { + return new vscode.Range(startPosition, startPosition); + } + } + + private getExecutableFilePath(langID: HdlLangID): string | Path | undefined { + // verilator install path stored in prj.verilator.install.path + const verilatorConfig = vscode.workspace.getConfiguration('prj.verilator'); + const verilatorInstallPath = verilatorConfig.get('install.path', ''); + const executorName = this.executableFileMap.get(langID); + if (executorName === undefined) { + return undefined; + } + + // e.g. vlog.exe in windows, vlog in linux + const fullExecutorName = opeParam.os === 'win32' ? executorName + '.exe' : executorName; + + if (verilatorInstallPath.trim() === '' || !fs.existsSync(verilatorInstallPath)) { + LspOutput.report(`User's verilator Install Path ${verilatorInstallPath}, which is invalid. Use ${executorName} in default.`, ReportType.Warn); + LspOutput.report('If you have doubts, check prj.verilator.install.path in setting', ReportType.Warn); + return executorName; + } else { + LspOutput.report(`User's verilator Install Path ${verilatorInstallPath}, which is invalid`); + + const executorPath = hdlPath.join( + hdlPath.toSlash(verilatorInstallPath), + fullExecutorName + ); + // prevent path like C://stupid name/xxx/xxx/bin + // blank space + const safeExecutorPath = '"' + executorPath + '"'; + return safeExecutorPath; + } + } + + + public async setExecutableFilePath(executorPath: string | Path | undefined, langID: HdlLangID): Promise { + if (executorPath === undefined) { + return false; + } + const { stderr } = await easyExec(executorPath, []); + if (stderr.length === 0) { + this.executableInvokeNameMap.set(langID, undefined); + LspOutput.report(`fail to execute ${executorPath}! Reason: ${stderr}`, ReportType.Error); + return false; + } else { + this.executableInvokeNameMap.set(langID, executorPath); + LspOutput.report(`success to verify ${executorPath}, linter from verilator is ready to go!`, ReportType.Launch); + return true; + } + } + + public initialise(langID: HdlLangID) { + const executorPath = this.getExecutableFilePath(langID); + this.setExecutableFilePath(executorPath, langID); + } +} + +const verilatorLinter = new VerilatorLinter(); + +export { + verilatorLinter, + VerilatorLinter +}; diff --git a/src/function/lsp/linter/vhdl.ts b/src/function/lsp/linter/vhdl.ts index 3474181..ede90f5 100644 --- a/src/function/lsp/linter/vhdl.ts +++ b/src/function/lsp/linter/vhdl.ts @@ -1,13 +1,92 @@ import * as vscode from 'vscode'; -import { BaseManager } from './base'; +import { LspOutput, ReportType } from '../../../global'; +import { HdlLangID } from '../../../global/enum'; +import { BaseLinter, BaseManager } from './base'; +import { defaultVlogLinter } from './default'; +import { modelsimLinter } from './modelsim'; +import { vivadoLinter } from './vivado'; class VhdlLinterManager implements BaseManager { - constructor() { + currentLinter: BaseLinter | undefined; + activateList: Map = new Map(); + activateLinterName: string; + constructor() { + this.activateList.set('vivado', false); + this.activateList.set('modelsim', false); + this.activateList.set('default', false); + this.activateLinterName = 'default'; + + this.updateLinter(); + + // update when user's config is changed + vscode.workspace.onDidChangeConfiguration(() => { + const diagnostor = this.getUserDiagnostorSelection(); + const lastDiagnostor = this.activateLinterName; + if (diagnostor !== lastDiagnostor) { + LspOutput.report(`[vhdl lsp manager] detect linter setting changes, switch ${lastDiagnostor} to ${diagnostor}.`, ); + this.updateLinter(); + } + }); } async initialise(): Promise { - + + } + + public getUserDiagnostorSelection() { + const vlogLspConfig = vscode.workspace.getConfiguration('function.lsp.linter.vlog'); + const diagnostor = vlogLspConfig.get('diagnostor', 'default'); + return diagnostor; + } + + public updateLinter() { + const diagnostor = this.getUserDiagnostorSelection(); + switch (diagnostor) { + case 'vivado': this.activateVivado(); break; + case 'modelsim': this.activateModelsim(); break; + case 'default': this.activateDefault(); break; + case default: this.activateDefault(); break; + } + } + + public activateVivado() { + const selectedLinter = vivadoLinter; + + if (this.activateList.get('vivado') === false) { + selectedLinter.initialise(HdlLangID.Verilog); + this.activateList.set('vivado', true); + LspOutput.report('[vhdl lsp manager] vivado linter has been activated', ReportType.Info); + } + + this.currentLinter = selectedLinter; + this.activateLinterName = 'vivado'; + } + + public activateModelsim() { + const selectedLinter = modelsimLinter; + + if (this.activateList.get('modelsim') === false) { + selectedLinter.initialise(HdlLangID.Verilog); + this.activateList.set('modelsim', true); + LspOutput.report('[vhdl lsp manager] modelsim linter has been activated', ReportType.Info); + } + + this.currentLinter = selectedLinter; + this.activateLinterName = 'modelsim'; + } + + public activateDefault() { + const selectedLinter = defaultVlogLinter; + + if (this.activateList.get('default') === false) { + + this.activateList.set('default', true); + LspOutput.report('[vhdl lsp manager] default build-in linter has been activated', ReportType.Info); + } + + this.currentLinter = selectedLinter; + this.activateLinterName = 'default'; } } @@ -15,4 +94,4 @@ const vhdlLinterManager = new VhdlLinterManager(); export { vhdlLinterManager -}; \ No newline at end of file +}; diff --git a/src/function/lsp/linter/vivado.ts b/src/function/lsp/linter/vivado.ts index 21c6ac6..f48cf9a 100644 --- a/src/function/lsp/linter/vivado.ts +++ b/src/function/lsp/linter/vivado.ts @@ -29,9 +29,9 @@ class VivadoLinter implements BaseLinter { this.linterArgsMap.set(HdlLangID.SystemVerilog, ['--sv', '--nolog']); this.linterArgsMap.set(HdlLangID.Unknown, []); - this.initialise(HdlLangID.Verilog); - this.initialise(HdlLangID.Vhdl); - this.initialise(HdlLangID.SystemVerilog); + // this.initialise(HdlLangID.Verilog); + // this.initialise(HdlLangID.Vhdl); + // this.initialise(HdlLangID.SystemVerilog); } @@ -40,10 +40,15 @@ class VivadoLinter implements BaseLinter { const langID = hdlFile.getLanguageId(filePath); // acquire install path - const args = [hdlPath.toSlash(filePath), ...this.linterArgsMap]; + const linterArgs = this.linterArgsMap.get(langID); + if (linterArgs === undefined) { + return; + } + + const args = [filePath, ...linterArgs]; const executor = this.executableInvokeNameMap.get(langID); if (executor !== undefined) { - const { stdout, stderr } = await easyExec(executor, args); + const { stdout } = await easyExec(executor, args); if (stdout.length > 0) { const diagnostics = this.provideDiagnostics(document, stdout); this.diagnostic.set(document.uri, diagnostics); @@ -122,7 +127,7 @@ class VivadoLinter implements BaseLinter { if (executorPath === undefined) { return false; } - const { stdout, stderr } = await easyExec(executorPath, []); + const { stderr } = await easyExec(executorPath, []); if (stderr.length === 0) { this.executableInvokeNameMap.set(langID, undefined); LspOutput.report(`fail to execute ${executorPath}! Reason: ${stderr}`, ReportType.Error); @@ -140,7 +145,9 @@ class VivadoLinter implements BaseLinter { } } +const vivadoLinter = new VivadoLinter(); export { + vivadoLinter, VivadoLinter }; diff --git a/src/function/lsp/linter/vlog.ts b/src/function/lsp/linter/vlog.ts index f43d406..4142176 100644 --- a/src/function/lsp/linter/vlog.ts +++ b/src/function/lsp/linter/vlog.ts @@ -1,14 +1,92 @@ import * as vscode from 'vscode'; -import { BaseManager } from './base'; +import { LspOutput, ReportType } from '../../../global'; +import { HdlLangID } from '../../../global/enum'; +import { BaseLinter, BaseManager } from './base'; +import { defaultVlogLinter } from './default'; +import { modelsimLinter } from './modelsim'; +import { vivadoLinter } from './vivado'; class VlogLinterManager implements BaseManager { + currentLinter: BaseLinter | undefined; + activateList: Map = new Map(); + activateLinterName: string; + constructor() { - const vlogLspConfig = vscode.workspace.getConfiguration('digital-ide.lsp.verilog.linter'); - const + this.activateList.set('vivado', false); + this.activateList.set('modelsim', false); + this.activateList.set('default', false); + this.activateLinterName = 'default'; + + this.updateLinter(); + + // update when user's config is changed + vscode.workspace.onDidChangeConfiguration(() => { + const diagnostor = this.getUserDiagnostorSelection(); + const lastDiagnostor = this.activateLinterName; + if (diagnostor !== lastDiagnostor) { + LspOutput.report(`detect linter setting changes, switch ${lastDiagnostor} to ${diagnostor}.`, ); + this.updateLinter(); + } + }); } async initialise(): Promise { - + + } + + public getUserDiagnostorSelection() { + const vlogLspConfig = vscode.workspace.getConfiguration('function.lsp.linter.vlog'); + const diagnostor = vlogLspConfig.get('diagnostor', 'default'); + return diagnostor; + } + + public updateLinter() { + const diagnostor = this.getUserDiagnostorSelection(); + switch (diagnostor) { + case 'vivado': this.activateVivado(); break; + case 'modelsim': this.activateModelsim(); break; + case 'default': this.activateDefault(); break; + case default: this.activateDefault(); break; + } + } + + public activateVivado() { + const selectedLinter = vivadoLinter; + + if (this.activateList.get('vivado') === false) { + selectedLinter.initialise(HdlLangID.Verilog); + this.activateList.set('vivado', true); + LspOutput.report('vivado linter has been activated', ReportType.Info); + } + + this.currentLinter = selectedLinter; + this.activateLinterName = 'vivado'; + } + + public activateModelsim() { + const selectedLinter = modelsimLinter; + + if (this.activateList.get('modelsim') === false) { + selectedLinter.initialise(HdlLangID.Verilog); + this.activateList.set('modelsim', true); + LspOutput.report('modelsim linter has been activated', ReportType.Info); + } + + this.currentLinter = selectedLinter; + this.activateLinterName = 'modelsim'; + } + + public activateDefault() { + const selectedLinter = defaultVlogLinter; + + if (this.activateList.get('default') === false) { + + this.activateList.set('default', true); + LspOutput.report('default build-in linter has been activated', ReportType.Info); + } + + this.currentLinter = selectedLinter; + this.activateLinterName = 'default'; } }