/* (c) 2021 Aaron Taylor <ataylor at subgeniuskitty dot com> */
/* See LICENSE.txt file for copyright and license details. */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
// TODO: Make a bunch of functions private in this file.
FD_SET(STDIN_FILENO
, &read_fds
);
int retval
= select(1, &read_fds
, NULL
, NULL
, &timeout
);
/* TODO: How do I want to handle this error? */
generate_binary_psw(struct NEDstate
* state
)
if (state
->active_thread
->psw
->zero
) psw
|= 0b1;
if (state
->active_thread
->psw
->negative
) psw
|= 0b10;
ram_w_byte(struct NEDstate
* state
, uint32_t address
, uint8_t data
)
state
->ram
[address
] = data
;
ram_r_byte(struct NEDstate
* state
, uint32_t address
)
return state
->ram
[address
];
/* For now, with only a terminal for IO, we pick off IO requests when accessing RAM. */
/* TODO: Improve this before adding any other IO devices like disks. */
ram_w_word(struct NEDstate
* state
, uint32_t address
, uint32_t data
)
/* TODO: Since PC and PSW are memory mapped, they should accept writes. */
/* Should writes to the PC automatically reset the syllable counter? */
if (address
== 0x8000000) { /* SLU: XBUF */
} else if (address
== 0x0 || address
== 0x4) {
/* Intentionally empty */
} else if (address
>= 0x20000000) {
for (int i
=3; i
>=0; i
--) {
uint8_t tmp_byte
= ((data
>> (8*(3-i
))) & 0xff);
ram_w_byte(state
,address
+i
,tmp_byte
);
ram_r_word(struct NEDstate
* state
, uint32_t address
)
if (address
== 0x0) { /* Zero register */
} else if (address
== 0x4) { /* 0x80000000 register */
} else if (address
== 0x8) { /* PC register */
return state
->active_thread
->pc
;
} else if (address
== 0xC) { /* PSW register */
return generate_binary_psw(state
);
} else if (address
== 0x8000004) { /* SLU: XCSR */
/* TODO: Should I artificially restrict printing in the simulator? */
/* It might help catch bugs like the GCC bug that slipped past SIMH. */
} else if (address
== 0x8000008) { /* SLU: RBUF */
if (is_stdin_nonempty()) {
} else if (address
== 0x800000C) { /* SLU: RCSR */
if (is_stdin_nonempty()) {
} else if (address
>= 0x20000000) { /* RAM */
for (int i
=0; i
<4; i
++) word
|= (ram_r_byte(state
,address
+i
)) << (8*(3-i
));
fetch_instruction_word(struct NEDstate
* state
)
uint32_t word
= ram_r_word(state
, state
->active_thread
->pc
);
state
->active_thread
->pc
+= BPW
;
stack_w(struct NEDthread
* thread
, uint32_t value
, uint8_t offset
)
thread
->stack
[thread
->sp
- (offset
+ 1)] = value
;
stack_r(struct NEDthread
* thread
, uint8_t offset
)
return thread
->stack
[thread
->sp
- (offset
+ 1)];
stack_push(struct NEDthread
* thread
, uint32_t value
)
thread
->stack
[thread
->sp
++] = value
;
stack_pop(struct NEDthread
* thread
)
return thread
->stack
[--thread
->sp
];
set_psw_flags(uint32_t word
, struct NEDstate
* state
)
state
->active_thread
->psw
->zero
= true;
state
->active_thread
->psw
->zero
= false;
state
->active_thread
->psw
->negative
= true;
state
->active_thread
->psw
->negative
= false;
ned_instruction_and(struct NEDstate
* state
)
uint32_t operand1
= stack_pop(state
->active_thread
);
uint32_t operand2
= stack_pop(state
->active_thread
);
stack_push(state
->active_thread
, (operand1
& operand2
));
set_psw_flags(stack_r(state
->active_thread
,0), state
);
ned_instruction_or(struct NEDstate
* state
)
uint32_t operand1
= stack_pop(state
->active_thread
);
uint32_t operand2
= stack_pop(state
->active_thread
);
stack_push(state
->active_thread
, (operand1
| operand2
));
set_psw_flags(stack_r(state
->active_thread
,0), state
);
ned_instruction_not(struct NEDstate
* state
)
stack_push(state
->active_thread
, ~stack_pop(state
->active_thread
));
set_psw_flags(stack_r(state
->active_thread
,0), state
);
ned_instruction_xor(struct NEDstate
* state
)
uint32_t operand1
= stack_pop(state
->active_thread
);
uint32_t operand2
= stack_pop(state
->active_thread
);
stack_push(state
->active_thread
, (operand1
^ operand2
));
set_psw_flags(stack_r(state
->active_thread
,0), state
);
ned_instruction_add(struct NEDstate
* state
)
uint32_t operand1
= stack_pop(state
->active_thread
);
uint32_t operand2
= stack_pop(state
->active_thread
);
stack_push(state
->active_thread
, (operand1
+ operand2
));
set_psw_flags(stack_r(state
->active_thread
,0), state
);
ned_instruction_mvstck(struct NEDstate
* state
)
uint32_t new_id
= stack_pop(state
->active_thread
);
if (new_id
< THREAD_COUNT
) {
state
->active_thread
= state
->thread
[new_id
];
printf("ERROR: Attempted MVSTCK to ID higher than THREAD_COUNT.\n");
ned_instruction_shift(struct NEDstate
* state
)
/* TODO: Bounds check: Either all inputs are valid OR shift_by < 32. */
/* I guess this also depends if I'm shifting-and-dropping, or barrel-shifting. */
/* How should I pad for a right shift if I shift-and-drop? Sign extend? */
uint32_t shift_by
= stack_pop(state
->active_thread
);
uint32_t word
= stack_pop(state
->active_thread
);
if (shift_by
& 0x80000000) {
stack_push(state
->active_thread
, (word
<< (shift_by
& 0x7fffffff)));
stack_push(state
->active_thread
, (word
>> shift_by
));
set_psw_flags(stack_r(state
->active_thread
,0), state
);
ned_instruction_test(struct NEDstate
* state
)
uint32_t word
= stack_pop(state
->active_thread
);
set_psw_flags(word
, state
);
ned_instruction_jmp(struct NEDstate
* state
)
state
->active_thread
->pc
= stack_pop(state
->active_thread
);
// The SC is caught and reset by the main loop since the PC changed.
ned_instruction_swap(struct NEDstate
* state
)
uint32_t temp1
= stack_pop(state
->active_thread
);
uint32_t temp2
= stack_pop(state
->active_thread
);
stack_push(state
->active_thread
, temp1
);
stack_push(state
->active_thread
, temp2
);
set_psw_flags(stack_r(state
->active_thread
,0), state
);
ned_instruction_brz(struct NEDstate
* state
)
uint32_t new_pc
= stack_pop(state
->active_thread
);
uint32_t test_word
= stack_pop(state
->active_thread
);
state
->active_thread
->pc
= new_pc
;
// The SC is caught and reset by the main loop since the PC changed.
ned_instruction_load(struct NEDstate
* state
)
uint32_t address
= stack_pop(state
->active_thread
);
stack_push(state
->active_thread
, ram_r_word(state
, address
));
set_psw_flags(stack_r(state
->active_thread
,0), state
);
ned_instruction_store(struct NEDstate
* state
)
uint32_t address
= stack_pop(state
->active_thread
);
uint32_t data
= stack_pop(state
->active_thread
);
ram_w_word(state
, address
, data
);
ned_instruction_halt(struct NEDstate
* state
)
execute_syllable(struct NEDstate
* state
, enum syllables syllable
)
if (syllable
& 0b100000) { /* Check the first bit of the syllable. 1 means IM_x. */
stack_push(state
->active_thread
, (uint32_t)(syllable
& 0b11111));
} else if (syllable
& 0b10000) { /* 1 in 2nd bit means LDSP+x or STSP+x instruction. */
if (syllable
& 0b1000) { /* LDSP+x */
stack_push(state
->active_thread
,stack_r(state
->active_thread
,(syllable
& 0b111)));
set_psw_flags(stack_r(state
->active_thread
,0), state
);
stack_w(state
->active_thread
,stack_pop(state
->active_thread
),(syllable
& 0b111));
case AND
: ned_instruction_and(state
); break;
case OR
: ned_instruction_or(state
); break;
case NOT
: ned_instruction_not(state
); break;
case XOR
: ned_instruction_xor(state
); break;
case ADD
: ned_instruction_add(state
); break;
case MVSTCK
: ned_instruction_mvstck(state
); break;
case SHIFT
: ned_instruction_shift(state
); break;
case CMPSWP
: /* TODO */ break;
case TEST
: ned_instruction_test(state
); break;
case JMP
: ned_instruction_jmp(state
); break;
case SWAP
: ned_instruction_swap(state
); break;
case BRZ
: ned_instruction_brz(state
); break;
case LOAD
: ned_instruction_load(state
); break;
case STORE
: ned_instruction_store(state
); break;
case NOP
: /* Intentionally blank */ break;
case HALT
: ned_instruction_halt(state
); break;
printf("ERROR: Attempted to execute illegal syllable: 0o%o\n", syllable
);
extract_syllable_from_word(uint32_t word
, uint8_t index
)
uint32_t mask
= 0b111111 << 6*(4-index
);
return (word
& mask
) >> 6*(4-index
);
parse_aout_file(FILE * input
, struct exec
* aout_exec
, uint8_t * text_segment
,
struct nlist
** symbol_table
, uint32_t * symbol_count
)
/* Read in and check the a.out header. */
for (uint32_t i
=0; i
<8; i
++) {
case 0: read_count
= fread(&(aout_exec
->a_midmag
), 4, 1, input
); break;
case 1: read_count
= fread(&(aout_exec
->a_text
), 4, 1, input
); break;
case 2: read_count
= fread(&(aout_exec
->a_data
), 4, 1, input
); break;
case 3: read_count
= fread(&(aout_exec
->a_bss
), 4, 1, input
); break;
case 4: read_count
= fread(&(aout_exec
->a_syms
), 4, 1, input
); break;
case 5: read_count
= fread(&(aout_exec
->a_entry
), 4, 1, input
); break;
case 6: read_count
= fread(&(aout_exec
->a_trsize
), 4, 1, input
); break;
case 7: read_count
= fread(&(aout_exec
->a_drsize
), 4, 1, input
); break;
fprintf(stderr
, "ERROR: Invalid a.out header.\n");
if (N_BADMAG(*aout_exec
)) {
fprintf(stderr
, "ERROR: Invalid magic number in a.out header.\n");
} else if (N_GETMID(*aout_exec
) != MID_NED
) {
fprintf(stderr
, "ERROR: Executable not intended for NED Machine ID.\n");
/* Read in the text segment. */
uint32_t text_segment_size
= (N_DATOFF(*aout_exec
) - N_TXTOFF(*aout_exec
));
read_count
= fread(text_segment
, 1, text_segment_size
, input
);
if (read_count
!= text_segment_size
) {
fprintf(stderr
, "ERROR: Failed to read entire text segment.\n");
/* Correct the byte order. */
for (uint32_t i
=0; i
< (text_segment_size
/ 4); i
++) {
for (uint8_t j
=0; j
<4; j
++) temp_word
[j
] = text_segment
[((i
*4)+j
)];
for (uint8_t j
=0; j
<4; j
++) text_segment
[((i
*4)+j
)] = temp_word
[(3-j
)];
/* Read in the symbol table. */
*symbol_count
= ((N_STROFF(*aout_exec
) - N_SYMOFF(*aout_exec
)) / 20); /* 20 bytes per symbol. */
*symbol_table
= malloc((*symbol_count
) * sizeof(struct nlist
));
for (uint32_t i
=0; i
< *symbol_count
; i
++) {
for (uint32_t j
=0; j
<5; j
++) {
case 0: read_count
= fread(&((*symbol_table
)[i
].n_un
.n_strx
), 4, 1, input
); break;
case 1: read_count
= fread(&((*symbol_table
)[i
].n_type
), 4, 1, input
); break;
case 2: read_count
= fread(&((*symbol_table
)[i
].n_other
), 4, 1, input
); break;
case 3: read_count
= fread(&((*symbol_table
)[i
].n_desc
), 4, 1, input
); break;
case 4: read_count
= fread(&((*symbol_table
)[i
].n_value
), 4, 1, input
); break;
fprintf(stderr
, "ERROR: Unable to read entire symbol table.\n");
/* Read in the string table and update the symbol table entries with pointers to new strings. */
uint32_t string_table_size
;
read_count
= fread(&string_table_size
, 4, 1, input
);
fprintf(stderr
, "ERROR: Failed to read string table size.\n");
for (uint32_t i
=0; i
< *symbol_count
; i
++) {
if (i
< ((*symbol_count
)-1)) {
len
= ((*symbol_table
)[i
+1].n_un
.n_strx
- (*symbol_table
)[i
].n_un
.n_strx
);
len
= (string_table_size
- (*symbol_table
)[i
].n_un
.n_strx
);
(*symbol_table
)[i
].n_un
.n_name
= malloc(len
);
read_count
= fread((*symbol_table
)[i
].n_un
.n_name
, 1, len
, input
);
fprintf(stderr
, "ERROR: Failed to read a string from the string table.\n");
struct NEDstate
* state
= malloc(sizeof(struct NEDstate
));
state
->hack
= malloc(sizeof(struct NEDhack
));
for (size_t i
=0; i
< THREAD_COUNT
; i
++) {
state
->thread
[i
] = malloc(sizeof(struct NEDthread
));
state
->thread
[i
]->psw
= malloc(sizeof(struct NEDpsw
));
state
->thread
[0]->pc
= 0;
state
->thread
[0]->sc
= 0;
state
->thread
[0]->sp
= 0;
state
->thread
[0]->psw
->zero
= false;
state
->thread
[0]->psw
->negative
= false;
state
->thread
[0]->pc
= 0x20000000; /* Data region starts 512 MB into address space. */
state
->active_thread
= state
->thread
[0]; /* By convention, use thread 0 for init. */
state
->hack
->resume_word
= false;
// TODO: This needs to be passed in as a CLI option.
#define AOUT_PATH "./test.out"
/* Load an initial image into memory. */
uint32_t address
= 0x20000000;
struct nlist
* symbol_table
;
if ((input
= fopen(AOUT_PATH
, "r")) == NULL
) {
fprintf(stderr
, "ERROR: %s: %s\n", AOUT_PATH
, strerror(errno
));
parse_aout_file(input
, &aout_exec
, &(state
->ram
[address
]), &symbol_table
, &symbol_count
);
run_simulator(struct NEDstate
* state
)
if (state
->halted
) return state
;
/* Fetch instruction word. */
if (state
->hack
->resume_word
) {
iw
= fetch_instruction_word(state
);
/* Decode instruction word format and execute. */
if (iw
& (0b1 << 31)) { /* Instruction word is type A. */
stack_push(state
->active_thread
, (iw
<< 1));
} else if ((iw
& (0b11 << 30)) == 0) { /* Instruction word is type C. */
uint8_t syllable
= extract_syllable_from_word(iw
, state
->active_thread
->sc
);
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().
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.
execute_syllable(state
, syllable
);
if (state
->active_thread
->pc
!= pre_execution_pc
) {
// Jumped to a new address, so prepare to execute a new instruction word.
state
->active_thread
->sc
= 0;
state
->hack
->resume_word
= false;
} else if (state
->active_thread
->sc
>= SPW
) {
// Just executed the last syllable in this word, time to follow the PC to the next word.
state
->active_thread
->sc
= 0;
state
->hack
->resume_word
= false;
// More syllables remain to be executed in this instruction word.
state
->hack
->resume_word
= true;
fprintf(stderr
, "WARNING: Halting due to attempted execution of illegal instruction.\n");