Various bug fixes to assembler, added more data pointers.
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include <ctype.h>
|
||||
#include "Assm-util.h"
|
||||
#include "assembly.h"
|
||||
#include "../Emulator/cpu.h" // For DATA_POINTERS, so the CPU stays the one source of truth.
|
||||
|
||||
int debug = 0;
|
||||
|
||||
@@ -43,42 +44,105 @@ int checkIfKeyword(intermediateElement *currentElement) {
|
||||
|
||||
int checkIfInstruction(intermediateElement *currentElement) {
|
||||
char token[32];
|
||||
strncpy(token, currentElement->token, sizeof(token)-1); //copying over one less than the total size of the buffer ensures we wind up with a null terminated string.
|
||||
toUppercase(token);
|
||||
if (getOpcode(token) != 0xFE) {
|
||||
// It's a valid instruction, save its value and set its type.
|
||||
currentElement->type = INSTRUCTION;
|
||||
currentElement->byteValue = getOpcode(token);
|
||||
currentElement->byteLength = 1;
|
||||
if (debug) printf("Token: %s is an instruction.\n", currentElement->token);
|
||||
return 1;
|
||||
if (strlen(currentElement->token) >= sizeof(token)) {
|
||||
// Longer than any mnemonic could be, so it is not one.
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
strcpy(token, currentElement->token);
|
||||
toUppercase(token);
|
||||
|
||||
// An instruction that works through a Data Pointer may name which one by
|
||||
// hanging a selector off the mnemonic, as in LDA.2. Split that off before
|
||||
// looking the mnemonic up.
|
||||
char *selector = strchr(token, '.');
|
||||
if (selector) {
|
||||
*selector = '\0';
|
||||
selector++;
|
||||
}
|
||||
|
||||
uint8_t opcode = getOpcode(token);
|
||||
if (opcode == 0xFE) {
|
||||
// It's not an instruction.
|
||||
return 0;
|
||||
}
|
||||
|
||||
currentElement->type = INSTRUCTION;
|
||||
currentElement->byteValue = opcode;
|
||||
currentElement->byteLength = 1;
|
||||
currentElement->dataPointer = 0;
|
||||
|
||||
if (instructionTakesDataPointer(opcode)) {
|
||||
// The selector is emitted whether or not it was written, so these are
|
||||
// always two bytes. Leaving it off is the same as writing 0.
|
||||
currentElement->byteLength = 2;
|
||||
if (selector) {
|
||||
char *end;
|
||||
long value = strtol(selector, &end, 10);
|
||||
if (*selector == '\0' || *end != '\0' || value < 0 || value >= DATA_POINTERS) {
|
||||
fprintf(stderr, RED "Error: \"%s\" does not name a Data Pointer.\n Selectors run from 0 to %d.\n" RESET, currentElement->token, DATA_POINTERS - 1);
|
||||
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
currentElement->dataPointer = (uint8_t)value;
|
||||
}
|
||||
} else if (selector) {
|
||||
fprintf(stderr, RED "Error: %s does not work through a Data Pointer, so it cannot take a selector.\n" RESET, token);
|
||||
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (debug) printf("Token: %s is an instruction using Data Pointer %d.\n", currentElement->token, currentElement->dataPointer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int checkIfLiteralValue(intermediateElement *currentElement) {
|
||||
char token[32];
|
||||
strncpy(token, currentElement->token, sizeof(token)-1);
|
||||
if (token[0] == '0') {
|
||||
// It's a literal value. Check if it's hex or dec.
|
||||
if(token[1] == 'x') {
|
||||
// It's a hex literal. Set its value and type.
|
||||
currentElement->type = VALUE;
|
||||
currentElement->byteLength = 1;
|
||||
memmove(token, token + 2, strlen(token)); // Shift the string over to get rid of the 0x.
|
||||
currentElement->byteValue = (uint8_t)strtol(token, NULL, 16);
|
||||
if (debug) printf("Token: %s is a hexadecimal literal. \n", currentElement->token);
|
||||
} else if (token[1] == 'd') {
|
||||
// It's a decimal literal. Set its value and type.
|
||||
currentElement->type = VALUE;
|
||||
currentElement->byteLength = 1;
|
||||
memmove(token, token + 2, strlen(token)); // Shift the string over to get rid of the 0d.
|
||||
currentElement->byteValue = (uint8_t)strtol(token, NULL, 10);
|
||||
if (debug) printf("Token: %s is a decimal literal. \n", currentElement->token);
|
||||
}
|
||||
return 1;
|
||||
const char *token = currentElement->token;
|
||||
if (token[0] != '0') {
|
||||
// It's not a literal value.
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
// A leading zero means the programmer was trying to write a literal, so anything
|
||||
// malformed from here on is an error. Falling through to the label check instead
|
||||
// would quietly emit the wrong number of bytes and shift the rest of the program.
|
||||
int base;
|
||||
const char *baseName;
|
||||
if (token[1] == 'x') {
|
||||
base = 16;
|
||||
baseName = "hexadecimal";
|
||||
} else if (token[1] == 'd') {
|
||||
base = 10;
|
||||
baseName = "decimal";
|
||||
} else {
|
||||
fprintf(stderr, RED "Error: Malformed literal value \"%s\".\n Literals must be prefaced with 0x for hexadecimal or 0d for decimal.\n" RESET, token);
|
||||
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
// Everything after the prefix has to be a digit in that base.
|
||||
const char *digits = token + 2;
|
||||
if (*digits == '\0') {
|
||||
fprintf(stderr, RED "Error: Literal value \"%s\" has no digits after its prefix.\n" RESET, token);
|
||||
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
for (const char *c = digits; *c; c++) {
|
||||
if (!(base == 16 ? isxdigit((unsigned char)*c) : isdigit((unsigned char)*c))) {
|
||||
fprintf(stderr, RED "Error: \"%c\" is not a %s digit, in literal value \"%s\".\n" RESET, *c, baseName, token);
|
||||
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
// The digits are all valid, so the only thing left to get wrong is the range.
|
||||
long value = strtol(digits, NULL, base);
|
||||
if (value > 255) {
|
||||
fprintf(stderr, RED "Error: Literal value \"%s\" is too large to fit in one byte.\n Values must be in the range 0x00 to 0xFF, or 0d0 to 0d255.\n" RESET, token);
|
||||
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
currentElement->type = VALUE;
|
||||
currentElement->byteLength = 1;
|
||||
currentElement->byteValue = (uint8_t)value;
|
||||
if (debug) printf("Token: %s is a %s literal. \n", token, baseName);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int checkIfLabel(intermediateElement *currentElement) {
|
||||
|
||||
@@ -48,6 +48,7 @@ typedef struct {
|
||||
char* fileName;
|
||||
int lineNumber;
|
||||
uint8_t byteValue;
|
||||
uint8_t dataPointer; // Which Data Pointer this instruction works through, if it works through one.
|
||||
int byteLength;
|
||||
uint16_t address;
|
||||
int type; // "KEYWORD", "INSTRUCTION" , "LABEL" , "VALUE", "STRING"
|
||||
|
||||
@@ -82,6 +82,28 @@ const char* getMnemonic(uint8_t opcode) {
|
||||
return "---";
|
||||
}
|
||||
|
||||
int instructionTakesDataPointer(uint8_t opcode) {
|
||||
// These instructions all work through a Data Pointer, and so are followed by a
|
||||
// byte naming which one. Everything else is a single byte opcode as before.
|
||||
switch (opcode) {
|
||||
case 0x33: // PSHD
|
||||
case 0x36: // POPD
|
||||
case 0x40: // INCD
|
||||
case 0x41: // DECD
|
||||
case 0x42: // LDA
|
||||
case 0x43: // LDB
|
||||
case 0x44: // STQ
|
||||
case 0x45: // STA
|
||||
case 0x46: // STB
|
||||
case 0x47: // SETD
|
||||
case 0x48: // DPUP
|
||||
case 0x49: // DPDN
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t getOpcode(char* mnemonic) {
|
||||
for (int i = 0; i < num_instructions; i++) {
|
||||
if (strcmp(instruction_set[i].mnemonic, mnemonic) == 0) {
|
||||
|
||||
@@ -12,4 +12,6 @@ const char* getMnemonic(uint8_t opcode);
|
||||
|
||||
uint8_t getOpcode(char* mnemonic);
|
||||
|
||||
int instructionTakesDataPointer(uint8_t opcode);
|
||||
|
||||
#endif // CPU_H
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <stdint.h>
|
||||
#include "secondPass.h"
|
||||
#include "Assm-util.h"
|
||||
#include "assembly.h"
|
||||
|
||||
int debugSecondPass = 0;
|
||||
|
||||
@@ -103,6 +104,55 @@ void fillInLabelAddresses(intermediateElement *intermediateArray, int arraySize)
|
||||
}
|
||||
}
|
||||
|
||||
// Reports the type of the token following index i, or UNKNOWN if there isn't one.
|
||||
// The operand checks go through this so that an instruction sitting at the very end of
|
||||
// a program is reported as a missing operand instead of reading off the end of the array.
|
||||
static int nextTokenType(intermediateElement *intermediateArray, int arraySize, int i) {
|
||||
if (i + 1 >= arraySize) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
return intermediateArray[i + 1].type;
|
||||
}
|
||||
|
||||
// Every instruction that reads operand bytes out of Program Memory needs those bytes to
|
||||
// actually be there. If they aren't, the following instruction gets eaten as an operand
|
||||
// and everything after it shifts, so these all have to be hard errors.
|
||||
static void checkOperands(intermediateElement *intermediateArray, int arraySize, int i) {
|
||||
uint8_t opcode = intermediateArray[i].byteValue;
|
||||
int nextType = nextTokenType(intermediateArray, arraySize, i);
|
||||
const char *problem = NULL;
|
||||
|
||||
if (((opcode & 0xF0) == 0x10) && (opcode != 0x1F)) {
|
||||
// Branches and CALL take a two byte address, which only a label can supply.
|
||||
if (nextType != LABEL) problem = "Branch without label.";
|
||||
} else if ((opcode & 0xF0) == 0xD0 || (opcode & 0xF0) == 0xE0) {
|
||||
// The instruction is either an input or output and must be followed by a value.
|
||||
if (nextType != VALUE) problem = "I/O without destination port.";
|
||||
} else if (opcode == 0x26 || opcode == 0x27) {
|
||||
// INIA and INIB must be followed by the literal value to load.
|
||||
if (nextType != VALUE) problem = "Immediate load without a value to load.";
|
||||
} else if (opcode == 0x48 || opcode == 0x49) {
|
||||
// DPUP and DPDN must be followed by the literal offset to apply.
|
||||
if (nextType != VALUE) problem = "Data Pointer offset without an offset value.";
|
||||
} else if (opcode == 0x47) {
|
||||
// SETD takes a two byte address, as either a label or a pair of literal bytes.
|
||||
if (nextType == VALUE) {
|
||||
if (nextTokenType(intermediateArray, arraySize, i + 1) != VALUE) {
|
||||
problem = "SETD given one literal byte, but an address is two bytes.";
|
||||
}
|
||||
} else if (nextType != LABEL) {
|
||||
problem = "SETD without an address.";
|
||||
}
|
||||
}
|
||||
|
||||
if (problem) {
|
||||
fprintf(stderr, RED "Error: %s\n" RESET, problem);
|
||||
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
|
||||
printf("Token: %s\n", intermediateArray[i].token);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize, uint8_t *Program, int *programCount, uint8_t *Data, int *dataCount) {
|
||||
for (int i = 0; i < arraySize; i++) {
|
||||
if (intermediateArray[i].destination == PROGRAM) {
|
||||
@@ -110,23 +160,13 @@ void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize
|
||||
case INSTRUCTION:
|
||||
// Add instruction byte to Program buffer.
|
||||
Program[(*programCount)++] = intermediateArray[i].byteValue;
|
||||
|
||||
// Check if it's a branch instruction.
|
||||
if (((intermediateArray[i].byteValue & 0xF0) == 0x10) && (intermediateArray[i].byteValue != 0x1F)){
|
||||
if (intermediateArray[i + 1].type != LABEL) {
|
||||
fprintf(stderr, RED "Error: Branch without label.\n" RESET);
|
||||
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
} else if ((intermediateArray[i].byteValue & 0xF0) == 0xD0 || (intermediateArray[i].byteValue & 0xF0) == 0xE0) {
|
||||
// The instruction is either an input or output and must be followed by a value
|
||||
if (intermediateArray[i + 1].type != VALUE) {
|
||||
fprintf(stderr, RED "Error: I/O without destination port.\n" RESET);
|
||||
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
|
||||
printf("Token: %s\n", intermediateArray[i].token);
|
||||
exit(1);
|
||||
}
|
||||
// Instructions that work through a Data Pointer carry a selector
|
||||
// byte naming which one, whether or not the programmer wrote it.
|
||||
if (instructionTakesDataPointer(intermediateArray[i].byteValue)) {
|
||||
Program[(*programCount)++] = intermediateArray[i].dataPointer;
|
||||
}
|
||||
// Make sure any operand bytes this instruction expects are present.
|
||||
checkOperands(intermediateArray, arraySize, i);
|
||||
break;
|
||||
case VALUE:
|
||||
// Add literal value to Program buffer.
|
||||
@@ -151,6 +191,14 @@ void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize
|
||||
}
|
||||
Data[(*dataCount)++] = '\0'; // Add null terminator to Data buffer
|
||||
break;
|
||||
case LABEL:
|
||||
// The label table reserved two bytes for this, but there's nothing here
|
||||
// that knows how to emit them, so every later Data label would be shifted
|
||||
// out of place. Refuse it rather than assemble something that looks fine.
|
||||
fprintf(stderr, RED "Error: Label \"%s\" used as a value in the Data Segment.\n Label references are only supported in the Program Segment.\n" RESET, intermediateArray[i].token);
|
||||
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+76
-43
@@ -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
@@ -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
@@ -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
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user