Initial commit of NED1 front panel code I've been hacking on.
[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
30int
31is_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
50uint32_t
51generate_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
59void
60ram_w_byte(struct NEDstate * state, uint32_t address, uint8_t data)
61{
62 state->ram[address] = data;
63}
64
65uint8_t
66ram_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
74void
75ram_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
92uint32_t
93ram_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
127uint32_t
128fetch_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
135void
136stack_w(struct NEDthread * thread, uint32_t value, uint8_t offset)
137{
138 thread->stack[thread->sp - (offset + 1)] = value;
139}
140
141uint32_t
142stack_r(struct NEDthread * thread, uint8_t offset)
143{
144 return thread->stack[thread->sp - (offset + 1)];
145}
146
147void
148stack_push(struct NEDthread * thread, uint32_t value)
149{
150 thread->stack[thread->sp++] = value;
151}
152
153uint32_t
154stack_pop(struct NEDthread * thread)
155{
156 return thread->stack[--thread->sp];
157}
158
159void
160set_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
174void
175ned_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
183void
184ned_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
192void
193ned_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
199void
200ned_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
208void
209ned_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
217void
218ned_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
229void
230ned_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
245void
246ned_instruction_test(struct NEDstate * state)
247{
248 uint32_t word = stack_pop(state->active_thread);
249 set_psw_flags(word, state);
250}
251
252void
253ned_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
259void
260ned_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
269void
270ned_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
280void
281ned_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
288void
289ned_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
296void
297ned_instruction_halt(struct NEDstate * state)
298{
299 printf("Halting.\n");
300 state->halted = true;
301}
302
303void
304execute_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
341uint8_t
342extract_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
348void
349parse_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
437struct NEDstate *
438init_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
475struct NEDstate *
476run_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}