├── .gitignore ├── 8bitemu ├── Makefile ├── README.md ├── asm.py ├── computer.c ├── computer.h ├── main.c ├── programmes ├── 3x5.s └── add.s └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | objs/ 3 | -------------------------------------------------------------------------------- /8bitemu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapilpokhrel/eater-8bitemu/b65aca190c43092b5623b04c7e37b7b4a21d3400/8bitemu -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -g 3 | LDFLAGS = -lncurses -lpanel 4 | 5 | BUILD_DIR := ./objs 6 | SOURCES := $(wildcard *.c) 7 | OBJECTS := $(SOURCES:%.c=$(BUILD_DIR)/%.o) 8 | TARGET := 8bitemu 9 | 10 | $(TARGET): $(OBJECTS) 11 | $(CC) $(LDFLAGS) $(OBJECTS) -o $@ 12 | 13 | $(BUILD_DIR)/%.o: %.c 14 | @mkdir -p $(BUILD_DIR) 15 | $(CC) $(CFLAGS) -c $< -o $@ 16 | 17 | .PHONY: clean 18 | 19 | clean: 20 | rm -rf $(BUILD_DIR) $(TARGET) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ben Eater's 8 bit computer emulator 2 | This is complete [ben eater's 8 bit breadboard computer](https://www.youtube.com/watch?v=HyznrdDSSGM&list=PLowKtXNTBypGqImE405J2565dvjafglHU) emulator written in C. 3 | ![Screenshot](screenshot.png) 4 | ## Featerus 5 | - Ncurses graphics 6 | - Assembler (You can directly load assembly file) 7 | - Allows to step through each instruction or each clock pulse (each microcode) 8 | - Shows CPU information and memory contents 9 | ## Installing 10 | Ncurses should be installed. (It is probably already installed.) 11 | ``` 12 | git clone https://github.com/kapilpokhrel/eater-8bitemu.git 13 | cd eater-8bitemu 14 | make 15 | ``` 16 | ## Usage 17 | ``` 18 | ./8bitemu -b binary_file 19 | ``` 20 | or 21 | ``` 22 | ./8bitemu -a assembly_file 23 | ``` 24 | ## Assembler 25 | The current assembler is very simple that also supports ***label***, ***comment*** and ***mathematical expressions***. It also report the errors with line number. 26 | 27 | Here is the program to multiply 3 and 5: 28 | ``` 29 | top: 30 | lda x 31 | sub one ; sub subtracts the A from content of given memory address 32 | jc continue 33 | lda product 34 | out 35 | hlt 36 | continue: 37 | sta x 38 | lda product 39 | add y 40 | sta product 41 | jmp top 42 | 0 43 | one:1 44 | product:0 45 | x: 3 46 | y: 5 47 | ``` 48 | # TODO 49 | - [x] Complete Emulation with all instruction 50 | - [x] Assembler 51 | - [x] Step through each microcode 52 | - [x] Ncurses Output 53 | - [ ] Option to change memory contents and register value. 54 | -------------------------------------------------------------------------------- /asm.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | from math import * 3 | from sys import argv, stdout 4 | import getopt 5 | 6 | def error(message): 7 | print(message) 8 | exit(-1) 9 | 10 | opcodes = { 11 | "nop": 0, 12 | "lda": 1, 13 | "add": 2, 14 | "sub": 3, 15 | "sta": 4, 16 | "ldi": 5, 17 | "jmp": 6, 18 | "jc": 7, 19 | "jz": 8, 20 | "out": 14, 21 | "hlt": 15 22 | } 23 | 24 | # 1st pass 25 | # Remove comments, and add lables in symbol talbe and store int instruction in tokens form 26 | def first_pass(lines): 27 | lables = dict() 28 | line_tokens = list() 29 | 30 | curr_addr = 0 31 | for line in lines: 32 | # Remove comments 33 | modified_line = line.split(";")[0] 34 | # Remove whitespaces from beginning and end 35 | modified_line = modified_line.strip() 36 | # Split label and instruction 37 | modified_line = modified_line.split(":") 38 | 39 | # if line has label add it in symbol table and remove it from line 40 | if(len(modified_line) > 1): 41 | lables[modified_line[0]] = curr_addr 42 | modified_line = modified_line[1] 43 | else: 44 | modified_line = modified_line[0] 45 | 46 | if(modified_line != ""): 47 | curr_addr += 1 48 | 49 | tokens = modified_line.split(maxsplit=1) 50 | line_tokens.append(tokens) 51 | 52 | return lables,line_tokens 53 | 54 | # 2nd pass 55 | # Replace the lables, does error checking, and convert the assembly instructions in byte form 56 | def second_pass(lines,lables,line_tokens): 57 | curr_line = 1 58 | bytes = [] 59 | 60 | # Go through each line and parse the tokens 61 | for tokens in line_tokens: 62 | if(tokens != []): 63 | # storing the original from of tokens for showing errors 64 | orig_tokens = list(tokens) 65 | 66 | # Parse and change into numeric form 67 | 68 | # replace the opcode if present with its real numeric value 69 | if tokens[0] in opcodes: 70 | tokens[0] = str(opcodes[tokens[0]] << 4) 71 | try: 72 | tokens[0] = eval(tokens[0]) 73 | except: 74 | error(f"Error on line {curr_line - 1}.\n\t{lines[curr_line - 1].strip()}\n" 75 | + f"Couldn't parse '{orig_tokens[0]}'") 76 | 77 | if(len(tokens) > 1): 78 | # if operand contains any label, replace it with label value 79 | for label in lables: 80 | tokens[1] = tokens[1].replace(label,str(lables[label])) 81 | try: 82 | tokens[1] = eval(tokens[1]) 83 | except: 84 | error(f"Error on line {curr_line - 1}.\n\t{lines[curr_line - 1].strip()}\n" 85 | + f"Couldn't parse '{orig_tokens[1]}'") 86 | 87 | # operand must be of 4 bits; not greater than 15 88 | if(tokens[1] > 15): 89 | error(f"Error on line {curr_line - 1}.\n\t{lines[curr_line - 1].strip()}\n" 90 | + f"Operand Exceeds 4 bits (greater than 15)") 91 | 92 | # Store the parsed data 93 | 94 | if(len(tokens) > 1): 95 | bytes.append(tokens[0] | tokens[1]) 96 | else: 97 | 98 | # can't exceed a byte 99 | if(tokens[0] > 255): 100 | error(f"Error on line {curr_line - 1}.\n\t{lines[curr_line - 1].strip()}\n" 101 | + f"Instruction Exceeds a byte") 102 | else: 103 | bytes.append(tokens[0]) 104 | 105 | curr_line += 1 106 | 107 | return bytearray(bytes) 108 | 109 | def main(argv): 110 | 111 | if(len(argv) < 2): 112 | error("Usage: ./asm.py [-o output_file]") 113 | input_file = argv[1] 114 | output_file = "" 115 | 116 | options = "o:" 117 | arguments,values = getopt.getopt(argv[2:],options) 118 | for argument,value in arguments: 119 | if(argument == "-o"): 120 | output_file = value 121 | else: 122 | error(f"Unknown argument {argument}") 123 | 124 | lines = list() 125 | try: 126 | with open(input_file) as asm_file: 127 | lines = asm_file.readlines() 128 | except: 129 | error(f"Couldn't open file") 130 | 131 | lables,line_tokens = first_pass(lines) 132 | bytes = second_pass(lines,lables,line_tokens) 133 | 134 | # Output bytes 135 | if(output_file != ""): 136 | with open(output_file,"wb") as out_file: 137 | out_file.write(bytes) 138 | 139 | stdout.buffer.write(bytes) 140 | 141 | # Start 142 | if __name__ == "__main__": 143 | main(argv) -------------------------------------------------------------------------------- /computer.c: -------------------------------------------------------------------------------- 1 | #include "computer.h" 2 | #include 3 | #include 4 | #include 5 | 6 | typedef struct { 7 | char* name; 8 | 9 | // Function of corresponding opcode 10 | // Opocde takes current_microcode as argument and do different things based on it 11 | void (*operation)(uint8_t opcode,uint8_t current_microcode); 12 | 13 | }InstructionList; 14 | 15 | static Cpu cpu; 16 | uint8_t ram[16]; 17 | 18 | //instuctions 19 | void NOP(uint8_t opcode,uint8_t current_microcode) 20 | { 21 | return; 22 | } 23 | 24 | void LDA(uint8_t opcode,uint8_t current_microcode) 25 | { 26 | switch (current_microcode) { 27 | case 2: 28 | cpu.MAR = opcode & 0x0f; 29 | break; 30 | case 3: 31 | cpu.A = ram[cpu.MAR]; 32 | break; 33 | default: 34 | break; 35 | } 36 | return; 37 | } 38 | 39 | void ADD(uint8_t opcode,uint8_t current_microcode) 40 | { 41 | switch (current_microcode) { 42 | case 2: 43 | cpu.MAR = opcode & 0x0f; 44 | break; 45 | case 3: 46 | cpu.B = ram[cpu.MAR]; 47 | cpu.SUM = cpu.A + cpu.B; 48 | 49 | break; 50 | case 4: 51 | cpu.A = cpu.SUM; 52 | // carry check 53 | cpu.CY = ((cpu.A + cpu.B) > 255) ? 1:0; 54 | 55 | //zero check 56 | cpu.ZR = (cpu.SUM) ? 0:1; 57 | break; 58 | default: 59 | break; 60 | } 61 | return; 62 | } 63 | 64 | void SUB(uint8_t opcode,uint8_t current_microcode) 65 | { 66 | switch (current_microcode) { 67 | case 2: 68 | cpu.MAR = opcode & 0x0f; 69 | break; 70 | case 3: 71 | cpu.B = ram[cpu.MAR]; 72 | cpu.SUM = cpu.A - cpu.B; 73 | 74 | break; 75 | case 4: 76 | /* Ben eater's computer add the two's compliement of B with A for subtraction.*/ 77 | // carry check 78 | uint8_t two_comp_of_B = ~cpu.B + 1; 79 | cpu.CY = ((two_comp_of_B + cpu.A) > 255); 80 | 81 | cpu.A = cpu.SUM; 82 | 83 | //zero check 84 | cpu.ZR = (cpu.SUM) ? 0:1; 85 | 86 | break; 87 | default: 88 | break; 89 | } 90 | return; 91 | } 92 | 93 | void STA(uint8_t opcode,uint8_t current_microcode) 94 | { 95 | switch (current_microcode) { 96 | case 2: 97 | cpu.MAR = opcode & 0x0f; 98 | break; 99 | case 3: 100 | ram[cpu.MAR] = cpu.A; 101 | break; 102 | default: 103 | break; 104 | } 105 | return; 106 | } 107 | 108 | void LDI(uint8_t opcode,uint8_t current_microcode) 109 | { 110 | switch (current_microcode) { 111 | case 2: 112 | cpu.A = opcode & 0x0f; 113 | break; 114 | default: 115 | break; 116 | } 117 | return; 118 | } 119 | 120 | void JMP(uint8_t opcode,uint8_t current_microcode) 121 | { 122 | switch (current_microcode) { 123 | case 2: 124 | cpu.PC = opcode & 0x0f; 125 | break; 126 | default: 127 | break; 128 | } 129 | return; 130 | } 131 | 132 | void JC(uint8_t opcode,uint8_t current_microcode) 133 | { 134 | switch (current_microcode) { 135 | case 2: 136 | if(cpu.CY) 137 | cpu.PC = opcode & 0x0f; 138 | break; 139 | default: 140 | break; 141 | } 142 | return; 143 | } 144 | 145 | void JZ(uint8_t opcode,uint8_t current_microcode) 146 | { 147 | switch (current_microcode) { 148 | case 2: 149 | if(cpu.ZR) 150 | cpu.PC = opcode & 0x0f; 151 | break; 152 | default: 153 | break; 154 | } 155 | return; 156 | } 157 | 158 | void OUT(uint8_t opcode,uint8_t current_microcode) 159 | { 160 | switch (current_microcode) { 161 | case 2: 162 | cpu.OUT = cpu.A; 163 | break; 164 | default: 165 | break; 166 | } 167 | return; 168 | } 169 | 170 | void HLT(uint8_t opcode,uint8_t current_microcode) 171 | { 172 | switch (current_microcode) { 173 | case 2: 174 | cpu.HLT = 1; 175 | break; 176 | default: 177 | break; 178 | } 179 | return; 180 | } 181 | 182 | //list of all the available opcodes 183 | 184 | InstructionList instructions[16] = { 185 | {"NOP",NOP},{"LDA",LDA},{"ADD",ADD},{"SUB",SUB},{"STA",STA},{"LDI",LDI},{"JMP",JMP},{"JC",JC}, 186 | {"JZ",JZ},{"---",NOP},{"---",NOP},{"---",NOP},{"---",NOP},{"---",NOP},{"OUT",OUT},{"HLT",HLT} 187 | }; 188 | 189 | Cpu get_cpu() 190 | { 191 | return cpu; 192 | } 193 | 194 | uint8_t* get_memory() 195 | { 196 | uint8_t* memory = malloc(16); 197 | memcpy(memory,ram,16); 198 | return memory; 199 | } 200 | 201 | void set_memory(uint8_t address, uint8_t value) 202 | { 203 | ram[address] = value; 204 | } 205 | 206 | void load_from_bin(const char* filename) 207 | { 208 | FILE* bin_file = fopen(filename,"r"); 209 | if(bin_file == NULL){ 210 | fprintf(stderr,"Couldn't open binary file."); 211 | exit(-1); 212 | } 213 | 214 | fread(ram,1,16,bin_file); 215 | fclose(bin_file); 216 | return; 217 | } 218 | 219 | void load_from_asm(const char* filename) 220 | { 221 | char command[20 + strlen(filename)]; 222 | strcat(command,"/bin/python3 asm.py "); 223 | strcat(command,filename); 224 | 225 | FILE* bin_file = popen(command,"r"); 226 | if(bin_file == NULL){ 227 | fprintf(stderr,"Couldn't open Assembly file."); 228 | exit(-1); 229 | } 230 | 231 | fread(ram,1,16,bin_file); 232 | return; 233 | } 234 | 235 | static uint8_t current_microcode; 236 | 237 | uint8_t clock() 238 | { 239 | if(!cpu.HLT) { 240 | // each opcode is made up of 5 microcodes which takes 1 cycle each 241 | switch(current_microcode) { 242 | // First two microcode are same for all opcodes 243 | case 0: 244 | cpu.MAR = cpu.PC; 245 | break; 246 | case 1: 247 | cpu.IR = ram[cpu.MAR]; 248 | cpu.PC++; 249 | if(cpu.PC > 15) 250 | cpu.PC = 0; 251 | break; 252 | default: 253 | uint8_t addr = cpu.IR >> 4; 254 | instructions[addr].operation(cpu.IR,current_microcode); 255 | break; 256 | } 257 | 258 | current_microcode = (current_microcode == 4)? 0 : (current_microcode+1); 259 | return 1; 260 | } else 261 | return 0; 262 | } 263 | 264 | uint8_t execute_ins() 265 | { 266 | int first = 1; 267 | while(current_microcode || first) { 268 | first = 0; 269 | 270 | if(clock() == 0) 271 | return 0; 272 | } 273 | return 1; 274 | } 275 | 276 | char* get_curr_ins() 277 | { 278 | InstructionList curr_instruction = instructions[(cpu.IR & 0xf0) >> 4]; 279 | char* dis_instruction = malloc(10); 280 | if(current_microcode < 2) { 281 | uint8_t next_IR = ram[cpu.PC]; 282 | InstructionList next_instruction = instructions[(next_IR & 0xf0) >> 4]; 283 | sprintf(dis_instruction,"Fetching...(%s %d)", next_instruction.name, next_IR & 0x0f); 284 | } 285 | else 286 | sprintf(dis_instruction,"%s %d",curr_instruction.name,cpu.IR & 0x0f); 287 | return dis_instruction; 288 | } 289 | -------------------------------------------------------------------------------- /computer.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPUTER_H 2 | #define COMPUTER_H 3 | #include 4 | #include 5 | 6 | typedef struct { 7 | //registers 8 | uint8_t PC; // Program counter 9 | uint8_t MAR; // Memory Address register 10 | uint8_t IR; // Instruction register 11 | uint8_t A; // A register 12 | uint8_t B; // B register 13 | uint8_t SUM; // Sum register (A+B) 14 | uint8_t OUT; // Out register .. connected to display 15 | uint8_t CY; // Carry flag 16 | uint8_t ZR; // Zero flag 17 | uint8_t HLT; 18 | }Cpu; 19 | 20 | //road the binary file into ram 21 | void load_from_bin(const char* filename); 22 | void load_from_asm(const char* filename); 23 | 24 | //cpu information for debug information 25 | char* get_curr_ins(); 26 | Cpu get_cpu(); 27 | uint8_t* get_memory(); 28 | void set_memory(uint8_t address, uint8_t value); 29 | 30 | uint8_t clock(); 31 | 32 | uint8_t execute_ins(); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "computer.h" 4 | #include 5 | #include 6 | #include 7 | 8 | void print_output(WINDOW* output_window,uint8_t value) 9 | { 10 | mvwprintw(output_window,0,1,"OUTPUT:"); 11 | mvwprintw(output_window,1,1," %03d ",value); 12 | } 13 | 14 | void print_cpu(WINDOW* cpu_window,Cpu cpu) 15 | { 16 | mvwprintw(cpu_window,0,1,"CPU INFORMATION:"); 17 | mvwprintw(cpu_window,1,1," Program Counter: 0x%x",cpu.PC); 18 | mvwprintw(cpu_window,2,1," Memory address register: 0x%x",cpu.MAR); 19 | mvwprintw(cpu_window,3,1," Instruction register: 0x%x",cpu.IR); 20 | mvwprintw(cpu_window,4,1," A register: 0x%x",cpu.A); 21 | mvwprintw(cpu_window,5,1," B register: 0x%x",cpu.B); 22 | mvwprintw(cpu_window,6,1," SUM register: 0x%x",cpu.SUM); 23 | mvwprintw(cpu_window,7,1," OUT register: 0x%x",cpu.OUT); 24 | mvwprintw(cpu_window,8,1," Carry flag: %d",cpu.CY); 25 | mvwprintw(cpu_window,9,1," Zero flag: %d",cpu.ZR); 26 | mvwprintw(cpu_window,10,1," HLT flag: %d",cpu.HLT); 27 | } 28 | 29 | void print_memory(WINDOW* memory_window, uint8_t selected_address) 30 | { 31 | uint8_t* memory = get_memory(); 32 | mvwprintw(memory_window,0,1,"MEMORY:"); 33 | for(int i = 0; i < 16; i++) { 34 | if(i == selected_address) 35 | wattron(memory_window, A_STANDOUT); 36 | mvwprintw(memory_window,i+1,1," %02d: %03d ",i,memory[i]); 37 | wattroff(memory_window, A_STANDOUT); 38 | } 39 | free(memory); 40 | } 41 | 42 | void print_instruction(WINDOW* ins_window) 43 | { 44 | char* instruction = get_curr_ins(); 45 | mvwprintw(ins_window,0,1,"Current Instruction:"); 46 | mvwprintw(ins_window,1,1," "); /*Clearing the previous instruction*/ 47 | mvwprintw(ins_window,1,1," %s",instruction); 48 | free(instruction); 49 | } 50 | 51 | void print_usage(WINDOW* usage_window) 52 | { 53 | mvwprintw(usage_window,0,1,"USAGE:"); 54 | mvwprintw(usage_window,1,1," [c][CLOCK] [s][STEP INSTRUCTION] [q][Exit] [e][Edit]"); 55 | } 56 | 57 | int main(int argc,char** argv) 58 | { 59 | if(argc < 3) { 60 | printf("Usage: < -b or -a > \n"); 61 | exit(-1); 62 | } 63 | 64 | int loaded = 0; 65 | int opt; 66 | while((opt = getopt(argc,argv,"b:a:h")) != -1) { 67 | switch(opt) { 68 | case 'b': 69 | loaded = 1; 70 | load_from_bin(optarg); 71 | break; 72 | case 'a': 73 | loaded = 1; 74 | load_from_asm(optarg); 75 | break; 76 | case 'h': 77 | printf("Usage: < -b or -a > \n"); 78 | exit(-1); 79 | break; 80 | } 81 | } 82 | 83 | if(!loaded) { 84 | printf("Couldn't load program.\n"); 85 | exit(-1); 86 | } 87 | 88 | // ncurses init 89 | WINDOW* def_win = initscr(); 90 | cbreak(); 91 | noecho(); 92 | 93 | // Get screen size 94 | int y,x; 95 | getmaxyx(def_win,y,x); 96 | 97 | WINDOW* main_window = newwin(24,60,y/2-12,x/2-30); 98 | keypad(main_window, true); 99 | box(main_window,0,0); 100 | mvwprintw(main_window,0,1,"Eater's 8 bit computer:"); 101 | 102 | // Output window 103 | WINDOW* out_win = derwin(main_window,3,10,2,2); 104 | box(out_win,0,0); 105 | // Cpu window 106 | WINDOW* cpu_win = derwin(main_window,12,35,5,2); 107 | box(cpu_win,0,0); 108 | // Memory window 109 | WINDOW* mem_win = derwin(main_window,18,11,2,47); 110 | box(mem_win,0,0); 111 | // Instruction window 112 | WINDOW* ins_win = derwin(main_window,3,35,17,2); 113 | box(ins_win,0,0); 114 | // Usage window 115 | WINDOW* usage_win = derwin(main_window,3,56,20,2); 116 | box(usage_win,0,0); 117 | print_usage(usage_win); 118 | 119 | PANEL* main_panel = new_panel(main_window); 120 | 121 | uint8_t quit = 0; 122 | uint8_t selected_address = 0; 123 | uint8_t edit = 0; 124 | while(!quit) { 125 | //manage_windows(def_win,out_win,cpu_win,mem_win,usage_win); 126 | Cpu cpu = get_cpu(); 127 | 128 | // OUTPUT 129 | print_output(out_win,cpu.OUT); 130 | 131 | // CPU INFORMATION 132 | print_cpu(cpu_win,cpu); 133 | 134 | // MEMORY INFORMATION 135 | print_memory(mem_win, edit?selected_address:-1); 136 | 137 | // Usage Information 138 | print_instruction(ins_win); 139 | 140 | // Update panel and show it 141 | update_panels(); 142 | doupdate(); 143 | 144 | refresh(); 145 | int ch = wgetch(main_window); 146 | uint8_t* memory = get_memory(); 147 | uint8_t selected_value = memory[selected_address]; 148 | 149 | if(ch == 'c') 150 | clock(); 151 | else if(ch == 's') 152 | execute_ins(); 153 | else if(ch == 'q') 154 | quit = 1; 155 | else if(ch == 'e') 156 | edit ^= 1; 157 | else if(ch == KEY_UP && edit) { 158 | if(selected_address != 0) 159 | selected_address -= 1; 160 | } 161 | else if(ch == KEY_DOWN && edit) { 162 | if(selected_address != 15) 163 | selected_address += 1; 164 | } 165 | else if(ch == KEY_RIGHT && edit) { 166 | if(selected_value != 255) 167 | set_memory(selected_address, selected_value + 1); 168 | } 169 | else if(ch == KEY_LEFT && edit) { 170 | if(selected_value != 0) 171 | set_memory(selected_address, selected_value - 1); 172 | } 173 | 174 | 175 | // Always center the window 176 | getmaxyx(def_win,y,x); 177 | move_panel(main_panel,y/2-12,x/2-30); 178 | } 179 | 180 | // ncurses end 181 | endwin(); 182 | } 183 | -------------------------------------------------------------------------------- /programmes/3x5.s: -------------------------------------------------------------------------------- 1 | top: 2 | lda x 3 | sub one ; sub subtracts the A from content of given memory address 4 | jc continue 5 | lda product 6 | out 7 | hlt 8 | continue: 9 | sta x 10 | lda product 11 | add y 12 | sta product 13 | jmp top 14 | 0 15 | one:1 16 | product:0 17 | x: 3 18 | y: 5 19 | -------------------------------------------------------------------------------- /programmes/add.s: -------------------------------------------------------------------------------- 1 | start: 2 | lda magic ;load the content of memroy address 15 into A 3 | add (magic + 1) ; A = A + [15] 4 | out 5 | hlt 6 | 0 ;nop 7 | 0 ;nop 8 | 0 ;nop 9 | 0 ;nop 10 | 0 ;nop 11 | 0 ;nop 12 | 0 ;nop 13 | 0 ;nop 14 | 0 ;nop 15 | 0 ;nop 16 | magic: 17 | 0xe 18 | 0x2e -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kapilpokhrel/eater-8bitemu/b65aca190c43092b5623b04c7e37b7b4a21d3400/screenshot.png --------------------------------------------------------------------------------