@@ -4,7 +4,7 @@ import * as proto from '../cxxrtl/proto';
44import { ILink } from '../cxxrtl/link' ;
55import { Connection } from '../cxxrtl/client' ;
66import { TimeInterval , TimePoint } from '../model/time' ;
7- import { Diagnostic , Reference , Sample , UnboundReference } from '../model/sample' ;
7+ import { Diagnostic , DiagnosticType , Reference , Sample , UnboundReference } from '../model/sample' ;
88import { Variable } from '../model/variable' ;
99import { Scope } from '../model/scope' ;
1010import { Location } from '../model/source' ;
@@ -32,6 +32,11 @@ export interface ISimulationStatus {
3232 nextSampleTime ?: TimePoint ;
3333}
3434
35+ export enum SimulationPauseReason {
36+ TimeReached ,
37+ DiagnosticsReached ,
38+ }
39+
3540export class Session {
3641 private connection : Connection ;
3742
@@ -52,8 +57,10 @@ export class Session {
5257 for ( const secondaryLink of this . secondaryLinks ) {
5358 secondaryLink . onRecv ( event ) ;
5459 }
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 ( ) ;
5764 }
5865 } ;
5966 this . querySimulationStatus ( ) ; // populate nextSampleTime
@@ -313,9 +320,18 @@ export class Session {
313320
314321 private simulationStatusTimeout : NodeJS . Timeout | null = null ;
315322
316- private _onDidChangeSimulationStatus : vscode . EventEmitter < ISimulationStatus > = new vscode . EventEmitter < ISimulationStatus > ( ) ;
323+ private _onDidChangeSimulationStatus : vscode . EventEmitter < ISimulationStatus > = new vscode . EventEmitter ( ) ;
317324 readonly onDidChangeSimulationStatus : vscode . Event < ISimulationStatus > = this . _onDidChangeSimulationStatus . event ;
318325
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+
319335 private _simulationStatus : ISimulationStatus = {
320336 status : 'paused' ,
321337 latestTime : TimePoint . ZERO ,
@@ -354,23 +370,62 @@ export class Session {
354370 }
355371 }
356372
357- async runSimulation ( options : { untilTime ?: TimePoint } = { } ) : Promise < void > {
373+ async runSimulation ( options : {
374+ untilTime ?: TimePoint ,
375+ untilDiagnostics ?: DiagnosticType [ ]
376+ } = { } ) : Promise < void > {
358377 await this . connection . runSimulation ( {
359378 type : 'command' ,
360379 command : 'run_simulation' ,
361380 until_time : options . untilTime ?. toCXXRTL ( ) ?? null ,
362- until_diagnostics : [ ] ,
381+ until_diagnostics : options . untilDiagnostics ?. map ( ( type ) => < DiagnosticType > type ) ?? [ ] ,
363382 sample_item_values : true
364383 } ) ;
365384 await this . querySimulationStatus ( ) ;
385+ this . _onDidRunSimulation . fire ( ) ;
366386 }
367387
368388 async pauseSimulation ( ) : Promise < void > {
369389 await this . connection . pauseSimulation ( {
370390 type : 'command' ,
371391 command : 'pause_simulation'
372392 } ) ;
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 > {
373427 await this . querySimulationStatus ( ) ;
428+ this . _onDidFinishSimulation . fire ( ) ;
374429 }
375430
376431 get isSimulationRunning ( ) : boolean {
@@ -456,4 +511,52 @@ export class Session {
456511 }
457512 return this . timeCursor ;
458513 }
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+ }
459562}
0 commit comments