Interrupt system implemented, some new programs.
This commit is contained in:
@@ -80,6 +80,9 @@ void assemblerCleanup(intermediateElement *intermediateArray, int arraySize, cha
|
||||
// Free the list of labels.
|
||||
freeLabelList();
|
||||
|
||||
// Free the list of vectors.
|
||||
freeVectorList();
|
||||
|
||||
// Free the output file name
|
||||
free(outputFileName);
|
||||
}
|
||||
@@ -161,9 +164,11 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Allocate initial space for the intermediate array.
|
||||
// Allocate initial space for the intermediate array. calloc rather than malloc,
|
||||
// because not every element sets every one of its own fields, and a stray
|
||||
// byteLength would quietly shift every address that follows it.
|
||||
size_t arraySize = 1024;
|
||||
intermediateElement *intermediateArray = malloc(arraySize * sizeof(intermediateElement));
|
||||
intermediateElement *intermediateArray = calloc(arraySize, sizeof(intermediateElement));
|
||||
if (!intermediateArray) {
|
||||
fprintf(stderr, RED "Error: Memory allocation failed.\n" RESET);
|
||||
exit(1);
|
||||
@@ -176,6 +181,10 @@ int main(int argc, char *argv[]) {
|
||||
loadFile(&intermediateArray, source, &index, &arraySize);
|
||||
populateLabelTable(intermediateArray, index);
|
||||
fillInLabelAddresses(intermediateArray, index);
|
||||
// Vectors come after the labels, because a handler is named by its label, and before
|
||||
// the buffers are filled, because SWI needs the number its vector was given.
|
||||
populateVectorTable(intermediateArray, index);
|
||||
fillInVectorReferences(intermediateArray, index);
|
||||
populateOutputBuffers(intermediateArray, index, Program, &programLength, Data, &dataLength);
|
||||
if (!outputFileName) {
|
||||
outputFileName = createOutputFileName(fileName);
|
||||
|
||||
@@ -32,6 +32,19 @@ int checkIfKeyword(intermediateElement *currentElement) {
|
||||
} else if (strcmp(currentElement->token, "#Data") == 0) {
|
||||
// Same as for #Program, but mark for inclusion in the Data Segment.
|
||||
return KEYWORD_DATA;
|
||||
} else if (strcmp(currentElement->token, "#Align") == 0) {
|
||||
// Puts down as many zero bytes as it takes to reach the next multiple of
|
||||
// the number that follows. The file that needs the boundary is then the
|
||||
// file that asks for it, rather than relying on whatever came before.
|
||||
return KEYWORD_ALIGN;
|
||||
} else if (strcmp(currentElement->token, "#Reserve") == 0) {
|
||||
// Puts down the number of zero bytes that follows, so that a label can
|
||||
// stand for a region rather than just its first byte.
|
||||
return KEYWORD_RESERVE;
|
||||
} else if (strcmp(currentElement->token, "#Vectors") == 0) {
|
||||
// Names which handler belongs to which vector. Nothing here is assembled
|
||||
// into either segment; it is worked out and written into the vector table.
|
||||
return KEYWORD_VECTORS;
|
||||
} else {
|
||||
// It's a malformed keyword.
|
||||
fprintf(stderr, RED "Error: Invalid Keyword \"%s\" in file \"%s\" at line number %d.\n" RESET, currentElement->token, currentElement->fileName, currentElement->lineNumber);
|
||||
@@ -159,6 +172,44 @@ int checkIfLiteralValue(intermediateElement *currentElement) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint16_t readCount(intermediateElement *currentElement, const char *what) {
|
||||
const char *token = currentElement->token;
|
||||
int base;
|
||||
const char *baseName;
|
||||
if (token[0] != '0' || (token[1] != 'x' && token[1] != 'd')) {
|
||||
fprintf(stderr, RED "Error: %s needs a number, prefaced with 0x or 0d. Found \"%s\".\n" RESET, what, token);
|
||||
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
if (token[1] == 'x') {
|
||||
base = 16;
|
||||
baseName = "hexadecimal";
|
||||
} else {
|
||||
base = 10;
|
||||
baseName = "decimal";
|
||||
}
|
||||
const char *digits = token + 2;
|
||||
if (*digits == '\0') {
|
||||
fprintf(stderr, RED "Error: %s was given \"%s\", which has no digits after its prefix.\n" RESET, what, 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 \"%s\".\n" RESET, *c, baseName, token);
|
||||
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
long value = strtol(digits, NULL, base);
|
||||
if (value < 1 || value > 0xFFFF) {
|
||||
fprintf(stderr, RED "Error: %s was given \"%s\". It has to be at least 1 and no more than 0xFFFF.\n" RESET, what, token);
|
||||
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
return (uint16_t)value;
|
||||
}
|
||||
|
||||
int checkIfLabel(intermediateElement *currentElement) {
|
||||
char *token = currentElement->token;
|
||||
int length = strlen(token);
|
||||
@@ -207,7 +258,7 @@ int readToken(intermediateElement *currentElement, FILE *file, int *lineNumber)
|
||||
// Step 3: Handle string literals
|
||||
if (c == '"') {
|
||||
while ((c = fgetc(file)) != EOF && c != '"') {
|
||||
if (i < sizeof(buffer) - 1) {
|
||||
if (i < (int)(sizeof(buffer) - 1)) {
|
||||
buffer[i++] = c;
|
||||
} else {
|
||||
fprintf(stderr, "Error: String literal too long.\n");
|
||||
@@ -228,7 +279,7 @@ int readToken(intermediateElement *currentElement, FILE *file, int *lineNumber)
|
||||
// Step 4: Handle non-string tokens
|
||||
ungetc(c, file); // Put the first character back
|
||||
while ((c = fgetc(file)) != EOF && !isspace(c) && c != ';') {
|
||||
if (i < sizeof(buffer) - 1) {
|
||||
if (i < (int)(sizeof(buffer) - 1)) {
|
||||
buffer[i++] = c;
|
||||
} else {
|
||||
fprintf(stderr, "Error: Token too long.\n");
|
||||
|
||||
@@ -23,16 +23,31 @@
|
||||
#define LABEL_DEFINITION 4
|
||||
#define VALUE 5
|
||||
#define STRING 6
|
||||
// A name from the Vector Segment, used as the operand of SWI. It stands for a vector
|
||||
// number rather than an address, so it emits one byte where a label emits two.
|
||||
#define VECTOR_REFERENCE 7
|
||||
// Zero bytes put down to move the cursor along, from #Reserve. How many is known as
|
||||
// soon as it is read.
|
||||
#define PADDING 8
|
||||
// The same, from #Align, where how many depends on where the cursor has got to. The
|
||||
// count is worked out in the second pass and the alignment itself is kept in address.
|
||||
#define ALIGNMENT 9
|
||||
|
||||
// Keyword values.
|
||||
#define KEYWORD_INCLUDE 1
|
||||
#define KEYWORD_PROGRAM 2
|
||||
#define KEYWORD_DATA 3
|
||||
#define KEYWORD_VECTORS 4
|
||||
#define KEYWORD_ALIGN 5
|
||||
#define KEYWORD_RESERVE 6
|
||||
|
||||
// Destination values.
|
||||
#define NOWHERE 0
|
||||
#define PROGRAM 1
|
||||
#define DATA 2
|
||||
// The Vector Segment does not become bytes at an address the way the other two do. It
|
||||
// says which handler belongs to which vector, and the assembler works out the rest.
|
||||
#define VECTORS 3
|
||||
|
||||
// For colorful text.
|
||||
#define RESET "\x1B[0m"
|
||||
@@ -66,4 +81,9 @@ int checkIfLabel(intermediateElement *currentElement);
|
||||
|
||||
int readToken(intermediateElement *currentElement, FILE *file, int *lineNumber);
|
||||
|
||||
// Reads a count written the way a literal is, but allowing the full range of an address
|
||||
// rather than a single byte. #Align and #Reserve both take one, and neither number is
|
||||
// ever emitted as a byte, so there is no reason to hold them to a byte's range.
|
||||
uint16_t readCount(intermediateElement *currentElement, const char *what);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -28,7 +28,10 @@ Instruction instruction_set[] = {
|
||||
{0x12, "BRA"},
|
||||
{0x13, "BRB"},
|
||||
{0x14, "BRC"},
|
||||
{0x15, "BRD"},
|
||||
{0x17, "CALL"},
|
||||
{0x18, "SWI"},
|
||||
{0x19, "RETI"},
|
||||
{0x1F, "RET"},
|
||||
// Register Operations:
|
||||
{0x20, "RSTA"},
|
||||
@@ -40,6 +43,10 @@ Instruction instruction_set[] = {
|
||||
{0x26, "INIA"},
|
||||
{0x27, "INIB"},
|
||||
{0x28, "CCF"},
|
||||
{0x29, "MVQA"},
|
||||
{0x2A, "MVQB"},
|
||||
{0x2B, "SIF"},
|
||||
{0x2C, "CIF"},
|
||||
// Stack Operations:
|
||||
{0x30, "PSHQ"},
|
||||
{0x31, "PSHA"},
|
||||
@@ -61,6 +68,7 @@ Instruction instruction_set[] = {
|
||||
{0x49, "DPDN"},
|
||||
{0x4A, "LDD"},
|
||||
{0x4B, "STD"},
|
||||
{0x4C, "MVSD"},
|
||||
// Output Operations:
|
||||
{0xD0, "OUTQ"},
|
||||
{0xD1, "OUTA"},
|
||||
@@ -92,6 +100,7 @@ int dataPointerOperands(uint8_t opcode) {
|
||||
case 0x4A: // LDD
|
||||
case 0x4B: // STD
|
||||
return 2;
|
||||
case 0x15: // BRD
|
||||
case 0x33: // PSHD
|
||||
case 0x36: // POPD
|
||||
case 0x40: // INCD
|
||||
@@ -104,6 +113,7 @@ int dataPointerOperands(uint8_t opcode) {
|
||||
case 0x47: // SETD
|
||||
case 0x48: // DPUP
|
||||
case 0x49: // DPDN
|
||||
case 0x4C: // MVSD
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
|
||||
@@ -23,18 +23,62 @@
|
||||
// .. 3 "DAT"
|
||||
// .. 2 Data Segment length
|
||||
// .. M Data Segment
|
||||
// .. 3 "VEC", optional
|
||||
// .. 2 Vector Segment length, in bytes
|
||||
// .. K Vector Segment, four bytes per entry
|
||||
//
|
||||
// The Vector Segment is optional and comes last, so a binary written before it existed
|
||||
// simply ends after its Data Segment and still loads. Each entry is two bytes saying
|
||||
// where in Program Memory the vector sits, then two bytes saying where its handler is,
|
||||
// most significant byte first. It is a list rather than an image of the table, so a
|
||||
// program with three handlers costs twelve bytes instead of a padded kilobyte.
|
||||
//
|
||||
// The feature flags are how a binary says it needs something the base machine does
|
||||
// not provide, so that an emulator which cannot provide it refuses to run the binary
|
||||
// rather than quietly doing the wrong thing. No features are defined yet; the field
|
||||
// is here so that adding one later does not need another format version.
|
||||
|
||||
// ---- The vector table ----
|
||||
//
|
||||
// The top kilobyte of Program Memory is reserved for vectors. Both tools have to
|
||||
// agree on where it begins: the CPU starts execution through it, and the assembler
|
||||
// has to refuse program text that would run into it.
|
||||
//
|
||||
// Entries are two bytes each, most significant byte first, the same order the branch
|
||||
// instructions and this file format already use.
|
||||
//
|
||||
// 0xFC00 Software vectors 0 to 255
|
||||
// 0xFE00 Hardware vectors 0 to 255, one for each I/O port
|
||||
//
|
||||
// Software vectors 0 and 1 are start addresses rather than handlers. Vector 0 is
|
||||
// where the machine begins at power on and vector 1 is a warm restart, so a zero in
|
||||
// either of them is not "nothing installed" but the address 0x0000, which is where a
|
||||
// program carrying no vector table of its own begins. A zero in any other entry does
|
||||
// mean no handler is installed, and dispatching through one is a fault.
|
||||
|
||||
#define SOFTWARE_VECTOR_BASE 0xFC00
|
||||
#define HARDWARE_VECTOR_BASE 0xFE00
|
||||
#define VECTOR_ENTRY_BYTES 2
|
||||
#define VECTOR_BOOT 0
|
||||
#define VECTOR_SOFT_RESET 1
|
||||
#define VECTOR_INVALID_OPCODE 2
|
||||
// Vectors 3 to 15 are held back for faults that do not exist yet, so that each cause
|
||||
// can have an entry of its own rather than sharing one and needing a cause register to
|
||||
// tell them apart. Everything from 16 up belongs to programs.
|
||||
#define VECTOR_FIRST_FREE 16
|
||||
|
||||
// The first address the vector table occupies, and so the first address that program
|
||||
// text may not use.
|
||||
#define PROGRAM_TEXT_LIMIT SOFTWARE_VECTOR_BASE
|
||||
|
||||
#define SPLITBIT_MAGIC "SPBT"
|
||||
#define SPLITBIT_MAGIC_LENGTH 4
|
||||
#define SPLITBIT_FORMAT_VERSION 1
|
||||
#define SPLITBIT_FLAGS_LENGTH 4
|
||||
#define SEGMENT_MARKER_LENGTH 3
|
||||
#define SEGMENT_LENGTH_BYTES 2
|
||||
// Where a vector sits, and where its handler is.
|
||||
#define VECTOR_ENTRY_FILE_BYTES 4
|
||||
|
||||
// Everything the format costs a file, on top of the two segments themselves.
|
||||
#define SPLITBIT_HEADER_BYTES (SPLITBIT_MAGIC_LENGTH + 1 + SPLITBIT_FLAGS_LENGTH \
|
||||
|
||||
@@ -164,13 +164,22 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
|
||||
}
|
||||
// Read off tokens.
|
||||
while (readToken(&(*intermediateArray)[*intermediateIndex], file, &lineNumber)) {
|
||||
if (*intermediateIndex >= *arraySize - 1) {
|
||||
*arraySize *= 2; // Double the size of the array
|
||||
*intermediateArray = realloc(*intermediateArray, *arraySize * sizeof(intermediateElement));
|
||||
if (!intermediateArray) {
|
||||
if ((size_t)*intermediateIndex >= *arraySize - 1) {
|
||||
size_t grownSize = *arraySize * 2; // Double the size of the array.
|
||||
// Into a temporary, so that the old allocation is still ours to free if
|
||||
// this fails, rather than being lost the moment realloc returns NULL.
|
||||
intermediateElement *grown = realloc(*intermediateArray, grownSize * sizeof(intermediateElement));
|
||||
if (!grown) {
|
||||
fprintf(stderr, RED "Error: Memory reallocation failed.\n" RESET);
|
||||
exit(1);
|
||||
}
|
||||
// New elements have to start blank. realloc leaves the new space holding
|
||||
// whatever the heap had in it before, and an element that never sets its
|
||||
// own byteLength, such as a keyword or a label definition, would then add
|
||||
// rubbish to the running address and move everything after it.
|
||||
memset(grown + *arraySize, 0, (grownSize - *arraySize) * sizeof(intermediateElement));
|
||||
*intermediateArray = grown;
|
||||
*arraySize = grownSize;
|
||||
}
|
||||
//printf("Token number %d\n", intermediateIndex);
|
||||
// Go ahead and mark what we already know about this token.
|
||||
@@ -185,13 +194,24 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
|
||||
status = NOWHERE;
|
||||
// Get the filename and work out where it actually is.
|
||||
(*intermediateIndex)++;
|
||||
readToken(&(*intermediateArray)[*intermediateIndex], file, &lineNumber);
|
||||
if (!readToken(&(*intermediateArray)[*intermediateIndex], file, &lineNumber)) {
|
||||
// The file ended straight after the keyword, so there is no
|
||||
// name to read and nothing sensible to go looking for.
|
||||
fprintf(stderr, RED "Error: #Include without a file name.\n" RESET);
|
||||
printf(" File: %s at line %d.\n", fileName, lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
char *requested = (*intermediateArray)[*intermediateIndex].token;
|
||||
char *resolved = resolveInclude(fileName, requested);
|
||||
if (!resolved) {
|
||||
reportMissingInclude(fileName, requested, lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
// The included file's first token is about to be read into this
|
||||
// same slot, so let the file name go now. Leaving it would strand
|
||||
// the only pointer to it the moment it is overwritten.
|
||||
free((*intermediateArray)[*intermediateIndex].token);
|
||||
(*intermediateArray)[*intermediateIndex].token = NULL;
|
||||
// recordSourceFile takes the path, and hands back NULL if this file
|
||||
// has already been assembled. Including it twice is harmless, which
|
||||
// is what lets two libraries depend on a third.
|
||||
@@ -209,6 +229,56 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
|
||||
// Set the state to DATA so we mark additional tokens for inclusion into Data Memory.
|
||||
status = DATA;
|
||||
break;
|
||||
case KEYWORD_ALIGN:
|
||||
case KEYWORD_RESERVE: {
|
||||
// Both take a count, and both only make sense somewhere that has a
|
||||
// cursor to move along.
|
||||
const char *what = (testValue == KEYWORD_ALIGN) ? "#Align" : "#Reserve";
|
||||
if (status != PROGRAM && status != DATA) {
|
||||
fprintf(stderr, RED "Error: %s outside the Program or Data Segment.\n There is nothing there for it to move along.\n" RESET, what);
|
||||
printf(" File: %s at line %d.\n", fileName, lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
// The count is read here rather than being left to the literal check,
|
||||
// because it is an instruction to the assembler and never becomes a
|
||||
// byte, so a byte's range would be the wrong limit for it. A page
|
||||
// alignment needs 256, and a reservation is often far larger.
|
||||
intermediateElement *directive = &(*intermediateArray)[*intermediateIndex];
|
||||
(*intermediateIndex)++;
|
||||
if (!readToken(&(*intermediateArray)[*intermediateIndex], file, &lineNumber)) {
|
||||
fprintf(stderr, RED "Error: %s without a number.\n" RESET, what);
|
||||
printf(" File: %s at line %d.\n", fileName, lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
(*intermediateArray)[*intermediateIndex].fileName = fileName;
|
||||
(*intermediateArray)[*intermediateIndex].lineNumber = lineNumber;
|
||||
uint16_t count = readCount(&(*intermediateArray)[*intermediateIndex], what);
|
||||
|
||||
// The count token itself contributes nothing; the directive carries
|
||||
// everything, so that one element stands for one run of zeroes.
|
||||
(*intermediateArray)[*intermediateIndex].type = KEYWORD;
|
||||
(*intermediateArray)[*intermediateIndex].byteLength = 0;
|
||||
(*intermediateArray)[*intermediateIndex].destination = NOWHERE;
|
||||
|
||||
directive->destination = status;
|
||||
if (testValue == KEYWORD_ALIGN) {
|
||||
// How many zeroes this comes to depends on where the cursor has
|
||||
// reached, which is not known until the second pass walks it.
|
||||
directive->type = ALIGNMENT;
|
||||
directive->address = count;
|
||||
directive->byteLength = 0;
|
||||
} else {
|
||||
directive->type = PADDING;
|
||||
directive->byteLength = count;
|
||||
}
|
||||
(*intermediateIndex)++;
|
||||
continue;
|
||||
}
|
||||
case KEYWORD_VECTORS:
|
||||
// Set the state to VECTORS. Tokens from here on name handlers rather
|
||||
// than becoming bytes, and the second pass reads them.
|
||||
status = VECTORS;
|
||||
break;
|
||||
}
|
||||
// Next, check to see if it's an instruction.
|
||||
} else if (checkIfInstruction(&(*intermediateArray)[*intermediateIndex])) {
|
||||
@@ -235,6 +305,18 @@ int loadFile(intermediateElement **intermediateArray, char *fileName, int *inter
|
||||
printf(" File: %s at line %d.\n", fileName, lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
// A name written after SWI is a vector rather than an address, so it
|
||||
// stands for one byte instead of two. This is settled by what the name
|
||||
// follows, so that it does not depend on the Vector Segment having been
|
||||
// read first, which it may not have been: it can live in another file.
|
||||
if (status == PROGRAM
|
||||
&& (*intermediateArray)[*intermediateIndex].type == LABEL
|
||||
&& *intermediateIndex > 0
|
||||
&& (*intermediateArray)[*intermediateIndex - 1].type == INSTRUCTION
|
||||
&& (*intermediateArray)[*intermediateIndex - 1].byteValue == 0x18) {
|
||||
(*intermediateArray)[*intermediateIndex].type = VECTOR_REFERENCE;
|
||||
(*intermediateArray)[*intermediateIndex].byteLength = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
(*intermediateArray)[*intermediateIndex].destination = status;
|
||||
|
||||
+267
-10
@@ -29,7 +29,7 @@ void freeLabelList() {
|
||||
labelCount = 0;
|
||||
}
|
||||
|
||||
void addLabel(char *labelName, uint16_t address, int type) {
|
||||
void addLabel(char *labelName, uint16_t address, int type, const char *fileName, int lineNumber) {
|
||||
if (labelCount < MAX_LABELS) {
|
||||
// Duplicate labelName and remove the trailing colon, if present
|
||||
char *cleanedLabel = strdup(labelName);
|
||||
@@ -38,6 +38,18 @@ void addLabel(char *labelName, uint16_t address, int type) {
|
||||
cleanedLabel[len - 1] = '\0'; // Remove the colon
|
||||
}
|
||||
|
||||
// A name may only be defined once. Without this check a reference quietly
|
||||
// resolves to whichever definition came first, so a typo or a name that two
|
||||
// libraries both happen to use is very hard to track down.
|
||||
for (int i = 0; i < labelCount; i++) {
|
||||
if (strcmp(labelArray[i].label, cleanedLabel) == 0) {
|
||||
fprintf(stderr, RED "Error: Label \"%s\" is defined more than once.\n" RESET, cleanedLabel);
|
||||
printf("File: %s at line %d.\n", fileName, lineNumber);
|
||||
free(cleanedLabel);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
labelArray[labelCount].label = cleanedLabel;
|
||||
labelArray[labelCount].address = address;
|
||||
labelArray[labelCount].type = type;
|
||||
@@ -54,11 +66,22 @@ void populateLabelTable(intermediateElement *intermediateArray, int arraySize) {
|
||||
int dataCount = 0;
|
||||
// Loop through the array, if there's a label definition, add it to the label list.
|
||||
for (int i = 0; i < arraySize ; i++) {
|
||||
if (intermediateArray[i].type == LABEL_DEFINITION) {
|
||||
// How many zeroes an #Align comes to depends on where the cursor has reached,
|
||||
// so it can only be worked out here, walking the tokens in order. It has to be
|
||||
// settled before the running count moves past it, or every label after it lands
|
||||
// in the wrong place.
|
||||
if (intermediateArray[i].type == ALIGNMENT) {
|
||||
int cursor = (intermediateArray[i].destination == PROGRAM) ? programCount : dataCount;
|
||||
int alignment = intermediateArray[i].address;
|
||||
intermediateArray[i].byteLength = (alignment - (cursor % alignment)) % alignment;
|
||||
}
|
||||
if (intermediateArray[i].type == LABEL_DEFINITION && intermediateArray[i].destination != VECTORS) {
|
||||
if (intermediateArray[i].destination == PROGRAM) {
|
||||
addLabel(intermediateArray[i].token, (uint16_t)programCount, PROGRAM);
|
||||
addLabel(intermediateArray[i].token, (uint16_t)programCount, PROGRAM,
|
||||
intermediateArray[i].fileName, intermediateArray[i].lineNumber);
|
||||
} else {
|
||||
addLabel(intermediateArray[i].token, (uint16_t)dataCount, DATA);
|
||||
addLabel(intermediateArray[i].token, (uint16_t)dataCount, DATA,
|
||||
intermediateArray[i].fileName, intermediateArray[i].lineNumber);
|
||||
}
|
||||
}
|
||||
if (intermediateArray[i].destination == PROGRAM) {
|
||||
@@ -69,8 +92,10 @@ void populateLabelTable(intermediateElement *intermediateArray, int arraySize) {
|
||||
if (debugSecondPass) printf("Token: %s with byte length %d to destination %d of type %d\n", intermediateArray[i].token ,intermediateArray[i].byteLength, intermediateArray[i].destination, intermediateArray[i].type);
|
||||
|
||||
}
|
||||
if (programCount > 0xFFFF ) {
|
||||
fprintf(stderr, RED "Error: Program is too long to fit in Program Memory.\n" RESET);
|
||||
if (programCount > PROGRAM_TEXT_LIMIT ) {
|
||||
fprintf(stderr, RED "Error: Program is too long to fit in Program Memory.\n"
|
||||
" Program text may not run past 0x%04X, where the vector table begins.\n" RESET,
|
||||
PROGRAM_TEXT_LIMIT - 1);
|
||||
exit(1);
|
||||
}
|
||||
if (dataCount > 0xFFFF ) {
|
||||
@@ -79,6 +104,182 @@ void populateLabelTable(intermediateElement *intermediateArray, int arraySize) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- The Vector Segment ----
|
||||
|
||||
int findLabelAddress(const char *labelName);
|
||||
|
||||
VectorEntry vectorArray[MAX_VECTORS];
|
||||
int vectorArrayCount = 0;
|
||||
|
||||
int vectorCount() {
|
||||
return vectorArrayCount;
|
||||
}
|
||||
|
||||
void freeVectorList() {
|
||||
for (int i = 0; i < vectorArrayCount; i++) {
|
||||
if (vectorArray[i].name) {
|
||||
free(vectorArray[i].name);
|
||||
}
|
||||
}
|
||||
vectorArrayCount = 0;
|
||||
}
|
||||
|
||||
// The words the Vector Segment understands. These are spelled without regard to case,
|
||||
// the way mnemonics are, because they are part of the language rather than names the
|
||||
// programmer chose.
|
||||
static int sameWord(const char *a, const char *b) {
|
||||
while (*a && *b) {
|
||||
if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) {
|
||||
return 0;
|
||||
}
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
// The vectors that already mean something. Everything else a program names is numbered
|
||||
// for it, starting above the range held back for faults.
|
||||
static const struct {
|
||||
const char *name;
|
||||
uint8_t index;
|
||||
} reservedVectors[] = {
|
||||
{ "Boot", VECTOR_BOOT },
|
||||
{ "SoftReset", VECTOR_SOFT_RESET },
|
||||
{ "BadOpcode", VECTOR_INVALID_OPCODE },
|
||||
};
|
||||
static const int reservedVectorCount = (int)(sizeof(reservedVectors) / sizeof(reservedVectors[0]));
|
||||
|
||||
static void vectorError(const char *message, intermediateElement *element) {
|
||||
fprintf(stderr, RED "Error: %s\n" RESET, message);
|
||||
printf("File: %s at line %d.\n", element->fileName, element->lineNumber);
|
||||
printf("Token: %s\n", element->token);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// The next token belonging to the Vector Segment, or -1 if the segment has run out.
|
||||
static int nextVectorToken(intermediateElement *intermediateArray, int arraySize, int from) {
|
||||
for (int i = from; i < arraySize; i++) {
|
||||
if (intermediateArray[i].destination == VECTORS && intermediateArray[i].type != KEYWORD) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void addVector(char *name, uint8_t index, uint16_t base, uint16_t handler, intermediateElement *element) {
|
||||
if (vectorArrayCount >= MAX_VECTORS) {
|
||||
vectorError("Too many vectors defined.", element);
|
||||
}
|
||||
for (int i = 0; i < vectorArrayCount; i++) {
|
||||
if (vectorArray[i].index == index && vectorArray[i].base == base) {
|
||||
fprintf(stderr, RED "Error: That vector already has a handler.\n" RESET);
|
||||
printf("File: %s at line %d.\n", element->fileName, element->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
if (name && vectorArray[i].name && strcmp(vectorArray[i].name, name) == 0) {
|
||||
fprintf(stderr, RED "Error: Vector \"%s\" is named more than once.\n" RESET, name);
|
||||
printf("File: %s at line %d.\n", element->fileName, element->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
vectorArray[vectorArrayCount].name = name ? strdup(name) : NULL;
|
||||
vectorArray[vectorArrayCount].index = index;
|
||||
vectorArray[vectorArrayCount].base = base;
|
||||
vectorArray[vectorArrayCount].handler = handler;
|
||||
vectorArrayCount++;
|
||||
}
|
||||
|
||||
// Resolves the handler named by the token at the given index.
|
||||
static uint16_t resolveHandler(intermediateElement *intermediateArray, int at, const char *what) {
|
||||
if (at < 0) {
|
||||
fprintf(stderr, RED "Error: %s is not followed by a handler to go to.\n" RESET, what);
|
||||
exit(1);
|
||||
}
|
||||
if (intermediateArray[at].type != LABEL) {
|
||||
vectorError("A vector's handler has to be named by a label.", &intermediateArray[at]);
|
||||
}
|
||||
int address = findLabelAddress(intermediateArray[at].token);
|
||||
if (address == -1) {
|
||||
vectorError("That handler does not exist.", &intermediateArray[at]);
|
||||
}
|
||||
return (uint16_t)address;
|
||||
}
|
||||
|
||||
void populateVectorTable(intermediateElement *intermediateArray, int arraySize) {
|
||||
// Software vectors a program names for itself are numbered in the order they are
|
||||
// written, starting above the block held back for faults. A programmer never types
|
||||
// one, so there is no way to land on a reserved vector by accident.
|
||||
int nextFreeVector = VECTOR_FIRST_FREE;
|
||||
|
||||
int i = nextVectorToken(intermediateArray, arraySize, 0);
|
||||
while (i >= 0) {
|
||||
char *token = intermediateArray[i].token;
|
||||
|
||||
if (sameWord(token, "Device")) {
|
||||
// A device is named by the port it is plugged into, because that is what
|
||||
// decides which vector it arrives through. There is nothing to allocate.
|
||||
int portToken = nextVectorToken(intermediateArray, arraySize, i + 1);
|
||||
if (portToken < 0 || intermediateArray[portToken].type != VALUE) {
|
||||
vectorError("Device has to say which port, as a number.", &intermediateArray[i]);
|
||||
}
|
||||
int handlerToken = nextVectorToken(intermediateArray, arraySize, portToken + 1);
|
||||
uint16_t handler = resolveHandler(intermediateArray, handlerToken, "Device");
|
||||
addVector(NULL, intermediateArray[portToken].byteValue, HARDWARE_VECTOR_BASE,
|
||||
handler, &intermediateArray[i]);
|
||||
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t index;
|
||||
int reserved = 0;
|
||||
for (int r = 0; r < reservedVectorCount; r++) {
|
||||
if (sameWord(token, reservedVectors[r].name)) {
|
||||
index = reservedVectors[r].index;
|
||||
reserved = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!reserved) {
|
||||
if (nextFreeVector > 255) {
|
||||
vectorError("There are no software vectors left to give this one.", &intermediateArray[i]);
|
||||
}
|
||||
index = (uint8_t)nextFreeVector;
|
||||
nextFreeVector++;
|
||||
}
|
||||
|
||||
int handlerToken = nextVectorToken(intermediateArray, arraySize, i + 1);
|
||||
uint16_t handler = resolveHandler(intermediateArray, handlerToken, token);
|
||||
addVector(token, index, SOFTWARE_VECTOR_BASE, handler, &intermediateArray[i]);
|
||||
i = nextVectorToken(intermediateArray, arraySize, handlerToken + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void fillInVectorReferences(intermediateElement *intermediateArray, int arraySize) {
|
||||
for (int i = 0; i < arraySize; i++) {
|
||||
if (intermediateArray[i].type != VECTOR_REFERENCE) {
|
||||
continue;
|
||||
}
|
||||
int found = 0;
|
||||
for (int v = 0; v < vectorArrayCount; v++) {
|
||||
if (vectorArray[v].name && strcmp(vectorArray[v].name, intermediateArray[i].token) == 0) {
|
||||
if (vectorArray[v].base != SOFTWARE_VECTOR_BASE) {
|
||||
vectorError("SWI can only reach a software vector.", &intermediateArray[i]);
|
||||
}
|
||||
intermediateArray[i].byteValue = vectorArray[v].index;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
fprintf(stderr, RED "Error: \"%s\" is not a vector.\n Names used with SWI have to be given a handler in a #Vectors section.\n" RESET,
|
||||
intermediateArray[i].token);
|
||||
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int findLabelAddress(const char *labelName) {
|
||||
for (int i = 0; i < labelCount; i++) {
|
||||
if (strcmp(labelArray[i].label, labelName) == 0) {
|
||||
@@ -90,6 +291,12 @@ int findLabelAddress(const char *labelName) {
|
||||
|
||||
void fillInLabelAddresses(intermediateElement *intermediateArray, int arraySize) {
|
||||
for (int i = 0; i < arraySize; i++) {
|
||||
// The Vector Segment is resolved separately. Most of what it holds is not a
|
||||
// label at all: the words that name a vector are the segment's own, and looking
|
||||
// them up here would report them as undefined.
|
||||
if (intermediateArray[i].destination == VECTORS) {
|
||||
continue;
|
||||
}
|
||||
if (intermediateArray[i].type == LABEL) {
|
||||
// Look up the label in the label table
|
||||
int address = findLabelAddress(intermediateArray[i].token);
|
||||
@@ -122,12 +329,21 @@ static void checkOperands(intermediateElement *intermediateArray, int arraySize,
|
||||
int nextType = nextTokenType(intermediateArray, arraySize, i);
|
||||
const char *problem = NULL;
|
||||
|
||||
if (((opcode & 0xF0) == 0x10) && (opcode != 0x1F)) {
|
||||
// Listed rather than matched on the high nibble, because not every instruction in
|
||||
// the branch block takes an address: RET has none, and BRD gets its destination
|
||||
// from a Data Pointer instead of from the program.
|
||||
if (opcode == 0x10 || opcode == 0x11 || opcode == 0x12 ||
|
||||
opcode == 0x13 || opcode == 0x14 || opcode == 0x17) {
|
||||
// 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 == 0x18) {
|
||||
// SWI names a vector, either by the name it was given in the Vector Segment or,
|
||||
// rarely, as a literal number. Without one it swallows whatever follows it and
|
||||
// every address after that shifts.
|
||||
if (nextType != VECTOR_REFERENCE && nextType != VALUE) problem = "SWI without a vector to go to.";
|
||||
} 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.";
|
||||
@@ -172,6 +388,17 @@ void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize
|
||||
// Add literal value to Program buffer.
|
||||
Program[(*programCount)++] = intermediateArray[i].byteValue;
|
||||
break;
|
||||
case VECTOR_REFERENCE:
|
||||
// A vector is a number rather than a place, so this is one byte
|
||||
// where a label would be two.
|
||||
Program[(*programCount)++] = intermediateArray[i].byteValue;
|
||||
break;
|
||||
case PADDING:
|
||||
case ALIGNMENT:
|
||||
for (int z = 0; z < intermediateArray[i].byteLength; z++) {
|
||||
Program[(*programCount)++] = 0x00;
|
||||
}
|
||||
break;
|
||||
case LABEL:
|
||||
// Split 16-bit label address into high and low bytes.
|
||||
Program[(*programCount)++] = (intermediateArray[i].address >> 8) & 0xFF; // High byte
|
||||
@@ -199,6 +426,12 @@ void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize
|
||||
Data[(*dataCount)++] = (intermediateArray[i].address >> 8) & 0xFF; // High byte
|
||||
Data[(*dataCount)++] = intermediateArray[i].address & 0xFF; // Low byte
|
||||
break;
|
||||
case PADDING:
|
||||
case ALIGNMENT:
|
||||
for (int z = 0; z < intermediateArray[i].byteLength; z++) {
|
||||
Data[(*dataCount)++] = 0x00;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +463,7 @@ void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCo
|
||||
fputc(programSize & 0xFF, outputFile); // Low byte
|
||||
|
||||
// Write the Program buffer to the file
|
||||
if (fwrite(Program, sizeof(uint8_t), programCount, outputFile) != programCount) {
|
||||
if (fwrite(Program, sizeof(uint8_t), programCount, outputFile) != (size_t)programCount) {
|
||||
fprintf(stderr, RED "Error: Failed to write Program data to file \"%s\".\n" RESET, outputFileName);
|
||||
fclose(outputFile);
|
||||
exit(1);
|
||||
@@ -245,13 +478,37 @@ void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCo
|
||||
fputc(dataSize & 0xFF, outputFile); // Low byte
|
||||
|
||||
// Write the Data buffer to the file
|
||||
if (fwrite(Data, sizeof(uint8_t), dataCount, outputFile) != dataCount) {
|
||||
if (fwrite(Data, sizeof(uint8_t), dataCount, outputFile) != (size_t)dataCount) {
|
||||
fprintf(stderr, RED "Error: Failed to write Data data to file \"%s\".\n" RESET, outputFileName);
|
||||
fclose(outputFile);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// The Vector Segment, only if the program named any. Leaving it out entirely is
|
||||
// what lets a binary written before vectors existed still load: the reader treats
|
||||
// the end of the file as an empty table rather than a missing one.
|
||||
int vectorBytes = 0;
|
||||
if (vectorArrayCount > 0) {
|
||||
fwrite("VEC", sizeof(char), SEGMENT_MARKER_LENGTH, outputFile);
|
||||
vectorBytes = vectorArrayCount * VECTOR_ENTRY_FILE_BYTES;
|
||||
fputc((vectorBytes >> 8) & 0xFF, outputFile);
|
||||
fputc(vectorBytes & 0xFF, outputFile);
|
||||
for (int i = 0; i < vectorArrayCount; i++) {
|
||||
uint16_t slot = vectorArray[i].base + (uint16_t)vectorArray[i].index * VECTOR_ENTRY_BYTES;
|
||||
fputc((slot >> 8) & 0xFF, outputFile);
|
||||
fputc(slot & 0xFF, outputFile);
|
||||
fputc((vectorArray[i].handler >> 8) & 0xFF, outputFile);
|
||||
fputc(vectorArray[i].handler & 0xFF, outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(outputFile);
|
||||
printf("Successfully wrote SplitBit binary to \"%s\".\n", outputFileName);
|
||||
printf(GREEN " Program Segment size: %d bytes.\n Data Segment size: %d bytes.\n Total size: %d bytes.\n" RESET, programCount, dataCount, (programCount + dataCount + SPLITBIT_HEADER_BYTES));
|
||||
printf(GREEN " Program Segment size: %d bytes.\n Data Segment size: %d bytes.\n" RESET, programCount, dataCount);
|
||||
if (vectorArrayCount > 0) {
|
||||
printf(GREEN " Vectors: %d.\n" RESET, vectorArrayCount);
|
||||
}
|
||||
printf(GREEN " Total size: %d bytes.\n" RESET,
|
||||
(programCount + dataCount + SPLITBIT_HEADER_BYTES
|
||||
+ (vectorArrayCount > 0 ? SEGMENT_MARKER_LENGTH + SEGMENT_LENGTH_BYTES + vectorBytes : 0)));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Assm-util.h"
|
||||
|
||||
#define MAX_LABELS 256
|
||||
#define MAX_VECTORS 256
|
||||
|
||||
typedef struct {
|
||||
char* label;
|
||||
@@ -19,8 +20,28 @@ typedef struct {
|
||||
int type;
|
||||
} Label;
|
||||
|
||||
// One line of the Vector Segment, once it has been worked out.
|
||||
typedef struct {
|
||||
char* name; // What it was called, or NULL for a device, which is named by its port.
|
||||
uint8_t index; // Which vector in its table.
|
||||
uint16_t base; // Which table: software or hardware.
|
||||
uint16_t handler; // Where the handler ended up.
|
||||
} VectorEntry;
|
||||
|
||||
void freeLabelList();
|
||||
|
||||
void freeVectorList();
|
||||
|
||||
// Reads the Vector Segment: allocates a number to every named vector, works out which
|
||||
// vector each device line means, and resolves the handlers. Runs after the labels are
|
||||
// known, because a handler is named by its label.
|
||||
void populateVectorTable(intermediateElement *intermediateArray, int arraySize);
|
||||
|
||||
// Turns each vector name used as an operand of SWI into the number it was given.
|
||||
void fillInVectorReferences(intermediateElement *intermediateArray, int arraySize);
|
||||
|
||||
int vectorCount();
|
||||
|
||||
void populateLabelTable(intermediateElement *intermediateArray, int arraySize);
|
||||
|
||||
void fillInLabelAddresses(intermediateElement *intermediateArray, int arraySize);
|
||||
|
||||
@@ -101,6 +101,49 @@ static uint8_t readSegment(FILE *file, const char *marker, uint8_t *Memory) {
|
||||
return loadSegment(file, Memory, length);
|
||||
}
|
||||
|
||||
// Reads the Vector Segment, which is optional and last. A file that simply ends here
|
||||
// was written before vectors existed, and an empty table is exactly right for it: every
|
||||
// entry reads as zero, which means no handler, and the boot vector reading zero means
|
||||
// the program starts at 0x0000 the way it always did.
|
||||
//
|
||||
// Each entry says where in Program Memory the vector sits and where its handler is, so
|
||||
// installing one is a write straight into the vector table.
|
||||
static uint8_t readVectorSegment(FILE *file, uint8_t *Program) {
|
||||
int first = fgetc(file);
|
||||
if (first == EOF) {
|
||||
return 0;
|
||||
}
|
||||
ungetc(first, file);
|
||||
|
||||
char found[SEGMENT_MARKER_LENGTH + 1];
|
||||
if (readMarker(file, "VEC", SEGMENT_MARKER_LENGTH, found)) {
|
||||
fprintf(stderr, "Error: Expected a \"VEC\" segment here, found \"%s\".\n", found);
|
||||
return 1;
|
||||
}
|
||||
uint32_t length;
|
||||
if (readNumber(file, SEGMENT_LENGTH_BYTES, "the vector segment length", &length)) {
|
||||
return 1;
|
||||
}
|
||||
if (length % VECTOR_ENTRY_FILE_BYTES != 0) {
|
||||
fprintf(stderr, "Error: The vector segment is %u bytes, which is not a whole number of vectors.\n", length);
|
||||
return 1;
|
||||
}
|
||||
for (uint32_t i = 0; i < length / VECTOR_ENTRY_FILE_BYTES; i++) {
|
||||
uint32_t slot, handler;
|
||||
if (readNumber(file, 2, "a vector address", &slot)
|
||||
|| readNumber(file, 2, "a handler address", &handler)) {
|
||||
return 1;
|
||||
}
|
||||
if (slot < SOFTWARE_VECTOR_BASE) {
|
||||
fprintf(stderr, "Error: This binary puts a vector at 0x%04X, which is below the vector table.\n", slot);
|
||||
return 1;
|
||||
}
|
||||
Program[slot] = (handler >> 8) & 0xFF;
|
||||
Program[(uint16_t)(slot + 1)] = handler & 0xFF;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t loadFile(char *path, uint8_t *Program, uint8_t *Data) {
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
@@ -111,7 +154,8 @@ uint8_t loadFile(char *path, uint8_t *Program, uint8_t *Data) {
|
||||
// here means there is one exit, and so only one place that has to close the file.
|
||||
uint8_t failed = readFileHeader(file)
|
||||
|| readSegment(file, "PRG", Program)
|
||||
|| readSegment(file, "DAT", Data);
|
||||
|| readSegment(file, "DAT", Data)
|
||||
|| readVectorSegment(file, Program);
|
||||
fclose(file);
|
||||
return failed;
|
||||
}
|
||||
|
||||
+213
-20
@@ -5,15 +5,78 @@
|
||||
|
||||
#include "cpu.h"
|
||||
#include "io.h"
|
||||
#include "../Assembler/assembly.h" // For the vector table layout, which both tools share.
|
||||
|
||||
uint16_t shiftRegister;
|
||||
|
||||
// Reads one entry out of a vector table. Most significant byte first, matching the
|
||||
// branch instructions and the binary format.
|
||||
static uint16_t readVector(const uint8_t *programMemory, uint16_t base, uint8_t index) {
|
||||
uint16_t address = base + (uint16_t)index * VECTOR_ENTRY_BYTES;
|
||||
return ((uint16_t)programMemory[address] << 8) | (uint16_t)programMemory[address + 1];
|
||||
}
|
||||
|
||||
// Builds an interrupt frame and dispatches through a vector. The resume address is the
|
||||
// address execution should carry on from once the handler returns, and it goes into the
|
||||
// frame as a real address so that a handler can read it and make sense of it.
|
||||
//
|
||||
// Returns 0 if it dispatched. If the vector is empty there is nothing to dispatch to, so
|
||||
// it raises a fault and returns 1 rather than jumping to the bottom of Program Memory
|
||||
// and running whatever happens to be there.
|
||||
//
|
||||
// Note that a zero entry means "no handler" to everything that dispatches, including the
|
||||
// two entries the CPU treats as start addresses when it reads them at reset. The
|
||||
// exemption belongs to that one read, not to the entries themselves.
|
||||
static uint8_t enterInterrupt(CPURegisters *cpu, uint16_t base, uint8_t index, uint16_t resumeAddress) {
|
||||
uint16_t handler = readVector(cpu->Program, base, index);
|
||||
if (handler == 0x0000) {
|
||||
cpu->Fault = FAULT_NO_HANDLER;
|
||||
cpu->FaultVector = index;
|
||||
cpu->Status |= STATUS_FAULT;
|
||||
cpu->Status |= STATUS_HALT;
|
||||
return 1;
|
||||
}
|
||||
// Order mirrors genericCall exactly: low byte then high byte, lowest numbered Data
|
||||
// Pointer first, so that anything walking the Stack sees a familiar shape.
|
||||
cpu->Data[cpu->StackPointer] = resumeAddress & 0xFF;
|
||||
cpu->StackPointer--;
|
||||
cpu->Data[cpu->StackPointer] = (resumeAddress >> 8) & 0xFF;
|
||||
cpu->StackPointer--;
|
||||
for (int i = 0; i < DATA_POINTERS; i++) {
|
||||
cpu->Data[cpu->StackPointer] = cpu->DataPointer[i] & 0xFF;
|
||||
cpu->StackPointer--;
|
||||
cpu->Data[cpu->StackPointer] = (cpu->DataPointer[i] >> 8) & 0xFF;
|
||||
cpu->StackPointer--;
|
||||
}
|
||||
cpu->Data[cpu->StackPointer] = cpu->B;
|
||||
cpu->StackPointer--;
|
||||
cpu->Data[cpu->StackPointer] = cpu->A;
|
||||
cpu->StackPointer--;
|
||||
cpu->Data[cpu->StackPointer] = cpu->Q;
|
||||
cpu->StackPointer--;
|
||||
cpu->Data[cpu->StackPointer] = cpu->Status;
|
||||
cpu->StackPointer--;
|
||||
// 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
|
||||
// without bound. The old setting rode into the frame inside the Status register, so
|
||||
// RETI puts it back without anything having to remember it separately.
|
||||
cpu->Status &= ~STATUS_INTERRUPT;
|
||||
// The Program Counter is stepped after every instruction, so land one short of the
|
||||
// handler and let that step land on its first byte. genericBranch does the same.
|
||||
cpu->ProgramCounter = handler - 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void initializeCPU(CPURegisters *cpu, uint8_t *programMemory, uint8_t *dataMemory) {
|
||||
cpu->A = 0;
|
||||
cpu->B = 0;
|
||||
cpu->Q = 0;
|
||||
cpu->Status = 0;
|
||||
cpu->ProgramCounter = 0x0000;
|
||||
// Execution begins wherever the boot vector points. It is a start address rather
|
||||
// than a handler, so a zero there is not "nothing installed" but the address
|
||||
// 0x0000, which is where a program carrying no vector table of its own begins.
|
||||
// That is what lets everything written before the table existed still run.
|
||||
cpu->ProgramCounter = readVector(programMemory, SOFTWARE_VECTOR_BASE, VECTOR_BOOT);
|
||||
// Every Data Pointer starts at the bottom of Data Memory.
|
||||
for (int i = 0; i < DATA_POINTERS; i++) {
|
||||
cpu->DataPointer[i] = 0x0000;
|
||||
@@ -21,6 +84,8 @@ void initializeCPU(CPURegisters *cpu, uint8_t *programMemory, uint8_t *dataMemor
|
||||
cpu->StackPointer = 0xFFFF;
|
||||
cpu->Program = programMemory;
|
||||
cpu->Data = dataMemory;
|
||||
cpu->Fault = FAULT_NONE;
|
||||
cpu->FaultVector = 0;
|
||||
}
|
||||
|
||||
void genericBranch(CPURegisters *cpu){
|
||||
@@ -70,25 +135,29 @@ uint16_t *selectDataPointer(CPURegisters *cpu) {
|
||||
}
|
||||
|
||||
uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
// ADD and SUB share this. It is declared here rather than after a case label
|
||||
// because a label may only be followed by a statement in ISO C, and a
|
||||
// declaration is not one.
|
||||
uint16_t result;
|
||||
switch(Instruction) {
|
||||
// 0x - Arithmetic and Logic Operations.
|
||||
case 0x00:
|
||||
// ADD - A + B + Carry -> Q
|
||||
uint16_t result = (uint16_t)cpu->A + (uint16_t)cpu->B + (cpu->Status & 0x01);
|
||||
result = (uint16_t)cpu->A + (uint16_t)cpu->B + (cpu->Status & STATUS_CARRY);
|
||||
if (result > 255) {
|
||||
cpu->Status |= 0x01;
|
||||
cpu->Status |= STATUS_CARRY;
|
||||
} else {
|
||||
cpu->Status &= ~0x01;
|
||||
cpu->Status &= ~STATUS_CARRY;
|
||||
}
|
||||
cpu->Q = result & 0xFF;
|
||||
break;
|
||||
case 0x01:
|
||||
// SUB - A - B - Carry -> Q
|
||||
result = (uint16_t)cpu->A - (uint16_t)cpu->B - (cpu->Status & 0x01);
|
||||
result = (uint16_t)cpu->A - (uint16_t)cpu->B - (cpu->Status & STATUS_CARRY);
|
||||
if (result > 255) {
|
||||
cpu->Status |= 0x01;
|
||||
cpu->Status |= STATUS_CARRY;
|
||||
} else {
|
||||
cpu->Status &= ~0x01;
|
||||
cpu->Status &= ~STATUS_CARRY;
|
||||
}
|
||||
cpu->Q = result & 0xFF;
|
||||
break;
|
||||
@@ -162,16 +231,68 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
break;
|
||||
case 0x14:
|
||||
// BRC - Do an immediate branch if the Carry Flag is set.
|
||||
if (cpu->Status & 0x01) {
|
||||
if (cpu->Status & STATUS_CARRY) {
|
||||
genericBranch(cpu);
|
||||
} else {
|
||||
cpu->ProgramCounter+=2;
|
||||
}
|
||||
break;
|
||||
case 0x15: {
|
||||
// BRD - Branch to the address held in a Data Pointer.
|
||||
// This is the only branch whose destination is not written into the
|
||||
// program, which is what makes a table of addresses something a program
|
||||
// can dispatch through rather than only read.
|
||||
uint16_t destination = *selectDataPointer(cpu);
|
||||
// stepCPU adds one after every instruction, so aim one short.
|
||||
cpu->ProgramCounter = destination - 1;
|
||||
}
|
||||
break;
|
||||
case 0x17:
|
||||
// CALL - Push the Program Counter to the Stack, and perform an immediate branch.
|
||||
genericCall(cpu);
|
||||
break;
|
||||
case 0x18: {
|
||||
// SWI - Software Interrupt. The byte after the opcode names the vector.
|
||||
// Never masked: this is an instruction the program deliberately ran, not
|
||||
// something a device asked for.
|
||||
uint16_t site = cpu->ProgramCounter;
|
||||
cpu->ProgramCounter++;
|
||||
uint8_t vector = cpu->Program[cpu->ProgramCounter];
|
||||
// Execution resumes after the operand, which the Program Counter is sitting
|
||||
// on, so the resume address is one further on than that.
|
||||
if (enterInterrupt(cpu, SOFTWARE_VECTOR_BASE, vector, cpu->ProgramCounter + 1)) {
|
||||
// No handler. Leave the Program Counter on the SWI itself rather than
|
||||
// its operand, so the report names the instruction that failed.
|
||||
cpu->ProgramCounter = site - 1;
|
||||
}
|
||||
} break;
|
||||
case 0x19: {
|
||||
// RETI - Return from an interrupt. Pops the frame in the exact reverse of
|
||||
// the order enterInterrupt pushed it.
|
||||
cpu->StackPointer++;
|
||||
cpu->Status = cpu->Data[cpu->StackPointer];
|
||||
cpu->StackPointer++;
|
||||
cpu->Q = cpu->Data[cpu->StackPointer];
|
||||
cpu->StackPointer++;
|
||||
cpu->A = cpu->Data[cpu->StackPointer];
|
||||
cpu->StackPointer++;
|
||||
cpu->B = cpu->Data[cpu->StackPointer];
|
||||
for (int i = 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];
|
||||
}
|
||||
uint16_t resumeAddress;
|
||||
cpu->StackPointer++;
|
||||
resumeAddress = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
|
||||
cpu->StackPointer++;
|
||||
resumeAddress = resumeAddress | (uint16_t)cpu->Data[cpu->StackPointer];
|
||||
// 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
|
||||
// same job with its +2, for the same reason.
|
||||
cpu->ProgramCounter = resumeAddress - 1;
|
||||
} break;
|
||||
case 0x1F:
|
||||
// RET - Return from subroutine, restore the registers and set the Program Counter to the Return Address.
|
||||
// Pop A from the Stack.
|
||||
@@ -213,9 +334,9 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
// INCA - Add 1 to A.
|
||||
// Set the Carry Flag if the register overflows.
|
||||
if (cpu->A == 0xFF) {
|
||||
cpu->Status |= 0x01;
|
||||
cpu->Status |= STATUS_CARRY;
|
||||
} else {
|
||||
cpu->Status &= ~0x01;
|
||||
cpu->Status &= ~STATUS_CARRY;
|
||||
}
|
||||
cpu->A++;
|
||||
break;
|
||||
@@ -223,9 +344,9 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
// INCB - Add 1 to B.
|
||||
// Set the Carry Flag if the register overflows.
|
||||
if (cpu->B == 0xFF) {
|
||||
cpu->Status |= 0x01;
|
||||
cpu->Status |= STATUS_CARRY;
|
||||
} else {
|
||||
cpu->Status &= ~0x01;
|
||||
cpu->Status &= ~STATUS_CARRY;
|
||||
}
|
||||
cpu->B++;
|
||||
break;
|
||||
@@ -233,9 +354,9 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
// DECA - Subtract 1 from A.
|
||||
// Set the Carry Flag if the register underflows.
|
||||
if (cpu->A == 0x00) {
|
||||
cpu->Status |= 0x01;
|
||||
cpu->Status |= STATUS_CARRY;
|
||||
} else {
|
||||
cpu->Status &= ~0x01;
|
||||
cpu->Status &= ~STATUS_CARRY;
|
||||
}
|
||||
cpu->A--;
|
||||
break;
|
||||
@@ -243,9 +364,9 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
// DECB - Subtract 1 from B.
|
||||
// Set the Carry Flag if the register underflows.
|
||||
if (cpu->B == 0x00) {
|
||||
cpu->Status |= 0x01;
|
||||
cpu->Status |= STATUS_CARRY;
|
||||
} else {
|
||||
cpu->Status &= ~0x01;
|
||||
cpu->Status &= ~STATUS_CARRY;
|
||||
}
|
||||
cpu->B--;
|
||||
break;
|
||||
@@ -261,7 +382,25 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
break;
|
||||
case 0x28:
|
||||
// CCF - Clear the Carry Flag.
|
||||
cpu->Status &= ~0x01;
|
||||
cpu->Status &= ~STATUS_CARRY;
|
||||
break;
|
||||
case 0x29:
|
||||
// MVQA - Copy Q into A.
|
||||
cpu->A = cpu->Q;
|
||||
break;
|
||||
case 0x2A:
|
||||
// MVQB - Copy Q into B.
|
||||
cpu->B = cpu->Q;
|
||||
break;
|
||||
case 0x2B:
|
||||
// SIF - Set the Interrupt Flag, enabling hardware interrupts.
|
||||
cpu->Status |= STATUS_INTERRUPT;
|
||||
break;
|
||||
case 0x2C:
|
||||
// CIF - Clear the Interrupt Flag, disabling hardware interrupts.
|
||||
// Software interrupts and faults are delivered either way, so this
|
||||
// only ever holds off a device.
|
||||
cpu->Status &= ~STATUS_INTERRUPT;
|
||||
break;
|
||||
//
|
||||
// 3x - Stack Operations:
|
||||
@@ -390,6 +529,18 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
cpu->Data[(uint16_t)(address + 1)] = value & 0xFF;
|
||||
}
|
||||
break;
|
||||
case 0x4C: {
|
||||
// MVSD - Copy the Stack Pointer into the selected Data Pointer.
|
||||
//
|
||||
// The Stack Pointer still cannot be written, so this does not let a program
|
||||
// move the Stack. It lets a program find it, which is what reading anything
|
||||
// already on the Stack requires. An interrupt handler needs this to reach
|
||||
// its own frame, and so does anything that wants to walk back through the
|
||||
// calls that led to where it is.
|
||||
uint16_t *target = selectDataPointer(cpu);
|
||||
*target = cpu->StackPointer;
|
||||
}
|
||||
break;
|
||||
//
|
||||
// Dx - Output Operations:
|
||||
//
|
||||
@@ -429,7 +580,7 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
break;
|
||||
case 0xFF:
|
||||
// HALT - Set the Halt Bit of the Status Register.
|
||||
cpu->Status |= 0x80;
|
||||
cpu->Status |= STATUS_HALT;
|
||||
break;
|
||||
default:
|
||||
// Unknown Instruction.
|
||||
@@ -439,9 +590,51 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
}
|
||||
|
||||
void stepCPU(CPURegisters *cpu) {
|
||||
if (!(cpu->Status & 0x80)) {
|
||||
if (!(cpu->Status & STATUS_HALT)) {
|
||||
// A device asking for attention is answered between instructions and never
|
||||
// inside one, so the address that goes into the frame is always the start of an
|
||||
// instruction and RETI always lands somewhere meaningful.
|
||||
//
|
||||
// A line that is up while the Interrupt Flag is clear stays up. Masking holds a
|
||||
// device off; it does not lose what the device was asking for.
|
||||
if (cpu->Status & STATUS_INTERRUPT) {
|
||||
int port = nextPendingInterrupt();
|
||||
if (port >= 0) {
|
||||
clearInterrupt((uint8_t)port);
|
||||
if (enterInterrupt(cpu, HARDWARE_VECTOR_BASE, (uint8_t)port, cpu->ProgramCounter)) {
|
||||
// The device asked and nobody was listening. enterInterrupt has
|
||||
// already stopped the machine; correct the cause, because the empty
|
||||
// entry is in the hardware table rather than the software one.
|
||||
cpu->Fault = FAULT_NO_DEVICE_HANDLER;
|
||||
return;
|
||||
}
|
||||
// Entering the handler is what this cycle did, so no instruction runs.
|
||||
// The step puts the Program Counter on the handler's first byte, the
|
||||
// same way it does everywhere else.
|
||||
cpu->ProgramCounter++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// The CPU is not halted, so do a cycle.
|
||||
executeOperation(cpu->Program[cpu->ProgramCounter], cpu);
|
||||
if (executeOperation(cpu->Program[cpu->ProgramCounter], cpu)) {
|
||||
// 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
|
||||
// that a handler can read the byte that failed and say what it was.
|
||||
//
|
||||
// A handler returning with a bare RETI will therefore meet the same byte
|
||||
// again. That is the documented behaviour: resuming past a fault means
|
||||
// deciding where to resume, which is the handler's business and not the
|
||||
// CPU's guess.
|
||||
uint16_t faultingAddress = cpu->ProgramCounter;
|
||||
if (enterInterrupt(cpu, SOFTWARE_VECTOR_BASE, VECTOR_INVALID_OPCODE, faultingAddress)) {
|
||||
// Nothing is installed, so stop where we are. The Program Counter is
|
||||
// still on the offending byte, which is what the report wants. The
|
||||
// cause is the byte, not the empty vector, so say so.
|
||||
cpu->Fault = FAULT_BAD_OPCODE;
|
||||
return;
|
||||
}
|
||||
// Dispatched. Fall through, so the step below lands on the handler.
|
||||
}
|
||||
cpu->ProgramCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,30 @@
|
||||
#error "Cannot preserve more Data Pointers than the CPU has."
|
||||
#endif
|
||||
|
||||
// The bits of the Status register that mean something.
|
||||
#define STATUS_CARRY 0x01 // An arithmetic result carried out of, or borrowed into, a byte.
|
||||
#define STATUS_FAULT 0x02 // The CPU met a byte it could not decode, and stopped.
|
||||
#define STATUS_INTERRUPT 0x04 // Hardware interrupts are enabled. Nothing reads this yet.
|
||||
#define STATUS_HALT 0x80 // Execution has stopped, either from HALT or from a fault.
|
||||
|
||||
// What an interrupt puts on the Stack: the resume address, every Data Pointer, and
|
||||
// every register the CPU has. The CALL frame leaves Q and DP3 alone, but that is a
|
||||
// convention between a caller and the subroutine it called. An interrupt arrives in
|
||||
// code that never agreed to give anything up, so it saves the lot.
|
||||
#define INTERRUPT_FRAME_BYTES (2 + DATA_POINTERS * 2 + 4)
|
||||
|
||||
// Why the CPU stopped, when the Fault Flag is set. This is not something a program can
|
||||
// read, and it is deliberately not a register: when a handler is installed, the vector
|
||||
// it arrived through already says what happened, which is why the ISA has no fault
|
||||
// cause. This exists for the case where nothing is installed and the machine is dead,
|
||||
// so that whatever examines the wreckage can say something better than "it stopped".
|
||||
typedef enum {
|
||||
FAULT_NONE = 0,
|
||||
FAULT_BAD_OPCODE, // A byte that does not decode to an instruction.
|
||||
FAULT_NO_HANDLER, // Dispatched through a software vector with nothing in it.
|
||||
FAULT_NO_DEVICE_HANDLER // A device interrupted, and its vector was empty.
|
||||
} FaultCause;
|
||||
|
||||
// The struct containing the CPU registers.
|
||||
typedef struct {
|
||||
uint8_t A;
|
||||
@@ -37,6 +61,9 @@ typedef struct {
|
||||
uint16_t StackPointer;
|
||||
uint8_t *Program;
|
||||
uint8_t *Data;
|
||||
// Set alongside the Fault Flag, and read only by whatever reports the stop.
|
||||
uint8_t Fault; // A FaultCause.
|
||||
uint8_t FaultVector; // Which vector was empty, when Fault is FAULT_NO_HANDLER.
|
||||
} CPURegisters;
|
||||
|
||||
uint8_t executeOperation(uint8_t instruction, CPURegisters *cpu);
|
||||
|
||||
@@ -99,7 +99,12 @@ int main (int argc, char *argv[]) {
|
||||
cycle_timer_init(&timer, CYCLE_RATE);
|
||||
|
||||
uint8_t limitReached = 0;
|
||||
while (!(cpu.Status & 0x80) && !limitReached) {
|
||||
while (!(cpu.Status & STATUS_HALT) && !limitReached) {
|
||||
if (options.debug) {
|
||||
// Wait before advancing, not after, so that a keypress is what moves the
|
||||
// machine on rather than something that happens once it already has.
|
||||
getchar();
|
||||
}
|
||||
int cycles;
|
||||
if (options.debug) {
|
||||
// Debug mode advances one instruction per keypress, so the wall clock
|
||||
@@ -113,7 +118,7 @@ int main (int argc, char *argv[]) {
|
||||
for (int i = 0; i < cycles; i++) {
|
||||
stepCPU(&cpu);
|
||||
cycleCount++;
|
||||
if (cpu.Status & 0x80) {
|
||||
if (cpu.Status & STATUS_HALT) {
|
||||
// We've halted.
|
||||
break;
|
||||
}
|
||||
@@ -123,13 +128,26 @@ int main (int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
if (options.debug) {
|
||||
getchar();
|
||||
printRegisters(&cpu, Program, Data);
|
||||
printf("Cycle: %lu\n", cycleCount);
|
||||
}
|
||||
}
|
||||
if (limitReached) {
|
||||
printf("Execution stopped after %lu cycles. (cycle limit reached)\n", cycleCount);
|
||||
} else if (cpu.Status & STATUS_FAULT) {
|
||||
// The Program Counter is still pointing at whatever the CPU could not get past.
|
||||
printf("Execution halted after %lu cycles.\n", cycleCount);
|
||||
if (cpu.Fault == FAULT_NO_HANDLER) {
|
||||
fprintf(stderr, "Fault: Software vector %u, dispatched from Program Address 0x%04X, has no handler installed.\n",
|
||||
cpu.FaultVector, cpu.ProgramCounter);
|
||||
} else if (cpu.Fault == FAULT_NO_DEVICE_HANDLER) {
|
||||
fprintf(stderr, "Fault: The device on port %u interrupted at Program Address 0x%04X, and hardware vector %u has no handler installed.\n",
|
||||
cpu.FaultVector, cpu.ProgramCounter, cpu.FaultVector);
|
||||
} else {
|
||||
fprintf(stderr, "Fault: 0x%02X at Program Address 0x%04X is not an instruction.\n",
|
||||
Program[cpu.ProgramCounter], cpu.ProgramCounter);
|
||||
}
|
||||
return 1;
|
||||
} else {
|
||||
printf("Execution halted after %lu cycles.\n", cycleCount);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,37 @@
|
||||
#include "io.h"
|
||||
#include <stdio.h>
|
||||
|
||||
// One bit per port, so a device can ask for attention without anything having to poll
|
||||
// it. Eight ports to the byte, low bit first.
|
||||
#define INTERRUPT_LINE_BYTES 32
|
||||
|
||||
static uint8_t pendingInterrupts[INTERRUPT_LINE_BYTES];
|
||||
|
||||
void raiseInterrupt(uint8_t port) {
|
||||
pendingInterrupts[port >> 3] |= (uint8_t)(1u << (port & 7));
|
||||
}
|
||||
|
||||
void clearInterrupt(uint8_t port) {
|
||||
pendingInterrupts[port >> 3] &= (uint8_t)~(1u << (port & 7));
|
||||
}
|
||||
|
||||
int nextPendingInterrupt(void) {
|
||||
// Lowest numbered port wins. This is a scan rather than a priority encoder, which
|
||||
// means there is no arbitration to explain and a programmer can work out what
|
||||
// happens next by reading the port numbers.
|
||||
for (int group = 0; group < INTERRUPT_LINE_BYTES; group++) {
|
||||
if (pendingInterrupts[group] == 0) {
|
||||
continue;
|
||||
}
|
||||
for (int bit = 0; bit < 8; bit++) {
|
||||
if (pendingInterrupts[group] & (1u << bit)) {
|
||||
return group * 8 + bit;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) {
|
||||
// This function sends the DataByte to the appropriate place based on the Port Address.
|
||||
switch(Address) {
|
||||
@@ -15,6 +46,14 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) {
|
||||
// Later, I'll want to use a buffer for this for performance, probably.
|
||||
putchar(DataByte);
|
||||
break;
|
||||
case 0x10:
|
||||
// A test device, and about the simplest one that can exist: writing to it
|
||||
// puts its own line up. It stands in for the shape a real device has, where
|
||||
// the CPU asks for something and is interrupted once the answer is ready,
|
||||
// with the waiting taken out so that a test runs the same way every time.
|
||||
// The byte written is ignored; only the asking matters.
|
||||
raiseInterrupt(0x10);
|
||||
break;
|
||||
default:
|
||||
// Writes to unused Output Ports are ignored.
|
||||
return 1;
|
||||
|
||||
@@ -13,4 +13,20 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address);
|
||||
|
||||
uint8_t InputHandler(uint8_t Address);
|
||||
|
||||
// ---- Interrupt lines ----
|
||||
//
|
||||
// One line per port. A device puts its line up to ask for attention, and the CPU takes
|
||||
// it down when it answers. Which line a device uses is not a choice: a device on port N
|
||||
// interrupts on N, which is what saves the machine from needing any arbitration.
|
||||
//
|
||||
// These belong to the bus rather than to the CPU. Nothing here is saved in a frame, and
|
||||
// a program cannot read them except by being interrupted.
|
||||
|
||||
void raiseInterrupt(uint8_t port);
|
||||
|
||||
void clearInterrupt(uint8_t port);
|
||||
|
||||
// The lowest numbered port with its line up, or -1 if none of them are.
|
||||
int nextPendingInterrupt(void);
|
||||
|
||||
#endif // IO_H
|
||||
|
||||
@@ -68,11 +68,31 @@ uint8_t parseOptions(int argc, char *argv[], EmulatorOptions *options) {
|
||||
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%08b\n", cpu->A, cpu->B, cpu->Q, cpu->Status);
|
||||
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]));
|
||||
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]);
|
||||
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)]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user