Files
RISC-B/assembler/assembler.c

729 lines
27 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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
int addLabel(const char *name, int address) {
if (labelCount >= MAX_LABELS) {
fprintf(stderr, "Too many labels!\n");
return 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;
}
string++;
}
}
void toLowerCase(char *string) {
while (*string) {
if (*string >= 'A' && *string <= 'Z') {
(*string) += 0x20;
}
string++;
}
}
//
// 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 well 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, "HLT") == 0)
return HLT;
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;
labelCount = 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], operand3[64];
operand1[0] = '\0';
operand2[0] = '\0';
int tokenCount = sscanf(line, "%31s %63[^, ] %63[^, ] %63s",
mnemonic, operand1, operand2, operand3);
// 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:
case HLT:
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;
// Remove any label definitions (up to the colon).
char *colon = strchr(line, ':');
if (colon != NULL) {
*colon = ' '; // Replace the colon so the rest of the line can be parsed.
continue;
}
if (strlen(line) == 0)
continue;
// Parse mnemonic and up to three operands.
char mnemonic[32], operand1[64], operand2[64], operand3[64];
mnemonic[0] = operand1[0] = operand2[0] = operand3[0] = '\0';
int tokenCount = sscanf(line, "%31s %63[^, ] %63[^, ] %63s",
mnemonic, operand1, operand2, operand3);
// (Optionally, you might trim each operand individually here.)
// Map the mnemonic to a base opcode.
int baseOpcode = getOpcode(mnemonic);
if (baseOpcode == -1) {
fprintf(stderr, "Unknown instruction: %s\n", mnemonic);
return 1;
}
// --- MOV Instruction (baseOpcode == -2) ---
if (baseOpcode == -2) {
if (strlen(operand1) == 0 || strlen(operand2) == 0) {
fprintf(stderr, "Error: MOV requires two operands.\n");
return 1;
}
int resolvedOpcode = resolveMOV(operand1, operand2);
code[addr++] = resolvedOpcode;
if (resolvedOpcode == MOV_RN_IMM) {
int reg = parseRegister(operand1);
uint8_t imm = parseImmediate(operand2);
code[addr++] = reg;
code[addr++] = imm;
} else if (resolvedOpcode == MOV_RN_RM) {
int regDest = parseRegister(operand1);
int regSrc = parseRegister(operand2);
code[addr++] = regDest;
code[addr++] = regSrc;
} else if (resolvedOpcode == MOV_RN_ADDR) {
int reg = parseRegister(operand1);
// Assume source is written as "[address]": remove the brackets.
char addrStr[32];
strncpy(addrStr, operand2 + 1, strlen(operand2) - 2);
addrStr[strlen(operand2) - 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) {
// Destination is memory (written as "[address]").
char addrStr[32];
strncpy(addrStr, operand1 + 1, strlen(operand1) - 2);
addrStr[strlen(operand1) - 2] = '\0';
uint32_t memAddr = (uint32_t) strtoul(addrStr, NULL, 0);
int reg = parseRegister(operand2);
code[addr++] = (memAddr >> 24) & 0xFF;
code[addr++] = (memAddr >> 16) & 0xFF;
code[addr++] = (memAddr >> 8) & 0xFF;
code[addr++] = memAddr & 0xFF;
code[addr++] = reg;
}
}
// --- INC and DEC (baseOpcode == -12 or -13) ---
// These instructions require only a single operand.
else if (baseOpcode == -12 || baseOpcode == -13) {
if (strlen(operand1) == 0) {
fprintf(stderr, "Error: %s requires one operand.\n", mnemonic);
return 1;
}
int resolvedOpcode = resolveALU(baseOpcode, operand1);
code[addr++] = resolvedOpcode;
if (operand1[0] == 'R' || operand1[0] == 'r') {
int reg = parseRegister(operand1);
code[addr++] = reg;
} else {
// Assume memory reference written as "[address]".
char addrStr[32];
strncpy(addrStr, operand1 + 1, strlen(operand1) - 2);
addrStr[strlen(operand1) - 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;
}
}
// --- Other Ambiguous ALU Instructions (ADD, SUB, MUL, etc.) ---
// These require two operands (destination and source).
else if (baseOpcode < 0 && baseOpcode != -2 && baseOpcode != -11 &&
baseOpcode != -14 && baseOpcode != -15 && baseOpcode != -12 && baseOpcode != -13) {
if (strlen(operand1) == 0 || strlen(operand2) == 0) {
fprintf(stderr, "Error: %s requires two operands.\n", mnemonic);
return 1;
}
int resolvedOpcode = resolveALU(baseOpcode, operand2);
code[addr++] = resolvedOpcode;
int regDest = parseRegister(operand1);
code[addr++] = regDest;
if (operand2[0] == 'R' || operand2[0] == 'r') {
int regSrc = parseRegister(operand2);
code[addr++] = regSrc;
} else {
uint8_t imm = parseImmediate(operand2);
code[addr++] = imm;
}
}
// --- JMP Instruction (baseOpcode == -11) ---
else if (baseOpcode == -11) {
if (strlen(operand1) == 0) {
fprintf(stderr, "Error: JMP requires one operand.\n");
return 1;
}
int resolvedOpcode = resolveALU(baseOpcode, operand1);
code[addr++] = resolvedOpcode;
if (operand1[0] == '+' || operand1[0] == '-') {
// Relative jump: one-byte offset.
uint8_t offset = parseImmediate(operand1);
code[addr++] = offset;
} else {
// Absolute jump: use label lookup for 32-bit address.
uint32_t jumpAddr = (uint32_t) lookupLabel(operand1);
code[addr++] = (jumpAddr >> 24) & 0xFF;
code[addr++] = (jumpAddr >> 16) & 0xFF;
code[addr++] = (jumpAddr >> 8) & 0xFF;
code[addr++] = jumpAddr & 0xFF;
}
}
// --- Jump Bit Set/Clear Instructions (JMPBS, JMPBC) ---
else if (baseOpcode == -14 || baseOpcode == -15) {
if (strlen(operand1) == 0 || strlen(operand2) == 0 || strlen(operand3) == 0) {
fprintf(stderr, "Error: %s requires three operands.\n", mnemonic);
return 1;
}
int resolvedOpcode = resolveALU(baseOpcode, operand1);
code[addr++] = resolvedOpcode;
// Encode the source operand (register or memory).
if (operand1[0] == 'R' || operand1[0] == 'r') {
int reg = parseRegister(operand1);
code[addr++] = reg;
} else {
char addrStr[32];
strncpy(addrStr, operand1 + 1, strlen(operand1) - 2);
addrStr[strlen(operand1) - 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 (a one-byte immediate).
uint8_t bitVal = parseImmediate(operand2);
code[addr++] = bitVal;
// Encode the jump target (label -> 32-bit address).
uint32_t jumpAddr = (uint32_t) lookupLabel(operand3);
code[addr++] = (jumpAddr >> 24) & 0xFF;
code[addr++] = (jumpAddr >> 16) & 0xFF;
code[addr++] = (jumpAddr >> 8) & 0xFF;
code[addr++] = jumpAddr & 0xFF;
}
// --- Non-ambiguous Instructions ---
else if (baseOpcode > 0) {
switch (baseOpcode) {
case CMP:
case SWAP: {
if (strlen(operand1) == 0 || strlen(operand2) == 0) {
fprintf(stderr, "Error: %s requires two operands.\n", mnemonic);
return 1;
}
code[addr++] = baseOpcode;
int r1 = parseRegister(operand1);
int r2 = parseRegister(operand2);
code[addr++] = r1;
code[addr++] = r2;
break;
}
case SWAPN:
case NEG_RN:
case NOT_RN: {
if (strlen(operand1) == 0) {
fprintf(stderr, "Error: %s requires one operand.\n", mnemonic);
return 1;
}
code[addr++] = baseOpcode;
int reg = parseRegister(operand1);
code[addr++] = reg;
break;
}
case SHL_RN_IMM:
case SHR_RN_IMM:
case SAR_RN_IMM: {
if (strlen(operand1) == 0 || strlen(operand2) == 0) {
fprintf(stderr, "Error: %s requires two operands.\n", mnemonic);
return 1;
}
code[addr++] = baseOpcode;
int reg = parseRegister(operand1);
code[addr++] = reg;
uint8_t imm = parseImmediate(operand2);
code[addr++] = imm;
break;
}
case JE:
case JNE:
case JG:
case JL:
case JGE:
case JLE:
case CALL: {
if (strlen(operand1) == 0) {
fprintf(stderr, "Error: %s requires one operand.\n", mnemonic);
return 1;
}
code[addr++] = baseOpcode;
// If the operand isnt purely numeric, treat it as a label.
if (!isdigit(operand1[0])) {
int labelAddr = lookupLabel(operand1);
if (labelAddr < 0) {
fprintf(stderr, "Error: undefined label '%s'\n", operand1);
return 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(operand1, 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 HLT:
case NOP: {
code[addr++] = baseOpcode;
break;
}
default: {
fprintf(stderr, "Error: Unhandled opcode %d\n", baseOpcode);
return 1;
}
}
} else {
fprintf(stderr, "Error: Unknown instruction '%s'\n", mnemonic);
return 1;
}
}
return addr;
}
void completePass(const char *input, CPU *cpu, bool erase) {
// First pass: determine label addresses.
if (erase) {
init_cpu(cpu);
}
firstPass(input);
secondPass(input, cpu->memory);
}