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