Skip to content

Commit

Permalink
Display variable value in tooltip on hover.
Browse files Browse the repository at this point in the history
This requires a functioning language server, e.g. `verible-verilog-ls`.
  • Loading branch information
whitequark committed Oct 26, 2024
1 parent ff0d5bc commit 2d1729e
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 24 deletions.
43 changes: 28 additions & 15 deletions example/design.v
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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
6 changes: 2 additions & 4 deletions src/debug/observer.ts
Original file line number Diff line number Diff line change
@@ -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<T> {
Expand Down Expand Up @@ -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));
Expand Down
46 changes: 41 additions & 5 deletions src/debug/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(thunk: () => Thenable<T>): Thenable<T> {
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';
Expand All @@ -35,10 +48,6 @@ export class Session {
this.connection.dispose();
}

get connection2(): Connection {
return this.connection;
}

// ======================================== Inspecting the design

private itemCache: Map<string, proto.ItemDescriptionMap> = new Map();
Expand Down Expand Up @@ -145,6 +154,27 @@ export class Session {
}
}

async getVariablesForLocation(filename: string, position: vscode.Position): Promise<Variable[]> {
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<string, number> = new Map();
Expand Down Expand Up @@ -184,8 +214,8 @@ export class Session {
}

async queryInterval(
interval: TimeInterval,
reference: Reference,
interval: TimeInterval,
options: { collapse?: boolean } = {}
): Promise<Sample[]> {
this.checkReferenceEpoch(reference.name, reference.epoch);
Expand Down Expand Up @@ -213,6 +243,12 @@ export class Session {
});
}

async queryAtCursor(reference: Reference): Promise<Sample> {
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;
Expand Down
1 change: 1 addition & 0 deletions src/debugger.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
7 changes: 7 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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)));
Expand Down
4 changes: 4 additions & 0 deletions src/model/styling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
63 changes: 63 additions & 0 deletions src/ui/hover.ts
Original file line number Diff line number Diff line change
@@ -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<vscode.Hover | null> {
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<vscode.Hover | null> {
const session = this.rtlDebugger.session;
if (session !== null) {
const definitions = await (
vscode.commands.executeCommand('vscode.executeDefinitionProvider', document.uri, position) as
vscode.ProviderResult<vscode.Definition | vscode.LocationLink[]>
);
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;
}
}

0 comments on commit 2d1729e

Please sign in to comment.