Skip to content

Commit 2d1729e

Browse files
committed
Display variable value in tooltip on hover.
This requires a functioning language server, e.g. `verible-verilog-ls`.
1 parent ff0d5bc commit 2d1729e

File tree

7 files changed

+146
-24
lines changed

7 files changed

+146
-24
lines changed

example/design.v

+28-15
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
1-
module counter(...);
1+
module counter(
2+
input clk,
3+
output reg [31:0] cnt = 0
4+
);
5+
6+
parameter integer LIMIT = 0;
27

3-
input clk;
4-
output reg [7:0] cnt = 0;
58
always @(posedge clk)
6-
if (cnt < 13)
7-
cnt <= cnt + 1;
8-
else
9+
if (cnt == LIMIT)
910
cnt <= 0;
11+
else
12+
cnt <= cnt + 1;
1013

1114
endmodule
1215

1316
(* top *)
14-
module top(...);
17+
module top(
18+
input clk,
19+
output [7:0] data,
20+
output [31:0] timer
21+
);
1522

16-
input clk;
17-
output [7:0] data;
18-
19-
reg [7:0] message [0:13];
23+
reg [7:0] message [14];
2024
initial begin
2125
message[0] = "h";
2226
message[1] = "e";
@@ -33,12 +37,21 @@ module top(...);
3337
message[12] = "\n";
3438
end
3539

36-
wire [7:0] index;
37-
counter counter_inst(
40+
wire [7:0] message_index;
41+
counter #(
42+
.LIMIT(13)
43+
) counter_message(
3844
.clk(clk),
39-
.cnt(index)
45+
.cnt(message_index)
4046
);
4147

42-
assign data = message[index];
48+
assign data = message[message_index];
49+
50+
counter #(
51+
.LIMIT(32'hffffffff)
52+
) counter_timer(
53+
.clk(clk),
54+
.cnt(timer),
55+
);
4356

4457
endmodule

src/debug/observer.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as vscode from 'vscode';
22

33
import { UnboundReference, Designation, Reference } from '../model/sample';
4-
import { TimeInterval } from '../model/time';
54
import { Session } from './session';
65

