Various bug fixes to assembler, added more data pointers.

This commit is contained in:
Anachronaut
2026-08-13 23:41:22 -04:00
parent b50210d127
commit c2440ae5fa
38 changed files with 1028 additions and 236 deletions
+76 -43
View File
@@ -14,7 +14,10 @@ void initializeCPU(CPURegisters *cpu, uint8_t *programMemory, uint8_t *dataMemor
cpu->Q = 0;
cpu->Status = 0;
cpu->ProgramCounter = 0x0000;
cpu->DataPointer = 0x0000;
// Every Data Pointer starts at the bottom of Data Memory.
for (int i = 0; i < DATA_POINTERS; i++) {
cpu->DataPointer[i] = 0x0000;
}
cpu->StackPointer = 0xFFFF;
cpu->Program = programMemory;
cpu->Data = dataMemory;
@@ -37,12 +40,16 @@ void genericCall(CPURegisters *cpu){
cpu->StackPointer--;
cpu->Data[cpu->StackPointer] = (cpu->ProgramCounter >> 8) & 0xFF;
cpu->StackPointer--;
// Push the Data Pointer to the Stack.
// Order, low byte, high byte
cpu->Data[cpu->StackPointer] = cpu->DataPointer & 0xFF;
cpu->StackPointer--;
cpu->Data[cpu->StackPointer] = (cpu->DataPointer >> 8) & 0xFF;
cpu->StackPointer--;
// Push the preserved Data Pointers to the Stack, lowest numbered first.
// Order within each one, low byte, high byte.
// 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.
for (int i = 0; i < PRESERVED_DATA_POINTERS; i++) {
cpu->Data[cpu->StackPointer] = cpu->DataPointer[i] & 0xFF;
cpu->StackPointer--;
cpu->Data[cpu->StackPointer] = (cpu->DataPointer[i] >> 8) & 0xFF;
cpu->StackPointer--;
}
// Push B to the Stack.
cpu->Data[cpu->StackPointer] = cpu->B;
cpu->StackPointer--;
@@ -53,6 +60,15 @@ void genericCall(CPURegisters *cpu){
genericBranch(cpu);
}
uint16_t *selectDataPointer(CPURegisters *cpu) {
// Every instruction that works through a Data Pointer names which one in the
// byte immediately after the opcode. Out of range selectors are masked down
// 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.
cpu->ProgramCounter++;
return &cpu->DataPointer[cpu->Program[cpu->ProgramCounter] & (DATA_POINTERS - 1)];
}
uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
switch(Instruction) {
// 0x - Arithmetic and Logic Operations.
@@ -164,11 +180,15 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
// Pop B from the Stack.
cpu->StackPointer++;
cpu->B = cpu->Data[cpu->StackPointer];
// Pop the Data Pointer from the Stack.
cpu->StackPointer++;
cpu->DataPointer = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
cpu->StackPointer++;
cpu->DataPointer |= (uint16_t)cpu->Data[cpu->StackPointer];
// 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
// low byte, so that it exactly mirrors the way they were pushed.
for (int i = PRESERVED_DATA_POINTERS - 1; i >= 0; i--) {
cpu->StackPointer++;
cpu->DataPointer[i] = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
cpu->StackPointer++;
cpu->DataPointer[i] |= (uint16_t)cpu->Data[cpu->StackPointer];
}
// Pop the Return Address from the Stack.
cpu->StackPointer++;
cpu->ProgramCounter = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
@@ -261,14 +281,16 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
cpu->Data[cpu->StackPointer] = cpu->B;
cpu->StackPointer--;
break;
case 0x33:
// PSHD - Push the Data Pointer Address to the Stack.
case 0x33: {
// PSHD - Push the selected Data Pointer Address to the Stack.
// Order, high byte, low byte
// This ordering makes it easier to add offsets with register math.
cpu->Data[cpu->StackPointer] = (cpu->DataPointer >> 8) & 0xFF;
uint16_t pushed = *selectDataPointer(cpu);
cpu->Data[cpu->StackPointer] = (pushed >> 8) & 0xFF;
cpu->StackPointer--;
cpu->Data[cpu->StackPointer] = cpu->DataPointer & 0xFF;
cpu->Data[cpu->StackPointer] = pushed & 0xFF;
cpu->StackPointer--;
}
break;
case 0x34:
// POPA - Pop A from the Stack.
@@ -281,62 +303,70 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
cpu->StackPointer++;
cpu->B = cpu->Data[cpu->StackPointer];
break;
case 0x36:
// POPD - Pop Data Address from the Stack.
case 0x36: {
// POPD - Pop a Data Address from the Stack into the selected Data Pointer.
uint16_t *popped = selectDataPointer(cpu);
cpu->StackPointer++;
cpu->DataPointer = (uint16_t)cpu->Data[cpu->StackPointer];
*popped = (uint16_t)cpu->Data[cpu->StackPointer];
cpu->StackPointer++;
cpu->DataPointer |= (uint16_t)cpu->Data[cpu->StackPointer] << 8;
*popped |= (uint16_t)cpu->Data[cpu->StackPointer] << 8;
}
break;
//
// 4x - Data Operations:
//
case 0x40:
// INCD - Increment Data Pointer.
cpu->DataPointer++;
// INCD - Increment the selected Data Pointer.
(*selectDataPointer(cpu))++;
break;
case 0x41:
// DECD - Decrement Data Pointer.
cpu->DataPointer--;
// DECD - Decrement the selected Data Pointer.
(*selectDataPointer(cpu))--;
break;
case 0x42:
// LDA - Load A from Data.
cpu->A = cpu->Data[cpu->DataPointer];
cpu->A = cpu->Data[*selectDataPointer(cpu)];
break;
case 0x43:
// LDB - Load B from Data.
cpu->B = cpu->Data[cpu->DataPointer];
cpu->B = cpu->Data[*selectDataPointer(cpu)];
break;
case 0x44:
// STQ - Store Q into Data.
cpu->Data[cpu->DataPointer] = cpu->Q;
cpu->Data[*selectDataPointer(cpu)] = cpu->Q;
break;
case 0x45:
// STA - Store A into Data.
cpu->Data[cpu->DataPointer] = cpu->A;
cpu->Data[*selectDataPointer(cpu)] = cpu->A;
break;
case 0x46:
// STB - Store B into Data.
cpu->Data[cpu->DataPointer] = cpu->B;
cpu->Data[*selectDataPointer(cpu)] = cpu->B;
break;
case 0x47:
// SETD - Set the Data Pointer.
case 0x47: {
// SETD - Set the selected Data Pointer.
uint16_t *destination = selectDataPointer(cpu);
cpu->ProgramCounter++;
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.
cpu->ProgramCounter++;
Address |= (uint16_t)cpu->Program[cpu->ProgramCounter];
cpu-> DataPointer = Address;
*destination = Address;
}
break;
case 0x48:
// DPUP - Offset the Data Pointer up by the value of the next byte of Program Memory.
case 0x48: {
// DPUP - Offset the selected Data Pointer up by the value of the next byte of Program Memory.
uint16_t *target = selectDataPointer(cpu);
cpu->ProgramCounter++;
cpu->DataPointer += cpu->Program[cpu->ProgramCounter];
*target += cpu->Program[cpu->ProgramCounter];
}
break;
case 0x49:
// DPDN - Offset the Data Pointer down by the value of the next byte of Program Memory.
case 0x49: {
// DPDN - Offset the selected Data Pointer down by the value of the next byte of Program Memory.
uint16_t *target = selectDataPointer(cpu);
cpu->ProgramCounter++;
cpu->DataPointer -= cpu->Program[cpu->ProgramCounter];
*target -= cpu->Program[cpu->ProgramCounter];
}
break;
//
// Dx - Output Operations:
@@ -383,10 +413,13 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
// Unknown Instruction.
return 1;
}
if (cpu->DataPointer >= cpu->StackPointer) {
// A Stack Collision was detected.
cpu->Status |= 0x82; // Set Stack Collision Flag and Halt . (Bits 7 and 1 of the Status Register);
return 2;
}
return 0;
}
void stepCPU(CPURegisters *cpu) {
if (!(cpu->Status & 0x80)) {
// The CPU is not halted, so do a cycle.
executeOperation(cpu->Program[cpu->ProgramCounter], cpu);
cpu->ProgramCounter++;
}
}
+21 -1
View File
@@ -8,6 +8,24 @@
#include <stdint.h>
// How many Data Pointers the CPU has. The instructions that name one take a full
// byte to do it, so the encoding would allow up to 256. The limit here is the size
// of the register file and the cost of saving pointers across a CALL, not the
// instruction format. Must be a power of two, so that the selector can be masked
// down to a valid pointer.
#define DATA_POINTERS 4
// How many Data Pointers survive a CALL. The low numbered pointers are saved and
// restored around a subroutine; the rest are left alone, so a subroutine can use
// one to hand a pointer back to its caller the way Q hands back a byte. This is
// deliberately independent of DATA_POINTERS: adding more pointers should not make
// every CALL more expensive.
#define PRESERVED_DATA_POINTERS 3
#if PRESERVED_DATA_POINTERS > DATA_POINTERS
#error "Cannot preserve more Data Pointers than the CPU has."
#endif
// The struct containing the CPU registers.
typedef struct {
uint8_t A;
@@ -15,7 +33,7 @@ typedef struct {
uint8_t Q;
uint8_t Status;
uint16_t ProgramCounter;
uint16_t DataPointer;
uint16_t DataPointer[DATA_POINTERS];
uint16_t StackPointer;
uint8_t *Program;
uint8_t *Data;
@@ -25,4 +43,6 @@ uint8_t executeOperation(uint8_t instruction, CPURegisters *cpu);
void initializeCPU(CPURegisters *cpu, uint8_t *programMemory, uint8_t *dataMemory);
void stepCPU(CPURegisters *cpu);
#endif // CPU_H
+89 -16
View File
@@ -12,21 +12,65 @@
#include "utility.h"
#include <string.h>
#include <getopt.h>
#include <time.h>
uint8_t debugEnable = 0;
int cycleCount = 0;
// nanoseconds per second
#define NS_PER_SEC 1000000000LL
#define CYCLE_RATE 1000000
typedef struct {
long long cycles_per_sec; // e.g. 1000000 for 1 MHz
long long accumulator_ns; // unspent nanoseconds
struct timespec prev;
} CycleTimer;
static inline long long timespec_diff_ns(struct timespec a, struct timespec b) {
return (a.tv_sec - b.tv_sec) * NS_PER_SEC + (a.tv_nsec - b.tv_nsec);
}
void cycle_timer_init(CycleTimer *t, long long cycles_per_sec) {
t->cycles_per_sec = cycles_per_sec;
t->accumulator_ns = 0;
clock_gettime(CLOCK_MONOTONIC, &t->prev);
}
// Call once per host frame. Returns how many SplitBit cycles to execute.
int cycle_timer_tick(CycleTimer *t) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
long long elapsed = timespec_diff_ns(now, t->prev);
t->prev = now;
// optional: clamp to avoid spiral-of-death on hitches
if (elapsed > NS_PER_SEC / 10) elapsed = NS_PER_SEC / 10;
t->accumulator_ns += elapsed;
long long period_ns = NS_PER_SEC / t->cycles_per_sec;
int cycles = (int)(t->accumulator_ns / period_ns);
t->accumulator_ns %= period_ns;
return cycles;
}
// How many cycles to run between glances at the wall clock. In fast mode there is
// no clock to keep pace with, so run a large batch before looking up.
#define FAST_BATCH 65536
unsigned long cycleCount = 0;
char *programFile = NULL;
// Memory Banks:
uint8_t Program[0x10000], Data[0x10000];
int main (int argc, char *argv[]) {
uint8_t test = parseOptions(argc, argv);
if (test == 1){
// Enable the Debug Mode.
debugEnable = 1;
} else if (test == 2){
// User asked for help or gave a bad option, don't execute.
EmulatorOptions options;
uint8_t result = parseOptions(argc, argv, &options);
if (result == OPTIONS_HELP) {
// The user asked for help and got it, which is not a failure.
return 0;
} else if (result == OPTIONS_ERROR) {
// Bad command line, don't execute.
return 1;
}
if (optind < argc) {
@@ -47,18 +91,47 @@ int main (int argc, char *argv[]) {
}
CPURegisters cpu;
initializeCPU(&cpu, Program, Data);
if(debugEnable) {
if(options.debug) {
printRegisters(&cpu, Program, Data);
}
while (!(cpu.Status & 0x80)) {
executeOperation(cpu.Program[cpu.ProgramCounter], &cpu);
cpu.ProgramCounter++;
cycleCount++;
if (debugEnable) {
CycleTimer timer;
cycle_timer_init(&timer, CYCLE_RATE);
uint8_t limitReached = 0;
while (!(cpu.Status & 0x80) && !limitReached) {
int cycles;
if (options.debug) {
// Debug mode advances one instruction per keypress, so the wall clock
// has no say in how many cycles to run.
cycles = 1;
} else if (options.fast) {
cycles = FAST_BATCH;
} else {
cycles = cycle_timer_tick(&timer);
}
for (int i = 0; i < cycles; i++) {
stepCPU(&cpu);
cycleCount++;
if (cpu.Status & 0x80) {
// We've halted.
break;
}
if (options.cycles && cycleCount >= options.cycles) {
limitReached = 1;
break;
}
}
if (options.debug) {
getchar();
printRegisters(&cpu, Program, Data);
printf("Cycle: %u\n", cycleCount);
printf("Cycle: %lu\n", cycleCount);
}
}
printf("Execution halted after %u cycles.\n", cycleCount);
if (limitReached) {
printf("Execution stopped after %lu cycles. (cycle limit reached)\n", cycleCount);
} else {
printf("Execution halted after %lu cycles.\n", cycleCount);
}
return 0;
}
+36 -14
View File
@@ -5,6 +5,7 @@
#include "utility.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include "../Assembler/assembly.h"
@@ -14,43 +15,64 @@ void printHelp(const char *programName) {
printf("\n");
printf("Options:\n");
printf(" -d, --debug Enable debug mode.\n");
printf(" -c, --cycles N Stop after N cycles instead of running until the program halts.\n");
printf(" -f, --fast Run as fast as possible, ignoring the emulated cycle rate.\n");
printf(" -h, --help Display this help message.\n");
}
uint8_t parseOptions(int argc, char *argv[]) {
uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) {
static struct option long_options[] = {
{"debug", no_argument, 0, 'd'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0 }
{"debug", no_argument, 0, 'd'},
{"cycles", required_argument, 0, 'c'},
{"fast", no_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0 }
};
int opt;
int option_index = 0;
options->debug = 0;
options->fast = 0;
options->cycles = 0;
// Parse options
while ((opt = getopt_long(argc, argv, "dh", long_options, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "dc:fh", long_options, &option_index)) != -1) {
switch (opt) {
case 'd':
return 1;
break;
options->debug = 1;
break;
case 'c': {
// The count has to be a plain positive number. Anything else is
// almost certainly a mistyped command line rather than a request
// to run zero cycles.
char *end;
long value = strtol(optarg, &end, 10);
if (*end != '\0' || value <= 0) {
fprintf(stderr, "Error: --cycles needs a positive number, not \"%s\".\n", optarg);
return OPTIONS_ERROR;
}
options->cycles = (unsigned long)value;
}
break;
case 'f':
options->fast = 1;
break;
case 'h':
printHelp(argv[0]);
return 2;
case '?':
printHelp(argv[0]);
return 2;
return OPTIONS_HELP;
default:
printHelp(argv[0]);
return 2;
return OPTIONS_ERROR;
}
}
return 0;
return OPTIONS_OK;
}
void printRegisters(CPURegisters *cpu, uint8_t *Program, uint8_t *Data) {
printf("***** CPU Registers *****\n");
printf("A: 0x%02X\tB: 0x%02X\tQ: 0x%02X\tStatus: 0b%08b\n", cpu->A, cpu->B, cpu->Q, cpu->Status);
printf("Program Counter: 0x%04X Current Instruction: 0x%02X (%s)\n", cpu->ProgramCounter, Program[cpu->ProgramCounter],getMnemonic(Program[cpu->ProgramCounter]));
printf(" Data Pointer: 0x%04X Current Data Value: 0x%02X\n", cpu->DataPointer, Data[cpu->DataPointer]);
printf(" Data Pointer: 0x%04X Current Data Value: 0x%02X\n", cpu->DataPointer[0], Data[cpu->DataPointer[0]]);
printf(" Stack Pointer: 0x%04X Current Value: (0x%02X) (0x%02X)\n", cpu->StackPointer, Data[cpu->StackPointer+1], Data[cpu->StackPointer+2]);
}
+12 -1
View File
@@ -10,7 +10,18 @@
#include <stdint.h>
#include "cpu.h"
uint8_t parseOptions(int argc, char *argv[]);
// Results of reading the command line.
#define OPTIONS_OK 0 // Carry on and run the program.
#define OPTIONS_HELP 1 // The user asked for help, so stop, but not because of an error.
#define OPTIONS_ERROR 2 // The command line was no good, stop and complain.
typedef struct {
uint8_t debug; // Step one instruction at a time, printing the registers.
uint8_t fast; // Ignore the cycle rate and run as fast as the host allows.
unsigned long cycles; // Stop after this many cycles. Zero means run until the program halts.
} EmulatorOptions;
uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options);
void printHelp(const char *programName);