// utility.c // Utilities for the SplitBit CPU Emulator // Written by Anachronaut // 10/15/2024 #include "utility.h" #include #include #include #include #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(" -S, --screen FILE Save a picture of the screen, as a PPM, when the machine\n"); printf(" stops. Works with or without a window, which is how the\n"); printf(" tests look at a screen on a host that has no display.\n"); printf(" -K, --keyboard FILE Feed the console from this file as though it were a\n"); printf(" keyboard rather than a terminal. Which means the console does\n"); printf(" its own line editing, the way it must when a window is open\n"); printf(" and there is no terminal behind it to do it.\n"); printf(" -N, --sound FILE Save every sample the machine made, as raw signed 16 bit\n"); printf(" at 48kHz. What --screen is for a picture: the only way to\n"); printf(" check a sound on a machine with no speaker.\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'}, {"ram-disk", required_argument, 0, 'R'}, {"write-protect", no_argument, 0, 'W'}, {"disk-cycles", required_argument, 0, 'L'}, {"screen", required_argument, 0, 'S'}, {"keyboard", required_argument, 0, 'K'}, {"sound", required_argument, 0, 'N'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0 } }; int opt; int option_index = 0; // ---- Everything off, in one line rather than nine ---- // // This was a list of assignments, one per field, and a list beside a struct drifts from // the struct: adding `disks` and `diskCount` left them holding whatever was on the stack, // so a machine given one disk was told it already had four drives. The same struct // growing a field once before left Voyager linking against an object that disagreed // about its size. // // Every default here is nought or nothing, and a default that is not can be written // below this line where it will be read as the exception it is. *options = (EmulatorOptions){0}; // Parse options while ((opt = getopt_long(argc, argv, "dc:fhD:WL:S:K:N:R:", 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': // Each one is the next drive. The first is also left in `disk`, because a // machine with one disk is what almost every caller means and reading it // that way keeps them all unchanged. if (options->diskCount >= DISK_DRIVE_COUNT) { fprintf(stderr, "Error: This machine has %d drives.\n", DISK_DRIVE_COUNT); return 1; } options->disks[options->diskCount++] = optarg; if (options->disk == NULL) { options->disk = optarg; } break; case 'R': { char *end; const unsigned long blocks = strtoul(optarg, &end, 10); if (*optarg == '\0' || *end != '\0' || blocks == 0) { fprintf(stderr, "Error: --ram-disk wants a number of blocks.\n"); return 1; } options->ramDisk = blocks; } break; case 'W': options->writeProtect = 1; break; case 'L': options->diskCycles = strtoul(optarg, NULL, 0); break; case 'S': options->screen = optarg; break; case 'K': options->keyboard = optarg; break; case 'N': options->sound = optarg; 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)]); }