@specy/risc-v 3.4.1 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -291,7 +291,18 @@ declare enum BackStepAction {
291
291
  * Members mirror the core's `BackStepper.Action` by position, because the core reports an
292
292
  * action as that enum's ordinal. Append here; never insert.
293
293
  */
294
- CONTROL_AND_STATUS_COUNTERS_DECREMENT = 11
294
+ CONTROL_AND_STATUS_COUNTERS_DECREMENT = 11,
295
+ /**
296
+ * Restores one control and status register the way the host's setter wrote it, through the
297
+ * register's own value: a linked register writes the register it aliases, a read only counter
298
+ * is written anyway. Only a Poke records this, and only inside its own entry.
299
+ */
300
+ CONTROL_AND_STATUS_REGISTER_POKE_RESTORE = 12,
301
+ /**
302
+ * A whole Poke: every value one `beginPoke`/`endPoke` transaction wrote, restored together.
303
+ * A Poke is a single back step, so it takes one slot of the undo size whatever it wrote.
304
+ */
305
+ POKE = 13
295
306
  }
296
307
  /**
297
308
  * Represents a back step in the simulation undo stack.
@@ -306,14 +317,119 @@ interface JsBackStep {
306
317
  */
307
318
  readonly param1: number;
308
319
  /**
309
- * Information about the action
320
+ * Information about the action. A 64 bit value is truncated to 32 bits here, as it always has
321
+ * been; read `oldValue` for the whole of it.
310
322
  */
311
323
  readonly param2: number;
312
324
  /**
313
- * The program counter value before the action.
325
+ * The value this step replaced, as the simulator saw it at the write, whole and untruncated.
326
+ *
327
+ * Every value of this target is 64 bits wide, which no JS number holds, so it is a **signed
328
+ * decimal string**, the shape `JsPokeRegisterWrite` and `getRegistersValuesLong` already use:
329
+ * read it with `BigInt(step.oldValue)`, and with `BigInt.asUintN(64, BigInt(step.oldValue))`
330
+ * for the unsigned form.
331
+ *
332
+ * - `REGISTER_RESTORE`, `FLOATING_POINT_REGISTER_RESTORE`,
333
+ * `CONTROL_AND_STATUS_REGISTER_RESTORE`, `CONTROL_AND_STATUS_REGISTER_BACKDOOR`: the whole
334
+ * register before the write.
335
+ * - `MEMORY_RESTORE_RAW_WORD`, `MEMORY_RESTORE_WORD`, `MEMORY_RESTORE_DOUBLE_WORD`,
336
+ * `MEMORY_RESTORE_HALF`, `MEMORY_RESTORE_BYTE`: the bytes the write replaced, at the width it
337
+ * was made, in the low order bits.
338
+ * - `PC_RESTORE`: the address the restore puts back - the same value as `param1`, which is
339
+ * where this action has always kept it, its `param2` being unused and 0.
340
+ * - `'0'` for `CONTROL_AND_STATUS_COUNTERS_DECREMENT`, `DO_NOTHING` and `POKE`, which replace
341
+ * no single value; a Poke reports both sides of everything it wrote through its own `writes`.
342
+ */
343
+ readonly oldValue: string;
344
+ /**
345
+ * The value the write left, beside the `oldValue` it replaced, taken at the store: a register
346
+ * write reports the whole register after it, a memory write the bytes it left at the width it
347
+ * was made, and `PC_RESTORE` the address the instruction set. Same encoding as `oldValue`.
348
+ *
349
+ * `CONTROL_AND_STATUS_REGISTER_BACKDOOR` is the simulator sampling the wall clock into `time`
350
+ * rather than a program writing a register, but it is a write all the same and reports the
351
+ * millisecond reading it stored. `'0'` for `CONTROL_AND_STATUS_COUNTERS_DECREMENT` and
352
+ * `DO_NOTHING`, which write no value of their own, and for `POKE`, whose `writes` carry both
353
+ * sides already.
354
+ */
355
+ readonly newValue: string;
356
+ /**
357
+ * The program counter value before the action, or -1 for an action that belongs to no
358
+ * instruction: a Poke, or a host write made before anything ran.
314
359
  */
315
360
  readonly pc: number;
