Commit | Line | Data |
---|---|---|
84b74595 AT |
1 | /* (c) 2021 Aaron Taylor <ataylor at subgeniuskitty dot com> */ |
2 | /* See LICENSE.txt file for copyright and license details. */ | |
3 | ||
4 | /* -------------------------------------------------------------------------- */ | |
5 | /* NED1 Simulator */ | |
6 | /* -------------------------------------------------------------------------- */ | |
7 | ||
84b74595 AT |
8 | #include <stdio.h> |
9 | #include <stdint.h> | |
10 | #include <inttypes.h> | |
11 | #include <stdlib.h> | |
12 | #include <stdbool.h> | |
13 | #include <unistd.h> | |
14 | #include <fcntl.h> | |
15 | #include <string.h> | |
16 | #include <errno.h> | |
17 | #include <time.h> | |
18 | #include <termios.h> | |
19 | #include <signal.h> | |
20 | #include <sys/socket.h> | |
21 | #include <sys/types.h> | |
22 | #include <netinet/in.h> | |
23 | #include <arpa/inet.h> | |
24 | #include <netdb.h> | |
25 | ||
26 | #include "a.out.h" | |
27 | #include "simulator.h" | |
28 | ||
51228e25 | 29 | static uint32_t |
84b74595 AT |
30 | generate_binary_psw(struct NEDstate * state) |
31 | { | |
32 | uint32_t psw = 0; | |
33 | if (state->active_thread->psw->zero) psw |= 0b1; | |
34 | if (state->active_thread->psw->negative) psw |= 0b10; | |
35 | return psw; | |
36 | } | |
37 | ||
51228e25 | 38 | static void |
84b74595 AT |
39 | ram_w_byte(struct NEDstate * state, uint32_t address, uint8_t data) |
40 | { | |
d87b1e06 | 41 | state->ram[address-RAM_BASE_ADDRESS] = data; |
84b74595 AT |
42 | } |
43 | ||
51228e25 | 44 | static uint8_t |
84b74595 AT |
45 | ram_r_byte(struct NEDstate * state, uint32_t address) |
46 | { | |
d87b1e06 | 47 | return state->ram[address-RAM_BASE_ADDRESS]; |
84b74595 AT |
48 | } |
49 | ||
50 | /* For now, with only a terminal for IO, we pick off IO requests when accessing RAM. */ | |
84b74595 | 51 | |
51228e25 | 52 | static void |
84b74595 AT |
53 | ram_w_word(struct NEDstate * state, uint32_t address, uint32_t data) |
54 | { | |
d87b1e06 | 55 | if (address >= RAM_BASE_ADDRESS) { |
84b74595 AT |
56 | for (int i=3; i>=0; i--) { |
57 | uint8_t tmp_byte = ((data >> (8*(3-i))) & 0xff); | |
58 | ram_w_byte(state,address+i,tmp_byte); | |
59 | } | |
60 | } | |
61 | } | |
62 | ||
63 | uint32_t | |
64 | ram_r_word(struct NEDstate * state, uint32_t address) | |
65 | { | |
66 | if (address == 0x0) { /* Zero register */ | |
67 | return 0b0; | |
68 | } else if (address == 0x4) { /* 0x80000000 register */ | |
69 | return 0x80000000; | |
70 | } else if (address == 0x8) { /* PC register */ | |
71 | return state->active_thread->pc; | |
72 | } else if (address == 0xC) { /* PSW register */ | |
73 | return generate_binary_psw(state); | |
d87b1e06 | 74 | } else if (address >= RAM_BASE_ADDRESS) { /* RAM */ |
84b74595 AT |
75 | uint32_t word = 0; |
76 | for (int i=0; i<4; i++) word |= (ram_r_byte(state,address+i)) << (8*(3-i)); | |
77 | return word; | |
78 | } | |
79 | return 0b0; | |
80 | } | |
81 | ||
51228e25 | 82 | static uint32_t |
84b74595 AT |
83 | fetch_instruction_word(struct NEDstate * state) |
84 | { | |
85 | uint32_t word = ram_r_word(state, state->active_thread->pc); | |
86 | state->active_thread->pc += BPW; | |
87 | return word; | |
88 | } | |
89 | ||
51228e25 | 90 | static void |
84b74595 AT |
91 | stack_w(struct NEDthread * thread, uint32_t value, uint8_t offset) |
92 | { | |
93 | thread->stack[thread->sp - (offset + 1)] = value; | |
94 | } | |
95 | ||
51228e25 | 96 | static uint32_t |
84b74595 AT |
97 | stack_r(struct NEDthread * thread, uint8_t offset) |
98 | { | |
99 | return thread->stack[thread->sp - (offset + 1)]; | |
100 | } | |
101 | ||
51228e25 | 102 | static void |
84b74595 AT |
103 | stack_push(struct NEDthread * thread, uint32_t value) |
104 | { | |
105 | thread->stack[thread->sp++] = value; | |
106 | } | |
107 | ||
51228e25 | 108 | static uint32_t |
84b74595 AT |
109 | stack_pop(struct NEDthread * thread) |
110 | { | |
111 | return thread->stack[--thread->sp]; | |
112 | } | |
113 | ||
51228e25 | 114 | static void |
84b74595 AT |
115 | set_psw_flags(uint32_t word, struct NEDstate * state) |
116 | { | |
117 | if (word == 0) { | |
118 | state->active_thread->psw->zero = true; | |
119 | } else { | |
120 | state->active_thread->psw->zero = false; | |
121 | } | |
122 | if (word & 0x80000000) { | |
123 | state->active_thread->psw->negative = true; | |
124 | } else { | |
125 | state->active_thread->psw->negative = false; | |
126 | } | |
127 | } | |
128 | ||
51228e25 | 129 | static void |
84b74595 AT |
130 | ned_instruction_and(struct NEDstate * state) |
131 | { | |
132 | uint32_t operand1 = stack_pop(state->active_thread); | |
133 | uint32_t operand2 = stack_pop(state->active_thread); | |
134 | stack_push(state->active_thread, (operand1 & operand2)); | |
135 | set_psw_flags(stack_r(state->active_thread,0), state); | |
136 | } | |
137 | ||
51228e25 | 138 | static void |
84b74595 AT |
139 | ned_instruction_or(struct NEDstate * state) |
140 | { | |
141 | uint32_t operand1 = stack_pop(state->active_thread); | |
142 | uint32_t operand2 = stack_pop(state->active_thread); | |
143 | stack_push(state->active_thread, (operand1 | operand2)); | |
144 | set_psw_flags(stack_r(state->active_thread,0), state); | |
145 | } | |
146 | ||
51228e25 | 147 | static void |
84b74595 AT |
148 | ned_instruction_not(struct NEDstate * state) |
149 | { | |
150 | stack_push(state->active_thread, ~stack_pop(state->active_thread)); | |
151 | set_psw_flags(stack_r(state->active_thread,0), state); | |
152 | } | |
153 | ||
51228e25 | 154 | static void |
84b74595 AT |
155 | ned_instruction_xor(struct NEDstate * state) |
156 | { | |
157 | uint32_t operand1 = stack_pop(state->active_thread); | |
158 | uint32_t operand2 = stack_pop(state->active_thread); | |
159 | stack_push(state->active_thread, (operand1 ^ operand2)); | |
160 | set_psw_flags(stack_r(state->active_thread,0), state); | |
161 | } | |
162 | ||
51228e25 | 163 | static void |
84b74595 AT |
164 | ned_instruction_add(struct NEDstate * state) |
165 | { | |
166 | uint32_t operand1 = stack_pop(state->active_thread); | |
167 | uint32_t operand2 = stack_pop(state->active_thread); | |
168 | stack_push(state->active_thread, (operand1 + operand2)); | |
169 | set_psw_flags(stack_r(state->active_thread,0), state); | |
170 | } | |
171 | ||
51228e25 | 172 | static void |
84b74595 AT |
173 | ned_instruction_shift(struct NEDstate * state) |
174 | { | |
175 | /* TODO: Bounds check: Either all inputs are valid OR shift_by < 32. */ | |
176 | /* I guess this also depends if I'm shifting-and-dropping, or barrel-shifting. */ | |
177 | /* How should I pad for a right shift if I shift-and-drop? Sign extend? */ | |
178 | uint32_t shift_by = stack_pop(state->active_thread); | |
179 | uint32_t word = stack_pop(state->active_thread); | |
180 | if (shift_by & 0x80000000) { | |
181 | stack_push(state->active_thread, (word << (shift_by & 0x7fffffff))); | |
182 | } else { | |
183 | stack_push(state->active_thread, (word >> shift_by)); | |
184 | } | |
185 | set_psw_flags(stack_r(state->active_thread,0), state); | |
186 | } | |
187 | ||
51228e25 | 188 | static void |
84b74595 AT |
189 | ned_instruction_test(struct NEDstate * state) |
190 | { | |
191 | uint32_t word = stack_pop(state->active_thread); | |
192 | set_psw_flags(word, state); | |
193 | } | |
194 | ||
51228e25 | 195 | static void |
84b74595 AT |
196 | ned_instruction_jmp(struct NEDstate * state) |
197 | { | |
198 | state->active_thread->pc = stack_pop(state->active_thread); | |
199 | // The SC is caught and reset by the main loop since the PC changed. | |
200 | } | |
201 | ||
51228e25 | 202 | static void |
84b74595 AT |
203 | ned_instruction_swap(struct NEDstate * state) |
204 | { | |
205 | uint32_t temp1 = stack_pop(state->active_thread); | |
206 | uint32_t temp2 = stack_pop(state->active_thread); | |
207 | stack_push(state->active_thread, temp1); | |
208 | stack_push(state->active_thread, temp2); | |
209 | set_psw_flags(stack_r(state->active_thread,0), state); | |
210 | } | |
211 | ||
51228e25 | 212 | static void |
84b74595 AT |
213 | ned_instruction_brz(struct NEDstate * state) |
214 | { | |
215 | uint32_t new_pc = stack_pop(state->active_thread); | |
216 | uint32_t test_word = stack_pop(state->active_thread); | |
217 | if (test_word == 0) { | |
218 | state->active_thread->pc = new_pc; | |
219 | // The SC is caught and reset by the main loop since the PC changed. | |
220 | } | |
221 | } | |
222 | ||
51228e25 | 223 | static void |
84b74595 AT |
224 | ned_instruction_load(struct NEDstate * state) |
225 | { | |
226 | uint32_t address = stack_pop(state->active_thread); | |
227 | stack_push(state->active_thread, ram_r_word(state, address)); | |
228 | set_psw_flags(stack_r(state->active_thread,0), state); | |
229 | } | |
230 | ||
51228e25 | 231 | static void |
84b74595 AT |
232 | ned_instruction_store(struct NEDstate * state) |
233 | { | |
234 | uint32_t address = stack_pop(state->active_thread); | |
235 | uint32_t data = stack_pop(state->active_thread); | |
236 | ram_w_word(state, address, data); | |
237 | } | |
238 | ||
51228e25 | 239 | static void |
84b74595 AT |
240 | ned_instruction_halt(struct NEDstate * state) |
241 | { | |
242 | printf("Halting.\n"); | |
243 | state->halted = true; | |
244 | } | |
245 | ||
51228e25 | 246 | static void |
84b74595 AT |
247 | execute_syllable(struct NEDstate * state, enum syllables syllable) |
248 | { | |
249 | if (syllable & 0b100000) { /* Check the first bit of the syllable. 1 means IM_x. */ | |
250 | stack_push(state->active_thread, (uint32_t)(syllable & 0b11111)); | |
251 | } else if (syllable & 0b10000) { /* 1 in 2nd bit means LDSP+x or STSP+x instruction. */ | |
252 | if (syllable & 0b1000) { /* LDSP+x */ | |
253 | stack_push(state->active_thread,stack_r(state->active_thread,(syllable & 0b111))); | |
254 | set_psw_flags(stack_r(state->active_thread,0), state); | |
255 | } else { /* STSP+x */ | |
256 | stack_w(state->active_thread,stack_pop(state->active_thread),(syllable & 0b111)); | |
257 | } | |
258 | } else { | |
259 | switch (syllable) { | |
260 | case AND: ned_instruction_and(state); break; | |
261 | case OR: ned_instruction_or(state); break; | |
262 | case NOT: ned_instruction_not(state); break; | |
263 | case XOR: ned_instruction_xor(state); break; | |
264 | case ADD: ned_instruction_add(state); break; | |
d87b1e06 | 265 | case MVSTCK: /* Intentionally blank */ break; |
84b74595 | 266 | case SHIFT: ned_instruction_shift(state); break; |
d87b1e06 | 267 | case CMPSWP: /* Intentionally blank */ break; |
84b74595 AT |
268 | case TEST: ned_instruction_test(state); break; |
269 | case JMP: ned_instruction_jmp(state); break; | |
270 | case SWAP: ned_instruction_swap(state); break; | |
271 | case BRZ: ned_instruction_brz(state); break; | |
272 | case LOAD: ned_instruction_load(state); break; | |
273 | case STORE: ned_instruction_store(state); break; | |
274 | case NOP: /* Intentionally blank */ break; | |
275 | case HALT: ned_instruction_halt(state); break; | |
276 | default: | |
277 | printf("ERROR: Attempted to execute illegal syllable: 0o%o\n", syllable); | |
278 | state->halted = true; | |
279 | break; | |
280 | } | |
281 | } | |
282 | } | |
283 | ||
51228e25 | 284 | static uint8_t |
84b74595 AT |
285 | extract_syllable_from_word(uint32_t word, uint8_t index) |
286 | { | |
287 | uint32_t mask = 0b111111 << 6*(4-index); | |
288 | return (word & mask) >> 6*(4-index); | |
289 | } | |
290 | ||
51228e25 | 291 | static void |
84b74595 AT |
292 | parse_aout_file(FILE * input, struct exec * aout_exec, uint8_t * text_segment, |
293 | struct nlist ** symbol_table, uint32_t * symbol_count) | |
294 | { | |
295 | uint32_t read_count = 0; | |
296 | ||
297 | /* Read in and check the a.out header. */ | |
298 | for (uint32_t i=0; i<8; i++) { | |
299 | switch (i) { | |
300 | case 0: read_count = fread(&(aout_exec->a_midmag), 4, 1, input); break; | |
301 | case 1: read_count = fread(&(aout_exec->a_text), 4, 1, input); break; | |
302 | case 2: read_count = fread(&(aout_exec->a_data), 4, 1, input); break; | |
303 | case 3: read_count = fread(&(aout_exec->a_bss), 4, 1, input); break; | |
304 | case 4: read_count = fread(&(aout_exec->a_syms), 4, 1, input); break; | |
305 | case 5: read_count = fread(&(aout_exec->a_entry), 4, 1, input); break; | |
306 | case 6: read_count = fread(&(aout_exec->a_trsize), 4, 1, input); break; | |
307 | case 7: read_count = fread(&(aout_exec->a_drsize), 4, 1, input); break; | |
308 | } | |
309 | if (read_count != 1) { | |
310 | fprintf(stderr, "ERROR: Invalid a.out header.\n"); | |
311 | exit(EXIT_FAILURE); | |
312 | } | |
313 | } | |
314 | if (N_BADMAG(*aout_exec)) { | |
315 | fprintf(stderr, "ERROR: Invalid magic number in a.out header.\n"); | |
316 | exit(EXIT_FAILURE); | |
317 | } else if (N_GETMID(*aout_exec) != MID_NED) { | |
318 | fprintf(stderr, "ERROR: Executable not intended for NED Machine ID.\n"); | |
319 | exit(EXIT_FAILURE); | |
320 | } | |
321 | ||
322 | /* Read in the text segment. */ | |
323 | uint32_t text_segment_size = (N_DATOFF(*aout_exec) - N_TXTOFF(*aout_exec)); | |
324 | read_count = fread(text_segment, 1, text_segment_size, input); | |
325 | if (read_count != text_segment_size) { | |
326 | fprintf(stderr, "ERROR: Failed to read entire text segment.\n"); | |
327 | exit(EXIT_FAILURE); | |
328 | } | |
329 | ||
330 | /* Correct the byte order. */ | |
331 | for (uint32_t i=0; i < (text_segment_size / 4); i++) { | |
332 | uint8_t temp_word[4]; | |
333 | for (uint8_t j=0; j<4; j++) temp_word[j] = text_segment[((i*4)+j)]; | |
334 | for (uint8_t j=0; j<4; j++) text_segment[((i*4)+j)] = temp_word[(3-j)]; | |
335 | } | |
336 | ||
337 | /* Read in the symbol table. */ | |
338 | *symbol_count = ((N_STROFF(*aout_exec) - N_SYMOFF(*aout_exec)) / 20); /* 20 bytes per symbol. */ | |
339 | *symbol_table = malloc((*symbol_count) * sizeof(struct nlist)); | |
340 | for (uint32_t i=0; i < *symbol_count; i++) { | |
341 | for (uint32_t j=0; j<5; j++) { | |
342 | switch (j) { | |
343 | case 0: read_count = fread(&((*symbol_table)[i].n_un.n_strx), 4, 1, input); break; | |
344 | case 1: read_count = fread(&((*symbol_table)[i].n_type), 4, 1, input); break; | |
345 | case 2: read_count = fread(&((*symbol_table)[i].n_other), 4, 1, input); break; | |
346 | case 3: read_count = fread(&((*symbol_table)[i].n_desc), 4, 1, input); break; | |
347 | case 4: read_count = fread(&((*symbol_table)[i].n_value), 4, 1, input); break; | |
348 | } | |
349 | if (read_count != 1) { | |
350 | fprintf(stderr, "ERROR: Unable to read entire symbol table.\n"); | |
351 | exit(EXIT_FAILURE); | |
352 | } | |
353 | } | |
354 | } | |
355 | ||
356 | /* Read in the string table and update the symbol table entries with pointers to new strings. */ | |
357 | uint32_t string_table_size; | |
358 | read_count = fread(&string_table_size, 4, 1, input); | |
359 | if (read_count != 1) { | |
360 | fprintf(stderr, "ERROR: Failed to read string table size.\n"); | |
361 | exit(EXIT_FAILURE); | |
362 | } | |
363 | for (uint32_t i=0; i < *symbol_count; i++) { | |
364 | uint32_t len = 0; | |
365 | if (i < ((*symbol_count)-1)) { | |
366 | len = ((*symbol_table)[i+1].n_un.n_strx - (*symbol_table)[i].n_un.n_strx); | |
367 | } else { | |
368 | len = (string_table_size - (*symbol_table)[i].n_un.n_strx); | |
369 | } | |
370 | (*symbol_table)[i].n_un.n_name = malloc(len); | |
371 | read_count = fread((*symbol_table)[i].n_un.n_name, 1, len, input); | |
372 | if (read_count != len) { | |
373 | fprintf(stderr, "ERROR: Failed to read a string from the string table.\n"); | |
374 | exit(EXIT_FAILURE); | |
375 | } | |
376 | } | |
377 | ||
378 | } | |
379 | ||
380 | struct NEDstate * | |
5923644e | 381 | init_simulator(char * input_file) |
84b74595 AT |
382 | { |
383 | struct NEDstate * state = malloc(sizeof(struct NEDstate)); | |
384 | state->hack = malloc(sizeof(struct NEDhack)); | |
d87b1e06 AT |
385 | state->thread[0] = malloc(sizeof(struct NEDthread)); |
386 | state->thread[0]->psw = malloc(sizeof(struct NEDpsw)); | |
84b74595 AT |
387 | state->thread[0]->pc = 0; |
388 | state->thread[0]->sc = 0; | |
389 | state->thread[0]->sp = 0; | |
390 | state->thread[0]->psw->zero = false; | |
391 | state->thread[0]->psw->negative = false; | |
d87b1e06 AT |
392 | state->thread[0]->pc = RAM_BASE_ADDRESS; |
393 | state->active_thread = state->thread[0]; | |
84b74595 AT |
394 | state->halted = false; |
395 | state->hack->resume_word = false; | |
396 | ||
84b74595 | 397 | /* Load an initial image into memory. */ |
84b74595 AT |
398 | struct exec aout_exec; |
399 | struct nlist * symbol_table; | |
400 | uint32_t symbol_count; | |
401 | FILE * input = NULL; | |
5923644e AT |
402 | if ((input = fopen(input_file, "r")) == NULL) { |
403 | fprintf(stderr, "ERROR: %s: %s\n", input_file, strerror(errno)); | |
84b74595 AT |
404 | state->halted = true; |
405 | } | |
d87b1e06 | 406 | parse_aout_file(input, &aout_exec, state->ram, &symbol_table, &symbol_count); |
84b74595 | 407 | fclose(input); |
d87b1e06 AT |
408 | for (size_t i=0; i < symbol_count; i++) { |
409 | free(symbol_table[i].n_un.n_name); | |
410 | } | |
411 | free(symbol_table); | |
84b74595 AT |
412 | |
413 | return state; | |
414 | } | |
415 | ||
416 | struct NEDstate * | |
417 | run_simulator(struct NEDstate * state) | |
418 | { | |
419 | if (state->halted) return state; | |
420 | ||
421 | /* Fetch instruction word. */ | |
422 | uint32_t iw; | |
423 | if (state->hack->resume_word) { | |
424 | iw = state->hack->iw; | |
425 | } else { | |
426 | iw = fetch_instruction_word(state); | |
427 | } | |
428 | ||
429 | /* Decode instruction word format and execute. */ | |
430 | if (iw & (0b1 << 31)) { /* Instruction word is type A. */ | |
431 | stack_push(state->active_thread, (iw << 1)); | |
432 | } else if ((iw & (0b11 << 30)) == 0) { /* Instruction word is type C. */ | |
433 | uint8_t syllable = extract_syllable_from_word(iw, state->active_thread->sc); | |
434 | state->active_thread->sc++; // TODO: Should this be part of extract_syllable_from_word()? After all, incrementing the PC is done in fetch_instruction_word(). | |
435 | uint32_t pre_execution_pc = state->active_thread->pc; // TODO: This is so we can catch JMP/JSR/etc subroutines that need the SC to be reset to zero. | |
436 | execute_syllable(state, syllable); | |
437 | if (state->active_thread->pc != pre_execution_pc) { | |
438 | // Jumped to a new address, so prepare to execute a new instruction word. | |
439 | state->active_thread->sc = 0; | |
440 | state->hack->resume_word = false; | |
441 | } else if (state->active_thread->sc >= SPW) { | |
442 | // Just executed the last syllable in this word, time to follow the PC to the next word. | |
443 | state->active_thread->sc = 0; | |
444 | state->hack->resume_word = false; | |
445 | } else { | |
446 | // More syllables remain to be executed in this instruction word. | |
447 | state->hack->resume_word = true; | |
448 | state->hack->iw = iw; | |
449 | } | |
450 | } else { | |
451 | state->halted = true; | |
452 | fprintf(stderr, "WARNING: Halting due to attempted execution of illegal instruction.\n"); | |
453 | } | |
454 | ||
455 | return state; | |
456 | } |