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:
Anachronaut
2024-10-26 15:31:24 -04:00
committed by GitHub
parent 044c80d537
commit dfa5ad2638
23 changed files with 1627 additions and 22 deletions
+20
View File
@@ -0,0 +1,20 @@
; This is a basic hello world program for the SplitBit CPU.
; We'll create a loop that outputs each byte of our string to Output 0, the text console.
#Program
Start:
LDA ; Load a byte of the string into A.
BRA End ; If A is zero, branch out of the loop.
OUTA 0x00 ; Output the value in A to Port 0, the text console.
INCD ; Increment the Data Pointer to the next byte of the string.
BRI Start ; Branch immediately to the start of the loop.
End:
INIA 0x0A ; We'll load a linefeed into A and output it to make it look nice.
OUTA 0x00 ; Output it to the text console.
HALT ; Terminate the program.
#Data
"Hello, World!"
+17 -4
View File
@@ -10,6 +10,7 @@ SplitBit is a custom CPU designed for hobbyist projects and experimentation. The
- CLI Based: Debug messages and CPU input and output are supported through the command line.
- Binary File Support: Load programs and data from binary files.
- Modular Codebase: Mostly clean separation of CPU, I/O, and utility functions for easy modification.
- Assembler: Assemble human readable assembly language files directly into SplitBit compatible binary files. Supports including external files, handling labels, and defining Program and Data segments.
### Installation:
1) Clone the repository:
@@ -17,13 +18,18 @@ SplitBit is a custom CPU designed for hobbyist projects and experimentation. The
git clone https://github.com/RealBusinessAccount/SplitBit-Emulator.git
cd SplitBit-Emulator
```
2) Build the Emulator: You'll need gcc and make or similar.
2) Build the Emulator and the Assembler: You'll need gcc and make or similar.
```
make
```
3) Run the emulator:
3) Assemble a program:
```
./SplitBit Programs/hello.bin
./Assembler Programs/hello.asm
```
4) Run the program:
```
./SplitBit hello.bin
```
### Usage:
@@ -34,8 +40,15 @@ make
- -d, --debug: Enable debug mode to single step through cycles.
- -h, --help: Show help and usage information.
### Usage:
```
./Assembler [assembly file]
```
#### Notes:
- The assembled binaries are saved with the same name as the assembly source file they're assembled from, with a .bin extension, in the same directory.
### Additional Info:
For more information on the custom ISA and programming for SplitBit, see the Programming Manual.
For more information on the custom ISA and programming for SplitBit, see the Programming Manual and Assembler Manual.
### License:
This project is licensed under the Apache License, Version 2.0. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).
+109
View File
@@ -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;
}
+176
View File
@@ -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
}
+67
View File
@@ -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
+93
View File
@@ -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.
}
+15
View File
@@ -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
+130
View File
@@ -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;
}
+20
View File
@@ -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
+199
View File
@@ -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));
}
+32
View File
@@ -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
+97
View File
@@ -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;
}
+13
View File
@@ -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
+322
View File
@@ -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;
}
+28
View File
@@ -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
+64
View File
@@ -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);
}
+37
View File
@@ -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;
}
}
+16
View File
@@ -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
+56
View File
@@ -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]);
}
+23
View File
@@ -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
+61
View File
@@ -0,0 +1,61 @@
# SplitBit Assembler Manual:
SplitBit assembly syntax is similar to many other assembler syntaxes. Whitepsace at the start or end of a line is disregarded by the assembler and may be used to make programs more readable to the programmer. The Instruction Mnemonics are listed in the SplitBit Programming Manual, and the assembler is not case sensitve in regard to the mnemonics.
A semicolon, ';', denotes the start of a comment, anything beyond it on a line is disregarded by the assembler.
Special Keywords are denoted with hash marks, '#'. The Keywords are #Include, #Program, and #Data.
Literal values may be defined in a few ways. Numerical values must be within the range of a single 8 bit integer.
The assembler will accept:
- Hexadecimal values prefaced with 0x, eg. 0x00, 0x7F.
- Decimal values prefaced with 0d, eg. 0d0, 0d120, 0d255.
- Strings enclosed in double qoutes, eg. "a", "Hello, World!", "It is dark, you are likely to be eaten by a grue."
Labels may be a string of up to 32 alphanumeric characters that must end with a semicolon, ':'.
```
ProgramStart:
LoopStart:
ErrorHandler01:
```
The #Include Keyword tells the assembler to load another file to be assembled along with the current file. It is more or less equivalent to copying the contents of the included file into the current file being processed. You simply put the path to the file to include in quotes after the keyword.
```
#Include string.asm
```
SplitBit programs must have a Program Segment. You define the start of a program with the #Program Keyword.
SplitBit programs may have a Data Segment. You may define the start of the data with the #Data Keyword.
## An Example SplitBit Assembly Program:
```
; This is a basic hello world program for the SplitBit CPU.
; We'll create a loop that outputs each byte of our string to Output 0, the text console.
#Program
Start:
LDA ; Load a byte of the string into A.
BRA End ; If A is zero, branch out of the loop.
OUTA 0x00 ; Output the value in A to Port 0, the text console.
INCD ; Increment the Data Pointer to the next byte of the string.
BRI Start ; Branch immediately to the start of the loop.
End:
INIA 0x0A ; We'll load a linefeed into A and output it to make it look nice.
OUTA 0x00 ; Output it to the text console.
HALT ; Terminate the program.
#Data
"Hello, World!"
```
+4 -4
View File
@@ -37,7 +37,7 @@ Hex Code | Mnemonic | Description
12 | BRA | Branch on A. If A is zero, loads the immediate next two bytes of Program Memory into the Program Counter.
13 | BRB | Branch on B. If B is zero, loads the immediate next two bytes of Program Memory into the Program Counter.
### Register Operations: 10 Instructions
### Register Operations: 6 Instructions
| Hex Code | Mnemonic | Description |
| -------- | -------- | ------------------- |
| 20 | RSTA | Resets A to 0. |
@@ -61,10 +61,10 @@ Hex Code | Mnemonic | Description
34 | PSHD | Stores the Data Pointer to the next two bytes in the stack, decrements the Stack Pointer by two.
35 | POPA | Reads the location referenced by the Stack Pointer from Data Memory into A then increments the Stack Pointer.
36 | POPB | Reads the location referenced by the Stack Pointer from Data Memory into B then increments the Stack Pointer.
37 | POPP | Restores the Program Counter from the top two bytes in the stack and increments it by 3 to account for a subsequent branch instruction, increments the Stack Pointer by two.
38 | POPD | Restores the Data Pointer from the top two bytes in the stack, increments the Stack Pointer by two.
37 | POPP | Restores the Program Counter from the top two bytes in the stack, increments the Stack Pointer by two.
38 | POPD | Restores the Program Counter from the top two bytes in the stack, increments the Stack Pointer by two.
### Data Operations: 8 Instructions
### Data Operations: 7 Instructions
Hex Code | Mnemonic | Description
-- | -- | --
40 | INCD | Increments the Data Pointer.
+28 -14
View File
@@ -1,4 +1,4 @@
# SplitBit Emulator Makefile
# SplitBit Emulator and Assembler Makefile
# Anachronaut
# 10/16/2024
@@ -7,32 +7,46 @@ CC = gcc
CFLAGS = -Wall
# Directories
SRC_DIR = Source
SRC_DIR_EMU = Source/Emulator
SRC_DIR_ASM = Source/Assembler
OBJ_DIR = Object
# Source files
SRCS = emulator.c io.c utility.c assembly.c cpu.c bootstrap.c
OBJS = $(SRCS:%.c=$(OBJ_DIR)/%.o)
EMU_SRCS = emulator.c io.c utility.c cpu.c bootstrap.c assembly.c
ASM_SRCS = Assembler.c assembly.c firstPass.c Assm-util.c secondPass.c
# Output binary name
TARGET = SplitBit
EMU_OBJS = $(EMU_SRCS:%.c=$(OBJ_DIR)/%.o)
ASM_OBJS = $(ASM_SRCS:%.c=$(OBJ_DIR)/%.o)
# Default target
all: $(TARGET)
# Output binary names
EMU_TARGET = SplitBit
ASM_TARGET = Assembler
# Create binary by linking object files
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)
# Default target: build both emulator and assembler
all: $(EMU_TARGET) $(ASM_TARGET)
# Compile source files to object files
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
# Emulator binary
$(EMU_TARGET): $(EMU_OBJS)
$(CC) $(CFLAGS) -o $(EMU_TARGET) $(EMU_OBJS)
# Assembler binary
$(ASM_TARGET): $(ASM_OBJS)
$(CC) $(CFLAGS) -o $(ASM_TARGET) $(ASM_OBJS)
# Compile emulator source files to object files
$(OBJ_DIR)/%.o: $(SRC_DIR_EMU)/%.c
mkdir -p $(OBJ_DIR)
$(CC) $(CFLAGS) -c $< -o $@
# Compile assembler source files to object files
$(OBJ_DIR)/%.o: $(SRC_DIR_ASM)/%.c
mkdir -p $(OBJ_DIR)
$(CC) $(CFLAGS) -c $< -o $@
# Clean up object and binary files
clean:
rm -rf $(OBJ_DIR)
rm $(TARGET)
rm -f $(EMU_TARGET) $(ASM_TARGET)
# Phony targets
.PHONY: all clean