76
class Observable<T> {
@@ -87,9 +86,8 @@ export class Observer {
8786
}
8887
this.reference = this.session.bindReference(this.referenceName, unboundReference);
8988
}
90-
const interval = new TimeInterval(this.session.timeCursor, this.session.timeCursor);
91-
const reference = this.reference; // could get invalidated in the meantime
92-
const [sample] = await this.session.queryInterval(interval, reference);
89+
const reference = this.reference; // could get invalidated during `await` below
90+
const sample = await this.session.queryAtCursor(reference);
9391
for (const [designation, handle] of reference.allHandles()) {
9492
const observable = this.observables.get(designation.canonicalKey)!;
9593
observable.update(sample.extract(handle));

src/debug/session.ts

+41-5
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,23 @@ import { TimeInterval, TimePoint } from '../model/time';
77
import { Reference, Sample, UnboundReference } from '../model/sample';
88
import { Variable } from '../model/variable';
99
import { Scope } from '../model/scope';
10+
import { Location } from '../model/source';
1011

1112
function lazy<T>(thunk: () => Thenable<T>): Thenable<T> {
1213
return { then: (onfulfilled, onrejected) => thunk().then(onfulfilled, onrejected) };
1314
}
15+
function matchLocation(location: Location, filename: string, position: vscode.Position) {
16+
if (location.file !== filename) {
17+
return false;
18+
}
19+
if (location.startLine !== position.line) {
20+
return false;
21+
}
22+
if (location.startColumn !== undefined && location.startColumn !== position.character) {
23+
return false;
24+
}
25+
return true;
26+
}
1427

1528
export interface ISimulationStatus {
1629
status: 'running' | 'paused' | 'finished';
@@ -35,10 +48,6 @@ export class Session {
3548
this.connection.dispose();
3649
}
3750

38-
get connection2(): Connection {
39-
return this.connection;
40-
}
41-
4251
// ======================================== Inspecting the design
4352

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

157+
async getVariablesForLocation(filename: string, position: vscode.Position): Promise<Variable[]> {
158+
const variables: Variable[] = [];
159+
const extractVariablesForLocationFromScope = async (scope: string) => {
160+
const items = await this.listItemsInScope(scope);
161+
for (const [itemName, itemDesc] of Object.entries(items)) {
162+
const itemLocation = Location.fromCXXRTL(itemDesc.src);
163+
console.log(itemLocation, filename, position, itemLocation !== null && matchLocation(itemLocation, filename, position));
164+
if (itemLocation !== null && matchLocation(itemLocation, filename, position)) {
165+
variables.push(Variable.fromCXXRTL(itemName, itemDesc));
166+
}
167+
}
168+
const subScopes = await this.listScopesInScope(scope);
169+
for (const subScopeName of Object.keys(subScopes)) {
170+
await extractVariablesForLocationFromScope(subScopeName);
171+
}
172+
return null;
173+
};
174+
await extractVariablesForLocationFromScope('');
175+
return variables;
176+
}
177+
148178
// ======================================== Querying the database
149179

150180
private referenceEpochs: Map<string, number> = new Map();
@@ -184,8 +214,8 @@ export class Session {
184214
}
185215

186216
async queryInterval(
187-
interval: TimeInterval,
188217
reference: Reference,
218+
interval: TimeInterval,
189219
options: { collapse?: boolean } = {}
190220
): Promise<Sample[]> {
191221
this.checkReferenceEpoch(reference.name, reference.epoch);
@@ -213,6 +243,12 @@ export class Session {
213243
});
214244
}
215245

246+
async queryAtCursor(reference: Reference): Promise<Sample> {
247+
const interval = new TimeInterval(this.timeCursor, this.timeCursor);
248+
const [sample] = await this.queryInterval(reference, interval);
249+
return sample;
250+
}
251+
216252
// ======================================== Manipulating the simulation
217253

218254
private simulationStatusTimeout: NodeJS.Timeout | null = null;

src/debugger.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as net from 'net';
22
import * as vscode from 'vscode';
3+
34
import { NodeStreamLink } from './cxxrtl/link';
45
import { StatusBarItem } from './ui/status';
56
import { Session } from './debug/session';

src/extension.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import * as vscode from 'vscode';
2+
23
import { CXXRTLDebugger } from './debugger';
34
import * as sidebar from './ui/sidebar';
45
import { globalWatchList } from './debug/watch';
56
import { globalVariableOptions } from './debug/options';
7+
import { HoverProvider } from './ui/hover';
68
import { inputTime } from './ui/input';
79

810
export function activate(context: vscode.ExtensionContext) {
@@ -14,6 +16,11 @@ export function activate(context: vscode.ExtensionContext) {
1416
});
1517
context.subscriptions.push(sidebarTreeView);
1618

19+
const hoverProvider = new HoverProvider(rtlDebugger);
20+
for (const language of HoverProvider.SUPPORTED_LANGUAGES) {
21+
context.subscriptions.push(vscode.languages.registerHoverProvider(language, hoverProvider));
22+
}
23+
1724
vscode.commands.executeCommand('setContext', 'rtlDebugger.sessionStatus', rtlDebugger.sessionStatus);
1825
context.subscriptions.push(rtlDebugger.onDidChangeSessionStatus((state) =>
1926
vscode.commands.executeCommand('setContext', 'rtlDebugger.sessionStatus', state)));

src/model/styling.ts

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ export enum DisplayStyle {
99
VHDL = 'VHDL',
1010
}
1111

12+
export function languageForDisplayStyle(style: DisplayStyle): string {
13+
return style as string;
14+
}
15+
1216
export function variableDescription(style: DisplayStyle, variable: Variable, { scalar = false } = {}): string {
1317
let result = '';
1418
if (variable instanceof ScalarVariable && variable.lsbAt === 0 && variable.width === 1) {

src/ui/hover.ts

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as vscode from 'vscode';
2+
3+
import { CXXRTLDebugger } from '../debugger';
4+
import { UnboundReference } from '../model/sample';
5+
import { ScalarVariable, Variable } from '../model/variable';
6+
import { DisplayStyle, languageForDisplayStyle, variableDescription, variableValue } from '../model/styling';
7+
import { Session } from '../debug/session';
8+
9+
export class HoverProvider implements vscode.HoverProvider {
10+
static readonly SUPPORTED_LANGUAGES: string[] = ['verilog'];
11+
12+
constructor(
13+
private rtlDebugger: CXXRTLDebugger
14+
) {}
15+
16+
private async hoverForVariables(session: Session, variables: Variable[]): Promise<vscode.Hover | null> {
17+
if (variables.length === 0) {
18+
return null;
19+
}
20+
const displayStyle = vscode.workspace.getConfiguration('rtlDebugger').get('displayStyle') as DisplayStyle;
21+
const hoverText = new vscode.MarkdownString();
22+
const unboundReference = new UnboundReference();
23+
for (const variable of variables) {
24+
if (variable instanceof ScalarVariable) {
25+
unboundReference.add(variable.designation());
26+
}
27+
}
28+
const reference = session.bindReference('hover', unboundReference);
29+
const sample = await session.queryAtCursor(reference);
30+
for (const [designation, handle] of reference.allHandles()) {
31+
const variable = designation.variable;
32+
const descriptionText = variableDescription(displayStyle, variable);
33+
const valueText = variableValue(displayStyle, variable, sample.extract(handle));
34+
hoverText.appendCodeblock(
35+
`${variable.fullName.join('.')}${descriptionText} = ${valueText}`,
36+
languageForDisplayStyle(displayStyle)
37+
);
38+
}
39+
return new vscode.Hover(hoverText);
40+
}
41+
42+
async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Hover | null> {
43+
const session = this.rtlDebugger.session;
44+
if (session !== null) {
45+
const definitions = await (
46+
vscode.commands.executeCommand('vscode.executeDefinitionProvider', document.uri, position) as
47+
vscode.ProviderResult<vscode.Definition | vscode.LocationLink[]>
48+
);
49+
let definition: vscode.Location | undefined;
50+
if (definitions instanceof vscode.Location) {
51+
definition = definitions;
52+
} else if (definitions instanceof Array && definitions.length === 1 && definitions[0] instanceof vscode.Location) {
53+
definition = definitions[0];
54+
} else {
55+
console.warn('vscode.executeDefinitionProvider did not return a single Location: ', definition);
56+
return null;
57+
}
58+
const variables = await session.getVariablesForLocation(definition.uri.fsPath, definition.range.start);
59+
return await this.hoverForVariables(session, variables);
60+
}
61+
return null;
62+
}
63+
}

0 commit comments

Comments
 (0)