361
+ /**
362
+ * Whether this step is a whole Poke rather than one effect of an instruction. It is the
363
+ * discriminator this list needs, since a Poke and a pre-run host write both carry `pc` -1.
364
+ */
365
+ readonly isPoke: boolean;
316
366
  }
367
+ /**
368
+ * One value a Poke changed: a register, named the way this package's getters spell it, or a run of
369
+ * consecutive memory bytes. `old` is what the simulator held when the write happened and `new` what
370
+ * it held when the Poke was closed, so a value written twice inside one Poke reports its final
371
+ * state.
372
+ */
373
+ type JsPokeWrite = JsPokeRegisterWrite | JsPokeMemoryWrite;
374
+ /**
375
+ * A register a Poke wrote. `name` is the register's own name, as this package spells it: a key of
376
+ * `RISCVRegisters` (`t0`), an entry of `RISCV_FLOATING_POINT_REGISTERS` (`ft0`) or one of
377
+ * `RISCV_CSR_REGISTERS` (`fcsr`) - the file it belongs to is the file that name is in.
378
+ *
379
+ * Every register here is 64 bits wide, which no JS number holds, so both values are **signed
380
+ * decimal strings**, the shape `getRegisterValueLong` and `getRegistersValuesLong` already use:
381
+ * read them with `BigInt(write.old)`, and with `BigInt.asUintN(64, BigInt(write.old))` for the
382
+ * unsigned form that `highLowToBigint` returns for the same register.
383
+ */
384
+ type JsPokeRegisterWrite = {
385
+ readonly type: 'register';
386
+ readonly name: string;
387
+ readonly old: string;
388
+ readonly new: string;
389
+ };
390
+ /**
391
+ * A run of consecutive memory bytes a Poke wrote, `address` being the first one, unsigned, and the
392
+ * two arrays holding one byte (0 to 255) per address. A Poke that writes addresses that are not
393
+ * adjacent reports one of these per run, by ascending address. Unlike `readMemoryBytes`, these are
394
+ * ordinary JS arrays.
395
+ */
396
+ type JsPokeMemoryWrite = {
397
+ readonly type: 'memory';
398
+ readonly address: number;
399
+ readonly old: number[];
400
+ readonly new: number[];
401
+ };
402
+ /**
403
+ * One entry of the undo history: everything a single `undo()` reverts, which is either one executed
404
+ * instruction or one Poke. Entries, their back steps and their writes are ordinary objects with own
405
+ * properties, so a whole history can be cloned, serialized or deep-compared as it comes.
406
+ */
407
+ type JsUndoGroup = JsInstructionUndoGroup | JsPokeUndoGroup;
408
+ /**
409
+ * One executed instruction, at the address `pc`, with every back step it recorded: the values it
410
+ * overwrote, and the `cycle`/`instret` decrement every instruction pushes on top of them.
411
+ */
412
+ type JsInstructionUndoGroup = {
413
+ readonly kind: 'instruction';
414
+ readonly pc: number;
415
+ /** The instruction's back steps, newest first. */
416
+ readonly steps: JsBackStep[];
417
+ /** Always empty: only a Poke reports writes. */
418
+ readonly writes: readonly [];
419
+ };
420
+ /**
421
+ * One Poke: register or memory values written by the host between two instructions, recorded as a
422
+ * step of its own. It belongs to no instruction, so `pc` is -1 and undoing it restores exactly what
423
+ * `writes` lists, leaving the program counter, the counters, the call stack and everything else
424
+ * alone.
425
+ */
426
+ type JsPokeUndoGroup = {
427
+ readonly kind: 'poke';
428
+ readonly pc: -1;
429
+ /** The single back step the Poke is: one slot of the history, whatever the Poke wrote. */
430
+ readonly steps: [JsBackStep];
431
+ readonly writes: JsPokeWrite[];
432
+ };
317
433
  declare enum RISCVRegisters {
318
434
  zero = 0,
319
435
  ra = 1,
@@ -406,7 +522,8 @@ interface JsRiscV {
406
522
  */
407
523
  setUndoSize(size: number): void;
408
524
  /**
409
- * Undoes the last instruction executed.
525
+ * Undoes the newest entry of the history: the last instruction executed, or the last Poke made,
526
+ * whichever is on top. Undoing a Poke restores every value it wrote and touches nothing else.
410
527
  */
411
528
  undo(): void;
412
529
  /**
@@ -532,10 +649,10 @@ interface JsRiscV {
532
649
  */
533
650
  getFloatingPointRegistersValues(): Int32Array;
534
651
  /**
535
- * Sets one floating point register, writing it directly: no undo entry is recorded, because
536
- * presetting a register from the host is not something the program did. Split the value with
537
- * `bigintToHighLow`, and NaN-box a single yourself (`0xFFFFFFFFn << 32n | bits`) if that is
538
- * what you mean.
652
+ * Sets one floating point register. Outside a Poke the write is direct: no undo entry is
653
+ * recorded, because presetting a register from the host is not something the program did.
654
+ * Inside a Poke it joins the open transaction. Split the value with `bigintToHighLow`, and
655
+ * NaN-box a single yourself (`0xFFFFFFFFn << 32n | bits`) if that is what you mean.
539
656
  * @param index Position in `RISCV_FLOATING_POINT_REGISTERS`. Must be a whole number from 0 to
540
657
  * 31; anything else throws.
541
658
  */
@@ -549,18 +666,55 @@ interface JsRiscV {
549
666
  */
550
667
  getControlAndStatusRegistersValues(): Int32Array;
551
668
  /**
552
- * Sets one control and status register, writing it directly and recording no undo entry.
553
- * Writing `fflags` or `frm` updates `fcsr`, and writing a counter such as `cycle` is allowed
554
- * here even though the program cannot write it.
669
+ * Sets one control and status register. Outside a Poke the write is direct and records no undo
670
+ * entry; inside a Poke it joins the open transaction. Writing `fflags` or `frm` updates `fcsr`,
671
+ * and writing a counter such as `cycle` is allowed here even though the program cannot write
672
+ * it - so a counter is pokeable, and undoing that Poke puts the counter back.
555
673
  * @param index Position in `RISCV_CSR_REGISTERS`. Must be a whole number from 0 to 16;
556
674
  * anything else throws.
557
675
  */
558
676
  setControlAndStatusRegisterValue(index: number, high: number, low: number): void;
559
677
  /**
560
- * Gets the undo stack.
678
+ * Gets the undo stack, one element per back step, newest first. An instruction usually occupies
679
+ * several of them - it records the values it overwrote and the counter decrement - while a Poke
680
+ * is exactly one, the element with `isPoke` set, whatever it wrote. Use `getUndoGroups` to read
681
+ * the history the way `undo()` pops it, one entry per instruction or Poke.
682
+ *
683
+ * Every step that restores a value reports both sides of the write it undoes, as `oldValue` and
684
+ * `newValue`.
561
685
  * @returns An array of `JsBackStep` objects representing the history of the simulation.
562
686
  */
563
687
  getUndoStack(): JsBackStep[];
688
+ /**
689
+ * Gets the undo history grouped the way `undo()` pops it: one entry per executed instruction or
690
+ * per Poke, newest first, each carrying the back steps it is made of, every one of which
691
+ * reports both sides of the write it undoes as `oldValue` and `newValue`. A Poke entry also
692
+ * carries what it changed, with the old and the new value of each write.
693
+ */
694
+ getUndoGroups(): JsUndoGroup[];
695
+ /**
696
+ * Opens a Poke: a register or memory value changed by the host between two instructions,
697
+ * recorded in this history as a step of its own.
698
+ *
699
+ * Until `endPoke` the setters - `setRegisterValue`, `setFloatingPointRegisterValue`,
700
+ * `setControlAndStatusRegisterValue` and `setMemoryBytes` - journal what they write into the
701
+ * open transaction, however many of them are called, and `endPoke` records the lot as one
702
+ * history entry that one `undo()` reverts and that takes one slot of the undo size, whether it
703
+ * wrote one byte or a hundred. Outside a transaction the same setters stay direct and record
704
+ * nothing, which is what presetting state needs.
705
+ *
706
+ * Throws if a Poke is already open, or if a `step`/`simulate*` call is still in flight.
707
+ */
708
+ beginPoke(): void;
709
+ /**
710
+ * Closes the open Poke.
711
+ * @returns True if it recorded one history entry, false if nothing changed - or if undo is
712
+ * disabled or its size is 0, in which case the writes stand but cannot be undone.
713
+ * Throws if no Poke is open.
714
+ */
715
+ endPoke(): boolean;
716
+ /** Whether a Poke is open, so that the setters journal rather than writing straight through. */
717
+ pokeOpen(): boolean;
564
718
  /**
565
719
  * Reads a sequence of bytes from memory.
566
720
  *
@@ -574,9 +728,13 @@ interface JsRiscV {
574
728
  /**
575
729
  * Writes a sequence of bytes to memory.
576
730
  *
577
- * Unlike `readMemoryBytes`, this writes the way the program does: it notifies write observers
578
- * and, while undo is enabled, records an undo step per byte. Use `setPeripheralWord` for a
579
- * device keeping its own register up to date.
731
+ * Unlike `readMemoryBytes`, this writes the way the program does, so write observers are
732
+ * notified and a memory mapped display repaints. It records no undo step of its own: a host
733
+ * write is not an instruction, and recording it would make the next `undo()` revert it together
734
+ * with the instruction that ran before it. Inside a Poke the same write joins the open
735
+ * transaction instead, and a byte already holding the value written is skipped entirely.
736
+ *
737
+ * Use `setPeripheralWord` for a device keeping its own register up to date.
580
738
  * @param address The starting memory address.
581
739
  * @param bytes An array of bytes to write to memory.
582
740
  */
@@ -636,7 +794,9 @@ interface JsRiscV {
636
794
  */
637
795
  getNextStatement(): JsProgramStatement;
638
796
  /**
639
- * Sets the value of a register.
797
+ * Sets the value of a register. Outside a Poke the write is direct: it records no undo step.
798
+ * Inside a Poke it joins the open transaction, except for `zero`, which holds no value and so
799
+ * is left alone.
640
800
  * @param register The name of the register.
641
801
  * @param value The value to set the register to.
642
802
  */
@@ -669,4 +829,4 @@ declare function bigintToHighLow(value: bigint): [high: number, low: number];
669
829
  */
670
830
  declare function highLowToBigint(high: number, low: number): bigint;
671
831
 
672
- export { BackStepAction, ConfirmResult, type CsrRegisterName, DialogType, type FloatingPointRegisterName, type HandlerMap, type HandlerMapFns, type JsBackStep, type JsInstruction, type JsInstructionToken, type JsProgramStatement, type JsRiscV, type JsRiscVStackFrame, type MemoryAccessObserver, type MemoryObserverHandle, type MemoryWriteObserver, RISCV, type RISCVAssembleError, type RISCVAssembleResult, RISCVRegisters, type RISCVSourceLocation, type RISCVSourceSet, RISCV_CSR_REGISTERS, RISCV_FLOATING_POINT_REGISTERS, RISCV_REGISTERS, type RegisterName, type RiscvTokenizedLine, StopReason, bigintToHighLow, highLowToBigint, makeRiscVFromFiles, registerHandlers, unimplementedHandler };
832
+ export { BackStepAction, ConfirmResult, type CsrRegisterName, DialogType, type FloatingPointRegisterName, type HandlerMap, type HandlerMapFns, type JsBackStep, type JsInstruction, type JsInstructionToken, type JsInstructionUndoGroup, type JsPokeMemoryWrite, type JsPokeRegisterWrite, type JsPokeUndoGroup, type JsPokeWrite, type JsProgramStatement, type JsRiscV, type JsRiscVStackFrame, type JsUndoGroup, type MemoryAccessObserver, type MemoryObserverHandle, type MemoryWriteObserver, RISCV, type RISCVAssembleError, type RISCVAssembleResult, RISCVRegisters, type RISCVSourceLocation, type RISCVSourceSet, RISCV_CSR_REGISTERS, RISCV_FLOATING_POINT_REGISTERS, RISCV_REGISTERS, type RegisterName, type RiscvTokenizedLine, StopReason, bigintToHighLow, highLowToBigint, makeRiscVFromFiles, registerHandlers, unimplementedHandler };
package/dist/index.d.ts CHANGED
@@ -291,7 +291,18 @@ declare enum BackStepAction {
291
291
  * Members mirror the core's `BackStepper.Action` by position, because the core reports an
292
292
  * action as that enum's ordinal. Append here; never insert.
293
293
  */
294
- CONTROL_AND_STATUS_COUNTERS_DECREMENT = 11
294
+ CONTROL_AND_STATUS_COUNTERS_DECREMENT = 11,
295
+ /**
296
+ * Restores one control and status register the way the host's setter wrote it, through the
297
+ * register's own value: a linked register writes the register it aliases, a read only counter
298
+ * is written anyway. Only a Poke records this, and only inside its own entry.
299
+ */
300
+ CONTROL_AND_STATUS_REGISTER_POKE_RESTORE = 12,
301
+ /**
302
+ * A whole Poke: every value one `beginPoke`/`endPoke` transaction wrote, restored together.
303
+ * A Poke is a single back step, so it takes one slot of the undo size whatever it wrote.
304
+ */
305
+ POKE = 13
295
306
  }
296
307
  /**
297
308
  * Represents a back step in the simulation undo stack.
@@ -306,14 +317,119 @@ interface JsBackStep {
306
317
  */
307
318
  readonly param1: number;
308
319
  /**
309
- * Information about the action
320
+ * Information about the action. A 64 bit value is truncated to 32 bits here, as it always has
321
+ * been; read `oldValue` for the whole of it.
310
322
  */
311
323
  readonly param2: number;
312
324
  /**
313
- * The program counter value before the action.
325
+ * The value this step replaced, as the simulator saw it at the write, whole and untruncated.
326
+ *
327
+ * Every value of this target is 64 bits wide, which no JS number holds, so it is a **signed
328
+ * decimal string**, the shape `JsPokeRegisterWrite` and `getRegistersValuesLong` already use:
329
+ * read it with `BigInt(step.oldValue)`, and with `BigInt.asUintN(64, BigInt(step.oldValue))`
330
+ * for the unsigned form.
331
+ *
332
+ * - `REGISTER_RESTORE`, `FLOATING_POINT_REGISTER_RESTORE`,
333
+ * `CONTROL_AND_STATUS_REGISTER_RESTORE`, `CONTROL_AND_STATUS_REGISTER_BACKDOOR`: the whole
334
+ * register before the write.
335
+ * - `MEMORY_RESTORE_RAW_WORD`, `MEMORY_RESTORE_WORD`, `MEMORY_RESTORE_DOUBLE_WORD`,
336
+ * `MEMORY_RESTORE_HALF`, `MEMORY_RESTORE_BYTE`: the bytes the write replaced, at the width it
337
+ * was made, in the low order bits.
338
+ * - `PC_RESTORE`: the address the restore puts back - the same value as `param1`, which is
339
+ * where this action has always kept it, its `param2` being unused and 0.
340
+ * - `'0'` for `CONTROL_AND_STATUS_COUNTERS_DECREMENT`, `DO_NOTHING` and `POKE`, which replace
341
+ * no single value; a Poke reports both sides of everything it wrote through its own `writes`.
342
+ */
343
+ readonly oldValue: string;
344
+ /**
345
+ * The value the write left, beside the `oldValue` it replaced, taken at the store: a register
346
+ * write reports the whole register after it, a memory write the bytes it left at the width it
347
+ * was made, and `PC_RESTORE` the address the instruction set. Same encoding as `oldValue`.
348
+ *
349
+ * `CONTROL_AND_STATUS_REGISTER_BACKDOOR` is the simulator sampling the wall clock into `time`
350
+ * rather than a program writing a register, but it is a write all the same and reports the
351
+ * millisecond reading it stored. `'0'` for `CONTROL_AND_STATUS_COUNTERS_DECREMENT` and
352
+ * `DO_NOTHING`, which write no value of their own, and for `POKE`, whose `writes` carry both
353
+ * sides already.
354
+ */
355
+ readonly newValue: string;
356
+ /**
357
+ * The program counter value before the action, or -1 for an action that belongs to no
358
+ * instruction: a Poke, or a host write made before anything ran.
314
359
  */
315
360
  readonly pc: number;
361
+ /**
362
+ * Whether this step is a whole Poke rather than one effect of an instruction. It is the
363
+ * discriminator this list needs, since a Poke and a pre-run host write both carry `pc` -1.
364
+ */
365
+ readonly isPoke: boolean;
316
366
  }
367
+ /**
368
+ * One value a Poke changed: a register, named the way this package's getters spell it, or a run of
369
+ * consecutive memory bytes. `old` is what the simulator held when the write happened and `new` what
370
+ * it held when the Poke was closed, so a value written twice inside one Poke reports its final
371
+ * state.
372
+ */
373
+ type JsPokeWrite = JsPokeRegisterWrite | JsPokeMemoryWrite;
374
+ /**
375
+ * A register a Poke wrote. `name` is the register's own name, as this package spells it: a key of
376
+ * `RISCVRegisters` (`t0`), an entry of `RISCV_FLOATING_POINT_REGISTERS` (`ft0`) or one of
377
+ * `RISCV_CSR_REGISTERS` (`fcsr`) - the file it belongs to is the file that name is in.
378
+ *
379
+ * Every register here is 64 bits wide, which no JS number holds, so both values are **signed
380
+ * decimal strings**, the shape `getRegisterValueLong` and `getRegistersValuesLong` already use:
381
+ * read them with `BigInt(write.old)`, and with `BigInt.asUintN(64, BigInt(write.old))` for the
382
+ * unsigned form that `highLowToBigint` returns for the same register.
383
+ */
384
+ type JsPokeRegisterWrite = {
385
+ readonly type: 'register';
386
+ readonly name: string;
387
+ readonly old: string;
388
+ readonly new: string;
389
+ };
390
+ /**
391
+ * A run of consecutive memory bytes a Poke wrote, `address` being the first one, unsigned, and the
392
+ * two arrays holding one byte (0 to 255) per address. A Poke that writes addresses that are not
393
+ * adjacent reports one of these per run, by ascending address. Unlike `readMemoryBytes`, these are
394
+ * ordinary JS arrays.
395
+ */
396
+ type JsPokeMemoryWrite = {
397
+ readonly type: 'memory';
398
+ readonly address: number;
399
+ readonly old: number[];
400
+ readonly new: number[];
401
+ };
402
+ /**
403
+ * One entry of the undo history: everything a single `undo()` reverts, which is either one executed
404
+ * instruction or one Poke. Entries, their back steps and their writes are ordinary objects with own
405
+ * properties, so a whole history can be cloned, serialized or deep-compared as it comes.
406
+ */
407
+ type JsUndoGroup = JsInstructionUndoGroup | JsPokeUndoGroup;
408
+ /**
409
+ * One executed instruction, at the address `pc`, with every back step it recorded: the values it
410
+ * overwrote, and the `cycle`/`instret` decrement every instruction pushes on top of them.
411
+ */
412
+ type JsInstructionUndoGroup = {
413
+ readonly kind: 'instruction';
414
+ readonly pc: number;
415
+ /** The instruction's back steps, newest first. */
416
+ readonly steps: JsBackStep[];
417
+ /** Always empty: only a Poke reports writes. */
418
+ readonly writes: readonly [];
419
+ };
420
+ /**
421
+ * One Poke: register or memory values written by the host between two instructions, recorded as a
422
+ * step of its own. It belongs to no instruction, so `pc` is -1 and undoing it restores exactly what
423
+ * `writes` lists, leaving the program counter, the counters, the call stack and everything else
424
+ * alone.
425
+ */
426
+ type JsPokeUndoGroup = {
427
+ readonly kind: 'poke';
428
+ readonly pc: -1;
429
+ /** The single back step the Poke is: one slot of the history, whatever the Poke wrote. */
430
+ readonly steps: [JsBackStep];
431
+ readonly writes: JsPokeWrite[];
432
+ };
317
433
  declare enum RISCVRegisters {
318
434
  zero = 0,
319
435
  ra = 1,
@@ -406,7 +522,8 @@ interface JsRiscV {
406
522
  */
407
523
  setUndoSize(size: number): void;
408
524
  /**
409
- * Undoes the last instruction executed.
525
+ * Undoes the newest entry of the history: the last instruction executed, or the last Poke made,
526
+ * whichever is on top. Undoing a Poke restores every value it wrote and touches nothing else.
410
527
  */
411
528
  undo(): void;
412
529
  /**
@@ -532,10 +649,10 @@ interface JsRiscV {
532
649
  */
533
650
  getFloatingPointRegistersValues(): Int32Array;
534
651
  /**
535
- * Sets one floating point register, writing it directly: no undo entry is recorded, because
536
- * presetting a register from the host is not something the program did. Split the value with
537
- * `bigintToHighLow`, and NaN-box a single yourself (`0xFFFFFFFFn << 32n | bits`) if that is
538
- * what you mean.
652
+ * Sets one floating point register. Outside a Poke the write is direct: no undo entry is
653
+ * recorded, because presetting a register from the host is not something the program did.
654
+ * Inside a Poke it joins the open transaction. Split the value with `bigintToHighLow`, and
655
+ * NaN-box a single yourself (`0xFFFFFFFFn << 32n | bits`) if that is what you mean.
539
656
  * @param index Position in `RISCV_FLOATING_POINT_REGISTERS`. Must be a whole number from 0 to
540
657
  * 31; anything else throws.
541
658
  */
@@ -549,18 +666,55 @@ interface JsRiscV {
549
666
  */
550
667
  getControlAndStatusRegistersValues(): Int32Array;
551
668
  /**
552
- * Sets one control and status register, writing it directly and recording no undo entry.
553
- * Writing `fflags` or `frm` updates `fcsr`, and writing a counter such as `cycle` is allowed
554
- * here even though the program cannot write it.
669
+ * Sets one control and status register. Outside a Poke the write is direct and records no undo
670
+ * entry; inside a Poke it joins the open transaction. Writing `fflags` or `frm` updates `fcsr`,
671
+ * and writing a counter such as `cycle` is allowed here even though the program cannot write
672
+ * it - so a counter is pokeable, and undoing that Poke puts the counter back.
555
673
  * @param index Position in `RISCV_CSR_REGISTERS`. Must be a whole number from 0 to 16;
556
674
  * anything else throws.
557
675
  */
558
676
  setControlAndStatusRegisterValue(index: number, high: number, low: number): void;
559
677
  /**
560
- * Gets the undo stack.
678
+ * Gets the undo stack, one element per back step, newest first. An instruction usually occupies
679
+ * several of them - it records the values it overwrote and the counter decrement - while a Poke
680
+ * is exactly one, the element with `isPoke` set, whatever it wrote. Use `getUndoGroups` to read
681
+ * the history the way `undo()` pops it, one entry per instruction or Poke.
682
+ *
683
+ * Every step that restores a value reports both sides of the write it undoes, as `oldValue` and
684
+ * `newValue`.
561
685
  * @returns An array of `JsBackStep` objects representing the history of the simulation.
562
686
  */
563
687
  getUndoStack(): JsBackStep[];
688
+ /**
689
+ * Gets the undo history grouped the way `undo()` pops it: one entry per executed instruction or
690
+ * per Poke, newest first, each carrying the back steps it is made of, every one of which
691
+ * reports both sides of the write it undoes as `oldValue` and `newValue`. A Poke entry also
692
+ * carries what it changed, with the old and the new value of each write.
693
+ */
694
+ getUndoGroups(): JsUndoGroup[];
695
+ /**
696
+ * Opens a Poke: a register or memory value changed by the host between two instructions,
697
+ * recorded in this history as a step of its own.
698
+ *
699
+ * Until `endPoke` the setters - `setRegisterValue`, `setFloatingPointRegisterValue`,
700
+ * `setControlAndStatusRegisterValue` and `setMemoryBytes` - journal what they write into the
701
+ * open transaction, however many of them are called, and `endPoke` records the lot as one
702
+ * history entry that one `undo()` reverts and that takes one slot of the undo size, whether it
703
+ * wrote one byte or a hundred. Outside a transaction the same setters stay direct and record
704
+ * nothing, which is what presetting state needs.
705
+ *
706
+ * Throws if a Poke is already open, or if a `step`/`simulate*` call is still in flight.
707
+ */
708
+ beginPoke(): void;
709
+ /**
710
+ * Closes the open Poke.
711
+ * @returns True if it recorded one history entry, false if nothing changed - or if undo is
712
+ * disabled or its size is 0, in which case the writes stand but cannot be undone.
713
+ * Throws if no Poke is open.
714
+ */
715
+ endPoke(): boolean;
716
+ /** Whether a Poke is open, so that the setters journal rather than writing straight through. */
717
+ pokeOpen(): boolean;
564
718
  /**
565
719
  * Reads a sequence of bytes from memory.
566
720
  *
@@ -574,9 +728,13 @@ interface JsRiscV {
574
728
  /**
575
729
  * Writes a sequence of bytes to memory.
576
730
  *
577
- * Unlike `readMemoryBytes`, this writes the way the program does: it notifies write observers
578
- * and, while undo is enabled, records an undo step per byte. Use `setPeripheralWord` for a
579
- * device keeping its own register up to date.
731
+ * Unlike `readMemoryBytes`, this writes the way the program does, so write observers are
732
+ * notified and a memory mapped display repaints. It records no undo step of its own: a host
733
+ * write is not an instruction, and recording it would make the next `undo()` revert it together
734
+ * with the instruction that ran before it. Inside a Poke the same write joins the open
735
+ * transaction instead, and a byte already holding the value written is skipped entirely.
736
+ *
737
+ * Use `setPeripheralWord` for a device keeping its own register up to date.
580
738
  * @param address The starting memory address.
581
739
  * @param bytes An array of bytes to write to memory.
582
740
  */
@@ -636,7 +794,9 @@ interface JsRiscV {
636
794
  */
637
795
  getNextStatement(): JsProgramStatement;
638
796
  /**
639
- * Sets the value of a register.
797
+ * Sets the value of a register. Outside a Poke the write is direct: it records no undo step.
798
+ * Inside a Poke it joins the open transaction, except for `zero`, which holds no value and so
799
+ * is left alone.
640
800
  * @param register The name of the register.
641
801
  * @param value The value to set the register to.
642
802
  */
@@ -669,4 +829,4 @@ declare function bigintToHighLow(value: bigint): [high: number, low: number];
669
829
  */
670
830
  declare function highLowToBigint(high: number, low: number): bigint;
671
831
 
672
- export { BackStepAction, ConfirmResult, type CsrRegisterName, DialogType, type FloatingPointRegisterName, type HandlerMap, type HandlerMapFns, type JsBackStep, type JsInstruction, type JsInstructionToken, type JsProgramStatement, type JsRiscV, type JsRiscVStackFrame, type MemoryAccessObserver, type MemoryObserverHandle, type MemoryWriteObserver, RISCV, type RISCVAssembleError, type RISCVAssembleResult, RISCVRegisters, type RISCVSourceLocation, type RISCVSourceSet, RISCV_CSR_REGISTERS, RISCV_FLOATING_POINT_REGISTERS, RISCV_REGISTERS, type RegisterName, type RiscvTokenizedLine, StopReason, bigintToHighLow, highLowToBigint, makeRiscVFromFiles, registerHandlers, unimplementedHandler };
832
+ export { BackStepAction, ConfirmResult, type CsrRegisterName, DialogType, type FloatingPointRegisterName, type HandlerMap, type HandlerMapFns, type JsBackStep, type JsInstruction, type JsInstructionToken, type JsInstructionUndoGroup, type JsPokeMemoryWrite, type JsPokeRegisterWrite, type JsPokeUndoGroup, type JsPokeWrite, type JsProgramStatement, type JsRiscV, type JsRiscVStackFrame, type JsUndoGroup, type MemoryAccessObserver, type MemoryObserverHandle, type MemoryWriteObserver, RISCV, type RISCVAssembleError, type RISCVAssembleResult, RISCVRegisters, type RISCVSourceLocation, type RISCVSourceSet, RISCV_CSR_REGISTERS, RISCV_FLOATING_POINT_REGISTERS, RISCV_REGISTERS, type RegisterName, type RiscvTokenizedLine, StopReason, bigintToHighLow, highLowToBigint, makeRiscVFromFiles, registerHandlers, unimplementedHandler };