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