A beat a program sets for itself

The only regular thing on this machine was the screen finishing a frame,
sixty times a second and not negotiable - a clock a program BORROWS rather
than one it sets. Every duration became a multiple of 16,667 cycles, so a
sixteenth note at 120 beats a minute, which is 125,000, is seven and a half
frames and cannot be asked for at all. The way round it was to choose a
tempo whose subdivisions happen to land on whole frames, which is making
the music fit the machine. Examples/tune.asm says so in its own header.

  0x50  Status: a period went by, it is running, it will interrupt
  0x51  Control: run, repeat, interrupt
  0x52-0x54  The period, in cycles, most significant first

THE PERIOD IS IN CYCLES because that is what everything else here is
counted in - the cost model counts them and a frame is measured in them -
so a timer counting anything else would be a second unit to remember.
Twenty four bits reaches from one cycle to sixteen and a half seconds, with
120 beats a minute at 500,000 in the middle, and there is no range left for
a prescaler to buy.

Starting loads the period; asking it to run while it already is does not,
so turning interrupts on half way through a period does not silently move
the beat being kept. What is left over carries into the next period, so a
period of 1,000 ticks every 1,000 and not every 1,000 plus however late
anybody looked. Reading the status takes the tick down and the line with
it, which is the rule this machine settled two days ago about every status
port.

