diff --git a/package.json b/package.json index 0942180..d90fd28 100644 --- a/package.json +++ b/package.json @@ -142,39 +142,59 @@ "description": "Select the verilog and systemverilog formatter style." }, "function.lsp.formatter.vlog.default.args": { - "scope": "window", - "type": "string", - "default": "", - "description": "Add verilog formatter arguments here (like istyle)." - }, - "function.lsp.formatter.vhdl.default.keyword-case": { - "description": "Keyword case", - "type": "string", - "default": "LowerCase", - "enum": [ - "LowerCase", - "UpperCase" - ] - }, - "function.lsp.formatter.vhdl.default.align-comments": { - "description": "Align comments", - "type": "boolean", - "default": false - }, - "function.lsp.formatter.vhdl.default.type-name-case": { - "description": "Type name case", - "type": "string", - "default": "LowerCase", - "enum": [ - "LowerCase", - "UpperCase" - ] - }, - "function.lsp.formatter.vhdl.default.indentation": { - "description": "Indentation", - "type": "number", - "default": 4 - } + "scope": "window", + "type": "string", + "default": "", + "description": "Add verilog formatter arguments here (like istyle)." + }, + "function.lsp.formatter.vhdl.default.keyword-case": { + "description": "Keyword case", + "type": "string", + "default": "LowerCase", + "enum": [ + "LowerCase", + "UpperCase" + ] + }, + "function.lsp.formatter.vhdl.default.align-comments": { + "description": "Align comments", + "type": "boolean", + "default": false + }, + "function.lsp.formatter.vhdl.default.type-name-case": { + "description": "Type name case", + "type": "string", + "default": "LowerCase", + "enum": [ + "LowerCase", + "UpperCase" + ] + }, + "function.lsp.formatter.vhdl.default.indentation": { + "description": "Indentation", + "type": "number", + "default": 4 + }, + "function.lsp.completion.vlog.autoAddInclude": { + "description": "`include \"xxx.v\" will be added to the top of the file automatically", + "type": "boolean", + "default": true + }, + "function.lsp.completion.vlog.completeWholeInstante": { + "description": "complete everything invoking a module needs including paramters and ports", + "type": "boolean", + "default": true + }, + "function.instantiation.addComment": { + "description": "add comment like // ports, // input, // output when doing instantiation, including completion for module invoking", + "type": "boolean", + "default": true + }, + "function.instantiation.autoNetOutputDeclaration": { + "description": "auto declare output type nets in the scope when instantiation happens.", + "type": "boolean", + "default": true + } } }, "commands": [ @@ -385,6 +405,10 @@ }, "category": "tool", "title": "%digital-ide.fsm.title%" + }, + { + "command": "digital-ide.lsp.tool.insertTextToUri", + "title": "%digital-ide.lsp.tool.insertTextToUri.title%" } ], "menus": { @@ -797,4 +821,4 @@ "vscode-textmate": "^9.0.0", "wavedrom": "^2.9.1" } -} +} \ No newline at end of file diff --git a/package.nls.json b/package.nls.json index a3be695..fdc55dd 100644 --- a/package.nls.json +++ b/package.nls.json @@ -35,5 +35,6 @@ "digital-ide.pl.addFile.title": "add file", "digital-ide.pl.delFile.title": "del file", "digital-ide.netlist.title": "netlist", - "digital-ide.fsm.title": "finite state machine" + "digital-ide.fsm.title": "finite state machine", + "digital-ide.lsp.tool.insertTextToUri.title": "insert text to uri" } \ No newline at end of file diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 65a1a05..86c70bd 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -35,5 +35,6 @@ "digital-ide.pl.addFile.title": "添加文件", "digital-ide.pl.delFile.title": "d删除文件", "digital-ide.netlist.title": "netlist", - "digital-ide.fsm.title": "有限状态机" + "digital-ide.fsm.title": "有限状态机", + "digital-ide.lsp.tool.insertTextToUri.title": "插入文本uri" } \ No newline at end of file diff --git a/package.nls.zh-tw.json b/package.nls.zh-tw.json index 86ebf78..a556923 100644 --- a/package.nls.zh-tw.json +++ b/package.nls.zh-tw.json @@ -28,12 +28,13 @@ "digital-ide.hard.gui.title": "打開界面", "digital-ide.hard.exit.title": "退出當前項目", "digital-ide.pickLibrary.title": "從自定義選擇自由和普遍", - "digital-ide.pl.setSrcTop.title": "設置為src的文件", - "digital-ide.pl.setSimTop.title": "設置為文件的sim卡", - "digital-ide.pl.addDevice.title": "添加設備", - "digital-ide.pl.delDevice.title": "德爾設備", + "digital-ide.pl.setSrcTop.title": "設置為src的top", + "digital-ide.pl.setSimTop.title": "設置為sim的top", + "digital-ide.pl.addDevice.title": "添加device", + "digital-ide.pl.delDevice.title": "刪除device", "digital-ide.pl.addFile.title": "添加文件", - "digital-ide.pl.delFile.title": "del文件", - "digital-ide.netlist.title": "網表", - "digital-ide.fsm.title": "有限狀態機" + "digital-ide.pl.delFile.title": "d刪除文件", + "digital-ide.netlist.title": "netlist", + "digital-ide.fsm.title": "有限狀態機", + "digital-ide.lsp.tool.insertTextToUri.title": "插入文本uri" } \ No newline at end of file diff --git a/resources/hdlParser/index.d.ts b/resources/hdlParser/index.d.ts index 058f7f2..2028a27 100644 --- a/resources/hdlParser/index.d.ts +++ b/resources/hdlParser/index.d.ts @@ -7,7 +7,7 @@ type Path = AbsPath | RelPath; interface Fast { content: RawHdlModule[] - languageId: HdlLangID + languageId: string macro: Macro } diff --git a/src/extension.ts b/src/extension.ts index ee455af..317474a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,20 +2,25 @@ import * as vscode from 'vscode'; import { opeParam, MainOutput, ReportType } from './global'; import { hdlParam } from './hdlParser'; -import { prjManage, registerManagerCommands } from './manager'; -import { registerFunctionCommands, registerLsp } from './function'; +import * as manager from './manager'; +import * as func from './function'; +import { hdlMonitor } from './monitor'; async function registerCommand(context: vscode.ExtensionContext) { - registerFunctionCommands(context); - registerManagerCommands(context); - registerLsp(context); + manager.registerManagerCommands(context); + + func.registerFunctionCommands(context); + func.registerLsp(context); + func.registerToolCommands(context); } -async function launch(context: vscode.ExtensionContext) { - await prjManage.initialise(context); +async function launch(context: vscode.ExtensionContext) { + await manager.prjManage.initialise(context); await registerCommand(context); - + hdlMonitor.start(); + console.log(hdlParam); + MainOutput.report('Digital-IDE has launched, Version: 0.3.0'); MainOutput.report('OS: ' + opeParam.os); } diff --git a/src/function/hdlDoc/index.ts b/src/function/hdlDoc/index.ts index 824683d..8b60258 100644 --- a/src/function/hdlDoc/index.ts +++ b/src/function/hdlDoc/index.ts @@ -26,7 +26,7 @@ class ExportFunctionItem { }; function registerFileDocExport(context: vscode.ExtensionContext) { - vscode.commands.registerCommand('digital-ide.hdlDoc.exportFile', () => { + vscode.commands.registerCommand('digital-ide.hdlDoc.exportFile', async () => { const option = { placeHolder: 'Select an Export Format' }; @@ -35,17 +35,16 @@ function registerFileDocExport(context: vscode.ExtensionContext) { new ExportFunctionItem('pdf', ' pdf', 'only support light theme', exportCurrentFileDocAsPDF), new ExportFunctionItem('html', ' html', 'only support light theme', exportCurrentFileDocAsHTML) ]; - - vscode.window.showQuickPick(items, option).then(item => { - if (item) { - item.exportFunc(); - } - }); + + const item = await vscode.window.showQuickPick(items, option); + if (item) { + item.exportFunc(); + } }); } function registerProjectDocExport(context: vscode.ExtensionContext) { - vscode.commands.registerCommand('digital-ide.hdlDoc.exportProject', () => { + vscode.commands.registerCommand('digital-ide.hdlDoc.exportProject', async () => { const option = { placeHolder: 'Select an Export Format' }; @@ -54,12 +53,11 @@ function registerProjectDocExport(context: vscode.ExtensionContext) { new ExportFunctionItem('pdf', ' pdf', 'only support light theme', exportProjectDocAsPDF), new ExportFunctionItem('html', ' html', 'only support light theme', exportProjectDocAsHTML) ]; - - vscode.window.showQuickPick(items, option).then(item => { - if (item) { - item.exportFunc(); - } - }); + + const item = await vscode.window.showQuickPick(items, option); + if (item) { + item.exportFunc(); + } }); } diff --git a/src/function/index.ts b/src/function/index.ts index 73622a9..dec50ec 100644 --- a/src/function/index.ts +++ b/src/function/index.ts @@ -9,6 +9,9 @@ import * as lspDocSymbol from './lsp/docSymbol'; import * as lspDefinition from './lsp/definition'; import * as lspHover from './lsp/hover'; import * as lspFormatter from '../../resources/formatter'; +import * as lspDocSemantic from './lsp/docSemantic'; + +import * as tool from './tool'; function registerDocumentation(context: vscode.ExtensionContext) { vscode.commands.registerCommand('digital-ide.hdlDoc.showWebview', hdlDoc.showDocWebview); @@ -65,11 +68,18 @@ function registerLsp(context: vscode.ExtensionContext) { vscode.languages.registerCompletionItemProvider(vlogSelector, lspCompletion.vlogMacroCompletionProvider, '`'); vscode.languages.registerCompletionItemProvider(vlogSelector, lspCompletion.vlogPositionPortProvider, '.'); vscode.languages.registerCompletionItemProvider(vlogSelector, lspCompletion.vlogCompletionProvider); - // vhdl lsp + vscode.languages.registerDocumentSemanticTokensProvider(vlogSelector, lspDocSemantic.vlogDocSenmanticProvider, lspDocSemantic.vlogLegend); + + // vhdl lsp +} + +function registerToolCommands(context: vscode.ExtensionContext) { + vscode.commands.registerCommand('digital-ide.lsp.tool.insertTextToUri', tool.insertTextToUri); } export { registerFunctionCommands, - registerLsp + registerLsp, + registerToolCommands }; \ No newline at end of file diff --git a/src/function/lsp/completion/vlog.ts b/src/function/lsp/completion/vlog.ts index 6331b70..40eca88 100644 --- a/src/function/lsp/completion/vlog.ts +++ b/src/function/lsp/completion/vlog.ts @@ -4,10 +4,11 @@ import * as fs from 'fs'; import * as util from '../util'; import { hdlFile, hdlPath } from '../../../hdlFs'; import { hdlParam, HdlSymbol } from '../../../hdlParser'; -import { AbsPath, MainOutput, ReportType } from '../../../global'; +import { AbsPath, MainOutput, RelPath, ReportType } from '../../../global'; import { Define, Include, RawSymbol } from '../../../hdlParser/common'; import { HdlInstance, HdlModule } from '../../../hdlParser/core'; import { vlogKeyword } from '../util/keyword'; +import { instanceVlogCode } from '../../sim/instance'; class VlogIncludeCompletionProvider implements vscode.CompletionItemProvider { public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext): vscode.ProviderResult> { @@ -176,7 +177,9 @@ class VlogCompletionProvider implements vscode.CompletionItemProvider { const filePath = hdlPath.toSlash(document.fileName); // 1. provide keyword - const completions = this.getKeyWordItem(); + const completions = this.makeKeywordItems(); + completions.push(...this.makeCompilerKeywordItems()); + completions.push(...this.makeSystemKeywordItems()); const symbolResult = await HdlSymbol.all(filePath); if (!symbolResult) { @@ -199,7 +202,7 @@ class VlogCompletionProvider implements vscode.CompletionItemProvider { } // 3. provide modules - const suggestModulesPromise = this.provideModules(filePath, symbolResult.macro.includes); + const suggestModulesPromise = this.provideModules(document, position, filePath, symbolResult.macro.includes); // 4. provide params and ports of wrapper module const suggestParamsPortsPromise = this.provideParamsPorts(currentModule); @@ -219,7 +222,7 @@ class VlogCompletionProvider implements vscode.CompletionItemProvider { } } - private getKeyWordItem(): vscode.CompletionItem[] { + private makeKeywordItems(): vscode.CompletionItem[] { const vlogKeywordItem = []; for (const keyword of vlogKeyword.keys()) { const clItem = this.makekeywordCompletionItem(keyword); @@ -229,6 +232,29 @@ class VlogCompletionProvider implements vscode.CompletionItemProvider { return vlogKeywordItem; } + private makeCompilerKeywordItems(): vscode.CompletionItem[] { + const items = []; + for (const keyword of vlogKeyword.compilerKeys()) { + const clItem = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword); + clItem.insertText = new vscode.SnippetString('`' + keyword); + clItem.detail = 'compiler directive'; + items.push(clItem); + } + return items; + } + + private makeSystemKeywordItems(): vscode.CompletionItem[] { + const items = []; + for (const keyword of vlogKeyword.systemKeys()) { + const clItem = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Method); + clItem.insertText = new vscode.SnippetString('\\$' + keyword + '($1);'); + clItem.detail = 'system task'; + items.push(clItem); + } + return items; + } + + private makekeywordCompletionItem(keyword: string): vscode.CompletionItem { const clItem = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword); clItem.detail = 'keyword'; @@ -240,12 +266,53 @@ class VlogCompletionProvider implements vscode.CompletionItemProvider { return clItem; } - private async provideModules(filePath: AbsPath, includes: Include[]): Promise { + private async provideModules(document: vscode.TextDocument, position: vscode.Position, filePath: AbsPath, includes: Include[]): Promise { const suggestModules: vscode.CompletionItem[] = []; - // TODO : add `include xxx automatically - for (const module of hdlParam.getAllHdlModules()) { + const lspVlogConfig = vscode.workspace.getConfiguration('function.lsp.completion.vlog'); + const autoAddInclude: boolean = lspVlogConfig.get('autoAddInclude', true); + const completeWholeInstante: boolean = lspVlogConfig.get('completeWholeInstante', true); + + const includePaths = new Set(); + let lastIncludeLine = 0; + for (const include of includes) { + const absIncludePath = hdlPath.rel2abs(filePath, include.path); + includePaths.add(absIncludePath); + lastIncludeLine = Math.max(include.range.end.line, lastIncludeLine); + } + const insertPosition = new vscode.Position(lastIncludeLine, 0); + const insertRange = new vscode.Range(insertPosition, insertPosition); + const fileFolder = hdlPath.resolve(filePath, '..'); + + // used only when completeWholeInstante is true + let completePrefix = ''; + if (completeWholeInstante) { + const wordRange = document.getWordRangeAtPosition(position); + const countStart = wordRange ? wordRange.start.character : position.character; + const spaceNumber = Math.floor(countStart / 4) * 4; + console.log(wordRange, countStart, spaceNumber); + + completePrefix = ' '.repeat(spaceNumber); + } + + + for (const module of hdlParam.getAllHdlModules()) { const clItem = new vscode.CompletionItem(module.name, vscode.CompletionItemKind.Class); + + // feature 1 : auto add include path if there's no corresponding include path + if (autoAddInclude && !includePaths.has(module.path)) { + const relPath: RelPath = hdlPath.relative(fileFolder, module.path); + const includeString = '`include "' + relPath + '"\n'; + const textEdit = new vscode.TextEdit(insertRange, includeString); + clItem.additionalTextEdits = [textEdit]; + } + + // feature 2 : auto complete instance + if (completeWholeInstante) { + const snippetString = instanceVlogCode(module, '', true); + clItem.insertText = new vscode.SnippetString(snippetString); + } + clItem.detail = 'module'; suggestModules.push(clItem); } @@ -281,6 +348,7 @@ class VlogCompletionProvider implements vscode.CompletionItemProvider { for (const symbol of symbols) { if (symbol.type === 'wire' || symbol.type === 'reg') { const clItem = new vscode.CompletionItem(symbol.name, vscode.CompletionItemKind.Variable); + clItem.sortText = ''; clItem.detail = symbol.type; suggestNets.push(clItem); } diff --git a/src/function/lsp/definition/vlog.ts b/src/function/lsp/definition/vlog.ts index fb31d5c..4f3cda5 100644 --- a/src/function/lsp/definition/vlog.ts +++ b/src/function/lsp/definition/vlog.ts @@ -52,12 +52,13 @@ class VlogDefinitionProvider implements vscode.DefinitionProvider { // match `include const includeResult = util.matchInclude(document, position, all.macro.includes); + if (includeResult) { const absPath = hdlPath.rel2abs(filePath, includeResult.name); const targetFile = vscode.Uri.file(absPath); const targetPosition = new vscode.Position(0, 0); const targetRange = new vscode.Range(targetPosition, targetPosition); - const originSelectionRange = document.getWordRangeAtPosition(position, /[\."_0-9a-zA-Z]+/); + const originSelectionRange = document.getWordRangeAtPosition(position, /["\.\\\/_0-9A-Za-z]+/); const link: vscode.LocationLink = { targetUri: targetFile, targetRange, originSelectionRange }; return [link]; } @@ -142,10 +143,7 @@ class VlogDefinitionProvider implements vscode.DefinitionProvider { // match others const normalResult = util.matchNormalSymbol(targetWord, scopeSymbols.symbols); if (normalResult) { - const targetRange = util.transformRange(normalResult.range, -1, 0); - - console.log(targetRange, normalResult); - + const targetRange = util.transformRange(normalResult.range, -1, 0); const link: vscode.LocationLink = { targetUri: document.uri, targetRange }; return [link]; } diff --git a/src/function/lsp/docSemantic/index.ts b/src/function/lsp/docSemantic/index.ts new file mode 100644 index 0000000..840b29d --- /dev/null +++ b/src/function/lsp/docSemantic/index.ts @@ -0,0 +1,6 @@ +import { vlogDocSenmanticProvider, vlogLegend } from './vlog'; + +export { + vlogDocSenmanticProvider, + vlogLegend +}; \ No newline at end of file diff --git a/src/function/lsp/docSemantic/vlog.ts b/src/function/lsp/docSemantic/vlog.ts new file mode 100644 index 0000000..4ec96a5 --- /dev/null +++ b/src/function/lsp/docSemantic/vlog.ts @@ -0,0 +1,21 @@ +import * as vscode from 'vscode'; + +import { HdlSymbol } from '../../../hdlParser'; + +const tokenTypes = ['class', 'interface', 'enum', 'function', 'variable']; +const tokenModifiers = ['declaration', 'documentation']; +const vlogLegend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); + +class VlogDocSenmanticProvider implements vscode.DocumentSemanticTokensProvider { + public async provideDocumentSemanticTokens(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { + const tokensBuilder = new vscode.SemanticTokensBuilder(vlogLegend); + return tokensBuilder.build(); + } +} + +const vlogDocSenmanticProvider = new VlogDocSenmanticProvider(); + +export { + vlogDocSenmanticProvider, + vlogLegend +}; \ No newline at end of file diff --git a/src/function/lsp/docSymbol/vlog.ts b/src/function/lsp/docSymbol/vlog.ts index 3749d1d..238eeed 100644 --- a/src/function/lsp/docSymbol/vlog.ts +++ b/src/function/lsp/docSymbol/vlog.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import { AllowNull } from '../../../global'; import { HdlSymbol } from '../../../hdlParser'; -import { RawSymbol, makeVscodePosition, Range } from '../../../hdlParser/common'; +import { RawSymbol, Range } from '../../../hdlParser/common'; import { positionAfterEqual } from '../util'; diff --git a/src/function/lsp/hover/vlog.ts b/src/function/lsp/hover/vlog.ts index aedc71d..5045cf3 100644 --- a/src/function/lsp/hover/vlog.ts +++ b/src/function/lsp/hover/vlog.ts @@ -159,10 +159,7 @@ class VlogHoverProvider implements vscode.HoverProvider { if (normalResult) { const normalComment = await util.searchCommentAround(filePath, normalResult.range); const normalDesc = util.makeNormalDesc(normalResult); - - console.log(normalResult); - content.appendCodeblock(normalDesc, HdlLangID.Verilog); if (normalComment) { content.appendCodeblock(normalComment, HdlLangID.Verilog); diff --git a/src/function/lsp/util/index.ts b/src/function/lsp/util/index.ts index a0e7253..06c264a 100644 --- a/src/function/lsp/util/index.ts +++ b/src/function/lsp/util/index.ts @@ -123,7 +123,7 @@ function isInComment(document: vscode.TextDocument, position: Position, comments function matchInclude(document: vscode.TextDocument, position: vscode.Position, includes: Include[]) : AllowNull { - const selectFileRange = document.getWordRangeAtPosition(position, /[\._0-9A-Za-z]+/); + const selectFileRange = document.getWordRangeAtPosition(position, /[\.\\\/_0-9A-Za-z]+/); const selectFileName = document.getText(selectFileRange); if (!includes) { @@ -318,7 +318,8 @@ function makeParamDesc(param: HdlModuleParam): string { } function makeNormalDesc(normal: RawSymbol): string { - const desc = normal.type + ' ' + normal.width + ' ' + normal.name; + const width = normal.width ? normal.width : ''; + const desc = normal.type + ' ' + width + ' ' + normal.name; return desc; } diff --git a/src/function/lsp/util/keyword.ts b/src/function/lsp/util/keyword.ts index e7436e2..3a26bd2 100644 --- a/src/function/lsp/util/keyword.ts +++ b/src/function/lsp/util/keyword.ts @@ -2,19 +2,12 @@ import * as vscode from 'vscode'; class Keywords { - keywords: Set; - keywordItems: vscode.CompletionItem[]; - compilerKeywords: string[]; // start with ` - systemKeywords: string[]; // start with $ + private keywords: Set; + private compilerKeywords: string[]; // start with ` + private systemKeywords: string[]; // start with $ constructor(keywords: string[], compilerKeywords: string[], systemKeywords: string[]) { this.keywords = new Set(keywords); const keywordItems = []; - for (const keyword of keywords) { - const clItem = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword); - clItem.detail = "keyword"; - keywordItems.push(clItem); - } - this.keywordItems = keywordItems; this.compilerKeywords = compilerKeywords; this.systemKeywords = systemKeywords; } @@ -23,6 +16,14 @@ class Keywords { return this.keywords; } + public compilerKeys(): string[] { + return this.compilerKeywords; + } + + public systemKeys(): string[] { + return this.systemKeywords; + } + public isKeyword(word: string): boolean { return this.keywords.has(word); } diff --git a/src/function/sim/instance.ts b/src/function/sim/instance.ts index 208caf0..b01044b 100644 --- a/src/function/sim/instance.ts +++ b/src/function/sim/instance.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { HdlLangID } from '../../global/enum'; import { hdlParam } from '../../hdlParser'; -import { HdlModulePort, HdlModuleParam } from '../../hdlParser/common'; +import { HdlModulePort, HdlModuleParam, HdlModulePortType } from '../../hdlParser/common'; import { HdlModule } from '../../hdlParser/core'; class ModuleInfoItem { @@ -28,23 +28,33 @@ class ModuleInfoItem { * @description verilog模式下生成整个例化的内容 * @param module 模块信息 */ -function instanceVlogCode(module: HdlModule) { - let vlogPortStr = vlogPort(module.ports); - let vlogParamStr = vlogParam(module.params); +function instanceVlogCode(module: HdlModule, prefix: string = '', returnSnippetString: boolean = false): string { + const instantiationConfig = vscode.workspace.getConfiguration('function.instantiation'); + const needComment = instantiationConfig.get('addComment', true); + const autoNetOutputDeclaration = instantiationConfig.get('autoNetOutputDeclaration', true); - let instContent = ''; - instContent += vlogPortStr.wireStr; - instContent += module.name + ' '; + const content = new vscode.SnippetString(); - if (vlogParamStr !== '') { - instContent += `#(\n${vlogParamStr})\n`; + // make net declaration if needed + if (autoNetOutputDeclaration) { + const netDeclarationString = makeNetOutputDeclaration(module.ports, prefix, needComment); + if (netDeclarationString) { + content.appendText(netDeclarationString); + } } - instContent += `u_${module.name}(\n`; - instContent += vlogPortStr.portStr; - instContent += ');\n'; + content.appendText(prefix + module.name + ' '); + if (returnSnippetString) { + content.appendPlaceholder('u_' + module.name); + } else { + content.appendText('u_' + module.name); + } - return instContent; + makeVlogParamAssignments(content, module.params, prefix, returnSnippetString, needComment); + makeVlogPortAssignments(content, module.ports, prefix, returnSnippetString, needComment); + + const instanceString = content.value; + return instanceString; } /** @@ -67,69 +77,95 @@ function instanceVhdlCode(module: HdlModule) { return instContent; } +function makeNetOutputDeclaration(ports: HdlModulePort[], prefix: string, needComment: boolean): string | null { + const maxWidthLength = Math.max(...ports.map(p => p.width.length)); + + let netOutputDeclaration = prefix + (needComment ? '// outports wire\n' : ''); + let haveOutput = false; + for (const port of ports) { + if (port.type === HdlModulePortType.Output) { + haveOutput = true; + let portWidth = port.width ? port.width : ''; + portWidth += ' '.repeat(maxWidthLength - portWidth.length + 1); + const netDeclaration = prefix + `wire ${portWidth}\t${port.name};\n`; + netOutputDeclaration += netDeclaration; + } + } + + if (!haveOutput) { + return null; + } else { + netOutputDeclaration += '\n'; + return netOutputDeclaration; + } +} + /** * @description verilog模式下对端口信息生成要例化的内容 * @param ports 端口信息列表 */ -function vlogPort(ports: HdlModulePort[]) : { wireStr: string, portStr: string} { - let nmax = getlmax(ports, 'name'); - let wmax = getlmax(ports, 'width'); - - let portStr = `\t// ports\n`; - let wireStr = '// outports wire\n'; - for (let i = 0; i < ports.length; i++) { - const port = ports[i]; - - if (port.type === 'output') { - let width = port.width; - let wpadding = wmax - width.length + 1; - width += ' '.repeat(wpadding); - // TODO: vhdl type - wireStr += `wire ${width}\t${port.name};\n`; - } - - let name = port.name; - let npadding = nmax - name.length + 1; - name += ' '.repeat(npadding); - portStr += `\t.${name}\t( ${name} )`; - if (i !== ports.length - 1) { - portStr += ','; - } - portStr += '\n'; +function makeVlogPortAssignments(content: vscode.SnippetString, ports: HdlModulePort[], prefix: string = '', returnSnippetString: boolean, needComment: boolean) { + if (ports.length === 0) { + content.appendText('();'); + return; } - - return { wireStr, portStr }; + const maxNameLength = Math.max(...ports.map(p => p.name.length)); + + content.appendText('(\n'); + + for (let i = 0; i < ports.length; ++ i) { + const port = ports[i]; + const paddingName = port.name + ' '.repeat(maxNameLength - port.name.length + 1); + + content.appendText(prefix + '\t.' + paddingName + '\t( '); + if (returnSnippetString) { + content.appendPlaceholder(port.name); + } else { + content.appendText(port.name); + } + content.appendText(' '.repeat(maxNameLength - port.name.length + 1) + ' )'); + if (i < ports.length - 1) { + content.appendText(','); + } + content.appendText('\n'); + } + + content.appendText(prefix + ');\n'); } + /** * @description verilog模式下对参数信息生成要例化的内容 * @param params 参数信息列表 */ -function vlogParam(params: HdlModuleParam[]): string { - let paramStr = ''; - let nmax = getlmax(params, 'name'); - let imax = getlmax(params, 'init'); - - // .NAME ( INIT ), - for (let i = 0; i < params.length; i++) { - let name = params[i].name; - let init = params[i].init; - - let namePadding = nmax - name.length + 1; - let initPadding = imax - init.length + 1; - - name +=' '.repeat(namePadding); - init +=' '.repeat(initPadding); - - paramStr += `\t.${name}\t( ${init} )`; - if (i !== (params.length - 1)) { - paramStr += ','; - paramStr += '\n'; - } +function makeVlogParamAssignments(content: vscode.SnippetString, params: HdlModuleParam[], prefix: string = '', returnSnippetString: boolean, needComment: boolean) { + if (params.length === 0) { + return; } - return paramStr; + const maxNameLength = Math.max(...params.map(p => p.name.length)); + const maxInitLength = Math.max(...params.map(p => p.init.length)); + + content.appendText('#(\n'); + + // .NAME ( INIT ), + for (let i = 0; i < params.length; ++ i) { + const param = params[i]; + const paddingName = param.name + ' '.repeat(maxNameLength - param.name.length + 1); + content.appendText(prefix + '\t.' + paddingName + '\t( '); + if (returnSnippetString) { + content.appendPlaceholder(param.init); + } else { + content.appendText(param.init); + } + content.appendText(' '.repeat(maxInitLength - param.init.length + 1) + ' )'); + if (i < params.length - 1) { + content.appendText(','); + } + content.appendText('\n'); + } + content.appendText(prefix + ')\n'); } /** @@ -249,7 +285,7 @@ async function selectModuleFromAll() { } } -function instanceByLangID(module: HdlModule): string { +function instanceByLangID(module: HdlModule): string { switch (module.languageId) { case HdlLangID.Verilog: return instanceVlogCode(module); case HdlLangID.Vhdl: return instanceVhdlCode(module); @@ -260,9 +296,11 @@ function instanceByLangID(module: HdlModule): string { } async function instantiation() { - const module = await selectModuleFromAll(); + const module = await selectModuleFromAll(); if (module) { - const code = instanceByLangID(module); + console.log(module); + + const code = instanceByLangID(module); const editor = vscode.window.activeTextEditor; if (editor) { selectInsert(code, editor); @@ -271,6 +309,7 @@ async function instantiation() { } export { + instanceVlogCode, instantiation, instanceByLangID, getSelectItem diff --git a/src/function/sim/testbench.ts b/src/function/sim/testbench.ts index 87e989c..ec10972 100644 --- a/src/function/sim/testbench.ts +++ b/src/function/sim/testbench.ts @@ -62,6 +62,8 @@ async function testbench() { if (!hdlFile.isHDLFile(path)) { return; } + console.log(path); + const currentHdlFile = hdlParam.getHdlFile(path); if (!currentHdlFile) { vscode.window.showErrorMessage('There is no hdlFile respect to ' + path); diff --git a/src/function/tool.ts b/src/function/tool.ts new file mode 100644 index 0000000..c34ce4b --- /dev/null +++ b/src/function/tool.ts @@ -0,0 +1,18 @@ +import * as vscode from 'vscode'; + +async function insertTextToUri(uri: vscode.Uri, text: string, position?: vscode.Position) { + if (!position) { + position = new vscode.Position(0, 0); + } + const editor = vscode.window.activeTextEditor; + if (editor) { + const edit = new vscode.WorkspaceEdit(); + edit.insert(uri, position, text); + vscode.workspace.applyEdit(edit); + } +} + + +export { + insertTextToUri +}; \ No newline at end of file diff --git a/src/function/treeView/index.ts b/src/function/treeView/index.ts index 42fbce7..3eef854 100644 --- a/src/function/treeView/index.ts +++ b/src/function/treeView/index.ts @@ -21,7 +21,7 @@ function openFileByUri(path: string, range: Range) { } } -function refreshArchTree(element: ModuleDataItem) { +function refreshArchTree(element?: ModuleDataItem) { // TODO : diff and optimize moduleTreeProvider.refresh(element); } diff --git a/src/global/opeParam.ts b/src/global/opeParam.ts index e57a06a..7273e11 100644 --- a/src/global/opeParam.ts +++ b/src/global/opeParam.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; -import { Arch, PrjInfo, RawPrjInfo, resolve, toSlash } from './prjInfo'; +import { Arch, PrjInfo, RawPrjInfo, resolve } from './prjInfo'; type AbsPath = string; type RelPath = string; @@ -70,14 +70,23 @@ class OpeParam { return this._prjInfo; } + /** + * path of property.json + */ public get propertyJsonPath(): AbsPath { return this._propertyJsonPath; } + /** + * path of property-schema.json + */ public get propertySchemaPath() : AbsPath { return this._propertySchemaPath; } + /** + * path of property-init.json + */ public get propertyInitPath() : AbsPath { return this._propertyInitPath; } @@ -156,19 +165,16 @@ class OpeParam { * get User's property.json */ public getUserPrjInfo(): PrjInfo { - const propertyJsonPath = this.propertyJsonPath; const userPrjInfo = new PrjInfo(); - if (fs.existsSync(propertyJsonPath)) { - const rawPrjInfo = readJSON(propertyJsonPath); - userPrjInfo.merge(rawPrjInfo); - } else { - // use default config instead - const rawPrjInfo = readJSON(this.propertyInitPath); - userPrjInfo.merge(rawPrjInfo); - } + const rawPrjInfo = this.getRawUserPrjInfo(); + userPrjInfo.merge(rawPrjInfo); return userPrjInfo; } + /** + * get content from property.json (disk IO) + * @returns + */ public getRawUserPrjInfo(): RawPrjInfo { const propertyJsonPath = this.propertyJsonPath; if (fs.existsSync(propertyJsonPath)) { diff --git a/src/global/prjInfo.ts b/src/global/prjInfo.ts index 0535ecd..2b41900 100644 --- a/src/global/prjInfo.ts +++ b/src/global/prjInfo.ts @@ -203,7 +203,7 @@ class PrjInfo implements PrjInfoMeta { */ public uniformisePath(path: AbsPath): AbsPath { const slashPath = toSlash(path); - const replacedPath = this.replacePathToken(path); + const replacedPath = this.replacePathToken(slashPath); return replacedPath; } @@ -240,7 +240,7 @@ class PrjInfo implements PrjInfoMeta { public updateToolChain(toolChain?: ToolChainType) { if (toolChain) { if (!validToolChainType(toolChain)) { - vscode.window.showErrorMessage('expect toolChain to be "xilinx"'); + vscode.window.showErrorMessage('expect toolChain to be "xilinx", "intel", "custom"'); return; } this._toolChain = toolChain; @@ -250,7 +250,7 @@ class PrjInfo implements PrjInfoMeta { public updatePathWisely(obj: Record, attr: T, path?: Path | Path[], - root?: AbsPath) { + root?: AbsPath) { if (path) { if (path instanceof Array) { const actualPaths = []; @@ -267,6 +267,8 @@ class PrjInfo implements PrjInfoMeta { obj[attr] = actualPath; } } + } else { + obj[attr] = ''; } } @@ -335,7 +337,7 @@ class PrjInfo implements PrjInfoMeta { private setDefaultValue(obj: Record, attr: T, defaultValue: K) { - const value: K = obj[attr]; + const value: K = obj[attr]; let isNull = !Boolean(value); if (typeof value === 'string') { isNull ||= value === 'none'; @@ -353,7 +355,6 @@ class PrjInfo implements PrjInfoMeta { public updateArch(arch?: Arch) { const workspacePath = this._workspacePath; - if (arch) { this.updatePathWisely(this.arch, 'prjPath', arch.prjPath); if (arch.hardware) { @@ -381,10 +382,10 @@ class PrjInfo implements PrjInfoMeta { this.arch.software.src = join(softwarePath, 'src'); this.arch.software.data = join(softwarePath, 'data'); } - - // if path is '', set as workspace + + // // if path is '', set as workspace this.setDefaultValue(this.arch.hardware, 'src', workspacePath); - this.setDefaultValue(this.arch.hardware, 'sim', workspacePath); + this.setDefaultValue(this.arch.hardware, 'sim', this.arch.hardware.src); this.setDefaultValue(this.arch.hardware, 'data', workspacePath); this.setDefaultValue(this.arch.software, 'src', workspacePath); this.setDefaultValue(this.arch.software, 'data', workspacePath); @@ -454,7 +455,7 @@ class PrjInfo implements PrjInfoMeta { * reserve the value that not covered in rawPrjInfo * @param rawPrjInfo */ - public merge(rawPrjInfo: RawPrjInfo) { + public merge(rawPrjInfo: RawPrjInfo) { this.updateToolChain(rawPrjInfo.toolChain); this.updatePrjName(rawPrjInfo.prjName); this.updateIP_REPO(rawPrjInfo.IP_REPO); @@ -491,6 +492,24 @@ class PrjInfo implements PrjInfoMeta { return libPath; } + public get hardwareSimPath(): AbsPath { + const simPath = this._arch.hardware.sim; + if (fspath.isAbsolute(simPath)) { + return simPath; + } + const workspace = this._workspacePath; + return hdlPath.join(workspace, simPath); + } + + public get hardwareSrcPath(): AbsPath { + const srcPath = this._arch.hardware.src; + if (fspath.isAbsolute(srcPath)) { + return srcPath; + } + const workspace = this._workspacePath; + return hdlPath.join(workspace, srcPath); + } + public json(): RawPrjInfo { return { toolChain: this._toolChain, @@ -505,9 +524,6 @@ class PrjInfo implements PrjInfoMeta { } }; - - - export { PrjInfo, PrjInfoDefaults, diff --git a/src/global/util.ts b/src/global/util.ts index 0316e85..283cfc9 100644 --- a/src/global/util.ts +++ b/src/global/util.ts @@ -16,7 +16,26 @@ class PathSet { } } +/** + * tell if two set are element-wise equal + * @param setA + * @param setB + */ +function isSameSet(setA: Set, setB: Set): boolean { + if (setA.size !== setB.size) { + return false; + } + + for (const el of setB) { + if (!setA.has(el)) { + return false; + } + } + return true; +} + export { - PathSet + PathSet, + isSameSet }; \ No newline at end of file diff --git a/src/hdlFs/file.ts b/src/hdlFs/file.ts index f350e90..df54414 100644 --- a/src/hdlFs/file.ts +++ b/src/hdlFs/file.ts @@ -65,11 +65,11 @@ function isSystemVerilogFile(path: AbsPath): boolean { return systemVerilogExts.includes(ext); } -function isHDLFile(path: AbsPath): boolean { +function isHDLFile(path: AbsPath): boolean { if (!isFile(path)) { return false; } - const ext = hdlPath.extname(path, false); + const ext = hdlPath.extname(path, false); return hdlExts.includes(ext); } @@ -96,13 +96,13 @@ function pickFileRecursive(path: AbsPath | AbsPath[] | Set, ignores?: A const hdlFiles = []; for (const file of fs.readdirSync(path)) { - const filePath = hdlPath.join(path, file); + const filePath = hdlPath.join(path, file); if (isDir(filePath)) { - const subHdlFiles = pickFileRecursive(filePath, ignores); + const subHdlFiles = pickFileRecursive(filePath, ignores, condition); if (subHdlFiles.length > 0) { hdlFiles.push(...subHdlFiles); } - } else if (!condition || condition(filePath)) { + } else if (!condition || condition(filePath)) { hdlFiles.push(filePath); } } @@ -178,9 +178,7 @@ function writeFile(path: AbsPath, content: string): boolean { } function readJSON(path: AbsPath): object { - try { - console.log(path); - + try { const context = fs.readFileSync(path, 'utf-8'); return JSON.parse(context); } catch (err) { diff --git a/src/hdlFs/path.ts b/src/hdlFs/path.ts index db46279..4e46163 100644 --- a/src/hdlFs/path.ts +++ b/src/hdlFs/path.ts @@ -26,6 +26,15 @@ function rel2abs(curPath: AbsPath, relPath: RelPath): AbsPath { return toSlash(absPath); } + +function relative(from: AbsPath, to: AbsPath): RelPath { + let rel = fspath.relative(from, to); + if (!rel.startsWith('.') && !rel.startsWith('./')) { + rel = './' + rel; + } + return toSlash(rel); +} + /** * cat paths with '/' * @param paths @@ -87,6 +96,7 @@ function exist(path: AbsPath | undefined): boolean { export { toSlash, rel2abs, + relative, join, resolve, filename, diff --git a/src/hdlParser/common.ts b/src/hdlParser/common.ts index e4eefdc..fa4385f 100644 --- a/src/hdlParser/common.ts +++ b/src/hdlParser/common.ts @@ -25,7 +25,7 @@ enum HdlModulePortType { Inout = 'inout', Output = 'output', Input = 'input', - Unknown = 'Unknown' + Unknown = 'unknown' }; enum HdlModuleParamType {LocalParam, Parameter, Unknown}; diff --git a/src/hdlParser/core.ts b/src/hdlParser/core.ts index e4046ec..9d9014d 100644 --- a/src/hdlParser/core.ts +++ b/src/hdlParser/core.ts @@ -11,7 +11,7 @@ class HdlParam { private readonly srcTopModules : Set = new Set(); private readonly simTopModules : Set = new Set(); private readonly pathToHdlFiles : Map = new Map(); - private readonly modules : Set = new Set(); + public readonly modules : Set = new Set(); private readonly unhandleInstances : Set = new Set(); public hasHdlFile(path: AbsPath): boolean { @@ -34,11 +34,34 @@ class HdlParam { return hdlFiles; } + /** + * used only in initialization stage + * @param hdlFile + */ public addHdlFile(hdlFile: HdlFile) { const path = hdlFile.path; this.pathToHdlFiles.set(path, hdlFile); } + /** + * add a file by path and create context + * @param path absolute path of the file to be added + */ + public async addHdlPath(path: AbsPath) { + path = hdlPath.toSlash(path); + await this.initHdlFiles([path]); + const hdlFile = this.getHdlFile(path); + if (!hdlFile) { + MainOutput.report('error happen when we attempt to add file by path: ' + path, ReportType.Error); + } else { + hdlFile.makeInstance(); + // when a new file is added, retry the solution of dependency + for (const hdlModule of hdlFile.getAllHdlModules()) { + hdlModule.solveUnhandleInstance(); + } + } + } + public hasHdlModule(path: AbsPath | undefined, name: string): boolean { if (!path) { return false; @@ -184,12 +207,28 @@ class HdlParam { this.unhandleInstances.delete(inst); } + /** + * vlog -> HdlLangID.Verilog + * svlog -> HdlLangID.SystemVerilog + * vhdl -> HdlLangID.Vhdl + * @param langID + */ + private alignLanguageId(langID: string) : HdlLangID { + switch (langID) { + case 'vhdl': return HdlLangID.Vhdl; + case 'vlog': return HdlLangID.Verilog; + case 'svlog': return HdlLangID.SystemVerilog; + default: return HdlLangID.Unknown; + } + } + private async doHdlFast(path: AbsPath) { try { - const fast = await HdlSymbol.fast(path); + const fast = await HdlSymbol.fast(path); if (fast) { + const languageId = this.alignLanguageId(fast.languageId); new HdlFile(path, - fast.languageId, + languageId, fast.macro, fast.content); } @@ -254,6 +293,18 @@ class HdlParam { } return moduleFiles; } + + + public deleteHdlFile(path: AbsPath) { + path = hdlPath.toSlash(path); + const moduleFile = this.getHdlFile(path); + if (moduleFile) { + for (const name of moduleFile.getAllModuleNames()) { + moduleFile.deleteHdlModule(name); + } + this.pathToHdlFiles.delete(path); + } + } }; const hdlParam = new HdlParam(); @@ -324,6 +375,13 @@ class HdlInstance { } return false; } + + public update(newInstance: common.RawHdlInstance) { + this.type = newInstance.type; + this.range = newInstance.range; + this.instparams = newInstance.instparams; + this.instports = newInstance.instports; + } }; class HdlModule { @@ -348,22 +406,10 @@ class HdlModule { this.file = file; this.name = name; this.range = range; - this.params = params; - this.ports = ports; + this.params = params ? params : []; + this.ports = ports ? ports : []; - if (!this.params) { - this.params = []; - } - - if (!this.ports) { - this.ports = []; - } - - // make instance this.rawInstances = instances; - if (!this.rawInstances) { - this.rawInstances = []; - } this.nameToInstances = new Map(); // add in hdlParam data structure @@ -431,14 +477,13 @@ class HdlModule { return hdlInstance; } - public makeNameToInstances() { - - if (this.rawInstances) { + public makeNameToInstances() { + if (this.rawInstances !== undefined) { this.nameToInstances.clear(); for (const inst of this.rawInstances) { this.createHdlInstance(inst); } - this.rawInstances = undefined; + // this.rawInstances = undefined; } else { MainOutput.report('call makeNameToInstances but this.rawInstances is undefined', ReportType.Warn); @@ -450,7 +495,7 @@ class HdlModule { this.deleteInstance(inst); } - public deleteInstance(inst: HdlInstance | undefined) { + public deleteInstance(inst?: HdlInstance) { if (inst) { this.deleteUnhandleInstance(inst); hdlParam.deleteUnhandleInstance(inst); @@ -585,13 +630,40 @@ class HdlModule { inst.locateHdlModule(); } } + + public update(newModule: common.RawHdlModule) { + this.ports = newModule.ports; + this.params = newModule.params; + this.range = newModule.range; + // compare and make change to instance + const uncheckedInstanceNames = new Set(); + for (const inst of this.getAllInstances()) { + uncheckedInstanceNames.add(inst.name); + } + + for (const newInst of newModule.instances) { + if (uncheckedInstanceNames.has(newInst.name)) { + // match exist instance, compare and update + const originalInstance = this.getInstance(newInst.name); + originalInstance?.update(newInst); + uncheckedInstanceNames.delete(newInst.name); + } else { + // unknown instance, create it + this.createHdlInstance(newInst); + } + } + // delete Instance that not visited + for (const instName of uncheckedInstanceNames) { + this.deleteInstanceByName(instName); + } + } }; class HdlFile { - path: string; - languageId: HdlLangID; - type: common.HdlFileType; - macro: common.Macro; + public path: string; + public languageId: HdlLangID; + public type: common.HdlFileType; + public macro: common.Macro; private readonly nameToModule: Map; constructor(path: string, @@ -652,7 +724,27 @@ class HdlFile { public deleteHdlModule(name: string) { const hdlModule = this.getHdlModule(name); if (hdlModule) { + // delete child reference in the module which use this + for (const childInst of hdlModule.getAllGlobalRefers()) { + const userModule = childInst.parentMod; + childInst.module = undefined; + childInst.instModPath = undefined; + childInst.instModPathStatus = common.InstModPathStatus.Unknown; + hdlParam.addUnhandleInstance(childInst); + userModule.addUnhandleInstance(childInst); + } + + // delete all the instance in the module + for (const inst of hdlModule.getAllInstances()) { + hdlModule.deleteInstance(inst); + } + + // delete any variables containing module + hdlParam.deleteTopModule(hdlModule); + hdlParam.deleteTopModuleToSource(hdlModule); + hdlParam.modules.delete(hdlModule); + this.nameToModule.delete(hdlModule.name); } } @@ -661,6 +753,10 @@ class HdlFile { module.makeNameToInstances(); } } + + public updateMacro(macro: common.Macro) { + this.macro = macro; + } } diff --git a/src/manager/prj.ts b/src/manager/prj.ts index 9f7d66a..3bdb4e8 100644 --- a/src/manager/prj.ts +++ b/src/manager/prj.ts @@ -87,6 +87,10 @@ class PrjManage { return []; } + /** + * get all the hdl files that to be parsed in the project + * @returns + */ public getPrjHardwareFiles(): AbsPath[] { const searchPathSet = new PathSet(); const prjInfo = opeParam.prjInfo; @@ -97,24 +101,32 @@ class PrjManage { MainOutput.report(`libManage finish process, add ${fileChange.add.length} files, del ${fileChange.del.length} files`, ReportType.Info); // add possible folder to search - searchPathSet.checkAdd(hardwareInfo.src); + searchPathSet.checkAdd(prjInfo.hardwareSrcPath); + searchPathSet.checkAdd(prjInfo.hardwareSimPath); searchPathSet.checkAdd(hardwareInfo.sim); searchPathSet.checkAdd(prjInfo.getLibraryCommonPaths()); searchPathSet.checkAdd(prjInfo.getLibraryCustomPaths()); + + + MainOutput.report(' search folders: ', ReportType.Debug); + searchPathSet.files.forEach(p => MainOutput.report(p, ReportType.Debug)); // TODO : make something like .gitignore const ignores = this.getIgnoreFiles(); // do search const searchPaths = searchPathSet.files; - return hdlFile.getHDLFiles(searchPaths, ignores); + + const hdlFiles = hdlFile.getHDLFiles(searchPaths, ignores); + return hdlFiles; } + + public async initialise(context: vscode.ExtensionContext, countTimeCost: boolean = true) { if (countTimeCost) { console.time('launch'); - } - + } await this.initOpeParam(context); MainOutput.report('finish initialise opeParam', ReportType.Info); diff --git a/src/monitor/event.ts b/src/monitor/event.ts new file mode 100644 index 0000000..3b9bc02 --- /dev/null +++ b/src/monitor/event.ts @@ -0,0 +1,238 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import assert = require('assert'); +import * as chokidar from 'chokidar'; +import * as vscode from 'vscode'; +import { refreshArchTree } from '../function/treeView'; + +import { AbsPath, MainOutput, opeParam, RelPath, ReportType } from '../global'; +import { isSameSet } from '../global/util'; +import { hdlFile, hdlPath } from '../hdlFs'; +import { hdlParam, HdlSymbol } from '../hdlParser'; +import { prjManage } from '../manager'; + +import type { HdlMonitor } from './index'; + +enum Event { + Add = 'add', // emit when add file + AddDir = 'addDir', // emit when add folder + Unlink = 'unlink', // emit when delete file + UnlinkDir = 'unlinkDir', // emit when delete folder + Change = 'change', // emit when file changed + All = 'all', // all the change above + Ready = 'ready', + Raw = 'raw', + Error = 'error' +}; + + +abstract class BaseAction { + public listenChange(m: HdlMonitor) { + const fSWatcher = this.selectFSWatcher(m); + if (!fSWatcher) { + MainOutput.report("FSWatcher hasn't been made!", ReportType.Error); + return; + } + fSWatcher.on(Event.Change, path => this.change(path, m)); + } + + public listenAdd(m: HdlMonitor) { + const fSWatcher = this.selectFSWatcher(m); + if (!fSWatcher) { + MainOutput.report("FSWatcher hasn't been made!", ReportType.Error); + return; + } + fSWatcher.on(Event.Add, path => this.add(path, m)); + } + + public listenUnlink(m: HdlMonitor) { + const fSWatcher = this.selectFSWatcher(m); + if (!fSWatcher) { + MainOutput.report("FSWatcher hasn't been made!", ReportType.Error); + return; + } + fSWatcher.on(Event.Unlink, path => this.unlink(path, m)); + } + + abstract selectFSWatcher(m: HdlMonitor): chokidar.FSWatcher | undefined; + abstract change(path: AbsPath, m: HdlMonitor): Promise; + abstract add(path: AbsPath, m: HdlMonitor): Promise; + abstract unlink(path: AbsPath, m: HdlMonitor): Promise; +} + +class HdlAction extends BaseAction { + selectFSWatcher(m: HdlMonitor): chokidar.FSWatcher | undefined { + return m.hdlMonitor; + } + + async add(path: string, m: HdlMonitor): Promise { + console.log('HdlAction add'); + + path = hdlPath.toSlash(path); + // create corresponding moduleFile + hdlParam.initHdlFiles([path]); + + const moduleFile = hdlParam.getHdlFile(path); + if (!moduleFile) { + console.log('error happen when create moduleFile', path); + } else { + moduleFile.makeInstance(); + for (const module of moduleFile.getAllHdlModules()) { + module.solveUnhandleInstance(); + } + } + refreshArchTree(); + } + + async unlink(path: string, m: HdlMonitor): Promise { + console.log('HdlAction unlink'); + + path = hdlPath.toSlash(path); + hdlParam.deleteHdlFile(path); + refreshArchTree(); + } + + async change(path: string, m: HdlMonitor): Promise { + console.log('HdlAction change'); + + path = hdlPath.toSlash(path); + const moduleFile = hdlParam.getHdlFile(path); + + if (!moduleFile) { + return; + } + + const fast = await HdlSymbol.fast(path); + if (!fast) { + vscode.window.showErrorMessage('error happen when parse ' + path + '\nFail to update'); + return; + } + + // 1. update marco directly + moduleFile.updateMacro(fast.macro); + + // 2. update modules one by one + const uncheckedModuleNames = new Set(); + for (const name of moduleFile.getAllModuleNames()) { + uncheckedModuleNames.add(name); + } + + for (const rawHdlModule of fast.content) { + const moduleName = rawHdlModule.name; + if (uncheckedModuleNames.has(moduleName)) { + // match the same module, check then + const originalModule = moduleFile.getHdlModule(moduleName); + uncheckedModuleNames.delete(moduleName); + originalModule?.update(rawHdlModule); + } else { + // no matched, create it + const newModule = moduleFile.createHdlModule(rawHdlModule); + newModule.makeNameToInstances(); + newModule.solveUnhandleInstance(); + } + } + + // 3. delete module not visited yet + for (const moduleName of uncheckedModuleNames) { + moduleFile.deleteHdlModule(moduleName); + } + + refreshArchTree(); + } +} + + +class PpyAction extends BaseAction { + selectFSWatcher(m: HdlMonitor): chokidar.FSWatcher | undefined { + return m.ppyMonitor; + } + + async add(path: string, m: HdlMonitor): Promise { + console.log('PpyAction add'); + assert.equal(hdlPath.toSlash(path), opeParam.propertyJsonPath); + this.updateProperty(m); + } + + async unlink(path: string, m: HdlMonitor): Promise { + console.log('PpyAction unlink'); + assert.equal(hdlPath.toSlash(path), opeParam.propertyJsonPath); + this.updateProperty(m); + } + + async change(path: string, m: HdlMonitor): Promise { + console.log('PpyAction change'); + assert.equal(hdlPath.toSlash(path), opeParam.propertyJsonPath); + this.updateProperty(m); + } + + // get path set from opeParam that used to tell if need to remake HdlMonitor + private getImportantPathSet(): Set { + const pathSet = new Set(); + pathSet.add(opeParam.prjInfo.arch.hardware.sim); + pathSet.add(opeParam.prjInfo.arch.hardware.src); + pathSet.add(opeParam.prjInfo.libCommonPath); + pathSet.add(opeParam.prjInfo.libCustomPath); + return pathSet; + } + + public async updateProperty(m: HdlMonitor) { + const originalPathSet = this.getImportantPathSet(); + const originalHdlFiles = prjManage.getPrjHardwareFiles(); + + const rawPrjInfo = opeParam.getRawUserPrjInfo(); + opeParam.mergePrjInfo(rawPrjInfo); + + const currentPathSet = this.getImportantPathSet(); + if (isSameSet(originalPathSet, currentPathSet)) { + return; + } + const options: vscode.ProgressOptions = { location: vscode.ProgressLocation.Notification, title: 'modify the project' }; + vscode.window.withProgress(options, async () => await this.refreshHdlMonitor(m, originalHdlFiles)); + } + + public async refreshHdlMonitor(m: HdlMonitor, originalHdlFiles: AbsPath[]) { + m.remakeHdlMonitor(); + + // update pl + const currentHdlFiles = prjManage.getPrjHardwareFiles(); + await this.updatePL(originalHdlFiles, currentHdlFiles); + + refreshArchTree(); + } + + public async updatePL(oldFiles: AbsPath[], newFiles: AbsPath[]) { + if (prjManage.pl) { + const uncheckHdlFileSet = new Set(oldFiles); + const addFiles: AbsPath[] = []; + const delFiles: AbsPath[] = []; + + for (const path of newFiles) { + if (!uncheckHdlFileSet.has(path)) { + await hdlParam.addHdlPath(path); + addFiles.push(path); + } else { + uncheckHdlFileSet.delete(path); + } + } + const vivadoAddPromise = prjManage.pl.addFiles(addFiles); + + for (const path of uncheckHdlFileSet) { + hdlParam.deleteHdlFile(path); + delFiles.push(path); + } + const vivadoDelPromise = prjManage.pl.delFiles(delFiles); + + await vivadoAddPromise; + await vivadoDelPromise; + } else { + MainOutput.report('PL is not registered', ReportType.Warn); + } + } +} + +const hdlAction = new HdlAction(); +const ppyAction = new PpyAction(); + +export { + hdlAction, + ppyAction +}; \ No newline at end of file diff --git a/src/monitor/index.ts b/src/monitor/index.ts new file mode 100644 index 0000000..f555464 --- /dev/null +++ b/src/monitor/index.ts @@ -0,0 +1,103 @@ +import * as chokidar from 'chokidar'; +import { MainOutput, opeParam, ReportType } from '../global'; +import { hdlExts } from '../global/lang'; +import { PathSet } from '../global/util'; +import { hdlPath } from '../hdlFs'; + +import * as Event from './event'; + +class HdlMonitor{ + private monitorConfig: chokidar.WatchOptions; + public hdlMonitor?: chokidar.FSWatcher; + public ppyMonitor?: chokidar.FSWatcher; + + constructor() { + // public config for monitor + this.monitorConfig = { + persistent: true, + usePolling: false, + ignoreInitial: true, + }; + } + + public makeMonitor(paths: string | string[], config?: chokidar.WatchOptions): chokidar.FSWatcher { + if (!config) { + config = this.monitorConfig; + } + return chokidar.watch(paths, config); + } + + /** + * @description get monitor for property.json + */ + public getPpyMonitor() { + const watcherPath = opeParam.propertyJsonPath; + return this.makeMonitor(watcherPath); + } + + /** + * @description get monitor for HDLParam update + */ + public getHdlMonitor() { + const hdlExtsGlob = `**/*.{${hdlExts.join(',')}}`; + const prjInfo = opeParam.prjInfo; + + const monitorPathSet = new PathSet(); + monitorPathSet.checkAdd(prjInfo.hardwareSimPath); + monitorPathSet.checkAdd(prjInfo.hardwareSrcPath); + monitorPathSet.checkAdd(prjInfo.libCommonPath); + monitorPathSet.checkAdd(prjInfo.libCustomPath); + + const monitorFoldersWithGlob = []; + for (const folder of monitorPathSet.files) { + const globPath = hdlPath.join(folder, hdlExtsGlob); + monitorFoldersWithGlob.push(globPath); + } + MainOutput.report('Following folders are tracked: '); + monitorPathSet.files.forEach(p => MainOutput.report(p)); + + return this.makeMonitor(monitorFoldersWithGlob); + } + + public close() { + this.hdlMonitor?.close(); + this.ppyMonitor?.close(); + } + + public start() { + // make monitor + this.hdlMonitor = this.getHdlMonitor(); + this.ppyMonitor = this.getPpyMonitor(); + + this.registerHdlMonitorListener(); + this.registerPpyMonitorListener(); + } + + public remakeHdlMonitor() { + if (this.hdlMonitor) { + this.hdlMonitor.close(); + this.hdlMonitor = this.getHdlMonitor(); + this.registerHdlMonitorListener(); + } + } + + public registerHdlMonitorListener() { + Event.hdlAction.listenAdd(this); + Event.hdlAction.listenChange(this); + Event.hdlAction.listenUnlink(this); + } + + public registerPpyMonitorListener() { + Event.ppyAction.listenAdd(this); + Event.ppyAction.listenChange(this); + Event.ppyAction.listenUnlink(this); + } +}; + +const hdlMonitor = new HdlMonitor(); + +export { + hdlMonitor, +}; + +export type { HdlMonitor }; \ No newline at end of file diff --git a/src/test/monitor/.vscode/property.json b/src/test/monitor/.vscode/property.json new file mode 100644 index 0000000..da1fccd --- /dev/null +++ b/src/test/monitor/.vscode/property.json @@ -0,0 +1,17 @@ +{ + "toolChain": "xilinx", + "prjName": { + "PL": "template" + }, + "soc": { + "core": "none" + }, + "enableShowLog": false, + "device": "none", + "arch": { + "hardware": { + "src": "./src1", + "sim": "./sim1" + } + } +} \ No newline at end of file diff --git a/src/test/monitor/sim1/testbench.v b/src/test/monitor/sim1/testbench.v new file mode 100644 index 0000000..ebc7f5f --- /dev/null +++ b/src/test/monitor/sim1/testbench.v @@ -0,0 +1,50 @@ +module testbench(); + +parameter DATA_WIDTH = 32; +parameter ADDR_WIDTH = 32; +parameter MAIN_FRE = 100; //unit MHz +reg sys_clk = 0; +reg sys_rst = 1; +reg [DATA_WIDTH-1:0] data = 0; +reg [ADDR_WIDTH-1:0] addr = 0; + +always begin + #(500/MAIN_FRE) sys_clk = ~sys_clk; +end + +always begin + #50 sys_rst = 0; +end + +always @(posedge sys_clk) begin + if (sys_rst) + addr = 0; + else + addr = addr + 1; +end +always @(posedge sys_clk) begin + if (sys_rst) + data = 0; + else + data = data + 1; +end + +//Instance +// outports wire +wire [8:0] c; + +SimpleAdd_1 u_SimpleAdd_1( + .a ( a ), + .b ( b ), + .c ( c ) +); + + + +initial begin + $dumpfile("wave.vcd"); + $dumpvars(0, testbench); + #50000 $finish; +end + +endmodule //TOP diff --git a/src/test/monitor/src1/add.v b/src/test/monitor/src1/add.v new file mode 100644 index 0000000..f544fb7 --- /dev/null +++ b/src/test/monitor/src1/add.v @@ -0,0 +1,11 @@ +module SimpleAdd_1( + input [8:0] a, b, + output [8:0] c +); + + assign c = a + b; + + +endmodule //SimpleAdd + + diff --git a/src/test/monitor/src2/add.v b/src/test/monitor/src2/add.v new file mode 100644 index 0000000..4f5d1e2 --- /dev/null +++ b/src/test/monitor/src2/add.v @@ -0,0 +1,9 @@ +module SimpleAdd_2( + input [8:0] a, b, + output [8:0] c +); + + assign c = a + b; + + +endmodule //SimpleAdd