Added `-binary` CLI flag to NEDsim for loading a.out format programs.
[screensavers] / hacks / NEDsim / simulator.c
CommitLineData
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
30uint32_t
31generate_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
39void
40ram_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
45uint8_t
46ram_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
53void
54ram_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
64uint32_t
65ram_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
83uint32_t
84fetch_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
91void
92stack_w(struct NEDthread * thread, uint32_t value, uint8_t offset)
93{
94 thread->stack[thread->sp - (offset + 1)] = value;
95}
96
97uint32_t
98stack_r(struct NEDthread * thread, uint8_t offset)
99{
100 return thread->stack[thread->sp - (offset + 1)];
101}
102
103void
104stack_push(struct NEDthread * thread, uint32_t value)
105{
106 thread->stack[thread->sp++] = value;
107}
108
109uint32_t
110stack_pop(struct NEDthread * thread)
111{
112 return thread->stack[--thread->sp];
113}
114
115void
116set_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
130void
131ned_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
139void
140ned_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
148void
149ned_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
155void
156ned_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
164void
165ned_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
173void
174ned_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
189void
190ned_instruction_test(struct NEDstate * state)
191{
192 uint32_t word = stack_pop(state->active_thread);
193 set_psw_flags(word, state);
194}
195
196void
197ned_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
203void
204ned_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
213void
214ned_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
224void
225ned_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
232void
233ned_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
240void
241ned_instruction_halt(struct NEDstate * state)
242{
243 printf("Halting.\n");
244 state->halted = true;
245}
246
247void
248execute_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
285uint8_t
286extract_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
292void
293parse_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
381struct NEDstate *
5923644e 382init_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
417struct NEDstate *
418run_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}