700 lines
26 KiB
C
700 lines
26 KiB
C
//
|
||
// Created by bruno on 1.2.2025.
|
||
//
|
||
|
||
#include "assembler.h"
|
||
|
||
Label labels[MAX_LABELS];
|
||
int labelCount = 0;
|
||
|
||
//
|
||
// Helper functions for string manipulation
|
||
//
|
||
void trim(char *s) {
|
||
// Remove leading whitespace
|
||
while (isspace((unsigned char) *s)) s++;
|
||
|
||
// Remove trailing whitespace
|
||
char *end = s + strlen(s) - 1;
|
||
while (end > s && isspace((unsigned char) *end)) {
|
||
*end = '\0';
|
||
end--;
|
||
}
|
||
}
|
||
|
||
// Look up a label by name; returns -1 if not found.
|
||
int lookupLabel(const char *name) {
|
||
for (int i = 0; i < labelCount; i++) {
|
||
if (strcmp(labels[i].name, name) == 0)
|
||
return labels[i].address;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
// Add a label to the table
|
||
void addLabel(const char *name, int address) {
|
||
if (labelCount >= MAX_LABELS) {
|
||
fprintf(stderr, "Too many labels!\n");
|
||
exit(1);
|
||
}
|
||
strncpy(labels[labelCount].name, name, sizeof(labels[labelCount].name));
|
||
labels[labelCount].address = address;
|
||
labelCount++;
|
||
}
|
||
|
||
//
|
||
// Parse a register string (e.g., "R0", "R1", etc.) and return it's number.
|
||
// Returns -1 on error.
|
||
int parseRegister(const char *token) {
|
||
if (token[0] == 'R' || token[0] == 'r') {
|
||
int reg = atoi(token + 1);
|
||
if (reg >= 0 && reg < REG_COUNT)
|
||
return reg;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
// Parse an immediate value (supports decimal and 0x... hexadecimal)
|
||
uint8_t parseImmediate(const char *token) {
|
||
int16_t value = 0; // Temporary variable as signed int16_t
|
||
|
||
// Check if the value starts with '0x' or '0X' for hexadecimal
|
||
if (strlen(token) > 2 && token[0] == '0' && (token[1] == 'x' || token[1] == 'X')) {
|
||
// Handle hexadecimal: Check for signed hex (e.g. -0x1F4, +0x1F4)
|
||
if (token[2] == '+' || token[2] == '-') {
|
||
sscanf(token, "%hx", &value); // Hexadecimal signed (same as unsigned in sscanf)
|
||
if (token[2] == '-') value = -value; // Adjust sign if negative
|
||
} else {
|
||
// Hexadecimal unsigned value
|
||
sscanf(token, "%hx", &value);
|
||
}
|
||
} else {
|
||
// Check if the value has a signed prefix (+ or -) for decimal
|
||
if (token[0] == '+' || token[0] == '-') {
|
||
sscanf(token, "%hd", &value); // Signed decimal
|
||
} else {
|
||
// Unsigned decimal value
|
||
unsigned int unsigned_value;
|
||
sscanf(token, "%u", &unsigned_value);
|
||
value = (int16_t) unsigned_value; // Cast unsigned to signed
|
||
}
|
||
}
|
||
|
||
// Convert signed 16-bit value to unsigned 8-bit value
|
||
// Ensure the value fits within the range of uint8_t (0 to 255)
|
||
return (uint8_t) (value & 0xFF); // Mask with 0xFF to discard upper bits
|
||
}
|
||
|
||
void toUpperCase(char *string) {
|
||
while (*string) {
|
||
if (*string > 0x60 && *string < 0x7b) {
|
||
(*string) -= 0x20;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Map an instruction mnemonic (string) to its opcode value and expected operand types.
|
||
// For simplicity, we will return the opcode value and then in our parser we’ll decide how many operands to expect.
|
||
// (In a full assembler you might use a more sophisticated data structure.)
|
||
//
|
||
int getOpcode(char *mnemonic) {
|
||
toUpperCase(mnemonic);
|
||
if (strcmp(mnemonic, "NOP") == 0)
|
||
return NOP;
|
||
else if (strcmp(mnemonic, "BRK") == 0)
|
||
return BRK;
|
||
else if (strcmp(mnemonic, "MOV") == 0)
|
||
return -2; // Special case: we must decide between MOV_RN_IMM, MOV_RN_RM, MOV_RN_ADDR, MOV_ADDR_RN
|
||
else if (strcmp(mnemonic, "SWAP") == 0)
|
||
return SWAP;
|
||
else if (strcmp(mnemonic, "SWAPN") == 0)
|
||
return SWAPN;
|
||
else if (strcmp(mnemonic, "ADD") == 0)
|
||
return -3; // Special: decide between ADD_RN_RM and ADD_RN_IMM
|
||
else if (strcmp(mnemonic, "SUB") == 0)
|
||
return -4; // Special: decide between SUB_RN_RM and SUB_RN_IMM
|
||
else if (strcmp(mnemonic, "MUL") == 0)
|
||
return -5; // Special: decide between MUL_RN_RM and MUL_RN_IMM
|
||
else if (strcmp(mnemonic, "DIV") == 0)
|
||
return -6; // Special: decide between DIV_RN_RM and DIV_RN_IMM
|
||
else if (strcmp(mnemonic, "MOD") == 0)
|
||
return -7; // Special: decide between MOD_RN_RM and MOD_RN_IMM
|
||
else if (strcmp(mnemonic, "NEG") == 0)
|
||
return NEG_RN;
|
||
else if (strcmp(mnemonic, "AND") == 0)
|
||
return -8; // Special: decide between AND_RN_RM and AND_RN_IMM
|
||
else if (strcmp(mnemonic, "OR") == 0)
|
||
return -9; // Special: decide between OR_RN_RM and OR_RN_IMM
|
||
else if (strcmp(mnemonic, "XOR") == 0)
|
||
return -10; // Special: decide between XOR_RN_RM and XOR_RN_IMM
|
||
else if (strcmp(mnemonic, "NOT") == 0)
|
||
return NOT_RN;
|
||
else if (strcmp(mnemonic, "SHL") == 0)
|
||
return SHL_RN_IMM;
|
||
else if (strcmp(mnemonic, "SHR") == 0)
|
||
return SHR_RN_IMM;
|
||
else if (strcmp(mnemonic, "SAR") == 0)
|
||
return SAR_RN_IMM;
|
||
else if (strcmp(mnemonic, "JMP") == 0)
|
||
return -11; //Special: if + or - present choose JMP_REL, otherwise JMP
|
||
else if (strcmp(mnemonic, "INC") == 0)
|
||
return -12; //Special: decide between INC_RN and INC_ADDR
|
||
else if (strcmp(mnemonic, "DEC") == 0)
|
||
return -13; //Special: decide between DEC_RN and DEC_ADDR
|
||
else if (strcmp(mnemonic, "CMP") == 0)
|
||
return CMP;
|
||
else if (strcmp(mnemonic, "JE") == 0)
|
||
return JE;
|
||
else if (strcmp(mnemonic, "JNE") == 0)
|
||
return JNE;
|
||
else if (strcmp(mnemonic, "JMPBS") == 0)
|
||
return -14; //Special: decide between JMP_BIT_SET_RN and JMP_BIT_SET_ADDR
|
||
else if (strcmp(mnemonic, "JMPBC") == 0)
|
||
return -15; //Special: decide between JMP_BIT_CLEAR_RN and JMP_BIT_CLEAR_ADDR
|
||
else if (strcmp(mnemonic, "JG") == 0)
|
||
return JG;
|
||
else if (strcmp(mnemonic, "JL") == 0)
|
||
return JL;
|
||
else if (strcmp(mnemonic, "JGE") == 0)
|
||
return JGE;
|
||
else if (strcmp(mnemonic, "JLE") == 0)
|
||
return JLE;
|
||
else if (strcmp(mnemonic, "CALL") == 0)
|
||
return CALL;
|
||
else if (strcmp(mnemonic, "RET") == 0)
|
||
return RET;
|
||
else {
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
//
|
||
// In this simple assembler, some instructions share a mnemonic, and we must choose the correct opcode
|
||
// based on the type of the operand (register vs. immediate vs. memory).
|
||
// The following helper functions decide that, given two operands (as strings).
|
||
//
|
||
// For example, "MOV Rn, 42" should choose MOV_RN_IMM, while "MOV Rn, Rm" should choose MOV_RN_RM.
|
||
// We assume that memory addresses are written in square brackets, e.g. "[123]".
|
||
//
|
||
int resolveMOV(const char *dest, const char *src) {
|
||
// If dest starts with '[' then it is a memory destination.
|
||
if (dest[0] == '[') return MOV_ADDR_RN; // actually, MOV [Addr], Rn expects Rn in second operand
|
||
// Otherwise, dest is a register.
|
||
// Now, check src:
|
||
if (src[0] == 'R' || src[0] == 'r') {
|
||
return MOV_RN_RM;
|
||
} else if (src[0] == '[') {
|
||
return MOV_RN_ADDR;
|
||
} else {
|
||
return MOV_RN_IMM;
|
||
}
|
||
}
|
||
|
||
int resolveALU(int baseOpcode, const char *src) {
|
||
// baseOpcode is one of our special negative values for ADD, SUB, etc.
|
||
if (src[0] == 'R' || src[0] == 'r') {
|
||
switch (baseOpcode) {
|
||
case -3:
|
||
return ADD_RN_RM;
|
||
case -4:
|
||
return SUB_RN_RM;
|
||
case -5:
|
||
return MUL_RN_RM;
|
||
case -6:
|
||
return DIV_RN_RM;
|
||
case -7:
|
||
return MOD_RN_RM;
|
||
case -8:
|
||
return AND_RN_RM;
|
||
case -9:
|
||
return OR_RN_RM;
|
||
case -10:
|
||
return XOR_RN_RM;
|
||
case -12:
|
||
return INC_RN;
|
||
case -13:
|
||
return DEC_RN;
|
||
case -14:
|
||
return JMP_BIT_SET_RN;
|
||
case -15:
|
||
return JMP_BIT_CLEAR_RN;
|
||
default:
|
||
return -1;
|
||
}
|
||
} else if (src[0] == '+' || src[0] == '-') {
|
||
switch (baseOpcode) {
|
||
case -11:
|
||
return JMP_REL;
|
||
default:
|
||
return -1;
|
||
}
|
||
} else {
|
||
switch (baseOpcode) {
|
||
case -3:
|
||
return ADD_RN_IMM;
|
||
case -4:
|
||
return SUB_RN_IMM;
|
||
case -5:
|
||
return MUL_RN_IMM;
|
||
case -6:
|
||
return DIV_RN_IMM;
|
||
case -7:
|
||
return MOD_RN_IMM;
|
||
case -8:
|
||
return AND_RN_IMM;
|
||
case -9:
|
||
return OR_RN_IMM;
|
||
case -10:
|
||
return XOR_RN_IMM;
|
||
case -11:
|
||
return JMP;
|
||
case -12:
|
||
return INC_ADDR;
|
||
case -13:
|
||
return DEC_ADDR;
|
||
case -14:
|
||
return JMP_BIT_SET_ADDR;
|
||
case -15:
|
||
return JMP_BIT_CLEAR_ADDR;
|
||
default:
|
||
return -1;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Reads a single line from the source string.
|
||
const char *readLine(const char *source, char *buffer, size_t maxLen) {
|
||
size_t i = 0;
|
||
while (*source && *source != '\n' && i < maxLen - 1) {
|
||
buffer[i++] = *source++;
|
||
}
|
||
buffer[i] = '\0';
|
||
return (*source == '\n') ? source + 1 : source;
|
||
}
|
||
|
||
//
|
||
// The first pass scans the assembly source file to record all labels and their addresses.
|
||
// The address is simply the offset into the output machine code buffer.
|
||
// For this example, every instruction is assumed to have a fixed length (opcode plus operand bytes).
|
||
//
|
||
int firstPass(const char *source) {
|
||
char line[MAX_LINE_LENGTH];
|
||
int addr = 0;
|
||
const char *ptr = source;
|
||
|
||
while (*ptr) {
|
||
ptr = readLine(ptr, line, sizeof(line));
|
||
trim(line);
|
||
|
||
// Skip blank lines or comments.
|
||
if (line[0] == '\0' || line[0] == ';' || line[0] == '#')
|
||
continue;
|
||
|
||
// Process labels.
|
||
char *colon = strchr(line, ':');
|
||
if (colon != NULL) {
|
||
*colon = '\0';
|
||
trim(line);
|
||
addLabel(line, addr);
|
||
char *rest = colon + 1;
|
||
trim(rest);
|
||
if (strlen(rest) == 0)
|
||
continue;
|
||
strcpy(line, rest);
|
||
}
|
||
|
||
// Parse the mnemonic and operands.
|
||
char mnemonic[32], operand1[64], operand2[64];
|
||
operand1[0] = '\0';
|
||
operand2[0] = '\0';
|
||
sscanf(line, "%31s %63[^,], %63s", mnemonic, operand1, operand2);
|
||
|
||
// Use the mapper to get a base opcode.
|
||
int baseOpcode = getOpcode(mnemonic);
|
||
if (baseOpcode == -1) {
|
||
printf("Unknown instruction: %s\n", mnemonic);
|
||
continue;
|
||
}
|
||
|
||
int size = 0; // Instruction size in bytes.
|
||
if (baseOpcode == -2) {
|
||
// MOV instruction requires further resolution.
|
||
int resolvedOpcode = resolveMOV(operand1, operand2);
|
||
if (resolvedOpcode == MOV_RN_IMM || resolvedOpcode == MOV_RN_RM) {
|
||
size = 3; // opcode (1) + reg (1) + immediate or register (1)
|
||
} else if (resolvedOpcode == MOV_RN_ADDR || resolvedOpcode == MOV_ADDR_RN) {
|
||
size = 6; // opcode (1) + one operand as register (1) and one 32-bit address (4) [+ padding if needed]
|
||
} else {
|
||
size = 3; // fallback
|
||
}
|
||
} else if (baseOpcode < 0) {
|
||
// Ambiguous instructions that use resolveALU.
|
||
// For JMP and jump-bit instructions, the jump target is in operand1.
|
||
if (baseOpcode == -11) {
|
||
// JMP: if operand1 starts with '+' or '-', it's relative.
|
||
if (operand1[0] == '+' || operand1[0] == '-') {
|
||
// resolve as JMP_REL.
|
||
int resolvedOpcode = resolveALU(baseOpcode, operand1);
|
||
size = 2; // opcode (1) + 1-byte relative offset (1)
|
||
} else {
|
||
int resolvedOpcode = resolveALU(baseOpcode, operand1);
|
||
size = 5; // opcode (1) + 32-bit absolute address (4)
|
||
}
|
||
} else if (baseOpcode == -14 || baseOpcode == -15) {
|
||
// JMPBS or JMPBC (jump if bit set/clear)
|
||
int resolvedOpcode = resolveALU(baseOpcode, operand1);
|
||
if (operand1[0] == 'R' || operand1[0] == 'r')
|
||
size = 7; // opcode (1) + register (1) + bit (1) + 32-bit jump address (4)
|
||
else
|
||
size = 10; // opcode (1) + 32-bit memory address (4) + bit (1) + 32-bit jump address (4)
|
||
} else {
|
||
// For arithmetic ALU instructions and INC/DEC,
|
||
// use operand2 to resolve.
|
||
int resolvedOpcode = resolveALU(baseOpcode, operand2);
|
||
switch (resolvedOpcode) {
|
||
case ADD_RN_RM:
|
||
case SUB_RN_RM:
|
||
case MUL_RN_RM:
|
||
case DIV_RN_RM:
|
||
case MOD_RN_RM:
|
||
case AND_RN_RM:
|
||
case OR_RN_RM:
|
||
case XOR_RN_RM:
|
||
case ADD_RN_IMM:
|
||
case SUB_RN_IMM:
|
||
case MUL_RN_IMM:
|
||
case DIV_RN_IMM:
|
||
case MOD_RN_IMM:
|
||
case AND_RN_IMM:
|
||
case OR_RN_IMM:
|
||
case XOR_RN_IMM:
|
||
size = 3; // opcode (1) + register (1) + reg/immediate (1)
|
||
break;
|
||
case INC_RN:
|
||
case DEC_RN:
|
||
size = 2; // opcode (1) + register (1)
|
||
break;
|
||
case INC_ADDR:
|
||
case DEC_ADDR:
|
||
size = 5; // opcode (1) + 32-bit address (4)
|
||
break;
|
||
default:
|
||
size = 3;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
// Non-ambiguous instructions that have positive opcodes.
|
||
// Use the mapping value (baseOpcode) directly.
|
||
switch (baseOpcode) {
|
||
case NOP:
|
||
case BRK:
|
||
size = 1;
|
||
break;
|
||
case SWAP:
|
||
case CMP:
|
||
size = 3;
|
||
break;
|
||
case SWAPN:
|
||
case NEG_RN:
|
||
case NOT_RN:
|
||
size = 2;
|
||
break;
|
||
case SHL_RN_IMM:
|
||
case SHR_RN_IMM:
|
||
case SAR_RN_IMM:
|
||
size = 3;
|
||
break;
|
||
case JE:
|
||
case JNE:
|
||
case JG:
|
||
case JL:
|
||
case JGE:
|
||
case JLE:
|
||
case CALL:
|
||
size = 5; // opcode (1) + 32-bit address (4)\n break;
|
||
case RET:
|
||
size = 1;
|
||
break;
|
||
default:
|
||
size = 3;
|
||
break;
|
||
}
|
||
}
|
||
addr += size;
|
||
}
|
||
return addr;
|
||
}
|
||
|
||
//
|
||
// The second pass actually translates the assembly instructions to machine code.
|
||
// The machine code is written into the provided buffer. (It must be large enough.)
|
||
//
|
||
int secondPass(const char *source, uint8_t *code) {
|
||
char line[MAX_LINE_LENGTH];
|
||
int addr = 0;
|
||
const char *ptr = source;
|
||
|
||
while (*ptr) {
|
||
ptr = readLine(ptr, line, sizeof(line));
|
||
trim(line);
|
||
|
||
if (line[0] == '\0' || line[0] == ';' || line[0] == '#')
|
||
continue;
|
||
|
||
// Process labels: replace colon with a space.
|
||
char *colon = strchr(line, ':');
|
||
if (colon != NULL) {
|
||
*colon = ' ';
|
||
}
|
||
if (strlen(line) == 0)
|
||
continue;
|
||
|
||
// Parse the mnemonic and operands.
|
||
char mnemonic[32], operand1[64], operand2[64];
|
||
operand1[0] = '\0';
|
||
operand2[0] = '\0';
|
||
sscanf(line, "%31s %63[^,], %63s", mnemonic, operand1, operand2);
|
||
|
||
// Use the mapper to get the base opcode.
|
||
int baseOpcode = getOpcode(mnemonic);
|
||
if (baseOpcode == -1) {
|
||
fprintf(stderr, "Unknown instruction: %s\n", mnemonic);
|
||
exit(1);
|
||
}
|
||
|
||
// --- MOV Instruction ---
|
||
if (baseOpcode == -2) { // MOV is ambiguous.
|
||
char *dest = strtok(NULL, " ,");
|
||
char *src = strtok(NULL, " ,");
|
||
if (!dest || !src) {
|
||
fprintf(stderr, "Error: MOV requires two operands.\n");
|
||
exit(1);
|
||
}
|
||
int resolvedOpcode = resolveMOV(dest, src);
|
||
code[addr++] = resolvedOpcode;
|
||
if (resolvedOpcode == MOV_RN_IMM) {
|
||
int reg = parseRegister(dest);
|
||
uint8_t imm = parseImmediate(src);
|
||
code[addr++] = reg;
|
||
code[addr++] = imm;
|
||
} else if (resolvedOpcode == MOV_RN_RM) {
|
||
int regDest = parseRegister(dest);
|
||
int regSrc = parseRegister(src);
|
||
code[addr++] = regDest;
|
||
code[addr++] = regSrc;
|
||
} else if (resolvedOpcode == MOV_RN_ADDR) {
|
||
int reg = parseRegister(dest);
|
||
// Remove brackets from src, assuming format "[address]"
|
||
char addrStr[32];
|
||
strncpy(addrStr, src + 1, strlen(src) - 2);
|
||
addrStr[strlen(src) - 2] = '\0';
|
||
uint32_t memAddr = (uint32_t) strtoul(addrStr, NULL, 0);
|
||
code[addr++] = reg;
|
||
code[addr++] = (memAddr >> 24) & 0xFF;
|
||
code[addr++] = (memAddr >> 16) & 0xFF;
|
||
code[addr++] = (memAddr >> 8) & 0xFF;
|
||
code[addr++] = memAddr & 0xFF;
|
||
} else if (resolvedOpcode == MOV_ADDR_RN) {
|
||
// dest is memory reference.
|
||
char addrStr[32];
|
||
strncpy(addrStr, dest + 1, strlen(dest) - 2);
|
||
addrStr[strlen(dest) - 2] = '\0';
|
||
uint32_t memAddr = (uint32_t) strtoul(addrStr, NULL, 0);
|
||
int reg = parseRegister(src);
|
||
code[addr++] = (memAddr >> 24) & 0xFF;
|
||
code[addr++] = (memAddr >> 16) & 0xFF;
|
||
code[addr++] = (memAddr >> 8) & 0xFF;
|
||
code[addr++] = memAddr & 0xFF;
|
||
code[addr++] = reg;
|
||
}
|
||
}
|
||
// --- ALU Instructions (Arithmetic, INC/DEC, etc.) ---
|
||
else if (baseOpcode < 0 && baseOpcode != -2 && baseOpcode != -11 && baseOpcode != -14 && baseOpcode != -15) {
|
||
// For arithmetic and INC/DEC instructions, use operand2.
|
||
char *dest = strtok(NULL, " ,");
|
||
char *src = strtok(NULL, " ,");
|
||
if (!dest || !src) {
|
||
fprintf(stderr, "Error: %s requires two operands.\n", mnemonic);
|
||
exit(1);
|
||
}
|
||
int resolvedOpcode = resolveALU(baseOpcode, src);
|
||
code[addr++] = resolvedOpcode;
|
||
int regDest = parseRegister(dest);
|
||
code[addr++] = regDest;
|
||
if (src[0] == 'R' || src[0] == 'r') {
|
||
int regSrc = parseRegister(src);
|
||
code[addr++] = regSrc;
|
||
} else {
|
||
uint8_t imm = parseImmediate(src);
|
||
code[addr++] = imm;
|
||
}
|
||
}
|
||
// --- Jump Instructions ---
|
||
else if (baseOpcode == -11) { // JMP (ambiguous)
|
||
// For JMP, the operand is the jump target.
|
||
char *operand = strtok(NULL, " ,");
|
||
if (!operand) {
|
||
fprintf(stderr, "Error: JMP requires an operand.\n");
|
||
exit(1);
|
||
}
|
||
int resolvedOpcode = resolveALU(baseOpcode, operand);
|
||
code[addr++] = resolvedOpcode;
|
||
if (operand[0] == '+' || operand[0] == '-') {
|
||
// Relative jump: 1-byte offset.
|
||
uint8_t offset = parseImmediate(operand);
|
||
code[addr++] = offset;
|
||
} else {
|
||
// Absolute jump: 32-bit address.
|
||
uint32_t jumpAddr = (uint32_t) lookupLabel(operand);
|
||
code[addr++] = (jumpAddr >> 24) & 0xFF;
|
||
code[addr++] = (jumpAddr >> 16) & 0xFF;
|
||
code[addr++] = (jumpAddr >> 8) & 0xFF;
|
||
code[addr++] = jumpAddr & 0xFF;
|
||
}
|
||
}
|
||
// --- Jump Bit Set/Clear Instructions ---
|
||
else if (baseOpcode == -14 || baseOpcode == -15) {
|
||
// For JMPBS (jump if bit set) or JMPBC (jump if bit clear), the operand specifies the register/memory
|
||
// from which to test the bit, followed by the bit value and the jump target.
|
||
char *srcOperand = strtok(NULL, " ,"); // register or memory reference
|
||
char *bitToken = strtok(NULL, " ,");
|
||
char *target = strtok(NULL, " ,");
|
||
if (!srcOperand || !bitToken || !target) {
|
||
fprintf(stderr, "Error: %s requires three operands.\n", mnemonic);
|
||
exit(1);
|
||
}
|
||
int resolvedOpcode = resolveALU(baseOpcode, srcOperand);
|
||
code[addr++] = resolvedOpcode;
|
||
// Encode the source operand.
|
||
if (srcOperand[0] == 'R' || srcOperand[0] == 'r') {
|
||
int reg = parseRegister(srcOperand);
|
||
code[addr++] = reg;
|
||
} else {
|
||
// Memory reference: encode 32-bit address.
|
||
char addrStr[32];
|
||
strncpy(addrStr, srcOperand + 1, strlen(srcOperand) - 2);
|
||
addrStr[strlen(srcOperand) - 2] = '\\0';
|
||
uint32_t memAddr = (uint32_t) strtoul(addrStr, NULL, 0);
|
||
code[addr++] = (memAddr >> 24) & 0xFF;
|
||
code[addr++] = (memAddr >> 16) & 0xFF;
|
||
code[addr++] = (memAddr >> 8) & 0xFF;
|
||
code[addr++] = memAddr & 0xFF;
|
||
}
|
||
// Encode the bit number (assumed to be a one-byte immediate).
|
||
uint8_t bitVal = parseImmediate(bitToken);
|
||
code[addr++] = bitVal;
|
||
// Encode the jump target as a 32-bit address.
|
||
uint32_t jumpAddr = (uint32_t) lookupLabel(target);
|
||
code[addr++] = (jumpAddr >> 24) & 0xFF;
|
||
code[addr++] = (jumpAddr >> 16) & 0xFF;
|
||
code[addr++] = (jumpAddr >> 8) & 0xFF;
|
||
code[addr++] = jumpAddr & 0xFF;
|
||
}
|
||
// --- Other Instructions (CMP, SWAP, NEG, NOT, SHL, SHR, SAR, JE, JNE, JG, JL, JGE, JLE, CALL, RET) ---
|
||
else if (baseOpcode > 0) {
|
||
// For instructions that are not ambiguous, simply encode the opcode and its operands.
|
||
switch (baseOpcode) {
|
||
case CMP:
|
||
case SWAP: { // Two register operands.
|
||
char *op1 = strtok(NULL, " ,");
|
||
char *op2 = strtok(NULL, " ,");
|
||
if (!op1 || !op2) {
|
||
fprintf(stderr, "Error: %s requires two operands.\n", mnemonic);
|
||
exit(1);
|
||
}
|
||
code[addr++] = baseOpcode;
|
||
int r1 = parseRegister(op1);
|
||
int r2 = parseRegister(op2);
|
||
code[addr++] = r1;
|
||
code[addr++] = r2;
|
||
}
|
||
break;
|
||
case SWAPN:
|
||
case NEG_RN:
|
||
case NOT_RN: { // Single register operand.
|
||
char *op = strtok(NULL, " ,");
|
||
if (!op) {
|
||
fprintf(stderr, "Error: %s requires one operand.\n", mnemonic);
|
||
exit(1);
|
||
}
|
||
code[addr++] = baseOpcode;
|
||
int reg = parseRegister(op);
|
||
code[addr++] = reg;
|
||
}
|
||
break;
|
||
case SHL_RN_IMM:
|
||
case SHR_RN_IMM:
|
||
case SAR_RN_IMM: { // Shift: register and immediate operand.
|
||
char *regToken = strtok(NULL, " ,");
|
||
char *immToken = strtok(NULL, " ,");
|
||
if (!regToken || !immToken) {
|
||
fprintf(stderr, "Error: %s requires two operands.\n", mnemonic);
|
||
exit(1);
|
||
}
|
||
code[addr++] = baseOpcode;
|
||
int reg = parseRegister(regToken);
|
||
code[addr++] = reg;
|
||
uint8_t imm = parseImmediate(immToken);
|
||
code[addr++] = imm;
|
||
}
|
||
break;
|
||
case JE:
|
||
case JNE:
|
||
case JG:
|
||
case JL:
|
||
case JGE:
|
||
case JLE:
|
||
case CALL: {
|
||
// One operand: jump target (label or immediate 32-bit address).
|
||
char *operand = strtok(NULL, " ,");
|
||
if (!operand) {
|
||
fprintf(stderr, "Error: %s requires an operand.\n", mnemonic);
|
||
exit(1);
|
||
}
|
||
code[addr++] = baseOpcode;
|
||
if (!isdigit(operand[0])) {
|
||
int labelAddr = lookupLabel(operand);
|
||
if (labelAddr < 0) {
|
||
fprintf(stderr, "Error: undefined label '%s'\n", operand);
|
||
exit(1);
|
||
}
|
||
code[addr++] = (labelAddr >> 24) & 0xFF;
|
||
code[addr++] = (labelAddr >> 16) & 0xFF;
|
||
code[addr++] = (labelAddr >> 8) & 0xFF;
|
||
code[addr++] = labelAddr & 0xFF;
|
||
} else {
|
||
uint32_t immAddr = (uint32_t) strtoul(operand, NULL, 0);
|
||
code[addr++] = (immAddr >> 24) & 0xFF;
|
||
code[addr++] = (immAddr >> 16) & 0xFF;
|
||
code[addr++] = (immAddr >> 8) & 0xFF;
|
||
code[addr++] = immAddr & 0xFF;
|
||
}
|
||
}
|
||
break;
|
||
case RET:
|
||
case BRK:
|
||
case NOP:
|
||
code[addr++] = baseOpcode;
|
||
break;
|
||
default:
|
||
fprintf(stderr, "Error: Unhandled opcode %d\n", baseOpcode);
|
||
exit(1);
|
||
}
|
||
} else {
|
||
fprintf(stderr, "Error: Unknown instruction '%s'\n", mnemonic);
|
||
exit(1);
|
||
}
|
||
}
|
||
return addr;
|
||
}
|
||
|
||
void completePass(const char *input, CPU *cpu, bool erase) {
|
||
// First pass: determine label addresses.
|
||
firstPass(input);
|
||
if (erase) {
|
||
memset(cpu->memory, 0, MEM_SIZE);
|
||
}
|
||
secondPass(input, cpu->memory);
|
||
} |