./SplitBit --disk system.img stage two CosmOS > No boot image named. The emulator shadows its built in stage one into Program Memory - boot vector included - and the CPU then does exactly what it has always done: reads the boot vector and starts where it points. NOTHING ABOUT THE CPU CHANGED to make a machine that starts itself, which is what picking shadowing over a mapped ROM bought. The ROM is generated from Programs/Boot/stage1.asm by the makefile rather than committed beside it, because a copy of a program kept next to the program is a copy that goes stale. That makes the assembler a real dependency of the emulator, which it always sort of was and now says so. od and awk rather than xxd, which is not everywhere, or python, which the README does not ask anybody to install in order to build this. loadROM is loadFile given bytes instead of a path: both go through one reader over an fmemopen stream, because a ROM is a boot image and there is no reason for the machine to have two ways of understanding one. Naming an image still works and is what every other test here does. That path is not a shortcut to apologise for - placing memory from outside is a real thing real machines allow, and it is a debugger. The help says so now. No image and no disk is the one case with nothing to run, and it says that rather than printing a usage message about a missing file. run.sh gained a "rom" mode which hands the emulator a disk and nothing else. The source column still names stage1.asm, because that is what is IN the ROM: assembling it there says the thing the emulator carries is a thing that still assembles.
124 lines
5.0 KiB
C
124 lines
5.0 KiB
C
// utility.c
|
|
// Utilities for the SplitBit CPU Emulator
|
|
// Written by Anachronaut
|
|
// 10/15/2024
|
|
|
|
#include "utility.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <getopt.h>
|
|
#include "../Assembler/assembly.h"
|
|
|
|
void printHelp(const char *programName) {
|
|
printf("Usage: %s [OPTIONS] [boot image]\n", programName);
|
|
printf("\n");
|
|
printf("Named an image, it is placed into memory and started, which is what a\n");
|
|
printf("debugger does and how the test suite runs. Given only a disk, the machine\n");
|
|
printf("starts the way hardware would: the built in ROM is shadowed into Program\n");
|
|
printf("Memory, and it reads the disk for everything else.\n");
|
|
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(" -D, --disk FILE Attach a disk image, making one if it is not there.\n");
|
|
printf(" -L, --disk-cycles N How many cycles a block read or write takes. Zero, the\n");
|
|
printf(" default, finishes before the next instruction starts.\n");
|
|
printf(" -W, --write-protect Attach the disk read only. A disk the host will not let\n");
|
|
printf(" you write is read only whether you ask for this or not.\n");
|
|
printf(" -h, --help Display this help message.\n");
|
|
}
|
|
|
|
uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) {
|
|
static struct option long_options[] = {
|
|
{"debug", no_argument, 0, 'd'},
|
|
{"cycles", required_argument, 0, 'c'},
|
|
{"fast", no_argument, 0, 'f'},
|
|
{"disk", required_argument, 0, 'D'},
|
|
{"write-protect", no_argument, 0, 'W'},
|
|
{"disk-cycles", required_argument, 0, 'L'},
|
|
{"help", no_argument, 0, 'h'},
|
|
{0, 0, 0, 0 }
|
|
};
|
|
int opt;
|
|
int option_index = 0;
|
|
|
|
options->debug = 0;
|
|
options->fast = 0;
|
|
options->cycles = 0;
|
|
options->disk = NULL;
|
|
options->writeProtect = 0;
|
|
options->diskCycles = 0;
|
|
|
|
// Parse options
|
|
while ((opt = getopt_long(argc, argv, "dc:fhD:WL:", long_options, &option_index)) != -1) {
|
|
switch (opt) {
|
|
case 'd':
|
|
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 'D':
|
|
options->disk = optarg;
|
|
break;
|
|
case 'W':
|
|
options->writeProtect = 1;
|
|
break;
|
|
case 'L':
|
|
options->diskCycles = strtoul(optarg, NULL, 0);
|
|
break;
|
|
case 'h':
|
|
printHelp(argv[0]);
|
|
return OPTIONS_HELP;
|
|
default:
|
|
printHelp(argv[0]);
|
|
return OPTIONS_ERROR;
|
|
}
|
|
}
|
|
return OPTIONS_OK;
|
|
}
|
|
|
|
// Writes a byte out as eight binary digits, most significant first. printf's %b is
|
|
// a recent addition to C and not available everywhere, so this does it by hand.
|
|
// The buffer must have room for nine characters.
|
|
static void formatBinary(uint8_t value, char *out) {
|
|
for (int i = 0; i < 8; i++) {
|
|
out[i] = (value & (0x80 >> i)) ? '1' : '0';
|
|
}
|
|
out[8] = '\0';
|
|
}
|
|
|
|
void printRegisters(CPURegisters *cpu, uint8_t *Program, uint8_t *Data) {
|
|
char status[9];
|
|
formatBinary(cpu->Status, status);
|
|
printf("***** CPU Registers *****\n");
|
|
printf("A: 0x%02X\tB: 0x%02X\tQ: 0x%02X\tStatus: 0b%s\n", cpu->A, cpu->B, cpu->Q, status);
|
|
printf("Program Counter: 0x%04X Current Instruction: 0x%02X (%s)\n", cpu->ProgramCounter, Program[cpu->ProgramCounter],getMnemonic(Program[cpu->ProgramCounter]));
|
|
for (int i = 0; i < DATA_POINTERS; i++) {
|
|
printf(" Data Pointer %d: 0x%04X Current Data Value: 0x%02X%s\n",
|
|
i, cpu->DataPointer[i], Data[cpu->DataPointer[i]],
|
|
i >= PRESERVED_DATA_POINTERS ? " (volatile)" : "");
|
|
}
|
|
// The two casts keep these inside Data Memory. The Stack Pointer starts at the
|
|
// very top, so without them the display would read off the end of the array
|
|
// before a single byte has been pushed.
|
|
printf(" Stack Pointer: 0x%04X Current Value: (0x%02X) (0x%02X)\n", cpu->StackPointer,
|
|
Data[(uint16_t)(cpu->StackPointer + 1)], Data[(uint16_t)(cpu->StackPointer + 2)]);
|
|
}
|
|
|