@@ -4,7 +4,7 @@ import * as proto from '../cxxrtl/proto';
4
4
import { ILink } from '../cxxrtl/link' ;
5
5
import { Connection } from '../cxxrtl/client' ;
6
6
import { TimeInterval , TimePoint } from '../model/time' ;
7
- import { Diagnostic , Reference , Sample , UnboundReference } from '../model/sample' ;
7
+ import { Diagnostic , DiagnosticType , Reference , Sample , UnboundReference } from '../model/sample' ;
8
8
import { Variable } from '../model/variable' ;
9
9
import { Scope } from '../model/scope' ;
10
10
import { Location } from '../model/source' ;
@@ -32,6 +32,11 @@ export interface ISimulationStatus {
32
32
nextSampleTime ?: TimePoint ;
33
33
}
34
34
35
+ export enum SimulationPauseReason {
36
+ TimeReached ,
37
+ DiagnosticsReached ,
38
+ }
39
+
35
40
export class Session {
36
41
private connection : Connection ;
37
42
@@ -52,8 +57,10 @@ export class Session {
52
57
for ( const secondaryLink of this . secondaryLinks ) {
53
58
secondaryLink . onRecv ( event ) ;
54
59
}
55
- if ( event . event === 'simulation_paused' || event . event === 'simulation_finished' ) {
56
- await this . querySimulationStatus ( ) ;
60
+ if ( event . event === 'simulation_paused' ) {
61
+ await this . handleSimulationPausedEvent ( event . cause ) ;
62
+ } else if ( event . event === 'simulation_finished' ) {
63
+ await this . handleSimulationFinishedEvent ( ) ;
57
64
}
58
65
} ;
59
66
this . querySimulationStatus ( ) ; // populate nextSampleTime
@@ -313,9 +320,18 @@ export class Session {
313
320
314
321
private simulationStatusTimeout : NodeJS . Timeout | null = null ;
315
322
316
- private _onDidChangeSimulationStatus : vscode . EventEmitter < ISimulationStatus > = new vscode . EventEmitter < ISimulationStatus > ( ) ;
323
+ private _onDidChangeSimulationStatus : vscode . EventEmitter < ISimulationStatus > = new vscode . EventEmitter ( ) ;
317
324
readonly onDidChangeSimulationStatus : vscode . Event < ISimulationStatus > = this . _onDidChangeSimulationStatus . event ;
318
325
326
+ private _onDidRunSimulation : vscode . EventEmitter < void > = new vscode . EventEmitter ( ) ;
327
+ readonly onDidRunSimulation : vscode . Event < void > = this . _onDidRunSimulation . event ;
328
+
329
+ private _onDidPauseSimulation : vscode . EventEmitter < SimulationPauseReason > = new vscode . EventEmitter ( ) ;
330
+ readonly onDidPauseSimulation : vscode . Event < SimulationPauseReason > = this . _onDidPauseSimulation . event ;
331
+
332
+ private _onDidFinishSimulation : vscode . EventEmitter < void > = new vscode . EventEmitter ( ) ;
333
+ readonly onDidFinishSimulation : vscode . Event < void > = this . _onDidFinishSimulation . event ;
334
+
319
335
private _simulationStatus : ISimulationStatus = {
320
336
status : 'paused' ,
321
337
latestTime : TimePoint . ZERO ,
@@ -354,23 +370,62 @@ export class Session {
354
370
}
355
371
}
356
372
357
- async runSimulation ( options : { untilTime ?: TimePoint } = { } ) : Promise < void > {
373
+ async runSimulation ( options : {
374
+ untilTime ?: TimePoint ,
375
+ untilDiagnostics ?: DiagnosticType [ ]
376
+ } = { } ) : Promise < void > {
358
377
await this . connection . runSimulation ( {
359
378
type : 'command' ,
360
379
command : 'run_simulation' ,
361
380
until_time : options . untilTime ?. toCXXRTL ( ) ?? null ,
362
- until_diagnostics : [ ] ,
381
+ until_diagnostics : options . untilDiagnostics ?. map ( ( type ) => < DiagnosticType > type ) ?? [ ] ,
363
382
sample_item_values : true
364
383
} ) ;
365
384
await this . querySimulationStatus ( ) ;
385
+ this . _onDidRunSimulation . fire ( ) ;
366
386
}
367
387
368
388
async pauseSimulation ( ) : Promise < void > {
369
389
await this . connection . pauseSimulation ( {
370
390
type : 'command' ,
371
391
command : 'pause_simulation'
372
392
} ) ;
393
+ }
394
+
395
+ private async handleSimulationPausedEvent ( cause : proto . PauseCause ) : Promise < void > {
396
+ if ( cause === 'until_time' ) {
397
+ await this . querySimulationStatus ( ) ;
398
+ this . _onDidPauseSimulation . fire ( SimulationPauseReason . TimeReached ) ;
399
+ } else if ( cause === 'until_diagnostics' ) {
400
+ // The `until_diagnostics` cause is a little cursed. For `always @(posedge clk)`
401
+ // assertions, the pause time will be two steps ahead, and for `always @(*)` ones,
402
+ // it will usually be one step ahead. This is because of several fencepost issues with
403
+ // both the storage and the retrieval of diagnostics, which are baked into the CXXRTL
404
+ // execution and replay model. (Diagnostics recorded from C++ are fine.)
405
+ //
406
+ // To handle this, rather than relying on the event time, we scan the database for any
407
+ // diagnostics since the last time the simulation state was updated. (A diagnostic that
408
+ // caused the simulation to be paused must be somewhere between that and the latest
409
+ // sample in the database at the time of pausing.) This avoids the need to simulate
410
+ // the entire interval twice, as would happen if querying the interval between the "Run
411
+ // Simulation Until Diagnostics" command and the time of pausing.
412
+ const latestTimeBeforePause = this . simulationStatus . latestTime ;
413
+ await this . querySimulationStatus ( ) ;
414
+ const latestTimeAfterPause = this . simulationStatus . latestTime ;
415
+ const diagnosticAt = await this . searchIntervalForDiagnostics (
416
+ new TimeInterval ( latestTimeBeforePause , latestTimeAfterPause ) ) ;
417
+ if ( diagnosticAt === null ) {
418
+ console . error ( '[CXXRTL] Paused on diagnostic but no such diagnostics found' ) ;
419
+ return ;
420
+ }
421
+ this . timeCursor = diagnosticAt ;
422
+ this . _onDidPauseSimulation . fire ( SimulationPauseReason . DiagnosticsReached ) ;
423
+ }
424
+ }
425
+
426
+ private async handleSimulationFinishedEvent ( ) : Promise < void > {
373
427
await this . querySimulationStatus ( ) ;
428
+ this . _onDidFinishSimulation . fire ( ) ;
374
429
}
375
430
376
431
get isSimulationRunning ( ) : boolean {
@@ -456,4 +511,52 @@ export class Session {
456
511
}
457
512
return this . timeCursor ;
458
513
}
514
+
515
+ async continueForward ( ) : Promise < void > {
516
+ if ( this . timeCursor . lessThan ( this . simulationStatus . latestTime ) ) {
517
+ const diagnosticAt = await this . searchIntervalForDiagnostics (
518
+ new TimeInterval ( this . timeCursor , this . simulationStatus . latestTime ) ) ;
519
+ if ( diagnosticAt !== null ) {
520
+ this . timeCursor = diagnosticAt ;
521
+ return ;
522
+ }
523
+ }
524
+ // No diagnostics between time cursor and end of database; run the simulation.
525
+ if ( this . simulationStatus . status === 'paused' ) {
526
+ // The pause handler will run `searchIntervalForDiagnostics`.
527
+ await this . runSimulation ( {
528
+ untilDiagnostics : [
529
+ DiagnosticType . Assert ,
530
+ DiagnosticType . Assume ,
531
+ DiagnosticType . Break
532
+ ]
533
+ } ) ;
534
+ } else if ( this . simulationStatus . status === 'finished' ) {
535
+ this . timeCursor = this . simulationStatus . latestTime ;
536
+ }
537
+ }
538
+
539
+ private async searchIntervalForDiagnostics ( interval : TimeInterval ) : Promise < TimePoint | null > {
540
+ const response = await this . connection . queryInterval ( {
541
+ type : 'command' ,
542
+ command : 'query_interval' ,
543
+ interval : interval . toCXXRTL ( ) ,
544
+ collapse : true ,
545
+ items : null ,
546
+ item_values_encoding : null ,
547
+ diagnostics : true
548
+ } ) ;
549
+ for ( const sample of response . samples ) {
550
+ const sampleTime = TimePoint . fromCXXRTL ( sample . time ) ;
551
+ if ( ! sampleTime . greaterThan ( interval . begin ) ) {
552
+ continue ;
553
+ }
554
+ for ( const diagnostic of sample . diagnostics ! ) {
555
+ if ( [ 'break' , 'assert' , 'assume' ] . includes ( diagnostic . type ) ) {
556
+ return sampleTime ;
557
+ }
558
+ }
559
+ }
560
+ return null ;
561
+ }
459
562
}
0 commit comments