diff --git a/example/design.v b/example/design.v index 3ccc702..5fc3b9c 100644 --- a/example/design.v +++ b/example/design.v @@ -1,22 +1,26 @@ -module counter(...); +module counter( + input clk, + output reg [31:0] cnt = 0 +); + + parameter integer LIMIT = 0; - input clk; - output reg [7:0] cnt = 0; always @(posedge clk) - if (cnt < 13) - cnt <= cnt + 1; - else + if (cnt == LIMIT) cnt <= 0; + else + cnt <= cnt + 1; endmodule (* top *) -module top(...); +module top( + input clk, + output [7:0] data, + output [31:0] timer +); - input clk; - output [7:0] data; - - reg [7:0] message [0:13]; + reg [7:0] message [14]; initial begin message[0] = "h"; message[1] = "e"; @@ -33,12 +37,21 @@ module top(...); message[12] = "\n"; end - wire [7:0] index; - counter counter_inst( + wire [7:0] message_index; + counter #( + .LIMIT(13) + ) counter_message( .clk(clk), - .cnt(index) + .cnt(message_index) ); - assign data = message[index]; + assign data = message[message_index]; + + counter #( + .LIMIT(32'hffffffff) + ) counter_timer( + .clk(clk), + .cnt(timer), + ); endmodule diff --git a/src/debug/observer.ts b/src/debug/observer.ts index 494073f..6e6df0c 100644 --- a/src/debug/observer.ts +++ b/src/debug/observer.ts @@ -1,7 +1,6 @@ import * as vscode from 'vscode'; import { UnboundReference, Designation, Reference } from '../model/sample'; -import { TimeInterval } from '../model/time'; import { Session } from './session'; class Observable { @@ -87,9 +86,8 @@ export class Observer { } this.reference = this.session.bindReference(this.referenceName, unboundReference); } - const interval = new TimeInterval(this.session.timeCursor, this.session.timeCursor); - const reference = this.reference; // could get invalidated in the meantime - const [sample] = await this.session.queryInterval(interval, reference); + const reference = this.reference; // could get invalidated during `await` below + const sample = await this.session.queryAtCursor(reference); for (const [designation, handle] of reference.allHandles()) { const observable = this.observables.get(designation.canonicalKey)!; observable.update(sample.extract(handle)); diff --git a/src/debug/session.ts b/src/debug/session.ts index ca14949..47e578e 100644 --- a/src/debug/session.ts +++ b/src/debug/session.ts @@ -7,10 +7,23 @@ import { TimeInterval, TimePoint } from '../model/time'; import { Reference, Sample, UnboundReference } from '../model/sample'; import { Variable } from '../model/variable'; import { Scope } from '../model/scope'; +import { Location } from '../model/source'; function lazy(thunk: () => Thenable): Thenable { return { then: (onfulfilled, onrejected) => thunk().then(onfulfilled, onrejected) }; } +function matchLocation(location: Location, filename: string, position: vscode.Position) { + if (location.file !== filename) { + return false; + } + if (location.startLine !== position.line) { + return false; + } + if (location.startColumn !== undefined && location.startColumn !== position.character) { + return false; + } + return true; +} export interface ISimulationStatus { status: 'running' | 'paused' | 'finished'; @@ -35,10 +48,6 @@ export class Session { this.connection.dispose(); } - get connection2(): Connection { - return this.connection; - } - // ======================================== Inspecting the design private itemCache: Map = new Map(); @@ -145,6 +154,27 @@ export class Session { } } + async getVariablesForLocation(filename: string, position: vscode.Position): Promise { + const variables: Variable[] = []; + const extractVariablesForLocationFromScope = async (scope: string) => { + const items = await this.listItemsInScope(scope); + for (const [itemName, itemDesc] of Object.entries(items)) { + const itemLocation = Location.fromCXXRTL(itemDesc.src); + console.log(itemLocation, filename, position, itemLocation !== null && matchLocation(itemLocation, filename, position)); + if (itemLocation !== null && matchLocation(itemLocation, filename, position)) { + variables.push(Variable.fromCXXRTL(itemName, itemDesc)); + } + } + const subScopes = await this.listScopesInScope(scope); + for (const subScopeName of Object.keys(subScopes)) { + await extractVariablesForLocationFromScope(subScopeName); + } + return null; + }; + await extractVariablesForLocationFromScope(''); + return variables; + } + // ======================================== Querying the database private referenceEpochs: Map = new Map(); @@ -184,8 +214,8 @@ export class Session { } async queryInterval( - interval: TimeInterval, reference: Reference, + interval: TimeInterval, options: { collapse?: boolean } = {} ): Promise { this.checkReferenceEpoch(reference.name, reference.epoch); @@ -213,6 +243,12 @@ export class Session { }); } + async queryAtCursor(reference: Reference): Promise { + const interval = new TimeInterval(this.timeCursor, this.timeCursor); + const [sample] = await this.queryInterval(reference, interval); + return sample; + } + // ======================================== Manipulating the simulation private simulationStatusTimeout: NodeJS.Timeout | null = null; diff --git a/src/debugger.ts b/src/debugger.ts index b7134b9..3394ca5 100644 --- a/src/debugger.ts +++ b/src/debugger.ts @@ -1,5 +1,6 @@ import * as net from 'net'; import * as vscode from 'vscode'; + import { NodeStreamLink } from './cxxrtl/link'; import { StatusBarItem } from './ui/status'; import { Session } from './debug/session'; diff --git a/src/extension.ts b/src/extension.ts index 9ea8fb6..eba1334 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,8 +1,10 @@ import * as vscode from 'vscode'; + import { CXXRTLDebugger } from './debugger'; import * as sidebar from './ui/sidebar'; import { globalWatchList } from './debug/watch'; import { globalVariableOptions } from './debug/options'; +import { HoverProvider } from './ui/hover'; import { inputTime } from './ui/input'; export function activate(context: vscode.ExtensionContext) { @@ -14,6 +16,11 @@ export function activate(context: vscode.ExtensionContext) { }); context.subscriptions.push(sidebarTreeView); + const hoverProvider = new HoverProvider(rtlDebugger); + for (const language of HoverProvider.SUPPORTED_LANGUAGES) { + context.subscriptions.push(vscode.languages.registerHoverProvider(language, hoverProvider)); + } + vscode.commands.executeCommand('setContext', 'rtlDebugger.sessionStatus', rtlDebugger.sessionStatus); context.subscriptions.push(rtlDebugger.onDidChangeSessionStatus((state) => vscode.commands.executeCommand('setContext', 'rtlDebugger.sessionStatus', state))); diff --git a/src/model/styling.ts b/src/model/styling.ts index cdfff16..282f0c6 100644 --- a/src/model/styling.ts +++ b/src/model/styling.ts @@ -9,6 +9,10 @@ export enum DisplayStyle { VHDL = 'VHDL', } +export function languageForDisplayStyle(style: DisplayStyle): string { + return style as string; +} + export function variableDescription(style: DisplayStyle, variable: Variable, { scalar = false } = {}): string { let result = ''; if (variable instanceof ScalarVariable && variable.lsbAt === 0 && variable.width === 1) { diff --git a/src/ui/hover.ts b/src/ui/hover.ts new file mode 100644 index 0000000..889ac19 --- /dev/null +++ b/src/ui/hover.ts @@ -0,0 +1,63 @@ +import * as vscode from 'vscode'; + +import { CXXRTLDebugger } from '../debugger'; +import { UnboundReference } from '../model/sample'; +import { ScalarVariable, Variable } from '../model/variable'; +import { DisplayStyle, languageForDisplayStyle, variableDescription, variableValue } from '../model/styling'; +import { Session } from '../debug/session'; + +export class HoverProvider implements vscode.HoverProvider { + static readonly SUPPORTED_LANGUAGES: string[] = ['verilog']; + + constructor( + private rtlDebugger: CXXRTLDebugger + ) {} + + private async hoverForVariables(session: Session, variables: Variable[]): Promise { + if (variables.length === 0) { + return null; + } + const displayStyle = vscode.workspace.getConfiguration('rtlDebugger').get('displayStyle') as DisplayStyle; + const hoverText = new vscode.MarkdownString(); + const unboundReference = new UnboundReference(); + for (const variable of variables) { + if (variable instanceof ScalarVariable) { + unboundReference.add(variable.designation()); + } + } + const reference = session.bindReference('hover', unboundReference); + const sample = await session.queryAtCursor(reference); + for (const [designation, handle] of reference.allHandles()) { + const variable = designation.variable; + const descriptionText = variableDescription(displayStyle, variable); + const valueText = variableValue(displayStyle, variable, sample.extract(handle)); + hoverText.appendCodeblock( + `${variable.fullName.join('.')}${descriptionText} = ${valueText}`, + languageForDisplayStyle(displayStyle) + ); + } + return new vscode.Hover(hoverText); + } + + async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { + const session = this.rtlDebugger.session; + if (session !== null) { + const definitions = await ( + vscode.commands.executeCommand('vscode.executeDefinitionProvider', document.uri, position) as + vscode.ProviderResult + ); + let definition: vscode.Location | undefined; + if (definitions instanceof vscode.Location) { + definition = definitions; + } else if (definitions instanceof Array && definitions.length === 1 && definitions[0] instanceof vscode.Location) { + definition = definitions[0]; + } else { + console.warn('vscode.executeDefinitionProvider did not return a single Location: ', definition); + return null; + } + const variables = await session.getVariablesForLocation(definition.uri.fsPath, definition.range.start); + return await this.hoverForVariables(session, variables); + } + return null; + } +}