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 | { | |
84b74595 AT |
175 | uint32_t shift_by = stack_pop(state->active_thread); |
176 | uint32_t word = stack_pop(state->active_thread); | |
177 | if (shift_by & 0x80000000) { | |
178 | stack_push(state->active_thread, (word << (shift_by & 0x7fffffff))); | |
179 | } else { | |
180 | stack_push(state->active_thread, (word >> shift_by)); | |
181 | } | |
182 | set_psw_flags(stack_r(state->active_thread,0), state); | |
183 | } | |
184 | ||
51228e25 | 185 | static void |
84b74595 AT |
186 | ned_instruction_test(struct NEDstate * state) |
187 | { | |
188 | uint32_t word = stack_pop(state->active_thread); | |
189 | set_psw_flags(word, state); | |
190 | } | |
191 | ||
51228e25 | 192 | static void |
84b74595 AT |
193 | ned_instruction_jmp(struct NEDstate * state) |
194 | { | |
195 | state->active_thread->pc = stack_pop(state->active_thread); | |
196 | // The SC is caught and reset by the main loop since the PC changed. | |
197 | } | |
198 | ||
51228e25 | 199 | static void |
84b74595 AT |
200 | ned_instruction_swap(struct NEDstate * state) |
201 | { | |
202 | uint32_t temp1 = stack_pop(state->active_thread); | |
203 | uint32_t temp2 = stack_pop(state->active_thread); | |
204 | stack_push(state->active_thread, temp1); | |
205 | stack_push(state->active_thread, temp2); | |
206 | set_psw_flags(stack_r(state->active_thread,0), state); | |
207 | } | |
208 | ||
51228e25 | 209 | static void |
84b74595 AT |
210 | ned_instruction_brz(struct NEDstate * state) |
211 | { | |
212 | uint32_t new_pc = stack_pop(state->active_thread); | |
213 | uint32_t test_word = stack_pop(state->active_thread); | |
214 | if (test_word == 0) { | |
215 | state->active_thread->pc = new_pc; | |
216 | // The SC is caught and reset by the main loop since the PC changed. | |
217 | } | |
218 | } | |
219 | ||
51228e25 | 220 | static void |
84b74595 AT |
221 | ned_instruction_load(struct NEDstate * state) |
222 | { | |
223 | uint32_t address = stack_pop(state->active_thread); | |
224 | stack_push(state->active_thread, ram_r_word(state, address)); | |
225 | set_psw_flags(stack_r(state->active_thread,0), state); | |
226 | } | |
227 | ||
51228e25 | 228 | static void |
84b74595 AT |
229 | ned_instruction_store(struct NEDstate * state) |
230 | { | |
231 | uint32_t address = stack_pop(state->active_thread); | |
232 | uint32_t data = stack_pop(state->active_thread); | |
233 | ram_w_word(state, address, data); | |
234 | } | |
235 | ||
51228e25 | 236 | static void |
84b74595 AT |
237 | ned_instruction_halt(struct NEDstate * state) |
238 | { | |
239 | printf("Halting.\n"); | |
240 | state->halted = true; | |
241 | } | |
242 | ||
51228e25 | 243 | static void |
84b74595 AT |
244 | execute_syllable(struct NEDstate * state, enum syllables syllable) |
245 | { | |
246 | if (syllable & 0b100000) { /* Check the first bit of the syllable. 1 means IM_x. */ | |
247 | stack_push(state->active_thread, (uint32_t)(syllable & 0b11111)); | |
248 | } else if (syllable & 0b10000) { /* 1 in 2nd bit means LDSP+x or STSP+x instruction. */ | |
249 | if (syllable & 0b1000) { /* LDSP+x */ | |
250 | stack_push(state->active_thread,stack_r(state->active_thread,(syllable & 0b111))); | |
251 | set_psw_flags(stack_r(state->active_thread,0), state); | |
252 | } else { /* STSP+x */ | |
253 | stack_w(state->active_thread,stack_pop(state->active_thread),(syllable & 0b111)); | |
254 | } | |
255 | } else { | |
256 | switch (syllable) { | |
257 | case AND: ned_instruction_and(state); break; | |
258 | case OR: ned_instruction_or(state); break; | |
259 | case NOT: ned_instruction_not(state); break; | |
260 | case XOR: ned_instruction_xor(state); break; | |
261 | case ADD: ned_instruction_add(state); break; | |
d87b1e06 | 262 | case MVSTCK: /* Intentionally blank */ break; |
84b74595 | 263 | case SHIFT: ned_instruction_shift(state); break; |
d87b1e06 | 264 | case CMPSWP: /* Intentionally blank */ break; |
84b74595 AT |
265 | case TEST: ned_instruction_test(state); break; |
266 | case JMP: ned_instruction_jmp(state); break; | |
267 | case SWAP: ned_instruction_swap(state); break; | |
268 | case BRZ: ned_instruction_brz(state); break; | |
269 | case LOAD: ned_instruction_load(state); break; | |
270 | case STORE: ned_instruction_store(state); break; | |
271 | case NOP: /* Intentionally blank */ break; | |
272 | case HALT: ned_instruction_halt(state); break; | |
273 | default: | |
274 | printf("ERROR: Attempted to execute illegal syllable: 0o%o\n", syllable); | |
275 | state->halted = true; | |
276 | break; | |
277 | } | |
278 | } | |
279 | } | |
280 | ||
51228e25 | 281 | static uint8_t |
84b74595 AT |
282 | extract_syllable_from_word(uint32_t word, uint8_t index) |
283 | { | |
284 | uint32_t mask = 0b111111 << 6*(4-index); | |
285 | return (word & mask) >> 6*(4-index); | |
286 | } | |
287 | ||
51228e25 | 288 | static void |
84b74595 AT |
289 | parse_aout_file(FILE * input, struct exec * aout_exec, uint8_t * text_segment, |
290 | struct nlist ** symbol_table, uint32_t * symbol_count) | |
291 | { | |
292 | uint32_t read_count = 0; | |
293 | ||
294 | /* Read in and check the a.out header. */ | |
295 | for (uint32_t i=0; i<8; i++) { | |
296 | switch (i) { | |
297 | case 0: read_count = fread(&(aout_exec->a_midmag), 4, 1, input); break; | |
298 | case 1: read_count = fread(&(aout_exec->a_text), 4, 1, input); break; | |
299 | case 2: read_count = fread(&(aout_exec->a_data), 4, 1, input); break; | |
300 | case 3: read_count = fread(&(aout_exec->a_bss), 4, 1, input); break; | |
301 | case 4: read_count = fread(&(aout_exec->a_syms), 4, 1, input); break; | |
302 | case 5: read_count = fread(&(aout_exec->a_entry), 4, 1, input); break; | |
303 | case 6: read_count = fread(&(aout_exec->a_trsize), 4, 1, input); break; | |
304 | case 7: read_count = fread(&(aout_exec->a_drsize), 4, 1, input); break; | |
305 | } | |
306 | if (read_count != 1) { | |
307 | fprintf(stderr, "ERROR: Invalid a.out header.\n"); | |
308 | exit(EXIT_FAILURE); | |
309 | } | |
310 | } | |
311 | if (N_BADMAG(*aout_exec)) { | |
312 | fprintf(stderr, "ERROR: Invalid magic number in a.out header.\n"); | |
313 | exit(EXIT_FAILURE); | |
314 | } else if (N_GETMID(*aout_exec) != MID_NED) { | |
315 | fprintf(stderr, "ERROR: Executable not intended for NED Machine ID.\n"); | |
316 | exit(EXIT_FAILURE); | |
317 | } | |
318 | ||
319 | /* Read in the text segment. */ | |
320 | uint32_t text_segment_size = (N_DATOFF(*aout_exec) - N_TXTOFF(*aout_exec)); | |
321 | read_count = fread(text_segment, 1, text_segment_size, input); | |
322 | if (read_count != text_segment_size) { | |
323 | fprintf(stderr, "ERROR: Failed to read entire text segment.\n"); | |
324 | exit(EXIT_FAILURE); | |
325 | } | |
326 | ||
327 | /* Correct the byte order. */ | |
328 | for (uint32_t i=0; i < (text_segment_size / 4); i++) { | |
329 | uint8_t temp_word[4]; | |
330 | for (uint8_t j=0; j<4; j++) temp_word[j] = text_segment[((i*4)+j)]; | |
331 | for (uint8_t j=0; j<4; j++) text_segment[((i*4)+j)] = temp_word[(3-j)]; | |
332 | } | |
333 | ||
334 | /* Read in the symbol table. */ | |
335 | *symbol_count = ((N_STROFF(*aout_exec) - N_SYMOFF(*aout_exec)) / 20); /* 20 bytes per symbol. */ | |
336 | *symbol_table = malloc((*symbol_count) * sizeof(struct nlist)); | |
337 | for (uint32_t i=0; i < *symbol_count; i++) { | |
338 | for (uint32_t j=0; j<5; j++) { | |
339 | switch (j) { | |
340 | case 0: read_count = fread(&((*symbol_table)[i].n_un.n_strx), 4, 1, input); break; | |
341 | case 1: read_count = fread(&((*symbol_table)[i].n_type), 4, 1, input); break; | |
342 | case 2: read_count = fread(&((*symbol_table)[i].n_other), 4, 1, input); break; | |
343 | case 3: read_count = fread(&((*symbol_table)[i].n_desc), 4, 1, input); break; | |
344 | case 4: read_count = fread(&((*symbol_table)[i].n_value), 4, 1, input); break; | |
345 | } | |
346 | if (read_count != 1) { | |
347 | fprintf(stderr, "ERROR: Unable to read entire symbol table.\n"); | |
348 | exit(EXIT_FAILURE); | |
349 | } | |
350 | } | |
351 | } | |
352 | ||
353 | /* Read in the string table and update the symbol table entries with pointers to new strings. */ | |
354 | uint32_t string_table_size; | |
355 | read_count = fread(&string_table_size, 4, 1, input); | |
356 | if (read_count != 1) { | |
357 | fprintf(stderr, "ERROR: Failed to read string table size.\n"); | |
358 | exit(EXIT_FAILURE); | |
359 | } | |
360 | for (uint32_t i=0; i < *symbol_count; i++) { | |
361 | uint32_t len = 0; | |
362 | if (i < ((*symbol_count)-1)) { | |
363 | len = ((*symbol_table)[i+1].n_un.n_strx - (*symbol_table)[i].n_un.n_strx); | |
364 | } else { | |
365 | len = (string_table_size - (*symbol_table)[i].n_un.n_strx); | |
366 | } | |
367 | (*symbol_table)[i].n_un.n_name = malloc(len); | |
368 | read_count = fread((*symbol_table)[i].n_un.n_name, 1, len, input); | |
369 | if (read_count != len) { | |
370 | fprintf(stderr, "ERROR: Failed to read a string from the string table.\n"); | |
371 | exit(EXIT_FAILURE); | |
372 | } | |
373 | } | |
374 | ||
375 | } | |
376 | ||
377 | struct NEDstate * | |
5923644e | 378 | init_simulator(char * input_file) |
84b74595 AT |
379 | { |
380 | struct NEDstate * state = malloc(sizeof(struct NEDstate)); | |
381 | state->hack = malloc(sizeof(struct NEDhack)); | |
d87b1e06 AT |
382 | state->thread[0] = malloc(sizeof(struct NEDthread)); |
383 | state->thread[0]->psw = malloc(sizeof(struct NEDpsw)); | |
84b74595 AT |
384 | state->thread[0]->pc = 0; |
385 | state->thread[0]->sc = 0; | |
386 | state->thread[0]->sp = 0; | |
387 | state->thread[0]->psw->zero = false; | |
388 | state->thread[0]->psw->negative = false; | |
d87b1e06 AT |
389 | state->thread[0]->pc = RAM_BASE_ADDRESS; |
390 | state->active_thread = state->thread[0]; | |
84b74595 AT |
391 | state->halted = false; |
392 | state->hack->resume_word = false; | |
393 | ||
84b74595 | 394 | /* Load an initial image into memory. */ |
84b74595 AT |
395 | struct exec aout_exec; |
396 | struct nlist * symbol_table; | |
397 | uint32_t symbol_count; | |
398 | FILE * input = NULL; | |
5923644e AT |
399 | if ((input = fopen(input_file, "r")) == NULL) { |
400 | fprintf(stderr, "ERROR: %s: %s\n", input_file, strerror(errno)); | |
84b74595 AT |
401 | state->halted = true; |
402 | } | |
d87b1e06 | 403 | parse_aout_file(input, &aout_exec, state->ram, &symbol_table, &symbol_count); |
84b74595 | 404 | fclose(input); |
d87b1e06 AT |
405 | for (size_t i=0; i < symbol_count; i++) { |
406 | free(symbol_table[i].n_un.n_name); | |
407 | } | |
408 | free(symbol_table); | |
84b74595 AT |
409 | |
410 | return state; | |
411 | } | |
412 | ||
413 | struct NEDstate * | |
414 | run_simulator(struct NEDstate * state) | |
415 | { | |
416 | if (state->halted) return state; | |
417 | ||
418 | /* Fetch instruction word. */ | |
419 | uint32_t iw; | |
420 | if (state->hack->resume_word) { | |
421 | iw = state->hack->iw; | |
422 | } else { | |
423 | iw = fetch_instruction_word(state); | |
424 | } | |
425 | ||
426 | /* Decode instruction word format and execute. */ | |
427 | if (iw & (0b1 << 31)) { /* Instruction word is type A. */ | |
428 | stack_push(state->active_thread, (iw << 1)); | |
429 | } else if ((iw & (0b11 << 30)) == 0) { /* Instruction word is type C. */ | |
430 | uint8_t syllable = extract_syllable_from_word(iw, state->active_thread->sc); | |
0e83e980 AT |
431 | state->active_thread->sc++; |
432 | /* The following variable allows us to catch JMP/BRZ instructions that */ | |
433 | /* jump to a new PC and need the SC reset. */ | |
434 | uint32_t pre_execution_pc = state->active_thread->pc; | |
84b74595 AT |
435 | execute_syllable(state, syllable); |
436 | if (state->active_thread->pc != pre_execution_pc) { | |
0e83e980 | 437 | /* Jumped to a new address, so prepare to execute a new instruction word. */ |
84b74595 AT |
438 | state->active_thread->sc = 0; |
439 | state->hack->resume_word = false; | |
440 | } else if (state->active_thread->sc >= SPW) { | |
0e83e980 | 441 | /* Just executed the last syllable in this word, follow the PC to the next word. */ |
84b74595 AT |
442 | state->active_thread->sc = 0; |
443 | state->hack->resume_word = false; | |
444 | } else { | |
0e83e980 | 445 | /* More syllables remain to be executed in this instruction word. */ |
84b74595 AT |
446 | state->hack->resume_word = true; |
447 | state->hack->iw = iw; | |
448 | } | |
449 | } else { | |
450 | state->halted = true; | |
451 | fprintf(stderr, "WARNING: Halting due to attempted execution of illegal instruction.\n"); | |
452 | } | |
453 | ||
454 | return state; | |
455 | } |