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