A cycle is an access to memory, not an instruction
cycleCount used to tick once per instruction, so RSTA cost what SETD cost and a CALL moving ten bytes of Stack cost what a branch cost. No machine anybody could build works that way, and the emulator's job is to be the thing the hardware is designed against. Every touch of memory now goes through one of four accessors that charge for it: fetching an opcode, fetching the bytes after it, reading or writing Data Memory, and reaching a device port. One access, one cycle, nothing overlapped. The accessors exist so the cost is counted where the access happens rather than in a table of per instruction costs kept somewhere else - a table like that is a second copy of what the code does, and the two drift. The run loop spends a budget of cycles instead of running a count of instructions, so the emulated rate means something: an instruction costs what it touches, and a batch ends when the cycles are gone. What the numbers say now: RSTA 1 and SETD 4, being one byte and four. LDA 3, DPUA 2. CALL and RET together 24, RCAL and RRET together 8, because the first pair moves twenty bytes of Stack and the second moves four. The average SplitBit instruction costs 3.72 of these, measured over the native assembler assembling a program. And the measurement that prompted all of this: converting the filesystem's hottest leaf routine to RCAL is 3.1 per cent cheaper on a directory heavy workload. The old model said 0.0, which is what a model that cannot see memory traffic must say about a change that is nothing else. Three tests moved. settle() strips the cycle count from recorded output, so nothing should have churned - but it was anchored to the start of a line and replCalculator's last output has no newline on it, which leaves the halt message mid line where the pattern never reached. Not anchored any more. The two Life programs are bounded by a cycle count because they never end, and that number was rescaled from 3,000,000 to 11,200,000 - the same amount of work at 3.72 cycles to the instruction. Nothing about either program changed. No limit reproduces the old output exactly, because the cut now lands elsewhere in a frame, so they are recorded again rather than tuned to match. Whether hardware overlaps a fetch with the end of the previous instruction is left open on purpose. This is the conservative model; pipelining is a decision to make while drawing the hardware, not one to inherit from an emulator. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
co-authored by
Claude Opus 5
parent
54f5cfe8a4
commit
f1e5cc46f6
@@ -87,6 +87,25 @@ Then `dir` to see what is there, `load Snake.sbx` and `run` to play something, o
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `-d`, `--debug` | Single step through cycles. Each key press advances one instruction. |
|
| `-d`, `--debug` | Single step through cycles. Each key press advances one instruction. |
|
||||||
| `-c`, `--cycles N` | Stop after N cycles rather than running until the program halts. Useful for programs that never halt, and for getting the same output from a run every time. |
|
| `-c`, `--cycles N` | Stop after N cycles rather than running until the program halts. Useful for programs that never halt, and for getting the same output from a run every time. |
|
||||||
|
|
||||||
|
**A cycle is one access to memory**, not one instruction. Fetching an opcode is a cycle,
|
||||||
|
fetching each byte after it is another, reading or writing Data Memory is one, every byte a
|
||||||
|
CALL pushes or a RET pops is one, and reaching a device port is one. Nothing overlaps -
|
||||||
|
there is no fetching the next instruction while this one finishes - so the count is simply
|
||||||
|
how many times the machine used the bus.
|
||||||
|
|
||||||
|
That makes the numbers describe something buildable. `RSTA` costs 1 and `SETD` costs 4,
|
||||||
|
because one is a byte and the other is four. `CALL` and `RET` together cost 24 and `RCAL`
|
||||||
|
and `RRET` cost 8, because the first pair moves twenty bytes of Stack and the second moves
|
||||||
|
four. Counting instructions said those were the same, which is not true of any machine
|
||||||
|
anybody could build - and it is the emulator's job to be the thing the hardware is designed
|
||||||
|
against.
|
||||||
|
|
||||||
|
The average SplitBit instruction costs 3.72 cycles, measured over the native assembler
|
||||||
|
assembling a program. Whether real hardware would overlap a fetch with the end of the
|
||||||
|
previous instruction is left open, and deliberately: this is the conservative model, and
|
||||||
|
pipelining is a decision to make while drawing the hardware rather than one to inherit from
|
||||||
|
an emulator.
|
||||||
| `-f`, `--fast` | Run as fast as the host allows, ignoring the emulated cycle rate. |
|
| `-f`, `--fast` | Run as fast as the host allows, ignoring the emulated cycle rate. |
|
||||||
| `-D`, `--disk <file>` | Attach a disk image, creating a 128K one if the file is not there. |
|
| `-D`, `--disk <file>` | Attach a disk image, creating a 128K one if the file is not there. |
|
||||||
| `-W`, `--write-protect` | Attach the disk read only. A disk whose image the host will not let you write is read only whether you ask for this or not. |
|
| `-W`, `--write-protect` | Attach the disk read only. A disk whose image the host will not let you write is read only whether you ask for this or not. |
|
||||||
|
|||||||
+100
-66
@@ -11,6 +11,40 @@ uint16_t shiftRegister;
|
|||||||
|
|
||||||
// Reads one entry out of a vector table. Most significant byte first, matching the
|
// Reads one entry out of a vector table. Most significant byte first, matching the
|
||||||
// branch instructions and both file formats.
|
// branch instructions and both file formats.
|
||||||
|
// ---- Every touch of memory, and what it costs ----
|
||||||
|
//
|
||||||
|
// One access, one cycle. These exist so that the cost is counted in the one place the
|
||||||
|
// access happens, rather than in a table of per instruction costs kept somewhere else -
|
||||||
|
// a table like that is a second copy of what the code does, and the two drift.
|
||||||
|
static inline uint8_t fetchProgram(CPURegisters *cpu, uint16_t at) {
|
||||||
|
cpu->busCycles++;
|
||||||
|
return cpu->Program[at];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t readData(CPURegisters *cpu, uint16_t at) {
|
||||||
|
cpu->busCycles++;
|
||||||
|
return cpu->Data[at];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void writeData(CPURegisters *cpu, uint16_t at, uint8_t value) {
|
||||||
|
cpu->busCycles++;
|
||||||
|
cpu->Data[at] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A device port is reached over the same bus as memory, so it costs the same. That is a
|
||||||
|
// claim about the hardware rather than an observation - a machine could put devices
|
||||||
|
// somewhere faster or slower - and it is the simple thing until there is a reason to say
|
||||||
|
// otherwise.
|
||||||
|
static inline void portOut(CPURegisters *cpu, uint8_t value, uint8_t port) {
|
||||||
|
cpu->busCycles++;
|
||||||
|
OutputHandler(value, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t portIn(CPURegisters *cpu, uint8_t port) {
|
||||||
|
cpu->busCycles++;
|
||||||
|
return InputHandler(port);
|
||||||
|
}
|
||||||
|
|
||||||
static uint16_t readVector(const uint8_t *programMemory, uint16_t base, uint8_t index) {
|
static uint16_t readVector(const uint8_t *programMemory, uint16_t base, uint8_t index) {
|
||||||
uint16_t address = base + (uint16_t)index * VECTOR_ENTRY_BYTES;
|
uint16_t address = base + (uint16_t)index * VECTOR_ENTRY_BYTES;
|
||||||
return ((uint16_t)programMemory[address] << 8) | (uint16_t)programMemory[address + 1];
|
return ((uint16_t)programMemory[address] << 8) | (uint16_t)programMemory[address + 1];
|
||||||
@@ -38,23 +72,23 @@ static uint8_t enterInterrupt(CPURegisters *cpu, uint16_t base, uint8_t index, u
|
|||||||
}
|
}
|
||||||
// Order mirrors genericCall exactly: low byte then high byte, lowest numbered Data
|
// Order mirrors genericCall exactly: low byte then high byte, lowest numbered Data
|
||||||
// Pointer first, so that anything walking the Stack sees a familiar shape.
|
// Pointer first, so that anything walking the Stack sees a familiar shape.
|
||||||
cpu->Data[cpu->StackPointer] = resumeAddress & 0xFF;
|
writeData(cpu, cpu->StackPointer, resumeAddress & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
cpu->Data[cpu->StackPointer] = (resumeAddress >> 8) & 0xFF;
|
writeData(cpu, cpu->StackPointer, (resumeAddress >> 8) & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
for (int i = 0; i < DATA_POINTERS; i++) {
|
for (int i = 0; i < DATA_POINTERS; i++) {
|
||||||
cpu->Data[cpu->StackPointer] = cpu->DataPointer[i] & 0xFF;
|
writeData(cpu, cpu->StackPointer, cpu->DataPointer[i] & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
cpu->Data[cpu->StackPointer] = (cpu->DataPointer[i] >> 8) & 0xFF;
|
writeData(cpu, cpu->StackPointer, (cpu->DataPointer[i] >> 8) & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
}
|
}
|
||||||
cpu->Data[cpu->StackPointer] = cpu->B;
|
writeData(cpu, cpu->StackPointer, cpu->B);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
cpu->Data[cpu->StackPointer] = cpu->A;
|
writeData(cpu, cpu->StackPointer, cpu->A);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
cpu->Data[cpu->StackPointer] = cpu->Q;
|
writeData(cpu, cpu->StackPointer, cpu->Q);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
cpu->Data[cpu->StackPointer] = cpu->Status;
|
writeData(cpu, cpu->StackPointer, cpu->Status);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
// A handler runs with hardware interrupts held off unless it says otherwise, so an
|
// A handler runs with hardware interrupts held off unless it says otherwise, so an
|
||||||
// interrupt cannot arrive inside the handler for another one and grow the Stack
|
// interrupt cannot arrive inside the handler for another one and grow the Stack
|
||||||
@@ -112,33 +146,33 @@ void genericBranch(CPURegisters *cpu){
|
|||||||
// Byte order is imporant. Most Significant first, then Least Significant.
|
// Byte order is imporant. Most Significant first, then Least Significant.
|
||||||
cpu->ProgramCounter++; // Move to the next byte. (MSB)
|
cpu->ProgramCounter++; // Move to the next byte. (MSB)
|
||||||
uint16_t DestinationAddress;
|
uint16_t DestinationAddress;
|
||||||
DestinationAddress = (uint16_t)cpu->Program[cpu->ProgramCounter] << 8; // Cast the 8 bit value to a 16 bit value and shifts it up to the high byte.
|
DestinationAddress = (uint16_t)fetchProgram(cpu, cpu->ProgramCounter) << 8; // Cast the 8 bit value to a 16 bit value and shifts it up to the high byte.
|
||||||
cpu->ProgramCounter++; // Move to the next byte. (LSB)
|
cpu->ProgramCounter++; // Move to the next byte. (LSB)
|
||||||
DestinationAddress = DestinationAddress | (uint16_t)cpu->Program[cpu->ProgramCounter]; // Cast the 8 bit value to a 16 bit value and or it to add it to the desination.
|
DestinationAddress = DestinationAddress | (uint16_t)fetchProgram(cpu, cpu->ProgramCounter); // Cast the 8 bit value to a 16 bit value and or it to add it to the desination.
|
||||||
cpu->ProgramCounter = DestinationAddress-1;
|
cpu->ProgramCounter = DestinationAddress-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void genericCall(CPURegisters *cpu){
|
void genericCall(CPURegisters *cpu){
|
||||||
// Order, low byte, high byte
|
// Order, low byte, high byte
|
||||||
cpu->Data[cpu->StackPointer] = cpu->ProgramCounter & 0xFF;
|
writeData(cpu, cpu->StackPointer, cpu->ProgramCounter & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
cpu->Data[cpu->StackPointer] = (cpu->ProgramCounter >> 8) & 0xFF;
|
writeData(cpu, cpu->StackPointer, (cpu->ProgramCounter >> 8) & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
// Push the preserved Data Pointers to the Stack, lowest numbered first.
|
// Push the preserved Data Pointers to the Stack, lowest numbered first.
|
||||||
// Order within each one, low byte, high byte.
|
// Order within each one, low byte, high byte.
|
||||||
// The pointers above PRESERVED_DATA_POINTERS are deliberately left alone, so a
|
// The pointers above PRESERVED_DATA_POINTERS are deliberately left alone, so a
|
||||||
// subroutine can use one to hand an address back to whoever called it.
|
// subroutine can use one to hand an address back to whoever called it.
|
||||||
for (int i = 0; i < PRESERVED_DATA_POINTERS; i++) {
|
for (int i = 0; i < PRESERVED_DATA_POINTERS; i++) {
|
||||||
cpu->Data[cpu->StackPointer] = cpu->DataPointer[i] & 0xFF;
|
writeData(cpu, cpu->StackPointer, cpu->DataPointer[i] & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
cpu->Data[cpu->StackPointer] = (cpu->DataPointer[i] >> 8) & 0xFF;
|
writeData(cpu, cpu->StackPointer, (cpu->DataPointer[i] >> 8) & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
}
|
}
|
||||||
// Push B to the Stack.
|
// Push B to the Stack.
|
||||||
cpu->Data[cpu->StackPointer] = cpu->B;
|
writeData(cpu, cpu->StackPointer, cpu->B);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
// Push A to the Stack.
|
// Push A to the Stack.
|
||||||
cpu->Data[cpu->StackPointer] = cpu->A;
|
writeData(cpu, cpu->StackPointer, cpu->A);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
// Perform a Generic Branch to the Address.
|
// Perform a Generic Branch to the Address.
|
||||||
genericBranch(cpu);
|
genericBranch(cpu);
|
||||||
@@ -150,7 +184,7 @@ uint16_t *selectDataPointer(CPURegisters *cpu) {
|
|||||||
// rather than rejected, the way a narrow field in hardware would be. It is the
|
// rather than rejected, the way a narrow field in hardware would be. It is the
|
||||||
// assembler's job to refuse to emit one in the first place.
|
// assembler's job to refuse to emit one in the first place.
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
return &cpu->DataPointer[cpu->Program[cpu->ProgramCounter] & (DATA_POINTERS - 1)];
|
return &cpu->DataPointer[fetchProgram(cpu, cpu->ProgramCounter) & (DATA_POINTERS - 1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||||
@@ -278,9 +312,9 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// It must be returned from with RRET. The two frames are different sizes, so
|
// It must be returned from with RRET. The two frames are different sizes, so
|
||||||
// returning from one through the other walks the Stack to somewhere that was
|
// returning from one through the other walks the Stack to somewhere that was
|
||||||
// never a return address.
|
// never a return address.
|
||||||
cpu->Data[cpu->StackPointer] = cpu->ProgramCounter & 0xFF;
|
writeData(cpu, cpu->StackPointer, cpu->ProgramCounter & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
cpu->Data[cpu->StackPointer] = (cpu->ProgramCounter >> 8) & 0xFF;
|
writeData(cpu, cpu->StackPointer, (cpu->ProgramCounter >> 8) & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
genericBranch(cpu);
|
genericBranch(cpu);
|
||||||
break;
|
break;
|
||||||
@@ -326,7 +360,7 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// something a device asked for.
|
// something a device asked for.
|
||||||
uint16_t site = cpu->ProgramCounter;
|
uint16_t site = cpu->ProgramCounter;
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
uint8_t vector = cpu->Program[cpu->ProgramCounter];
|
uint8_t vector = fetchProgram(cpu, cpu->ProgramCounter);
|
||||||
// Execution resumes after the operand, which the Program Counter is sitting
|
// Execution resumes after the operand, which the Program Counter is sitting
|
||||||
// on, so the resume address is one further on than that.
|
// on, so the resume address is one further on than that.
|
||||||
if (enterInterrupt(cpu, SOFTWARE_VECTOR_BASE, vector, cpu->ProgramCounter + 1)) {
|
if (enterInterrupt(cpu, SOFTWARE_VECTOR_BASE, vector, cpu->ProgramCounter + 1)) {
|
||||||
@@ -339,24 +373,24 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// RETI - Return from an interrupt. Pops the frame in the exact reverse of
|
// RETI - Return from an interrupt. Pops the frame in the exact reverse of
|
||||||
// the order enterInterrupt pushed it.
|
// the order enterInterrupt pushed it.
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->Status = cpu->Data[cpu->StackPointer];
|
cpu->Status = readData(cpu, cpu->StackPointer);
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->Q = cpu->Data[cpu->StackPointer];
|
cpu->Q = readData(cpu, cpu->StackPointer);
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->A = cpu->Data[cpu->StackPointer];
|
cpu->A = readData(cpu, cpu->StackPointer);
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->B = cpu->Data[cpu->StackPointer];
|
cpu->B = readData(cpu, cpu->StackPointer);
|
||||||
for (int i = DATA_POINTERS - 1; i >= 0; i--) {
|
for (int i = DATA_POINTERS - 1; i >= 0; i--) {
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->DataPointer[i] = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
|
cpu->DataPointer[i] = (uint16_t)readData(cpu, cpu->StackPointer) << 8;
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->DataPointer[i] |= (uint16_t)cpu->Data[cpu->StackPointer];
|
cpu->DataPointer[i] |= (uint16_t)readData(cpu, cpu->StackPointer);
|
||||||
}
|
}
|
||||||
uint16_t resumeAddress;
|
uint16_t resumeAddress;
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
resumeAddress = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
|
resumeAddress = (uint16_t)readData(cpu, cpu->StackPointer) << 8;
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
resumeAddress = resumeAddress | (uint16_t)cpu->Data[cpu->StackPointer];
|
resumeAddress = resumeAddress | (uint16_t)readData(cpu, cpu->StackPointer);
|
||||||
// The frame holds the address to carry on from. The Program Counter is
|
// The frame holds the address to carry on from. The Program Counter is
|
||||||
// stepped after every instruction, so land one short of it. RET does the
|
// stepped after every instruction, so land one short of it. RET does the
|
||||||
// same job with its +2, for the same reason.
|
// same job with its +2, for the same reason.
|
||||||
@@ -365,9 +399,9 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
case 0x1E:
|
case 0x1E:
|
||||||
// RRET - Return from an RCAL, taking back nothing but the return address.
|
// RRET - Return from an RCAL, taking back nothing but the return address.
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->ProgramCounter = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
|
cpu->ProgramCounter = (uint16_t)readData(cpu, cpu->StackPointer) << 8;
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->ProgramCounter = cpu->ProgramCounter | (uint16_t)cpu->Data[cpu->StackPointer];
|
cpu->ProgramCounter = cpu->ProgramCounter | (uint16_t)readData(cpu, cpu->StackPointer);
|
||||||
// Two on, to step over the address the RCAL branched through, exactly as RET
|
// Two on, to step over the address the RCAL branched through, exactly as RET
|
||||||
// does. Everything else RET restores, this deliberately does not.
|
// does. Everything else RET restores, this deliberately does not.
|
||||||
cpu->ProgramCounter += 2;
|
cpu->ProgramCounter += 2;
|
||||||
@@ -376,24 +410,24 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// RET - Return from subroutine, restore the registers and set the Program Counter to the Return Address.
|
// RET - Return from subroutine, restore the registers and set the Program Counter to the Return Address.
|
||||||
// Pop A from the Stack.
|
// Pop A from the Stack.
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->A = cpu->Data[cpu->StackPointer];
|
cpu->A = readData(cpu, cpu->StackPointer);
|
||||||
// Pop B from the Stack.
|
// Pop B from the Stack.
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->B = cpu->Data[cpu->StackPointer];
|
cpu->B = readData(cpu, cpu->StackPointer);
|
||||||
// Pop the preserved Data Pointers from the Stack. This walks the pointers
|
// Pop the preserved Data Pointers from the Stack. This walks the pointers
|
||||||
// in the opposite order to genericCall, and takes the high byte before the
|
// in the opposite order to genericCall, and takes the high byte before the
|
||||||
// low byte, so that it exactly mirrors the way they were pushed.
|
// low byte, so that it exactly mirrors the way they were pushed.
|
||||||
for (int i = PRESERVED_DATA_POINTERS - 1; i >= 0; i--) {
|
for (int i = PRESERVED_DATA_POINTERS - 1; i >= 0; i--) {
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->DataPointer[i] = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
|
cpu->DataPointer[i] = (uint16_t)readData(cpu, cpu->StackPointer) << 8;
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->DataPointer[i] |= (uint16_t)cpu->Data[cpu->StackPointer];
|
cpu->DataPointer[i] |= (uint16_t)readData(cpu, cpu->StackPointer);
|
||||||
}
|
}
|
||||||
// Pop the Return Address from the Stack.
|
// Pop the Return Address from the Stack.
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->ProgramCounter = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
|
cpu->ProgramCounter = (uint16_t)readData(cpu, cpu->StackPointer) << 8;
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->ProgramCounter = cpu->ProgramCounter | (uint16_t)cpu->Data[cpu->StackPointer];
|
cpu->ProgramCounter = cpu->ProgramCounter | (uint16_t)readData(cpu, cpu->StackPointer);
|
||||||
// Add 2 to the Program Counter to skip over the address when it returns.
|
// Add 2 to the Program Counter to skip over the address when it returns.
|
||||||
cpu->ProgramCounter += 2;
|
cpu->ProgramCounter += 2;
|
||||||
|
|
||||||
@@ -452,12 +486,12 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
case 0x26:
|
case 0x26:
|
||||||
// INIA - Initialize A Immediately from Program Memory.
|
// INIA - Initialize A Immediately from Program Memory.
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
cpu->A = cpu->Program[cpu->ProgramCounter];
|
cpu->A = fetchProgram(cpu, cpu->ProgramCounter);
|
||||||
break;
|
break;
|
||||||
case 0x27:
|
case 0x27:
|
||||||
// INIB - Initialize A Immediately from Program Memory.
|
// INIB - Initialize A Immediately from Program Memory.
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
cpu->B = cpu->Program[cpu->ProgramCounter];
|
cpu->B = fetchProgram(cpu, cpu->ProgramCounter);
|
||||||
break;
|
break;
|
||||||
case 0x28:
|
case 0x28:
|
||||||
// CCF - Clear the Carry Flag.
|
// CCF - Clear the Carry Flag.
|
||||||
@@ -486,17 +520,17 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
//
|
//
|
||||||
case 0x30:
|
case 0x30:
|
||||||
// PSHQ - Push Q to the Stack.
|
// PSHQ - Push Q to the Stack.
|
||||||
cpu->Data[cpu->StackPointer] = cpu->Q;
|
writeData(cpu, cpu->StackPointer, cpu->Q);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
break;
|
break;
|
||||||
case 0x31:
|
case 0x31:
|
||||||
// PSHA - Push A to the Stack.
|
// PSHA - Push A to the Stack.
|
||||||
cpu->Data[cpu->StackPointer] = cpu->A;
|
writeData(cpu, cpu->StackPointer, cpu->A);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
break;
|
break;
|
||||||
case 0x32:
|
case 0x32:
|
||||||
// PSHB - Push B to the Stack.
|
// PSHB - Push B to the Stack.
|
||||||
cpu->Data[cpu->StackPointer] = cpu->B;
|
writeData(cpu, cpu->StackPointer, cpu->B);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
break;
|
break;
|
||||||
case 0x33: {
|
case 0x33: {
|
||||||
@@ -504,30 +538,30 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// Order, high byte, low byte
|
// Order, high byte, low byte
|
||||||
// This ordering makes it easier to add offsets with register math.
|
// This ordering makes it easier to add offsets with register math.
|
||||||
uint16_t pushed = *selectDataPointer(cpu);
|
uint16_t pushed = *selectDataPointer(cpu);
|
||||||
cpu->Data[cpu->StackPointer] = (pushed >> 8) & 0xFF;
|
writeData(cpu, cpu->StackPointer, (pushed >> 8) & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
cpu->Data[cpu->StackPointer] = pushed & 0xFF;
|
writeData(cpu, cpu->StackPointer, pushed & 0xFF);
|
||||||
cpu->StackPointer--;
|
cpu->StackPointer--;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x34:
|
case 0x34:
|
||||||
// POPA - Pop A from the Stack.
|
// POPA - Pop A from the Stack.
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->A = cpu->Data[cpu->StackPointer];
|
cpu->A = readData(cpu, cpu->StackPointer);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 0x35:
|
case 0x35:
|
||||||
// POPB - Pop B from the Stack.
|
// POPB - Pop B from the Stack.
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
cpu->B = cpu->Data[cpu->StackPointer];
|
cpu->B = readData(cpu, cpu->StackPointer);
|
||||||
break;
|
break;
|
||||||
case 0x36: {
|
case 0x36: {
|
||||||
// POPD - Pop a Data Address from the Stack into the selected Data Pointer.
|
// POPD - Pop a Data Address from the Stack into the selected Data Pointer.
|
||||||
uint16_t *popped = selectDataPointer(cpu);
|
uint16_t *popped = selectDataPointer(cpu);
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
*popped = (uint16_t)cpu->Data[cpu->StackPointer];
|
*popped = (uint16_t)readData(cpu, cpu->StackPointer);
|
||||||
cpu->StackPointer++;
|
cpu->StackPointer++;
|
||||||
*popped |= (uint16_t)cpu->Data[cpu->StackPointer] << 8;
|
*popped |= (uint16_t)readData(cpu, cpu->StackPointer) << 8;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
//
|
//
|
||||||
@@ -543,32 +577,32 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
break;
|
break;
|
||||||
case 0x42:
|
case 0x42:
|
||||||
// LDA - Load A from Data.
|
// LDA - Load A from Data.
|
||||||
cpu->A = cpu->Data[*selectDataPointer(cpu)];
|
cpu->A = readData(cpu, *selectDataPointer(cpu));
|
||||||
break;
|
break;
|
||||||
case 0x43:
|
case 0x43:
|
||||||
// LDB - Load B from Data.
|
// LDB - Load B from Data.
|
||||||
cpu->B = cpu->Data[*selectDataPointer(cpu)];
|
cpu->B = readData(cpu, *selectDataPointer(cpu));
|
||||||
break;
|
break;
|
||||||
case 0x44:
|
case 0x44:
|
||||||
// STQ - Store Q into Data.
|
// STQ - Store Q into Data.
|
||||||
cpu->Data[*selectDataPointer(cpu)] = cpu->Q;
|
writeData(cpu, *selectDataPointer(cpu), cpu->Q);
|
||||||
break;
|
break;
|
||||||
case 0x45:
|
case 0x45:
|
||||||
// STA - Store A into Data.
|
// STA - Store A into Data.
|
||||||
cpu->Data[*selectDataPointer(cpu)] = cpu->A;
|
writeData(cpu, *selectDataPointer(cpu), cpu->A);
|
||||||
break;
|
break;
|
||||||
case 0x46:
|
case 0x46:
|
||||||
// STB - Store B into Data.
|
// STB - Store B into Data.
|
||||||
cpu->Data[*selectDataPointer(cpu)] = cpu->B;
|
writeData(cpu, *selectDataPointer(cpu), cpu->B);
|
||||||
break;
|
break;
|
||||||
case 0x47: {
|
case 0x47: {
|
||||||
// SETD - Set the selected Data Pointer.
|
// SETD - Set the selected Data Pointer.
|
||||||
uint16_t *destination = selectDataPointer(cpu);
|
uint16_t *destination = selectDataPointer(cpu);
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
uint16_t Address;
|
uint16_t Address;
|
||||||
Address = (uint16_t)cpu->Program[cpu->ProgramCounter] << 8; // Cast the 8 bits to a 16 bit value and shift them to the high byte.
|
Address = (uint16_t)fetchProgram(cpu, cpu->ProgramCounter) << 8; // Cast the 8 bits to a 16 bit value and shift them to the high byte.
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
Address |= (uint16_t)cpu->Program[cpu->ProgramCounter];
|
Address |= (uint16_t)fetchProgram(cpu, cpu->ProgramCounter);
|
||||||
*destination = Address;
|
*destination = Address;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -576,14 +610,14 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// DPUP - Offset the selected Data Pointer up by the value of the next byte of Program Memory.
|
// DPUP - Offset the selected Data Pointer up by the value of the next byte of Program Memory.
|
||||||
uint16_t *target = selectDataPointer(cpu);
|
uint16_t *target = selectDataPointer(cpu);
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
*target += cpu->Program[cpu->ProgramCounter];
|
*target += fetchProgram(cpu, cpu->ProgramCounter);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x49: {
|
case 0x49: {
|
||||||
// DPDN - Offset the selected Data Pointer down by the value of the next byte of Program Memory.
|
// DPDN - Offset the selected Data Pointer down by the value of the next byte of Program Memory.
|
||||||
uint16_t *target = selectDataPointer(cpu);
|
uint16_t *target = selectDataPointer(cpu);
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
*target -= cpu->Program[cpu->ProgramCounter];
|
*target -= fetchProgram(cpu, cpu->ProgramCounter);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x4E: {
|
case 0x4E: {
|
||||||
@@ -624,8 +658,8 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// follows the pointer in DP0 rather than tripping over itself.
|
// follows the pointer in DP0 rather than tripping over itself.
|
||||||
uint16_t *destination = selectDataPointer(cpu);
|
uint16_t *destination = selectDataPointer(cpu);
|
||||||
uint16_t source = *selectDataPointer(cpu);
|
uint16_t source = *selectDataPointer(cpu);
|
||||||
*destination = (uint16_t)cpu->Data[source] << 8;
|
*destination = (uint16_t)readData(cpu, source) << 8;
|
||||||
*destination |= (uint16_t)cpu->Data[(uint16_t)(source + 1)];
|
*destination |= (uint16_t)readData(cpu, (uint16_t)(source + 1));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x4B: {
|
case 0x4B: {
|
||||||
@@ -634,8 +668,8 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// Data Memory when the pointer sits at the very top of it.
|
// Data Memory when the pointer sits at the very top of it.
|
||||||
uint16_t value = *selectDataPointer(cpu);
|
uint16_t value = *selectDataPointer(cpu);
|
||||||
uint16_t address = *selectDataPointer(cpu);
|
uint16_t address = *selectDataPointer(cpu);
|
||||||
cpu->Data[address] = (value >> 8) & 0xFF;
|
writeData(cpu, address, (value >> 8) & 0xFF);
|
||||||
cpu->Data[(uint16_t)(address + 1)] = value & 0xFF;
|
writeData(cpu, (uint16_t)(address + 1), value & 0xFF);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x4C: {
|
case 0x4C: {
|
||||||
@@ -674,7 +708,7 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// OUTQ - Write the value of Q to an output port.
|
// OUTQ - Write the value of Q to an output port.
|
||||||
uint16_t site = cpu->ProgramCounter;
|
uint16_t site = cpu->ProgramCounter;
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
OutputHandler(cpu->Q, cpu->Program[cpu->ProgramCounter]);
|
portOut(cpu, cpu->Q, fetchProgram(cpu, cpu->ProgramCounter));
|
||||||
answerRefusal(cpu, site);
|
answerRefusal(cpu, site);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -682,7 +716,7 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// OUTA - Write the value of A to an output port.
|
// OUTA - Write the value of A to an output port.
|
||||||
uint16_t site = cpu->ProgramCounter;
|
uint16_t site = cpu->ProgramCounter;
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
OutputHandler(cpu->A, cpu->Program[cpu->ProgramCounter]);
|
portOut(cpu, cpu->A, fetchProgram(cpu, cpu->ProgramCounter));
|
||||||
answerRefusal(cpu, site);
|
answerRefusal(cpu, site);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -690,7 +724,7 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// OUTB - Write the value of B to an output port.
|
// OUTB - Write the value of B to an output port.
|
||||||
uint16_t site = cpu->ProgramCounter;
|
uint16_t site = cpu->ProgramCounter;
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
OutputHandler(cpu->B, cpu->Program[cpu->ProgramCounter]);
|
portOut(cpu, cpu->B, fetchProgram(cpu, cpu->ProgramCounter));
|
||||||
answerRefusal(cpu, site);
|
answerRefusal(cpu, site);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -701,7 +735,7 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// INA - Read an Input to A.
|
// INA - Read an Input to A.
|
||||||
uint16_t site = cpu->ProgramCounter;
|
uint16_t site = cpu->ProgramCounter;
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
cpu->A = InputHandler(cpu->Program[cpu->ProgramCounter]);
|
cpu->A = portIn(cpu, fetchProgram(cpu, cpu->ProgramCounter));
|
||||||
answerRefusal(cpu, site);
|
answerRefusal(cpu, site);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -709,7 +743,7 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
|||||||
// INB - Read an Input to B.
|
// INB - Read an Input to B.
|
||||||
uint16_t site = cpu->ProgramCounter;
|
uint16_t site = cpu->ProgramCounter;
|
||||||
cpu->ProgramCounter++;
|
cpu->ProgramCounter++;
|
||||||
cpu->B = InputHandler(cpu->Program[cpu->ProgramCounter]);
|
cpu->B = portIn(cpu, fetchProgram(cpu, cpu->ProgramCounter));
|
||||||
answerRefusal(cpu, site);
|
answerRefusal(cpu, site);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -762,7 +796,7 @@ void stepCPU(CPURegisters *cpu) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The CPU is not halted, so do a cycle.
|
// The CPU is not halted, so do a cycle.
|
||||||
if (executeOperation(cpu->Program[cpu->ProgramCounter], cpu)) {
|
if (executeOperation(fetchProgram(cpu, cpu->ProgramCounter), cpu)) {
|
||||||
// Nothing decodes that byte. Hand it to the fault vector, which gets the
|
// Nothing decodes that byte. Hand it to the fault vector, which gets the
|
||||||
// address of the offending byte itself rather than the one after it, so
|
// address of the offending byte itself rather than the one after it, so
|
||||||
// that a handler can read the byte that failed and say what it was.
|
// that a handler can read the byte that failed and say what it was.
|
||||||
|
|||||||
@@ -62,6 +62,19 @@ typedef struct {
|
|||||||
uint16_t StackPointer;
|
uint16_t StackPointer;
|
||||||
uint8_t *Program;
|
uint8_t *Program;
|
||||||
uint8_t *Data;
|
uint8_t *Data;
|
||||||
|
|
||||||
|
// ---- What the machine has cost so far ----
|
||||||
|
//
|
||||||
|
// ONE BUS ACCESS IS ONE CYCLE, and every access goes through it: fetching an opcode,
|
||||||
|
// fetching the bytes after it, reading or writing Data Memory, pushing or popping the
|
||||||
|
// Stack, and reaching a device port. Nothing is overlapped - no fetching the next
|
||||||
|
// instruction while this one finishes - because that is a thing hardware may or may
|
||||||
|
// not do and this is the model to design against before deciding.
|
||||||
|
//
|
||||||
|
// It replaces counting instructions. Counting instructions said an RSTA and a SETD
|
||||||
|
// cost the same, and that a CALL moving ten bytes of Stack cost what a branch costs,
|
||||||
|
// which is not true of any machine anybody could build.
|
||||||
|
unsigned long busCycles;
|
||||||
// Set alongside the Fault Flag, and read only by whatever reports the stop.
|
// Set alongside the Fault Flag, and read only by whatever reports the stop.
|
||||||
uint8_t Fault; // A FaultCause.
|
uint8_t Fault; // A FaultCause.
|
||||||
uint8_t FaultVector; // Which vector was empty, when Fault is FAULT_NO_HANDLER.
|
uint8_t FaultVector; // Which vector was empty, when Fault is FAULT_NO_HANDLER.
|
||||||
|
|||||||
@@ -126,9 +126,17 @@ int main (int argc, char *argv[]) {
|
|||||||
} else {
|
} else {
|
||||||
cycles = cycle_timer_tick(&timer);
|
cycles = cycle_timer_tick(&timer);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < cycles; i++) {
|
// ---- Spending a budget of cycles, not running a count of instructions ----
|
||||||
|
//
|
||||||
|
// An instruction costs what it touches, so a batch is finished when the cycles are
|
||||||
|
// gone rather than after so many steps. In debug mode the budget is one, and any
|
||||||
|
// instruction costs at least the fetch of its own opcode, so one step still runs.
|
||||||
|
for (long spent = 0; spent < cycles; ) {
|
||||||
|
unsigned long before = cpu.busCycles;
|
||||||
stepCPU(&cpu);
|
stepCPU(&cpu);
|
||||||
cycleCount++;
|
unsigned long took = cpu.busCycles - before;
|
||||||
|
spent += (long)took;
|
||||||
|
cycleCount += took;
|
||||||
if (cpu.Status & STATUS_HALT) {
|
if (cpu.Status & STATUS_HALT) {
|
||||||
// We've halted.
|
// We've halted.
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -222,5 +222,240 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Execution stopped. (cycle limit reached)
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
##
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
##
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
##
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
##
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
|
Execution stopped. (cycle limit reached)
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -222,5 +222,261 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
##
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
##
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
##
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
##
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[H
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# #
|
||||||
|
##
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Execution stopped. (cycle limit reached)
|
Execution stopped. (cycle limit reached)
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ SplitBit calculator (+ - * & | ^), Q quits.
|
|||||||
|
|
||||||
> 18
|
> 18
|
||||||
|
|
||||||
> Goodbye!Execution halted after 2960 cycles.
|
> Goodbye!Execution halted.
|
||||||
[exit 0]
|
[exit 0]
|
||||||
|
|||||||
+8
-4
@@ -460,15 +460,19 @@ inputTestOld | testPrograms/inputTest.asm | run | inputTest
|
|||||||
replCalculator | Examples/replCalculator.asm | run | replCalculator.in | -
|
replCalculator | Examples/replCalculator.asm | run | replCalculator.in | -
|
||||||
|
|
||||||
# ---- Programs that run forever by design, bounded by a cycle count ----
|
# ---- Programs that run forever by design, bounded by a cycle count ----
|
||||||
# 3,000,000 cycles is about fourteen generations of the glider, which puts
|
# 11,200,000 cycles is about fourteen generations of the glider, which puts evolveBoard and
|
||||||
# evolveBoard and its pointer juggling through its paces many times over.
|
# its pointer juggling through its paces many times over.
|
||||||
16x16Life | Examples/gameOfLife/16x16Life.asm | run | - | 3000000
|
#
|
||||||
|
# It was 3,000,000, and the number changed rather than the amount of work: a cycle used to
|
||||||
|
# be an instruction and is now a memory access, and the average SplitBit instruction makes
|
||||||
|
# 3.72 of those. Nothing about these two programs changed at all.
|
||||||
|
16x16Life | Examples/gameOfLife/16x16Life.asm | run | - | 11200000
|
||||||
# The four pointer rewrite, on the same budget so the two can be compared directly.
|
# The four pointer rewrite, on the same budget so the two can be compared directly.
|
||||||
# Note that this cannot show a speed difference: frameDelay is 255 by 255 and swamps
|
# Note that this cannot show a speed difference: frameDelay is 255 by 255 and swamps
|
||||||
# the simulation, so both versions render the same fourteen generations and produce
|
# the simulation, so both versions render the same fourteen generations and produce
|
||||||
# identical bytes. What it checks is that the rewrite still evolves the board the same
|
# identical bytes. What it checks is that the rewrite still evolves the board the same
|
||||||
# way, which is what a regression test is for.
|
# way, which is what a regression test is for.
|
||||||
16x16LifeModern | Examples/gameOfLife/16x16LifeModern.asm | run | - | 3000000
|
16x16LifeModern | Examples/gameOfLife/16x16LifeModern.asm | run | - | 11200000
|
||||||
|
|
||||||
# ---- Libraries: no entry point, so only check that they assemble ----
|
# ---- Libraries: no entry point, so only check that they assemble ----
|
||||||
# The CosmOS libraries assemble on their own, unlike print.asm below, which cannot: it
|
# The CosmOS libraries assemble on their own, unlike print.asm below, which cannot: it
|
||||||
|
|||||||
+7
-2
@@ -98,8 +98,13 @@ trim() {
|
|||||||
# own rather than every test carrying the measurement and nothing asserting anything about
|
# own rather than every test carrying the measurement and nothing asserting anything about
|
||||||
# it.
|
# it.
|
||||||
settle() {
|
settle() {
|
||||||
sed -i -E 's/^Execution halted after [0-9]+ cycles\.$/Execution halted./;
|
# NOT ANCHORED TO THE START OF A LINE. A program whose last output has no newline on it
|
||||||
s/^Execution stopped after [0-9]+ cycles\. \(cycle limit reached\)$/Execution stopped. (cycle limit reached)/' "$1"
|
# leaves the cursor mid line, and the halt message is printed there - so the count this
|
||||||
|
# exists to remove was sitting inside a line rather than at the head of one, and
|
||||||
|
# survived. replCalculator is the one that does that, and it was the only test to churn
|
||||||
|
# when the machine started charging for memory instead of counting instructions.
|
||||||
|
sed -i -E 's/Execution halted after [0-9]+ cycles\./Execution halted./;
|
||||||
|
s/Execution stopped after [0-9]+ cycles\. \(cycle limit reached\)/Execution stopped. (cycle limit reached)/' "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
check() {
|
check() {
|
||||||
|
|||||||
Reference in New Issue
Block a user