mirror of
https://github.com/minkcv/vm.git
synced 2026-10-22 16:25:07 +00:00
363 lines
13 KiB
C
363 lines
13 KiB
C
#include "machine/opcodes.h"
|
|
#include "machine/constants.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <stdint.h>
|
|
|
|
#define MAX_LABELS 1024
|
|
|
|
// Used to map a label to an address
|
|
typedef struct
|
|
{
|
|
uint8_t segAddr;
|
|
uint8_t offsetAddr;
|
|
char* label;
|
|
} LabelAddress;
|
|
|
|
int countInstructions(FILE* src);
|
|
void getAddressesOfLabel(char* label, LabelAddress* labelMap[MAX_LABELS], uint8_t* segAddr, uint8_t* offsetAddr);
|
|
void createLabelMap(FILE* src, LabelAddress*** labelMap);
|
|
|
|
int main (int argc, char** argv)
|
|
{
|
|
char* filename = NULL;
|
|
int i;
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
if (!strcmp(argv[i], "-f"))
|
|
{
|
|
if (i + 1 < argc)
|
|
filename = argv[i + 1];
|
|
}
|
|
}
|
|
if (filename == NULL)
|
|
{
|
|
printf("Usage: assember -f sourcefile.asm\n");
|
|
exit(1);
|
|
}
|
|
|
|
|
|
FILE* src = fopen(filename, "r");
|
|
if (src)
|
|
{
|
|
uint16_t numInstructions = countInstructions(src);
|
|
printf("counted %d instructions\n", numInstructions);
|
|
uint16_t* binary = malloc(sizeof(uint16_t) * numInstructions);
|
|
char* line = NULL; // Causes getline to use malloc, we free this for every line
|
|
size_t toRead = 0; // Passing 0 to getline reads until \n
|
|
ssize_t charsRead;
|
|
int instrIndex = 0;
|
|
int lineCount = 0; // Used for error printing
|
|
LabelAddress** labelMap = malloc(sizeof(LabelAddress*) * MAX_LABELS);
|
|
createLabelMap(src, &labelMap);
|
|
|
|
// Read each line in the file
|
|
while ((charsRead = getline(&line, &toRead, src)) != -1)
|
|
{
|
|
lineCount++;
|
|
// If line is not a command and we haven't reached the end of the non comment code.
|
|
if (line[0] != '\n' && line[0] != ';' && line[0] != '@' && instrIndex < numInstructions)
|
|
{
|
|
// Parse tokens separated by a space or comment
|
|
char* token = strtok(line, " ;\n");
|
|
uint16_t instr; // Assembled instruction
|
|
int arg = 0; // Which arg we are currently parsing
|
|
while (token != NULL && arg < 4)
|
|
{
|
|
if (arg == 0)
|
|
{
|
|
if (!strcmp(token, "LRL")) // Special assembler macro "Load Registers Label"
|
|
{
|
|
uint8_t segAddr = 0;
|
|
uint8_t offsetAddr = 0;
|
|
uint8_t reg1;
|
|
uint8_t reg2;
|
|
token = strtok(NULL, " "); // Get the next token
|
|
if (token != NULL && token[0] == 'r')
|
|
reg1 = atoi(token + 1);
|
|
token = strtok(NULL, " "); // Get the next token
|
|
if (token != NULL && token[0] == 'r')
|
|
reg2 = atoi(token + 1);
|
|
token = strtok(NULL, " "); // Get the next token
|
|
if (token[0] == '@')
|
|
{
|
|
char* label = token;
|
|
strsep(&label, "@");
|
|
char* labelName = strsep(&label, " ;\n"); // Trim any comments or other text off the end
|
|
getAddressesOfLabel(labelName, labelMap, &segAddr, &offsetAddr);
|
|
instr = LRC << 12;
|
|
instr |= reg1 << 8;
|
|
instr |= segAddr;
|
|
binary[instrIndex] = instr; // Save the assembled instruction
|
|
instrIndex++;
|
|
instr = LRC << 12;
|
|
instr |= reg2 << 8;
|
|
instr |= offsetAddr;
|
|
}
|
|
arg += 3; // Parsed the entire instruction here
|
|
}
|
|
else if (!strcmp(token, "HALT"))
|
|
{
|
|
instr = EXT << 12;
|
|
instr |= EXT_HALT << 8;
|
|
arg++;
|
|
}
|
|
else if (!strcmp(token, "CPY"))
|
|
{
|
|
instr = EXT << 12;
|
|
instr |= EXT_CPY << 8;
|
|
arg++;
|
|
}
|
|
else if (!strcmp(token, "NOT"))
|
|
{
|
|
instr = EXT << 12;
|
|
instr |= EXT_NOT << 8;
|
|
arg++;
|
|
}
|
|
else if (!strcmp(token, "LSL"))
|
|
{
|
|
instr = EXT << 12;
|
|
instr |= EXT_LSL << 8;
|
|
arg++;
|
|
}
|
|
else if (!strcmp(token, "LSR"))
|
|
{
|
|
instr = EXT << 12;
|
|
instr |= EXT_LSR << 8;
|
|
arg++;
|
|
}
|
|
else if (!strcmp(token, "JMP"))
|
|
{
|
|
instr = EXT << 12;
|
|
instr |= EXT_JMP << 8;
|
|
arg++;
|
|
}
|
|
else if (!strcmp(token, "NOP"))
|
|
{
|
|
instr = EXT << 12;
|
|
instr |= EXT_NOP << 8;
|
|
arg++;
|
|
}
|
|
else if (!strcmp(token, "BRKP"))
|
|
{
|
|
instr = EXT << 12;
|
|
instr |= EXT_BRKP << 8;
|
|
arg++;
|
|
}
|
|
else if (!strcmp(token, "ADD"))
|
|
instr = ADD << 12;
|
|
else if (!strcmp(token, "SUB"))
|
|
instr = SUB << 12;
|
|
else if (!strcmp(token, "ADDC"))
|
|
instr = ADDC << 12;
|
|
else if (!strcmp(token, "SUBC"))
|
|
instr = SUBC << 12;
|
|
else if (!strcmp(token, "CMP"))
|
|
instr = CMP << 12;
|
|
else if (!strcmp(token, "JLT"))
|
|
instr = JLT << 12;
|
|
else if (!strcmp(token, "JGT"))
|
|
instr = JGT << 12;
|
|
else if (!strcmp(token, "JEQ"))
|
|
instr = JEQ << 12;
|
|
else if (!strcmp(token, "LDR"))
|
|
instr = LDR << 12;
|
|
else if (!strcmp(token, "STR"))
|
|
instr = STR << 12;
|
|
else if (!strcmp(token, "LRC"))
|
|
instr = LRC << 12;
|
|
else if (!strcmp(token, "AND"))
|
|
instr = AND << 12;
|
|
else if (!strcmp(token, "OR"))
|
|
instr = OR << 12;
|
|
else if (!strcmp(token, "XOR"))
|
|
instr = XOR << 12;
|
|
else
|
|
{
|
|
printf("Not a known instruction on line %d\n", lineCount);
|
|
exit(1);
|
|
}
|
|
arg++;
|
|
}
|
|
else if (arg < 4)
|
|
{
|
|
if (token[0] == 'r')
|
|
{
|
|
int shift = 0;
|
|
if (arg == 1)
|
|
shift = 8;
|
|
if (arg == 2)
|
|
shift = 4;
|
|
if (arg == 3)
|
|
shift = 0;
|
|
instr |= (atoi(token + 1) << shift);
|
|
}
|
|
else if (token[0] == '#' || token[0] == '$') // Token is a constant
|
|
{
|
|
char* num = token;
|
|
uint8_t constant;
|
|
if (token[0] == '#') // A constant in base 10
|
|
{
|
|
// Remove the # from the string
|
|
strsep(&num, "#");
|
|
// Convert to integer
|
|
constant = (uint8_t)atoi(num);
|
|
}
|
|
else if (token[0] == '$')
|
|
{
|
|
// Remove the $ from the string
|
|
strsep(&num, "$");
|
|
// Convert to base 16
|
|
constant = (uint8_t)strtol(num, NULL, 16);
|
|
}
|
|
instr |= constant;
|
|
arg++;
|
|
}
|
|
arg++;
|
|
}
|
|
token = strtok(NULL, " "); // Get the next token
|
|
}
|
|
binary[instrIndex] = instr; // Save the assembled instruction
|
|
instrIndex++;
|
|
}
|
|
// Free and null the line pointer. getline will reallocate for us
|
|
free(line);
|
|
line = NULL;
|
|
}
|
|
|
|
// Free the label map
|
|
int i;
|
|
for (i = 0; i < MAX_LABELS; i++)
|
|
{
|
|
if (labelMap[i] != NULL)
|
|
{
|
|
if (labelMap[i]->label != NULL)
|
|
{
|
|
free(labelMap[i]->label);
|
|
}
|
|
free(labelMap[i]);
|
|
}
|
|
}
|
|
free(labelMap);
|
|
|
|
char* filenameNoExtension = strsep(&filename, ".");
|
|
if (filenameNoExtension != NULL)
|
|
filename = filenameNoExtension;
|
|
|
|
// Now write the binary array out to a file with .bin appended to the name
|
|
FILE* bin = fopen(strcat(filename, ".bin"), "wb");
|
|
// Prefix binary with length
|
|
fwrite(&numInstructions, sizeof(uint16_t), 1, bin);
|
|
size_t written = fwrite(binary, sizeof(uint16_t), numInstructions, bin);
|
|
fclose(bin);
|
|
free(binary);
|
|
binary = NULL;
|
|
// Check that the instructions were written correctly
|
|
if (written != numInstructions)
|
|
{
|
|
printf("Error writing binary file\n");
|
|
}
|
|
else
|
|
{
|
|
printf("%zd instructions written to %s\n", written, filename);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("Failed to open file %s\n", filename);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Counts lines that don't start with a ; (comment) or @ (label)
|
|
// 2 instructions for LRL macro
|
|
int countInstructions(FILE* src)
|
|
{
|
|
char* line = NULL;
|
|
size_t toRead = 0; // Read until \n
|
|
ssize_t charsRead;
|
|
uint16_t instrCount = 0;
|
|
while ((charsRead = getline(&line, &toRead, src)) != -1)
|
|
{
|
|
if (line[0] != '\n' && line[0] != '@' && line[0] != ';')
|
|
{
|
|
char* token = strtok(line, " ");
|
|
if (!strcmp(token, "LRL"))
|
|
instrCount++;
|
|
instrCount++;
|
|
}
|
|
free(line);
|
|
line = NULL; // Causes getline to keep reading
|
|
}
|
|
rewind(src);
|
|
return instrCount;
|
|
}
|
|
|
|
void createLabelMap(FILE* src, LabelAddress*** labelMap)
|
|
{
|
|
char* line = NULL;
|
|
size_t toRead = 0; // Read until \n
|
|
ssize_t charsRead;
|
|
int labelCount = 0;
|
|
uint16_t instrCount = 0;
|
|
while ((charsRead = getline(&line, &toRead, src)) != -1)
|
|
{
|
|
if(line[0] == '@')
|
|
{
|
|
// Create the label entry in labelMap
|
|
char* start = line;
|
|
char* beforeAt = strsep(&line, "@");
|
|
char* labelName = strsep(&line, " ;\n"); // Trim any comments or other text off the end
|
|
LabelAddress* labeladdr = malloc(sizeof(LabelAddress));
|
|
labeladdr->label = malloc(sizeof(char) * (strlen(labelName) + 1));
|
|
strcpy(labeladdr->label, labelName);
|
|
labeladdr->segAddr = instrCount / JUMP_SEGMENT_SIZE;
|
|
labeladdr->offsetAddr = instrCount % JUMP_SEGMENT_SIZE;
|
|
(*labelMap)[labelCount] = labeladdr;
|
|
labelCount++;
|
|
free(start);
|
|
start = NULL;
|
|
labelName = NULL;
|
|
beforeAt = NULL;
|
|
line = NULL;
|
|
}
|
|
else if (line[0] != ';' && line[0] != '\n')
|
|
{
|
|
char* token = strtok(line, " ");
|
|
if (!strcmp(token, "LRL"))
|
|
instrCount++;
|
|
instrCount++;
|
|
free(line);
|
|
line = NULL;
|
|
}
|
|
else
|
|
{
|
|
free(line);
|
|
line = NULL;
|
|
}
|
|
}
|
|
rewind(src);
|
|
}
|
|
|
|
void getAddressesOfLabel(char* label, LabelAddress* labelMap[MAX_LABELS], uint8_t* segAddr, uint8_t* offsetAddr)
|
|
{
|
|
int i;
|
|
for (i = 0; i < MAX_LABELS; i++)
|
|
{
|
|
LabelAddress* current = labelMap[i];
|
|
if (current != NULL && current->label != NULL)
|
|
{
|
|
if (!strcmp(current->label, label))
|
|
{
|
|
*segAddr = current->segAddr;
|
|
*offsetAddr = current->offsetAddr;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
printf("Error: Label \"%s\" not defined\n", label);
|
|
exit(1);
|
|
}
|