The timing check is in terminal.sh and not the manifest, and the reason is
worth keeping: settle() strips cycle counts from recordings, which is right
for every other program and useless for a clock. "It printed eight dots"
would pass on a timer that fired them all at once. terminal.sh measures
that eight periods of 125,000 come to a million within a couple of hundred
cycles, and that 99.97% of them were spent asleep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-31 17:06:28 -04:00
co-authored by Claude Opus 5
parent 3e48e9690d
commit 6b51d6391f
11 changed files with 393 additions and 2 deletions
+48
View File
@@ -0,0 +1,48 @@
; The timer interrupting, which is what a music routine actually wants.
;
; 125,000 cycles is a sixteenth note at 120 beats a minute. THE SCREEN CANNOT EXPRESS IT: a
; frame is 16,667 cycles, so that beat is seven and a half of them, and a program timing music
; on frames has to pick a tempo whose subdivisions happen to land on whole ones.
;
; Eight of them is a second, and what is recorded is that it took one - and that the machine
; slept through nearly all of it, which is the difference between waiting for a beat and
; counting up to it.
;
; Written by Anachronaut
#Program
start:
; 125,000 cycles: a sixteenth note at 120 beats a minute, which the screen's frame
; cannot express at all.
INIA 0x01
OUTA 0x52
INIA 0xE8
OUTA 0x53
INIA 0x48
OUTA 0x54 ; 0x01E848 = 125,000
INIA 0x07
OUTA 0x51 ; Run, repeat, interrupt.
SIF
INIB 0d8
everyBeat:
WAIT
INIA 0d46
OUTA 0x00
DECB
BNB everyBeat
CIF
RSTA
OUTA 0x51
INIA 0d10
OUTA 0x00
HALT
beat:
RETI
#Vectors
Boot start
Device 0x50 beat
+93
View File
@@ -0,0 +1,93 @@
; A beat a program sets for itself.
;
; The only regular thing this machine had was the screen finishing a frame, sixty times a
; second and not negotiable. That is a clock a program BORROWS: every duration becomes a
; multiple of 16.67 ms, so a sixteenth note at 120 beats a minute - 125,000 cycles, which is
; seven and a half frames - cannot be asked for at all.
;
; Counted in cycles, because that is what everything else on this machine is counted in.
;
; Written by Anachronaut
#Program
start:
; ---- Repeating, and polled ----
;
; 100,000 cycles, a tenth of a second. Four of them, so the whole thing is 400,000 and the
; recorded cycle count is the check that the period is what it says.
INIA 0x01
OUTA 0x52
INIA 0x86
OUTA 0x53
INIA 0xA0
OUTA 0x54 ; 0x0186A0
INIA 0x03
OUTA 0x51 ; Run, repeat, no interrupt.
INIB 0d4
everyTick:
INA 0x50
PSHB
INIB 0x01
AND
POPB
BRQ everyTick ; Not yet.
INIA 0d46
OUTA 0x00
DECB
BNB everyTick
; ---- And looking is what answered it ----
;
; The bit came down when it was read, so asking again immediately says nothing has happened
; since. A timer whose flag stayed up would look like a beat every time round the loop.
INA 0x50
INIB 0x01
AND
BRQ tickCleared
INIA 0d78 ; 'N'
OUTA 0x00
BRI oneShot
tickCleared:
INIA 0d89 ; 'Y'
OUTA 0x00
oneShot:
; ---- Once, and then stopped ----
;
; Without the repeat bit it runs its period out and turns itself off, which the status port
; says: bit 1 is whether it is running.
RSTA
OUTA 0x51 ; Stop first, so starting below is a start.
INIA 0x01
OUTA 0x51 ; Run, no repeat.
waitOnce:
INA 0x50
INIB 0x01
AND
BRQ waitOnce
INIA 0d46
OUTA 0x00
INA 0x50
INIB 0x02 ; RUNNING
AND
BRQ stopped
INIA 0d78
OUTA 0x00
BRI done
stopped:
INIA 0d89
OUTA 0x00
done:
INIA 0d10
OUTA 0x00
HALT
#Vectors
Boot start
+120
View File
@@ -948,11 +948,121 @@ void deviceTick(unsigned long now) {
// And the sound, which makes whatever samples are due by now. On the machine's clock, // And the sound, which makes whatever samples are due by now. On the machine's clock,
// so the same program makes the same sound in the same cycles. // so the same program makes the same sound in the same cycles.
soundTick(now); soundTick(now);
// And the timer, which is the only beat a program can choose for itself.
timerTick(now);
if (diskPending && now >= diskReadyAt) { if (diskPending && now >= diskReadyAt) {
diskSettle(); diskSettle();
} }
} }
// ---- A beat a program sets for itself ----
//
// COUNTED IN CYCLES, which is this machine's unit of time everywhere else: it is what the
// cost model counts and what a frame is measured in. A timer counting anything else would be
// a second thing to remember, and a prescaler would buy range that twenty four bits already
// covers - one cycle at one end and sixteen point seven seconds at the other, with 120 beats
// a minute sitting at 500,000 in the middle of it.
static uint32_t timerPeriod = 0;
static uint32_t timerLeft = 0;
static uint8_t timerControl = 0;
static uint8_t timerTicked = 0;
static unsigned long timerLast = 0;
void timerReset(void) {
timerPeriod = 0;
timerLeft = 0;
timerControl = 0;
timerTicked = 0;
timerLast = 0;
clearInterrupt(PORT_TIMER);
}
// ---- Caught up rather than counted ----
//
// The same shape as the screen's frame: the machine runs in batches, so more than one period
// can pass between two looks. What is owed is worked out from how far the clock moved rather
// than by being told once per cycle, and several periods at once still mean one tick - a
// missed one is missed, which is what missing one is.
void timerTick(unsigned long now) {
const unsigned long moved = now - timerLast;
timerLast = now;
if (!(timerControl & TIMER_CONTROL_RUN) || timerPeriod == 0) {
return;
}
if ((unsigned long)timerLeft > moved) {
timerLeft -= (uint32_t)moved;
return;
}
timerTicked = 1;
if (timerControl & TIMER_CONTROL_INTERRUPT) {
raiseInterrupt(PORT_TIMER);
}
if (timerControl & TIMER_CONTROL_REPEAT) {
// What is left over carries into the next period, so a timer asked for 1,000 cycles
// gets a tick every 1,000 and not every 1,000 plus however late anybody looked.
const unsigned long over = moved - timerLeft;
timerLeft = timerPeriod - (uint32_t)(over % timerPeriod);
} else {
timerControl = (uint8_t)(timerControl & ~TIMER_CONTROL_RUN);
timerLeft = 0;
}
}
static uint8_t timerWrite(uint8_t value, uint8_t port) {
switch (port) {
case TIMER_CONTROL: {
const uint8_t wasRunning = timerControl & TIMER_CONTROL_RUN;
timerControl = value;
if ((value & TIMER_CONTROL_RUN) && !wasRunning) {
// Starting loads the period. Asking it to run while it already is does not,
// so a program that sets the interrupt bit half way through a period does not
// silently move the beat it was keeping.
timerLeft = timerPeriod;
}
if (!(value & TIMER_CONTROL_INTERRUPT)) {
// Asking to stop being interrupted takes down whatever was already asked for,
// the same as the screen and for the same reason.
clearInterrupt(PORT_TIMER);
}
}
break;
case TIMER_PERIOD_HIGH:
timerPeriod = (timerPeriod & 0x0000FFFFu) | ((uint32_t)value << 16);
break;
case TIMER_PERIOD_MID:
timerPeriod = (timerPeriod & 0x00FF00FFu) | ((uint32_t)value << 8);
break;
case TIMER_PERIOD_LOW:
timerPeriod = (timerPeriod & 0x00FFFF00u) | value;
break;
default: break;
}
return 0;
}
static uint8_t timerRead(uint8_t port) {
switch (port) {
case TIMER_STATUS: {
uint8_t status = 0;
if (timerTicked) status |= TIMER_STATUS_TICKED;
if (timerControl & TIMER_CONTROL_RUN) status |= TIMER_STATUS_RUNNING;
if (timerControl & TIMER_CONTROL_INTERRUPT) status |= TIMER_STATUS_INTERRUPT;
// Looking is what answers it, the same as every other status port here: a beat
// that has been noticed is not still waiting to be, and the line goes down with
// the flag because a program that polls is not one that will answer a handler.
timerTicked = 0;
clearInterrupt(PORT_TIMER);
return status;
}
case TIMER_CONTROL: return timerControl;
case TIMER_PERIOD_HIGH: return (uint8_t)(timerPeriod >> 16);
case TIMER_PERIOD_MID: return (uint8_t)(timerPeriod >> 8);
case TIMER_PERIOD_LOW: return (uint8_t)timerPeriod;
default: return 0;
}
}
// ---- A device that brings memory ---- // ---- A device that brings memory ----
// //
// The simplest thing that owns a bank. Writing to its port fills its memory with the // The simplest thing that owns a bank. Writing to its port fills its memory with the
@@ -1011,6 +1121,7 @@ static const DeviceRecord deviceTable[] = {
{ PORT_DISK, DEVICE_DISK, DEVICE_FLAG_HAS_MEMORY }, { PORT_DISK, DEVICE_DISK, DEVICE_FLAG_HAS_MEMORY },
{ PORT_VIDEO, DEVICE_VIDEO, DEVICE_FLAG_HAS_MEMORY }, { PORT_VIDEO, DEVICE_VIDEO, DEVICE_FLAG_HAS_MEMORY },
{ PORT_SOUND, DEVICE_SOUND, 0 }, { PORT_SOUND, DEVICE_SOUND, 0 },
{ PORT_TIMER, DEVICE_TIMER, 0 },
{ PORT_REGISTRY, DEVICE_REGISTRY, 0 }, { PORT_REGISTRY, DEVICE_REGISTRY, 0 },
}; };
static const int deviceCount = (int)(sizeof(deviceTable) / sizeof(deviceTable[0])); static const int deviceCount = (int)(sizeof(deviceTable) / sizeof(deviceTable[0]));
@@ -1046,6 +1157,9 @@ static const DeviceRecord *deviceOnPort(uint8_t port) {
if (port > PORT_SOUND && port <= PORT_SOUND_TOP) { if (port > PORT_SOUND && port <= PORT_SOUND_TOP) {
return deviceOnPort(PORT_SOUND); return deviceOnPort(PORT_SOUND);
} }
if (port > PORT_TIMER && port <= PORT_TIMER_TOP) {
return deviceOnPort(PORT_TIMER);
}
for (int i = 0; i < deviceCount; i++) { for (int i = 0; i < deviceCount; i++) {
if (deviceTable[i].port == port) { if (deviceTable[i].port == port) {
return &deviceTable[i]; return &deviceTable[i];
@@ -1082,6 +1196,9 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) {
if (Address >= PORT_SOUND && Address <= PORT_SOUND_TOP) { if (Address >= PORT_SOUND && Address <= PORT_SOUND_TOP) {
return soundWrite(DataByte, Address); return soundWrite(DataByte, Address);
} }
if (Address >= PORT_TIMER && Address <= PORT_TIMER_TOP) {
return timerWrite(DataByte, Address);
}
// This function sends the DataByte to the appropriate place based on the Port Address. // This function sends the DataByte to the appropriate place based on the Port Address.
switch(Address) { switch(Address) {
case CONSOLE_DATA: case CONSOLE_DATA:
@@ -1216,6 +1333,9 @@ uint8_t InputHandler(uint8_t Address) {
if (Address >= PORT_SOUND && Address <= PORT_SOUND_TOP) { if (Address >= PORT_SOUND && Address <= PORT_SOUND_TOP) {
return soundRead(Address); return soundRead(Address);
} }
if (Address >= PORT_TIMER && Address <= PORT_TIMER_TOP) {
return timerRead(Address);
}
switch(Address) { switch(Address) {
case CONSOLE_DATA: case CONSOLE_DATA:
// If data is sent here, it should be read from STDIN. // If data is sent here, it should be read from STDIN.
+29
View File
@@ -97,6 +97,30 @@
#define PORT_VIDEO 0x30 #define PORT_VIDEO 0x30
#define PORT_VIDEO_TOP 0x3F #define PORT_VIDEO_TOP 0x3F
// ---- The timer ----
//
// The only regular beat this machine had was the screen finishing a frame, which is fixed at
// sixty a second. That is a clock a program borrows rather than one it sets: every duration
// becomes a multiple of 16.67 ms, so a note worth a third of a beat cannot be asked for and
// the way round it is to choose a tempo whose subdivisions happen to land on whole frames -
// which is making the music fit the hardware.
#define PORT_TIMER 0x50
#define PORT_TIMER_TOP 0x54
#define TIMER_STATUS 0x50
#define TIMER_CONTROL 0x51
#define TIMER_PERIOD_HIGH 0x52
#define TIMER_PERIOD_MID 0x53
#define TIMER_PERIOD_LOW 0x54
#define TIMER_STATUS_TICKED 0x01
#define TIMER_STATUS_RUNNING 0x02
#define TIMER_STATUS_INTERRUPT 0x04
#define TIMER_CONTROL_RUN 0x01
#define TIMER_CONTROL_REPEAT 0x02
#define TIMER_CONTROL_INTERRUPT 0x04
#define PORT_REGISTRY 0xFF #define PORT_REGISTRY 0xFF
// ---- The console ---- // ---- The console ----
@@ -221,6 +245,7 @@ void consoleSetInputHook(int (*hook)(int mayWait));
#define DEVICE_DISK 0x13 #define DEVICE_DISK 0x13
#define DEVICE_VIDEO 0x14 #define DEVICE_VIDEO 0x14
#define DEVICE_SOUND 0x15 #define DEVICE_SOUND 0x15
#define DEVICE_TIMER 0x16
// What a device brings besides itself. This means memory that somebody has to register // What a device brings besides itself. This means memory that somebody has to register
// with the controller, so the controller's own bank 2 does not count: it is already there. // with the controller, so the controller's own bank 2 does not count: it is already there.
@@ -382,6 +407,10 @@ void clearInterrupt(uint8_t port);
// never asked the device for anything. // never asked the device for anything.
void clearAllInterrupts(void); void clearAllInterrupts(void);
// The timer, which counts the machine's own cycles.
void timerReset(void);
void timerTick(unsigned long now);
// The lowest numbered port with its line up, or -1 if none of them are. // The lowest numbered port with its line up, or -1 if none of them are.
int nextPendingInterrupt(void); int nextPendingInterrupt(void);
+2
View File
@@ -135,6 +135,7 @@ static int machineRestart(Machine *m) {
} }
videoReset(); videoReset();
soundReset(); soundReset();
timerReset();
consoleHome(); consoleHome();
consoleResetInput(); consoleResetInput();
// ---- And every line down ---- // ---- And every line down ----
@@ -201,6 +202,7 @@ uint8_t machineStart(Machine *m, const EmulatorOptions *options, const char *pro
// did not happen. // did not happen.
videoReset(); videoReset();
soundReset(); soundReset();
timerReset();
if (options->sound != NULL) { if (options->sound != NULL) {
soundKeepSamples(); soundKeepSamples();
} }
+53 -1
View File
@@ -551,6 +551,7 @@ If nothing is installed for the vector a device refused with, the machine stops
| 0x12 | A device that owns 256 bytes of memory. Writing to its port fills that memory with the byte written, standing in for a disk controller reading a sector. Its memory is unreachable until it is registered as a bank. | 0x12 | | 0x12 | A device that owns 256 bytes of memory. Writing to its port fills that memory with the byte written, standing in for a disk controller reading a sector. Its memory is unreachable until it is registered as a bank. | 0x12 |
| 0x30 - 0x3F | The screen. See The Screen. It brings video memory, which is unreachable until it is registered as a bank. | 0x14 | | 0x30 - 0x3F | The screen. See The Screen. It brings video memory, which is unreachable until it is registered as a bank. | 0x14 |
| 0x40 - 0x4F | The sound device. See Making A Noise. Four channels, played by writing to ports; it brings no memory. | 0x15 | | 0x40 - 0x4F | The sound device. See Making A Noise. Four channels, played by writing to ports; it brings no memory. | 0x15 |
| 0x50 - 0x54 | The timer. See Keeping Time. Counts the machine's cycles and says when a period has gone by. | 0x16 |
| 0xE0 - 0xEF | The memory controller. See The Memory Controller. | 0x03 | | 0xE0 - 0xEF | The memory controller. See The Memory Controller. | 0x03 |
| 0xFF | The bus registry. See Asking What Is There. | 0x01 | | 0xFF | The bus registry. See Asking What Is There. | 0x01 |
@@ -988,7 +989,58 @@ One thing to be careful of: the registry remembers which port it was asked about
| 0x13 | Disk. | | 0x13 | Disk. |
| 0x14 | Screen. | | 0x14 | Screen. |
| 0x15 | Sound. | | 0x15 | Sound. |
| 0x16 - 0xFF | Peripherals. | | 0x16 | Timer. |
| 0x17 - 0xFF | Peripherals. |
## Keeping Time:
A period, in cycles, and a bit that says when one has gone by.
Before this the only regular beat on the machine was the screen finishing a frame, and that is
a clock a program **borrows** rather than one it sets. A frame is 16,667 cycles and not
negotiable, so every duration becomes a multiple of it - and a sixteenth note at 120 beats a
minute is 125,000 cycles, which is seven and a half frames. It cannot be asked for at all. The
way round it is to choose a tempo whose subdivisions happen to land on whole frames, which is
making the music fit the machine.
| Port | Register |
| --- | --- |
| 0x50 | Status. Bit 0 a period has gone by, bit 1 it is running, bit 2 it is set to interrupt. |
| 0x51 | Control. Bit 0 run, bit 1 repeat, bit 2 interrupt. |
| 0x52 - 0x54 | The period, in cycles, most significant byte first. |
**The period is in cycles**, because that is what everything else here is counted in: it is
what the cost model counts and what a frame is measured in, so a timer counting anything else
would be a second unit to remember. Twenty four bits reaches from one cycle to sixteen and a
half seconds, and 120 beats a minute sits at 500,000 in the middle of it. There is no
prescaler, because there is no range left for one to buy.
**Starting it loads the period.** Writing the control byte with the run bit already set does
not, so a program that turns interrupts on half way through a period does not silently move
the beat it was keeping.
**With the repeat bit it reloads; without it, it stops** and the status port says so. What is
left over carries into the next period, so a timer asked for 1,000 cycles ticks every 1,000
and not every 1,000 plus however late anybody looked.
**Reading the status is what answers it**: the tick comes down when it is read, and the line
with it. A program that polls is not one that will answer a handler.
```asm
; A sixteenth note at 120 beats a minute, waited for rather than counted.
INIA 0x01
OUTA 0x52
INIA 0xE8
OUTA 0x53
INIA 0x48
OUTA 0x54 ; 0x01E848, which is 125,000
INIA 0x07
OUTA 0x51 ; Run, repeat, interrupt
SIF
WAIT
```
Eight of those is one second, and a machine doing it spends 999,720 of those cycles asleep.
## The Memory Controller: ## The Memory Controller:
+1 -1
View File
@@ -79,7 +79,7 @@ from `make`, not from here.
### 1. Recorded output ### 1. Recorded output
`Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares `Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares
everything it printed against a file in `Tests/expected`. 182 tests, of which 120 run, 35 everything it printed against a file in `Tests/expected`. 184 tests, of which 122 run, 35
only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image
given at all. given at all.
+3
View File
@@ -0,0 +1,3 @@
........
Execution halted.
[exit 0]
+3
View File
@@ -0,0 +1,3 @@
....Y.Y
Execution halted.
[exit 0]
+11
View File
@@ -838,6 +838,17 @@ cosmosDrivePath | CosmOS/Source/cosmos.asm | run | cosmosDri
# place the shell looks. The drive it says afterwards is the check that fetching a program # place the shell looks. The drive it says afterwards is the check that fetching a program
# did not move the person who ran it. # did not move the person who ran it.
cosmosCrossDisk | CosmOS/Source/cosmos.asm | run | cosmosCrossDisk.in | 90000000 | disks/cosmos.img+disks/other.img cosmosCrossDisk | CosmOS/Source/cosmos.asm | run | cosmosCrossDisk.in | 90000000 | disks/cosmos.img+disks/other.img
# ---- A beat a program sets for itself ----
#
# That reading the status is what takes the tick down, and that without the repeat bit it
# runs its period out and stops. NOT that the period is the length it says: settle() strips
# cycle counts from these recordings, so how long anything took cannot be checked here.
# Tests/terminal.sh measures that, which is where anything about cycles belongs.
timerTest | testPrograms/timerTest.asm | run | - | 5000000 | -
# And interrupting, which is what a music routine wants. 125,000 cycles is a sixteenth note
# at 120 beats a minute - seven and a half frames, so the screen cannot express it at all.
# Eight of them is a second, and nearly all of that second is spent asleep.
timerBeatTest | testPrograms/timerBeatTest.asm | run | - | 5000000 | -
printDecimalTest | testPrograms/printDecimalTest.asm | xfail | - | - printDecimalTest | testPrograms/printDecimalTest.asm | xfail | - | -
printDigitTest | testPrograms/printDigitTest.asm | xfail | - | - printDigitTest | testPrograms/printDigitTest.asm | xfail | - | -
printHexTest | testPrograms/printHexTest.asm | xfail | - | - printHexTest | testPrograms/printHexTest.asm | xfail | - | -
+30
View File
@@ -63,6 +63,8 @@ ASM
# And the one that waits. Assembled from the repository rather than written inline, because # And the one that waits. Assembled from the repository rather than written inline, because
# it is a real test program that run.sh also runs - there it proves the machine wakes up at # it is a real test program that run.sh also runs - there it proves the machine wakes up at
# all, and here it proves it was asleep. # all, and here it proves it was asleep.
"$ROOT/Assembler" "$ROOT/Programs/testPrograms/timerBeatTest.asm" \
-o "$BUILD/timerBeatTest.bin" >/dev/null
"$ROOT/Assembler" "$ROOT/Programs/testPrograms/waitTest.asm" -o "$BUILD/waitTest.bin" \ "$ROOT/Assembler" "$ROOT/Programs/testPrograms/waitTest.asm" -o "$BUILD/waitTest.bin" \
>/dev/null 2>&1 || { echo "Could not assemble waitTest."; exit 1; } >/dev/null 2>&1 || { echo "Could not assemble waitTest."; exit 1; }
"$ROOT/SplitDisk" format "$BUILD/wait.img" 32 1 >/dev/null 2>&1 || { "$ROOT/SplitDisk" format "$BUILD/wait.img" 32 1 >/dev/null 2>&1 || {
@@ -290,6 +292,34 @@ if os.path.exists(waitProgram) and os.path.exists(waitDisk):
else: else:
report(False, "a slow disk is waited for", "waitTest.bin or the disk is missing") report(False, "a slow disk is waited for", "waitTest.bin or the disk is missing")
# ---- A period is a number of cycles, and this is where that can be said ----
#
# The manifest records what a program printed with the cycle count stripped, which is right
# for everything else and useless for a clock: the whole claim the timer makes is about HOW
# LONG, and a recording that says "it printed eight dots" would pass on a timer that fired
# them all at once.
#
# Eight periods of 125,000 is a million cycles, and the program does nothing else worth
# counting. Within a couple of hundred, because starting and stopping cost a few instructions
# and the last tick is answered rather than waited for.
beatProgram = os.path.join(build, "timerBeatTest.bin")
if os.path.exists(beatProgram):
run = subprocess.run([emulator, beatProgram, "--fast", "--cycles", "5000000"],
capture_output=True, text=True)
got = re.search(r"halted after (\d+) cycles, (\d+) of them waiting", run.stdout)
if not got:
report(False, "eight beats take eight periods", "no cycle count was reported")
else:
total, idle = int(got.group(1)), int(got.group(2))
report(abs(total - 1000000) < 500, "eight beats take eight periods",
"%d cycles against 8 x 125,000" % total)
# And it slept through them rather than counting. A timer a program has to poll is
# a timer that costs the machine everything it saves.
report(idle > total * 0.99, "and the machine slept between them",
"%d of %d idle (%.1f%%)" % (idle, total, 100.0 * idle / total))
else:
report(False, "eight beats take eight periods", "timerBeatTest.bin is missing")
print() print()
if problems: if problems:
print("The terminal does not survive everything it should:") print("The terminal does not survive everything it should:")