Assembler Completed
Added the Assembler. Added instructions for the assembler to README.md Added Assembler Manual Modified Makefile to build the Assembler.
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
// Assembler.c
|
||||
// Basic Assembler for programs written for the SplitBit CPU
|
||||
// Written by Anachronaut
|
||||
// 10/18/2024
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <libgen.h>
|
||||
#include "Assm-util.h"
|
||||
#include "firstPass.h"
|
||||
#include "secondPass.h"
|
||||
|
||||
int programLength = 0;
|
||||
int dataLength = 0;
|
||||
|
||||
uint8_t Program[0xFFFF], Data[0xFFFF];
|
||||
|
||||
|
||||
char* createOutputFileName(const char *inputFilePath) {
|
||||
// Make a copy of inputFilePath, since basename may modify it
|
||||
char *pathCopy = strdup(inputFilePath);
|
||||
if (!pathCopy) {
|
||||
fprintf(stderr, "Error: Memory allocation failed for path copy.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Get the filename from the path
|
||||
char *fileName = basename(pathCopy);
|
||||
|
||||
// Find the length of the filename
|
||||
size_t len = strlen(fileName);
|
||||
|
||||
// Check if the filename ends with ".asm"
|
||||
char *outputFileName;
|
||||
if (len > 4 && strcmp(fileName + len - 4, ".asm") == 0) {
|
||||
// Allocate memory for the new file name with ".bin" extension
|
||||
outputFileName = malloc(len - 4 + 5); // Remove ".asm" (4 chars) and add ".bin" (4 chars + null terminator)
|
||||
if (!outputFileName) {
|
||||
fprintf(stderr, "Error: Memory allocation failed for output file name.\n");
|
||||
free(pathCopy);
|
||||
exit(1);
|
||||
}
|
||||
// Copy the filename up to ".asm" and add ".bin"
|
||||
strncpy(outputFileName, fileName, len - 4);
|
||||
strcpy(outputFileName + len - 4, ".bin");
|
||||
} else {
|
||||
// If there's no ".asm" extension, add ".bin" to the full filename
|
||||
outputFileName = malloc(len + 5); // Original length + ".bin" + null terminator
|
||||
if (!outputFileName) {
|
||||
fprintf(stderr, "Error: Memory allocation failed for output file name.\n");
|
||||
free(pathCopy);
|
||||
exit(1);
|
||||
}
|
||||
strcpy(outputFileName, fileName);
|
||||
strcat(outputFileName, ".bin");
|
||||
}
|
||||
|
||||
free(pathCopy); // Free the temporary path copy
|
||||
return outputFileName;
|
||||
}
|
||||
|
||||
// Remember to be a good programmer and free up all the allocated memory.
|
||||
void assemblerCleanup(intermediateElement *intermediateArray, int arraySize, char *outputFileName) {
|
||||
// Free each token in intermediateArray.
|
||||
for (int i = 0; i < arraySize; i++) {
|
||||
if (intermediateArray[i].token) {
|
||||
free(intermediateArray[i].token);
|
||||
}
|
||||
}
|
||||
// Free the intermediateArray itself.
|
||||
free(intermediateArray);
|
||||
|
||||
// Free the list of included files.
|
||||
freeIncludeList();
|
||||
|
||||
// Free the list of labels.
|
||||
freeLabelList();
|
||||
|
||||
// Free the output file name
|
||||
free(outputFileName);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
// Allocate initial space for the intermediate array.
|
||||
size_t arraySize = 1024;
|
||||
intermediateElement *intermediateArray = malloc(arraySize * sizeof(intermediateElement));
|
||||
if (!intermediateArray) {
|
||||
fprintf(stderr, RED "Error: Memory allocation failed.\n" RESET);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char *fileName = argv[1];
|
||||
int index = 0;
|
||||
loadFile(intermediateArray, fileName, &index, &arraySize);
|
||||
populateLabelTable(intermediateArray, index);
|
||||
fillInLabelAddresses(intermediateArray, index);
|
||||
populateOutputBuffers(intermediateArray, index, Program, &programLength, Data, &dataLength);
|
||||
char *outputFileName = createOutputFileName(fileName);
|
||||
writeOutputFile(outputFileName, Program, programLength, Data, dataLength);
|
||||
assemblerCleanup(intermediateArray, index, outputFileName);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
// Assm-util.c
|
||||
// Utility and helper functions for the SplitBit Assembler.
|
||||
// Written by Anachronaut
|
||||
// 10/25/2024
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include "Assm-util.h"
|
||||
#include "assembly.h"
|
||||
|
||||
int debug = 0;
|
||||
|
||||
void toUppercase(char *str) {
|
||||
for (int i = 0; str[i]; i++) {
|
||||
str[i] = toupper(str[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int checkIfKeyword(intermediateElement *currentElement) {
|
||||
if (currentElement->token[0] == '#') {
|
||||
if (debug) printf("Token: %s is a keyword.\n", currentElement->token);
|
||||
currentElement->type = KEYWORD;
|
||||
currentElement->destination = NOWHERE;
|
||||
if (strcmp(currentElement->token, "#Include") == 0) {
|
||||
return KEYWORD_INCLUDE;
|
||||
} else if (strcmp(currentElement->token, "#Program") == 0) {
|
||||
// We'll want to remember we encountered this and mark all the additional tokens until we hit another keyword for inclusion in the Program Segment.
|
||||
return KEYWORD_PROGRAM;
|
||||
} else if (strcmp(currentElement->token, "#Data") == 0) {
|
||||
// Same as for #Program, but mark for inclusion in the Data Segment.
|
||||
return KEYWORD_DATA;
|
||||
} 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);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
// It's not a keyword.
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int checkIfLabel(intermediateElement *currentElement) {
|
||||
char *token = currentElement->token;
|
||||
int length = strlen(token);
|
||||
// Check if the last character is a colon.
|
||||
if (token[length - 1] == ':') {
|
||||
// It is, so this is a label definition.
|
||||
currentElement->type = LABEL_DEFINITION;
|
||||
currentElement->destination = NOWHERE;
|
||||
if (debug) printf("Token: %s is a label definition.\n", token);
|
||||
return 1;
|
||||
} else {
|
||||
// By process of elimination, if whatever this is wasn't picked up by any of the other checks, it's either a label or is invalid.
|
||||
currentElement->type = LABEL;
|
||||
currentElement->byteLength = 2;
|
||||
if (debug) printf("Token: %s is probably a label?\n", token);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readToken(intermediateElement *currentElement, FILE *file, int *lineNumber) {
|
||||
int c;
|
||||
char buffer[256]; // Buffer to hold the token temporarily.
|
||||
int i = 0;
|
||||
|
||||
// Step 1: Skip whitespace and comments.
|
||||
while ((c = fgetc(file)) != EOF) {
|
||||
if (isspace(c)) {
|
||||
if (c == '\n') (*lineNumber)++; // Increment line count on newlines.
|
||||
continue; // Skip whitespace
|
||||
} else if (c == ';') {
|
||||
// A comment: ignore characters until the newline.
|
||||
while ((c = fgetc(file)) != EOF && c != '\n');
|
||||
if (c == '\n') (*lineNumber)++; // Increment line count after comment line.
|
||||
continue;
|
||||
} else {
|
||||
break; // Found a non-whitespace, non-comment character
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Handle EOF
|
||||
if (c == EOF) {
|
||||
return 0; // Indicate end of file
|
||||
}
|
||||
|
||||
// Step 3: Handle string literals
|
||||
if (c == '"') {
|
||||
while ((c = fgetc(file)) != EOF && c != '"') {
|
||||
if (i < sizeof(buffer) - 1) {
|
||||
buffer[i++] = c;
|
||||
} else {
|
||||
fprintf(stderr, "Error: String literal too long.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
buffer[i] = '\0'; // Null-terminate the string
|
||||
|
||||
// Store in intermediateElement and set type
|
||||
currentElement->token = strdup(buffer);
|
||||
currentElement->type = STRING;
|
||||
currentElement->byteLength = strlen(currentElement->token)+1;
|
||||
if (debug) printf("Token: %s is a string literal.\n", currentElement->token);
|
||||
return 1; // Success
|
||||
}
|
||||
|
||||
// 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) {
|
||||
buffer[i++] = c;
|
||||
} else {
|
||||
fprintf(stderr, "Error: Token too long.\n");
|
||||
printf(" File: %s at line %d.\n", currentElement->fileName, currentElement->lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
buffer[i] = '\0'; // Null-terminate the token
|
||||
|
||||
// Put back the last character if it's not whitespace or EOF
|
||||
if (c != EOF && c != ';') {
|
||||
ungetc(c, file);
|
||||
} else if (c == ';') {
|
||||
// Skip remaining characters on this line if a comment starts
|
||||
while ((c = fgetc(file)) != EOF && c != '\n');
|
||||
}
|
||||
|
||||
// Store the token in intermediateElement and set a default type
|
||||
currentElement->token = strdup(buffer);
|
||||
currentElement->type = UNKNOWN;
|
||||
|
||||
return 1; // Success
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Assm-util.h
|
||||
// Utility functions for the SplitBit Assembler's first pass.
|
||||
// Written by Anachronaut
|
||||
// 10/25/2024
|
||||
|
||||
#ifndef ASSMUTL_H
|
||||
#define ASSMUTL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "Assm-util.h"
|
||||
|
||||
#define MAX_INCLUDES 128
|
||||
|
||||
// Type values.
|
||||
#define UNKNOWN 0
|
||||
#define KEYWORD 1
|
||||
#define INSTRUCTION 2
|
||||
#define LABEL 3
|
||||
#define LABEL_DEFINITION 4
|
||||
#define VALUE 5
|
||||
#define STRING 6
|
||||
|
||||
// Keyword values.
|
||||
#define KEYWORD_INCLUDE 1
|
||||
#define KEYWORD_PROGRAM 2
|
||||
#define KEYWORD_DATA 3
|
||||
|
||||
// Destination values.
|
||||
#define NOWHERE 0
|
||||
#define PROGRAM 1
|
||||
#define DATA 2
|
||||
|
||||
// For colorful text.
|
||||
#define RESET "\x1B[0m"
|
||||
#define RED "\x1B[31m"
|
||||
#define GREEN "\x1B[32m"
|
||||
#define YELLOW "\x1B[33m"
|
||||
#define BLUE "\x1B[34m"
|
||||
#define MAGENTA "\x1B[35m"
|
||||
#define CYAN "\x1B[36m"
|
||||
#define WHITE "\x1B[37m"
|
||||
|
||||
typedef struct {
|
||||
char* token;
|
||||
char* fileName;
|
||||
int lineNumber;
|
||||
uint8_t byteValue;
|
||||
int byteLength;
|
||||
uint16_t address;
|
||||
int type; // "KEYWORD", "INSTRUCTION" , "LABEL" , "VALUE", "STRING"
|
||||
int destination; // "NOWHERE", "PROGRAM", "DATA"
|
||||
} intermediateElement;
|
||||
|
||||
int checkIfKeyword(intermediateElement *currentElement);
|
||||
|
||||
int checkIfInstruction(intermediateElement *currentElement);
|
||||
|
||||
int checkIfLiteralValue(intermediateElement *currentElement);
|
||||
|
||||
int checkIfLabel(intermediateElement *currentElement);
|
||||
|
||||
int readToken(intermediateElement *currentElement, FILE *file, int *lineNumber);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
// assembly.c
|
||||
// These are functions useful for translating assembly mnemonics to hex and vice-versa for the SplitBit CPU.
|
||||
// Written by Anachronaut
|
||||
// 10/18/2024
|
||||
|
||||
#include "assembly.h"
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t opcode;
|
||||
const char* mnemonic;
|
||||
} Instruction;
|
||||
|
||||
Instruction instruction_set[] = {
|
||||
// Arithmetic and Logic Operations:
|
||||
{0x00, "ADD"},
|
||||
{0x01, "SUB"},
|
||||
{0x02, "AND"},
|
||||
{0x03, "OR"},
|
||||
{0x04, "NOR"},
|
||||
{0x05, "NAND"},
|
||||
{0x06, "XOR"},
|
||||
{0x07, "NOTA"},
|
||||
{0x08, "NOTB"},
|
||||
// Branch Operations:
|
||||
{0x10, "BRI"},
|
||||
{0x11, "BRQ"},
|
||||
{0x12, "BRA"},
|
||||
{0x13, "BRB"},
|
||||
// Register Operations:
|
||||
{0x20, "RSTA"},
|
||||
{0x21, "RSTB"},
|
||||
{0x22, "INCA"},
|
||||
{0x23, "INCB"},
|
||||
{0x24, "DECA"},
|
||||
{0x25, "DECB"},
|
||||
{0x26, "LDA"},
|
||||
{0x27, "LDB"},
|
||||
{0x28, "INIA"},
|
||||
{0x29, "INIB"},
|
||||
// Stack Operations:
|
||||
{0x30, "PSHQ"},
|
||||
{0x31, "PSHA"},
|
||||
{0x32, "PSHB"},
|
||||
{0x33, "PSHP"},
|
||||
{0x34, "PSHD"},
|
||||
{0x35, "POPA"},
|
||||
{0x36, "POPB"},
|
||||
{0x37, "POPP"},
|
||||
{0x38, "POPD"},
|
||||
// Data Operations:
|
||||
{0x40, "INCD"},
|
||||
{0x41, "DECD"},
|
||||
{0x42, "LDA"},
|
||||
{0x43, "LDB"},
|
||||
{0x44, "STQ"},
|
||||
{0x45, "STA"},
|
||||
{0x46, "STB"},
|
||||
{0x47, "SETD"},
|
||||
// Output Operations:
|
||||
{0xD0, "OUTQ"},
|
||||
{0xD1, "OUTA"},
|
||||
{0xD2, "OUTB"},
|
||||
// Input Operations:
|
||||
{0xE0, "INA"},
|
||||
{0xE1, "INB"},
|
||||
{0xE2, "IND"},
|
||||
// Special Operations:
|
||||
{0xF0, "NOP"},
|
||||
{0xFF, "HALT"}
|
||||
};
|
||||
|
||||
int num_instructions = sizeof(instruction_set) / sizeof(Instruction);
|
||||
|
||||
const char* getMnemonic(uint8_t opcode) {
|
||||
for (int i = 0; i < num_instructions; i++) {
|
||||
if (instruction_set[i].opcode == opcode) {
|
||||
return instruction_set[i].mnemonic;
|
||||
}
|
||||
}
|
||||
return "---";
|
||||
}
|
||||
|
||||
uint8_t getOpcode(char* mnemonic) {
|
||||
for (int i = 0; i < num_instructions; i++) {
|
||||
if (strcmp(instruction_set[i].mnemonic, mnemonic) == 0) {
|
||||
return instruction_set[i].opcode;
|
||||
}
|
||||
}
|
||||
return 0xFE; // FE is an unused instruction, we'll use it to indicate an error.
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// assembly.h
|
||||
// These are functions useful for translating assembly mnemonics to hex and vice-versa for the SplitBit CPU.
|
||||
// Written by Anachronaut
|
||||
// 10/18/2024
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef ASSEMBLY_H
|
||||
#define ASSEMBLY_H
|
||||
|
||||
const char* getMnemonic(uint8_t opcode);
|
||||
|
||||
uint8_t getOpcode(char* mnemonic);
|
||||
|
||||
#endif // CPU_H
|
||||
@@ -0,0 +1,130 @@
|
||||
// firstPass.c
|
||||
// Functions for the 'first pass' of the SplitBit Assembler.
|
||||
// The first pass' primary goal is to get all the assembly files loaded into the Intermediate Array.
|
||||
// Because I want included files to load and assemble in-line with the current file, that needs to be handled at this stage.
|
||||
// Written by Anachronaut
|
||||
// 10/25/2024
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include "firstPass.h"
|
||||
#include "Assm-util.h"
|
||||
#include "assembly.h"
|
||||
|
||||
char *includeList[MAX_INCLUDES];
|
||||
int includeCount = 0;
|
||||
|
||||
void freeIncludeList() {
|
||||
for (int i = 0; i < includeCount; i++) {
|
||||
if (includeList[i]) {
|
||||
free(includeList[i]);
|
||||
}
|
||||
}
|
||||
includeCount = 0;
|
||||
}
|
||||
|
||||
int isFileIncluded(const char *fileName) {
|
||||
for (int i = 0; i < includeCount; i++) {
|
||||
if (strcmp(includeList[i], fileName) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void addIncludedFile(const char *fileName) {
|
||||
if (includeCount < MAX_INCLUDES) {
|
||||
includeList[includeCount] = strdup(fileName);
|
||||
includeCount++;
|
||||
} else {
|
||||
fprintf(stderr, RED "Error: Too many included files.\n" RESET);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int loadFile(intermediateElement *intermediateArray, char *fileName, int *intermediateIndex, size_t *arraySize){
|
||||
// Initial setup.
|
||||
addIncludedFile(fileName);
|
||||
int status = NOWHERE;
|
||||
int lineNumber = 1; // Line numbers start at 1.
|
||||
// Open the file.
|
||||
FILE *file = fopen(fileName, "r");
|
||||
if (!file) {
|
||||
fprintf(stderr, RED "Error: Couldn't open file \"%s\"\n" RESET, fileName);
|
||||
exit(1);
|
||||
}
|
||||
// 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) {
|
||||
fprintf(stderr, RED "Error: Memory reallocation failed.\n" RESET);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
//printf("Token number %d\n", intermediateIndex);
|
||||
// Go ahead and mark what we already know about this token.
|
||||
intermediateArray[*intermediateIndex].fileName = fileName;
|
||||
intermediateArray[*intermediateIndex].lineNumber = lineNumber;
|
||||
// Take a look at it and determine what it is.
|
||||
int testValue = checkIfKeyword(&intermediateArray[*intermediateIndex]);
|
||||
if(testValue > 0) {
|
||||
switch (testValue){
|
||||
case KEYWORD_INCLUDE:
|
||||
// We need to load another file and process it before continuing.
|
||||
status = NOWHERE;
|
||||
// Get the filename and load up the file.
|
||||
(*intermediateIndex)++;
|
||||
readToken(&intermediateArray[*intermediateIndex], file, &lineNumber);
|
||||
char *includeFile = intermediateArray[*intermediateIndex].token;
|
||||
if (isFileIncluded(includeFile)) {
|
||||
fprintf(stderr, RED "Error: File %s is included more than once.\n" RESET, includeFile);
|
||||
printf(" File: %s at line %d.\n", fileName, lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
loadFile(intermediateArray, includeFile, intermediateIndex, arraySize);
|
||||
break;
|
||||
case KEYWORD_PROGRAM:
|
||||
// Set the state PROGRAM so we mark additional tokens for inclusion into Program Memory.
|
||||
status = PROGRAM;
|
||||
break;
|
||||
case KEYWORD_DATA:
|
||||
// Set the state to DATA so we mark additional tokens for inclusion into Data Memory.
|
||||
status = DATA;
|
||||
break;
|
||||
}
|
||||
// Next, check to see if it's an instruction.
|
||||
} else if (checkIfInstruction(&intermediateArray[*intermediateIndex])) {
|
||||
// We should check if we're set up to mark this for the Program Segment.
|
||||
if (status != PROGRAM) {
|
||||
fprintf(stderr, RED "Error: Attempting to assemble outside the Program Segment.\n Did you forget to use the #Program keyword?\n" RESET);
|
||||
printf(" File: %s at line %d.\n", fileName, lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
// Next, check if it's a literal value.
|
||||
} else if (checkIfLiteralValue(&intermediateArray[*intermediateIndex])) {
|
||||
// We should check to make sure we have a destination for it.
|
||||
if (status == NOWHERE) {
|
||||
fprintf(stderr, RED "Error: Attempting to write a value to nowhere!\n Did you forget to use the #Program or #Data keyword?\n" RESET);
|
||||
printf(" File: %s at line %d.\n", fileName, lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
// Finally, check if it's a label or label definition.
|
||||
// First, make sure it hasn't already been marked as a string literal.
|
||||
} else if (intermediateArray[*intermediateIndex].type != STRING) {
|
||||
if (checkIfLabel(&intermediateArray[*intermediateIndex])) {
|
||||
if (status == NOWHERE) {
|
||||
fprintf(stderr, RED "Error: Attempting to create or use a label nowhere!\n Did you forget to use the #Program or #Data keyword?\n" RESET);
|
||||
printf(" File: %s at line %d.\n", fileName, lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
intermediateArray[*intermediateIndex].destination = status;
|
||||
(*intermediateIndex)++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// firstPass.h
|
||||
// Utility functions for the SplitBit Assembler.
|
||||
// Written by Anachronaut
|
||||
// 10/22/2024
|
||||
|
||||
#ifndef FIRSTPASS_H
|
||||
#define FIRSTPASS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "Assm-util.h"
|
||||
|
||||
void freeIncludeList();
|
||||
|
||||
int loadFile(intermediateElement *intermediateArray, char *fileName, int *intermediateIndex, size_t *arraySize);
|
||||
|
||||
#endif // FIRSTPASS_H
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
// secondPass.c
|
||||
// Functions for the 'second pass' of the SplitBit Assembler.
|
||||
// The goal here is to resolve the addresses of labels.
|
||||
// We'll want to abort if the program comes out to greater than the maximum memory for SplitBit.
|
||||
// We'll also want to abort if there's a label used with no definition.
|
||||
// Written by Anachronaut
|
||||
// 10/25/2024
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include "secondPass.h"
|
||||
#include "Assm-util.h"
|
||||
|
||||
int debugSecondPass = 0;
|
||||
|
||||
Label labelArray[MAX_LABELS];
|
||||
int labelCount = 0;
|
||||
|
||||
void freeLabelList() {
|
||||
for (int i = 0; i < labelCount; i++) {
|
||||
if (labelArray[i].label) {
|
||||
free(labelArray[i].label);
|
||||
}
|
||||
}
|
||||
labelCount = 0;
|
||||
}
|
||||
|
||||
void addLabel(char *labelName, uint16_t address, int type) {
|
||||
if (labelCount < MAX_LABELS) {
|
||||
// Duplicate labelName and remove the trailing colon, if present
|
||||
char *cleanedLabel = strdup(labelName);
|
||||
int len = strlen(cleanedLabel);
|
||||
if (cleanedLabel[len - 1] == ':') {
|
||||
cleanedLabel[len - 1] = '\0'; // Remove the colon
|
||||
}
|
||||
|
||||
labelArray[labelCount].label = cleanedLabel;
|
||||
labelArray[labelCount].address = address;
|
||||
labelArray[labelCount].type = type;
|
||||
if (debugSecondPass) printf("Added label %s with address %04X\n", labelName, labelArray[labelCount].address);
|
||||
labelCount++;
|
||||
} else {
|
||||
fprintf(stderr, "Error: Too many labels defined.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void populateLabelTable(intermediateElement *intermediateArray, int arraySize) {
|
||||
int programCount = 0;
|
||||
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) {
|
||||
if (intermediateArray[i].destination == PROGRAM) {
|
||||
addLabel(intermediateArray[i].token, (uint16_t)programCount, PROGRAM);
|
||||
} else {
|
||||
addLabel(intermediateArray[i].token, (uint16_t)dataCount, DATA);
|
||||
}
|
||||
}
|
||||
if (intermediateArray[i].destination == PROGRAM) {
|
||||
programCount += intermediateArray[i].byteLength;
|
||||
} else if (intermediateArray[i].destination == DATA) {
|
||||
dataCount += intermediateArray[i].byteLength;
|
||||
}
|
||||
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);
|
||||
exit(1);
|
||||
}
|
||||
if (dataCount > 0xFFFF ) {
|
||||
fprintf(stderr, RED "Error: Data is too long to fit in Data Memory.\n" RESET);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int findLabelAddress(const char *labelName) {
|
||||
for (int i = 0; i < labelCount; i++) {
|
||||
if (strcmp(labelArray[i].label, labelName) == 0) {
|
||||
return labelArray[i].address;
|
||||
}
|
||||
}
|
||||
return -1; // Label not found
|
||||
}
|
||||
|
||||
void fillInLabelAddresses(intermediateElement *intermediateArray, int arraySize) {
|
||||
for (int i = 0; i < arraySize; i++) {
|
||||
if (intermediateArray[i].type == LABEL) {
|
||||
// Look up the label in the label table
|
||||
int address = findLabelAddress(intermediateArray[i].token);
|
||||
if (address == -1) {
|
||||
fprintf(stderr, RED "Error: Undefined label \"%s\".\n" RESET, intermediateArray[i].token);
|
||||
printf("File: %s at line %d.\n", intermediateArray[i].fileName, intermediateArray[i].lineNumber);
|
||||
exit(1);
|
||||
}
|
||||
// Assign the found address to the element
|
||||
intermediateArray[i].address = address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (intermediateArray[i].type) {
|
||||
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) {
|
||||
if (intermediateArray[i + 1].type != LABEL) {
|
||||
fprintf(stderr, "Error: Branch without label.\n");
|
||||
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);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case VALUE:
|
||||
// Add literal value to Program buffer.
|
||||
Program[(*programCount)++] = intermediateArray[i].byteValue;
|
||||
break;
|
||||
case LABEL:
|
||||
// Split 16-bit label address into high and low bytes.
|
||||
Program[(*programCount)++] = (intermediateArray[i].address >> 8) & 0xFF; // High byte
|
||||
Program[(*programCount)++] = intermediateArray[i].address & 0xFF; // Low byte
|
||||
break;
|
||||
}
|
||||
} else if (intermediateArray[i].destination == DATA) {
|
||||
switch (intermediateArray[i].type) {
|
||||
case VALUE:
|
||||
// Add literal value to Data buffer.
|
||||
Data[(*dataCount)++] = intermediateArray[i].byteValue;
|
||||
break;
|
||||
case STRING:
|
||||
// Copy string literal to Data buffer, including null terminator.
|
||||
for (int j = 0; intermediateArray[i].token[j] != '\0'; j++) {
|
||||
Data[(*dataCount)++] = intermediateArray[i].token[j];
|
||||
}
|
||||
Data[(*dataCount)++] = '\0'; // Add null terminator to Data buffer
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCount, uint8_t *Data, int dataCount) {
|
||||
FILE *outputFile = fopen(outputFileName, "wb");
|
||||
if (!outputFile) {
|
||||
fprintf(stderr, RED "Error: Could not open file \"%s\" for writing.\n" RESET, outputFileName);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Write the "PRG" header for the program segment
|
||||
fwrite("PRG", sizeof(char), 3, outputFile);
|
||||
|
||||
// Write the program segment length as a 2-byte value (big-endian)
|
||||
uint16_t programSize = programCount;
|
||||
fputc((programSize >> 8) & 0xFF, outputFile); // High byte
|
||||
fputc(programSize & 0xFF, outputFile); // Low byte
|
||||
|
||||
// Write the Program buffer to the file
|
||||
if (fwrite(Program, sizeof(uint8_t), programCount, outputFile) != programCount) {
|
||||
fprintf(stderr, RED "Error: Failed to write Program data to file \"%s\".\n" RESET, outputFileName);
|
||||
fclose(outputFile);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Write the "DAT" header for the data segment
|
||||
fwrite("DAT", sizeof(char), 3, outputFile);
|
||||
|
||||
// Write the data segment length as a 2-byte value (big-endian)
|
||||
uint16_t dataSize = dataCount;
|
||||
fputc((dataSize >> 8) & 0xFF, outputFile); // High byte
|
||||
fputc(dataSize & 0xFF, outputFile); // Low byte
|
||||
|
||||
// Write the Data buffer to the file
|
||||
if (fwrite(Data, sizeof(uint8_t), dataCount, outputFile) != dataCount) {
|
||||
fprintf(stderr, RED "Error: Failed to write Data data to file \"%s\".\n" RESET, outputFileName);
|
||||
fclose(outputFile);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
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." RESET, programCount, dataCount, (programCount+dataCount+10));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// secondPass.c
|
||||
// Functions for the 'second pass' of the SplitBit Assembler.
|
||||
// Written by Anachronaut
|
||||
// 10/25/2024
|
||||
|
||||
#ifndef SECONDPASS_H
|
||||
#define SECONDPASS_H
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ctype.h>
|
||||
#include "Assm-util.h"
|
||||
|
||||
#define MAX_LABELS 256
|
||||
|
||||
typedef struct {
|
||||
char* label;
|
||||
uint16_t address;
|
||||
int type;
|
||||
} Label;
|
||||
|
||||
void freeLabelList();
|
||||
|
||||
void populateLabelTable(intermediateElement *intermediateArray, int arraySize);
|
||||
|
||||
void fillInLabelAddresses(intermediateElement *intermediateArray, int arraySize);
|
||||
|
||||
void populateOutputBuffers(intermediateElement *intermediateArray, int arraySize, uint8_t *Program, int *programCount, uint8_t *Data, int *dataCount);
|
||||
|
||||
void writeOutputFile(const char *outputFileName, uint8_t *Program, int programCount, uint8_t *Data, int dataCount);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,97 @@
|
||||
// boostrap.c
|
||||
// Boostrapping Functions for the SplitBit CPU Emulator
|
||||
// Written by Anachronaut
|
||||
// 10/16/2024
|
||||
|
||||
#include "bootstrap.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int byte = 0;
|
||||
const uint8_t HEADER_LENGTH = 3; // Number of bytes for the header.
|
||||
const uint8_t LENGTH_SIZE = 2; // Number of bytes for the segment length.
|
||||
|
||||
uint32_t readLength(FILE *file) {
|
||||
uint16_t length = 0;
|
||||
for (int i = 0; i < LENGTH_SIZE; i++) {
|
||||
int byte = fgetc(file);
|
||||
if (byte == EOF) {
|
||||
fprintf(stderr, "Error: Unexpected end of file while reading a segment length.\n");
|
||||
return UINT32_MAX;
|
||||
}
|
||||
length = (length << 8) | (uint8_t)byte;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
uint32_t readHeader(FILE *file, const char *expectedHeader) {
|
||||
char header[HEADER_LENGTH];
|
||||
for (int i = 0; i < HEADER_LENGTH; i++) {
|
||||
int byte = fgetc(file);
|
||||
if (byte == EOF) {
|
||||
fprintf(stderr, "Error: Unexpected end of file while reading a header.\n");
|
||||
return UINT32_MAX;
|
||||
}
|
||||
header[i] = (uint8_t)byte;
|
||||
}
|
||||
if (strncmp(header, expectedHeader, 3)) {
|
||||
fprintf(stderr, "Error: Bad header.\n");
|
||||
printf("Header: %s\nExpected Header: %s\n", header, expectedHeader);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t loadSegment(FILE *file, uint8_t *Memory, uint16_t length) {
|
||||
for (int16_t i = 0; i < length; i++) {
|
||||
byte = fgetc(file);
|
||||
if (byte == EOF) {
|
||||
fprintf(stderr, "Error: Unexpected end of file while reading a segment.\n");
|
||||
return 1;
|
||||
}
|
||||
Memory[i] = (uint8_t)byte;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t loadFile(char *path, uint8_t *Program, uint8_t *Data) {
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "Error: Couldn't open file: %s\n", path);
|
||||
return 1;
|
||||
}
|
||||
// Read the first three bytes and check if they're the PRG header.
|
||||
if (readHeader(file, "PRG") == UINT32_MAX) {
|
||||
fclose(file);
|
||||
return 1;
|
||||
}
|
||||
// Now we need to get the length of the Program Section.
|
||||
uint32_t length = readLength(file);
|
||||
if (length == UINT32_MAX) {
|
||||
fclose(file);
|
||||
return 1;
|
||||
}
|
||||
// Now we load the Program Segment.
|
||||
if (loadSegment(file, Program, (uint16_t)length)) {
|
||||
fclose(file);
|
||||
return 1;
|
||||
}
|
||||
// Okay, Program is loaded, now do the same thing but for Data.
|
||||
// Check the header.
|
||||
if (readHeader(file, "DAT") == UINT32_MAX) {
|
||||
return 1;
|
||||
}
|
||||
// Get the legnth of the Data Section.
|
||||
length = readLength(file);
|
||||
if (length == UINT32_MAX){
|
||||
fclose(file);
|
||||
return 1;
|
||||
}
|
||||
// Now load the Data Section.
|
||||
if (loadSegment(file, Data, (uint16_t)length)) {
|
||||
fclose(file);
|
||||
return 1;
|
||||
}
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// boostrap.h
|
||||
// Boostrapping Functions for the SplitBit CPU Emulator
|
||||
// Written by Anachronaut
|
||||
// 10/16/2024
|
||||
|
||||
#ifndef BOOTSTRAP_H
|
||||
#define BOOTSTRAP_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint8_t loadFile(char *path, uint8_t *Program, uint8_t *Data);
|
||||
|
||||
#endif // BOOTSTRAP_H
|
||||
@@ -0,0 +1,322 @@
|
||||
// cpu.c
|
||||
// SplitBit CPU Emulator Core
|
||||
// Written by Anachronaut
|
||||
// 10/16/2024
|
||||
|
||||
#include "cpu.h"
|
||||
#include "io.h"
|
||||
|
||||
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;
|
||||
cpu->DataPointer = 0x0000;
|
||||
cpu->StackPointer = 0xFFFF;
|
||||
cpu->Program = programMemory;
|
||||
cpu->Data = dataMemory;
|
||||
}
|
||||
|
||||
void genericBranch(CPURegisters *cpu){
|
||||
// Load the next two bytes from program memory into the Program Counter.
|
||||
// Byte order is imporant. Most Significant first, then Least Significant.
|
||||
cpu->ProgramCounter++; // Move to the next byte. (MSB)
|
||||
uint16_t DestinationAddress;
|
||||
DestinationAddress = (uint16_t)cpu->Program[cpu->ProgramCounter] << 8; // Cast the 8 bit value to a 16 bit value and shifts it up to the high byte.
|
||||
cpu->ProgramCounter++; // Move to the next byte. (LSB)
|
||||
DestinationAddress = DestinationAddress | (uint16_t)cpu->Program[cpu->ProgramCounter]; // Cast the 8 bit value to a 16 bit value and or it to add it to the desination.
|
||||
cpu->ProgramCounter = DestinationAddress-1;
|
||||
}
|
||||
|
||||
uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) {
|
||||
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);
|
||||
if (result > 255) {
|
||||
cpu->Status |= 0x01;
|
||||
} else {
|
||||
cpu->Status &= ~0x01;
|
||||
}
|
||||
cpu->Q = result & 0xFF;
|
||||
break;
|
||||
case 0x01:
|
||||
// SUB - A - B - Carry -> Q
|
||||
result = (uint16_t)cpu->A - (uint16_t)cpu->B - (cpu->Status & 0x01);
|
||||
if (result > 255) {
|
||||
cpu->Status |= 0x01;
|
||||
} else {
|
||||
cpu->Status &= ~0x01;
|
||||
}
|
||||
cpu->Q = result & 0xFF;
|
||||
break;
|
||||
case 0x02:
|
||||
// AND - A and B -> Q
|
||||
cpu->Q = cpu->A&cpu->B;
|
||||
break;
|
||||
case 0x03:
|
||||
// OR - A or B -> Q
|
||||
cpu->Q = cpu->A|cpu->B;
|
||||
break;
|
||||
case 0x04:
|
||||
// NAND - A nand B -> Q
|
||||
cpu->Q = ~(cpu->A&cpu->B);
|
||||
break;
|
||||
case 0x05:
|
||||
// NOR - A nor B -> Q
|
||||
cpu->Q = ~(cpu->A|cpu->B);
|
||||
break;
|
||||
case 0x06:
|
||||
// XOR - A xor B -> Q
|
||||
cpu->Q = cpu->A^cpu->B;
|
||||
break;
|
||||
case 0x07:
|
||||
// NOTA - not A -> Q
|
||||
cpu->Q = ~cpu->A;
|
||||
break;
|
||||
case 0x08:
|
||||
// NOTB - not B -> Q
|
||||
cpu->Q = ~cpu->B;
|
||||
break;
|
||||
//
|
||||
// 1x - Branch Operations:
|
||||
//
|
||||
case 0x10:
|
||||
// BRI - Branch Immediately
|
||||
genericBranch(cpu);
|
||||
break;
|
||||
case 0x11:
|
||||
// BRQ - Branch if Q = 0
|
||||
if(cpu->Q == 0) {
|
||||
genericBranch(cpu);
|
||||
} else {
|
||||
|
||||
cpu->ProgramCounter+=2;
|
||||
}
|
||||
break;
|
||||
case 0x12:
|
||||
// BRA - Branch if A = 0
|
||||
if(cpu->A == 0) {
|
||||
genericBranch(cpu);
|
||||
|
||||
} else {
|
||||
cpu->ProgramCounter+=2;
|
||||
}
|
||||
break;
|
||||
case 0x13:
|
||||
// BRB - if B = 0
|
||||
if(cpu->B == 0) {
|
||||
|
||||
genericBranch(cpu);
|
||||
} else {
|
||||
cpu->ProgramCounter+=2;
|
||||
}
|
||||
break;
|
||||
//
|
||||
// 2x - Register Operations:
|
||||
//
|
||||
case 0x20:
|
||||
// RSTA - Reset A to 0.
|
||||
cpu->A = 0;
|
||||
break;
|
||||
case 0x21:
|
||||
// RSTB - Reset B to 0.
|
||||
cpu->B = 0;
|
||||
break;
|
||||
case 0x22:
|
||||
// INCA - Add 1 to A.
|
||||
cpu->A++;
|
||||
break;
|
||||
case 0x23:
|
||||
// INCB - Add 1 to B.
|
||||
cpu->B++;
|
||||
break;
|
||||
case 0x24:
|
||||
// DECA - Subtract 1 from A.
|
||||
cpu->A--;
|
||||
break;
|
||||
case 0x25:
|
||||
// DECB - Subtract 1 from B.
|
||||
cpu->B--;
|
||||
break;
|
||||
case 0x26:
|
||||
// LDA - Load the byte referenced by the Data Pointer to A.
|
||||
cpu->A = cpu->Data[cpu->DataPointer];
|
||||
break;
|
||||
case 0x27:
|
||||
// LDB - Load the byte referenced by the Data Pointer to B.
|
||||
cpu->B = cpu->Data[cpu->DataPointer];
|
||||
break;
|
||||
case 0x28:
|
||||
// INIA - Initialize A Immediately from Program Memory.
|
||||
cpu->ProgramCounter++;
|
||||
cpu->A = cpu->Program[cpu->ProgramCounter];
|
||||
break;
|
||||
case 0x29:
|
||||
// INIB - Initialize A Immediately from Program Memory.
|
||||
cpu->ProgramCounter++;
|
||||
cpu->B = cpu->Program[cpu->ProgramCounter];
|
||||
break;
|
||||
//
|
||||
// 3x - Stack Operations:
|
||||
//
|
||||
case 0x30:
|
||||
// PSHQ - Push Q to the Stack.
|
||||
cpu->Data[cpu->StackPointer] = cpu->Q;
|
||||
cpu->StackPointer--;
|
||||
break;
|
||||
case 0x31:
|
||||
// PSHA - Push A to the Stack.
|
||||
cpu->Data[cpu->StackPointer] = cpu->A;
|
||||
cpu->StackPointer--;
|
||||
break;
|
||||
case 0x32:
|
||||
// PSHB - Push B to the Stack.
|
||||
cpu->Data[cpu->StackPointer] = cpu->B;
|
||||
cpu->StackPointer--;
|
||||
break;
|
||||
case 0x33:
|
||||
// PSHP - Push the Program Counter to the Stack.
|
||||
// Order, low byte, high byte
|
||||
cpu->Data[cpu->StackPointer] = cpu->ProgramCounter & 0xFF;
|
||||
cpu->StackPointer--;
|
||||
cpu->Data[cpu->StackPointer] = (cpu->ProgramCounter >> 8) & 0xFF;
|
||||
cpu->StackPointer--;
|
||||
break;
|
||||
case 0x34:
|
||||
// PSHD - 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--;
|
||||
break;
|
||||
case 0x35:
|
||||
// POPA - Pop Data to A.
|
||||
cpu->StackPointer++;
|
||||
cpu->A = cpu->Data[cpu->StackPointer];
|
||||
|
||||
break;
|
||||
case 0x36:
|
||||
// POPB - Pop Data to B.
|
||||
cpu->StackPointer++;
|
||||
cpu->B = cpu->Data[cpu->StackPointer];
|
||||
break;
|
||||
case 0x37:
|
||||
// POPP - Pop Data to the Program Counter
|
||||
cpu->StackPointer++;
|
||||
cpu->ProgramCounter = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
|
||||
cpu->StackPointer++;
|
||||
cpu->ProgramCounter = cpu->ProgramCounter | (uint16_t)cpu->Data[cpu->StackPointer];
|
||||
cpu->ProgramCounter += 3; // Because it needs to skip over the subsequent branch instruction on return.
|
||||
break;
|
||||
case 0x38:
|
||||
// POPD - Pop Data to the Data Pointer
|
||||
cpu->StackPointer++;
|
||||
cpu->DataPointer = (uint16_t)cpu->Data[cpu->StackPointer] << 8;
|
||||
cpu->StackPointer++;
|
||||
cpu->DataPointer |= (uint16_t)cpu->Data[cpu->StackPointer];
|
||||
break;
|
||||
//
|
||||
// 4x - Data Operations:
|
||||
//
|
||||
case 0x40:
|
||||
// INCD - Increment Data Pointer.
|
||||
cpu->DataPointer++;
|
||||
break;
|
||||
case 0x41:
|
||||
// DECD - Decrement Data Pointer.
|
||||
cpu->DataPointer--;
|
||||
break;
|
||||
case 0x42:
|
||||
// LDA - Load A from Data.
|
||||
cpu->A = cpu->Data[cpu->DataPointer];
|
||||
break;
|
||||
case 0x43:
|
||||
// LDB - Load B from Data.
|
||||
cpu->B = cpu->Data[cpu->DataPointer];
|
||||
break;
|
||||
case 0x44:
|
||||
// STQ - Store Q into Data.
|
||||
cpu->Data[cpu->DataPointer] = cpu->Q;
|
||||
break;
|
||||
case 0x45:
|
||||
// STA - Store A into Data.
|
||||
cpu->Data[cpu->DataPointer] = cpu->A;
|
||||
break;
|
||||
case 0x46:
|
||||
// STB - Store B into Data.
|
||||
cpu->Data[cpu->DataPointer] = cpu->B;
|
||||
break;
|
||||
case 0x47:
|
||||
// SETD - Set the Data Pointer.
|
||||
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;
|
||||
break;
|
||||
//
|
||||
// Dx - Output Operations:
|
||||
//
|
||||
case 0xD0:
|
||||
// OUTQ - Write the value of Q to an output port.
|
||||
cpu->ProgramCounter++;
|
||||
OutputHandler(cpu->Q, cpu->Program[cpu->ProgramCounter]);
|
||||
break;
|
||||
case 0xD1:
|
||||
// OUTA - Write the value of A to an output port.
|
||||
cpu->ProgramCounter++;
|
||||
OutputHandler(cpu->A, cpu->Program[cpu->ProgramCounter]);
|
||||
break;
|
||||
case 0xD2:
|
||||
// OUTB - Write the value of B to an output port.
|
||||
cpu->ProgramCounter++;
|
||||
OutputHandler(cpu->B, cpu->Program[cpu->ProgramCounter]);
|
||||
break;
|
||||
//
|
||||
// Ex - Input Operations:
|
||||
//
|
||||
case 0xE0:
|
||||
// CIN - Read Input Select to A.
|
||||
// cpu->A = InputSelect;
|
||||
break;
|
||||
case 0xE1:
|
||||
// RDA - Read an Input to A.
|
||||
cpu->ProgramCounter++;
|
||||
cpu->A = InputHandler(cpu->Program[cpu->ProgramCounter]);
|
||||
break;
|
||||
case 0xE2:
|
||||
// RDB - Read an Input to B.
|
||||
cpu->ProgramCounter++;
|
||||
cpu->B = InputHandler(cpu->Program[cpu->ProgramCounter]);
|
||||
break;
|
||||
case 0xE3:
|
||||
// RDD - Read an Input to Data Memory.
|
||||
cpu->ProgramCounter++;
|
||||
cpu->Data[cpu->DataPointer] = InputHandler(cpu->Program[cpu->ProgramCounter]);
|
||||
break;
|
||||
//
|
||||
// Fx - Special Operations:
|
||||
//
|
||||
case 0xF0:
|
||||
// NOP - Do nothing.
|
||||
break;
|
||||
case 0xFF:
|
||||
// HALT - Set the Halt Bit of the Status Register.
|
||||
cpu->Status |= 0x80;
|
||||
break;
|
||||
default:
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// cpu.h
|
||||
// SplitBit CPU Emulator Core
|
||||
// Written by Anachronaut
|
||||
// 10/16/2024
|
||||
|
||||
#ifndef CPU_H
|
||||
#define CPU_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// The struct containing the CPU registers.
|
||||
typedef struct {
|
||||
uint8_t A;
|
||||
uint8_t B;
|
||||
uint8_t Q;
|
||||
uint8_t Status;
|
||||
uint16_t ProgramCounter;
|
||||
uint16_t DataPointer;
|
||||
uint16_t StackPointer;
|
||||
uint8_t *Program;
|
||||
uint8_t *Data;
|
||||
} CPURegisters;
|
||||
|
||||
uint8_t executeOperation(uint8_t instruction, CPURegisters *cpu);
|
||||
|
||||
void initializeCPU(CPURegisters *cpu, uint8_t *programMemory, uint8_t *dataMemory);
|
||||
|
||||
#endif // CPU_H
|
||||
@@ -0,0 +1,64 @@
|
||||
// emulator.c
|
||||
|
||||
// SplitBit Emulator
|
||||
// Small 8-Bit Harvard Architecture CPU
|
||||
// Written by Anachronaut
|
||||
// 10/15/2024
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "cpu.h"
|
||||
#include "utility.h"
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
|
||||
uint8_t debugEnable = 0;
|
||||
int 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.
|
||||
return 1;
|
||||
}
|
||||
if (optind < argc) {
|
||||
programFile = argv[optind];
|
||||
optind++;
|
||||
} else {
|
||||
fprintf(stderr, "Error: No binary file specified.\n");
|
||||
printHelp(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
if (optind < argc) {
|
||||
fprintf(stderr, "Error: Unexpected argument: %s\n", argv[optind]);
|
||||
return 1;
|
||||
}
|
||||
if (loadFile(programFile, Program, Data)) {
|
||||
fprintf(stderr, "Error: Couldn't read file: %s\n", programFile);
|
||||
return 1;
|
||||
}
|
||||
CPURegisters cpu;
|
||||
initializeCPU(&cpu, Program, Data);
|
||||
if(debugEnable) {
|
||||
printRegisters(&cpu, Program, Data);
|
||||
}
|
||||
while (!(cpu.Status & 0x80)) {
|
||||
executeOperation(cpu.Program[cpu.ProgramCounter], &cpu);
|
||||
cpu.ProgramCounter++;
|
||||
cycleCount++;
|
||||
if (debugEnable) {
|
||||
getchar();
|
||||
printRegisters(&cpu, Program, Data);
|
||||
printf("Cycle: %u\n", cycleCount);
|
||||
}
|
||||
}
|
||||
printf("Execution halted after %u cycles.\n", cycleCount);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// io.c
|
||||
// I/O for the SplitBit CPU Emulator
|
||||
// Written by Anachronaut
|
||||
// 10/16/2024
|
||||
|
||||
#include "io.h"
|
||||
#include <stdio.h>
|
||||
|
||||
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) {
|
||||
case 0x00:
|
||||
// If data is sent here, it should be written to STDOUT.
|
||||
// For now, I'll implement this so it simply writes each byte out as it comes in.
|
||||
// Later, I'll want to use a buffer for this for performance, probably.
|
||||
putchar(DataByte);
|
||||
break;
|
||||
default:
|
||||
// Writes to unused Output Ports are ignored.
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t InputHandler(uint8_t Address) {
|
||||
switch(Address) {
|
||||
case 0x00:
|
||||
// If data is sent here, it should be read from STDIN.
|
||||
return getchar();
|
||||
break;
|
||||
default:
|
||||
// Reading from an unused port is ignored.
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// io.h
|
||||
// I/O for the SplitBit CPU Emulator
|
||||
// Written by Anachronaut
|
||||
// 10/16/2024
|
||||
|
||||
#ifndef IO_H
|
||||
#define IO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "cpu.h"
|
||||
|
||||
uint8_t OutputHandler(uint8_t DataByte, uint8_t Address);
|
||||
|
||||
uint8_t InputHandler(uint8_t Address);
|
||||
|
||||
#endif // IO_H
|
||||
@@ -0,0 +1,56 @@
|
||||
// utility.c
|
||||
// Utilities for the SplitBit CPU Emulator
|
||||
// Written by Anachronaut
|
||||
// 10/15/2024
|
||||
|
||||
#include "utility.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
#include "../Assembler/assembly.h"
|
||||
|
||||
void printHelp(const char *programName) {
|
||||
printf("Usage: %s [OPTIONS] <binaryfile>\n", programName);
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -d, --debug Enable debug mode.\n");
|
||||
printf(" -h, --help Display this help message.\n");
|
||||
}
|
||||
|
||||
uint8_t parseOptions(int argc, char *argv[]) {
|
||||
static struct option long_options[] = {
|
||||
{"debug", no_argument, 0, 'd'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
int opt;
|
||||
int option_index = 0;
|
||||
|
||||
// Parse options
|
||||
while ((opt = getopt_long(argc, argv, "dh", long_options, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
return 1;
|
||||
break;
|
||||
case 'h':
|
||||
printHelp(argv[0]);
|
||||
return 2;
|
||||
case '?':
|
||||
printHelp(argv[0]);
|
||||
return 2;
|
||||
default:
|
||||
printHelp(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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(" Stack Pointer: 0x%04X Current Value: (0x%02X) (0x%02X)\n", Data[cpu->StackPointer], Data[cpu->StackPointer+1], Data[cpu->StackPointer+2]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// utility.h
|
||||
// Utilities for the SplitBit CPU Emulator
|
||||
// Written by Anachronaut
|
||||
// 10/15/2024
|
||||
|
||||
|
||||
#ifndef UTILITY_H
|
||||
#define UTILITY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "cpu.h"
|
||||
|
||||
uint8_t parseOptions(int argc, char *argv[]);
|
||||
|
||||
void printHelp(const char *programName);
|
||||
|
||||
uint8_t loadFile(char *path, uint8_t *Program, uint8_t *Data);
|
||||
|
||||
void bootStrap(uint8_t *Program, uint8_t *Data);
|
||||
|
||||
void printRegisters(CPURegisters *cpu, uint8_t *Program, uint8_t *Data);
|
||||
|
||||
#endif // UTILITY_H
|
||||
Reference in New Issue
Block a user