* © 2018 Aaron Taylor <ataylor at subgeniuskitty dot com>
* See LICENSE.txt file for copyright and license details.
/* Number of stack words. */
#define STACK_LENGTH 1048576
/* Number of bytes of RAM. */
#define RAM_LENGTH 1073741824
/* Number of hardware threads. */
/* Number of syllables per word. */
uint32_t max_stack
; /* Highest stack entry reached during execution. */
uint32_t max_stack_address
; /* Address of PC when highest stack entry was reached. */
uint64_t ram_reads
; /* Number of words of RAM read during execution. */
uint64_t ram_writes
; /* Number of words of RAM written during execution. */
uint64_t cycle_count
; /* Total number of CPU cycles. One cycle corresponds */
/* to a single CPU action, either a WORD_x word */
/* or a single syllable from a five-syllable word. */
uint32_t stack
[STACK_LENGTH
];
struct DBGthread
* debug
;
struct NEDthread
* thread
[THREAD_COUNT
];
struct NEDthread
* active_thread
;
struct timespec
* prev_instruction_clock
;
uint32_t clock_period_ns
;
/* This variable allows printing the processor's state from a signal handler. */
/* It should not be used for anything else. */
struct NEDstate
* NEDdebug
= NULL
;
print_usage(char ** argv
)
printf( "NED Simulator v%d (www.subgeniuskitty.com)\n"
" -h Help (prints this message)\n"
" -i <file> Specify a binary image file to load in RAM.\n"
" -p <int ns, optional> Period in nanoseconds of simulated system clock.\n"
" Allowable values are 1 <= clock <= 1,000,000,000.\n"
thread_init(struct NEDthread
* thread
)
thread
->psw
->zero
= false;
thread
->psw
->negative
= false;
thread
->debug
->max_stack
= 0;
thread
->debug
->max_stack_address
= 0;
thread
->debug
->ram_reads
= 0;
thread
->debug
->ram_writes
= 0;
thread
->debug
->cycle_count
= 0;
tcgetattr(STDIN_FILENO
, &options
);
/* Create a cbreak-like environment through the following options. */
options
.c_lflag
&= ~ECHO
; /* Disable echoing of input characters. */
options
.c_lflag
&= ~ICANON
; /* Disable cooked/line-oriented mode. */
tcsetattr(STDIN_FILENO
, TCSANOW
, &options
);
unset_terminal_mode(void)
tcgetattr(STDIN_FILENO
, &options
);
/* Undo the changes made in set_terminal_mode(). */
options
.c_lflag
|= ECHO
; /* Enable echoing of input characters. */
options
.c_lflag
|= ICANON
; /* Enable cooked/line-oriented mode. */
options
.c_cc
[VMIN
] = 1; /* Default value from /usr/src/sys/sys/ttydefaults.h */
options
.c_cc
[VTIME
] = 0; /* Default value from /usr/src/sys/sys/ttydefaults.h */
tcsetattr(STDIN_FILENO
, TCSANOW
, &options
);
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) {
state
->active_thread
->debug
->ram_writes
+= 1;
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 */
state
->active_thread
->debug
->ram_reads
+= 1;
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
;
print_snapshot(struct NEDstate
* state
)
* This function is sometimes called from a signal handler that depends
* on a global variable. That variable is set to NULL in the declaration.
* For that reason, check for a NULL pointer here.
if (state
== NULL
) return;
printf("########## Processor Statistics ##########\n");
printf("Stack Max: %u @ 0x%08x\n", state
->active_thread
->debug
->max_stack
,
state
->active_thread
->debug
->max_stack_address
printf("Cycle Count: %lu\n", state
->active_thread
->debug
->cycle_count
);
printf("RAM Reads: %lu\n", state
->active_thread
->debug
->ram_reads
);
printf("RAM Writes: %lu\n", state
->active_thread
->debug
->ram_writes
);
printf("########## Processor Snapshot ##########\n");
printf("PC: 0x%X\n", state
->active_thread
->pc
);
printf("PSW:\n %d Zero\n %d Negative\n",
state
->active_thread
->psw
->zero
,
state
->active_thread
->psw
->negative
printf("Stack:\n ...\n");
int64_t i
= state
->active_thread
->sp
;
if ((i
+ 5) > (STACK_LENGTH
- 1)) {
if (i
== (state
->active_thread
->sp
- 1)) {
printf(" TOS> %ld: 0x%X\n", i
, state
->active_thread
->stack
[i
]);
printf(" %ld: 0x%X\n", i
, state
->active_thread
->stack
[i
]);
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
)
if (thread
->sp
> thread
->debug
->max_stack
) {
thread
->debug
->max_stack
= thread
->sp
;
thread
->debug
->max_stack_address
= thread
->pc
;
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
);
/* TODO: Find better way to communicate we're skipping ahead to the next word. */
state
->active_thread
->sc
= 4;
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
);
if (state
->active_thread
->psw
->zero
== true) {
state
->active_thread
->pc
= new_pc
;
/* TODO: Find better way to communicate we're skipping ahead to the next word. */
state
->active_thread
->sc
= 4;
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
);
ned_sigint_handler(int sig
)
print_snapshot(NEDdebug
);
wait_for_next_clock_cycle(struct NEDstate
* state
)
state
->prev_instruction_clock
->tv_nsec
+= state
->clock_period_ns
;
while (state
->prev_instruction_clock
->tv_nsec
> 1000000000) {
state
->prev_instruction_clock
->tv_nsec
-= 1000000000;
state
->prev_instruction_clock
->tv_sec
+= 1;
struct timespec current_time
;
if(clock_gettime(CLOCK_MONOTONIC
, ¤t_time
)) {
fprintf(stderr
, "WARNING: Unable to sync to requested CPU speed.\n");
bool sec_gt
= current_time
.tv_sec
> state
->prev_instruction_clock
->tv_sec
;
bool sec_eq
= current_time
.tv_sec
== state
->prev_instruction_clock
->tv_sec
;
bool nsec_gt
= current_time
.tv_nsec
> state
->prev_instruction_clock
->tv_nsec
;
if (sec_gt
|| (sec_eq
&& nsec_gt
)) break;
main(int argc
, char ** argv
)
* Process command line arguments
uint32_t clock_period
= 1;
while ((c
= getopt(argc
,argv
,"i:p:h")) != -1) {
if ((input
= fopen(optarg
, "r")) == NULL
) {
fprintf(stderr
, "ERROR: %s: %s\n", optarg
, strerror(errno
));
intmax_t temp_p
= strtoimax(optarg
, NULL
, 0);
if (1 <= temp_p
&& temp_p
<= 1000000000) {
fprintf(stderr
, "ERROR: Clock period out of range.\n");
fprintf(stderr
, "ERROR: Must specify a binary image file with -i flag.\n");
struct NEDstate
* state
= malloc(sizeof(struct NEDstate
));
state
->prev_instruction_clock
= malloc(sizeof(struct timespec
));
clock_gettime(CLOCK_MONOTONIC
, state
->prev_instruction_clock
);
state
->clock_period_ns
= clock_period
;
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
[i
]->debug
= malloc(sizeof(struct DBGthread
));
thread_init(state
->thread
[0]);
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. */
/* NEDdebug should only be used in ned_sigint_handler() to print state while exiting. */
signal(SIGINT
, ned_sigint_handler
);
/* Load an initial image into memory. */
uint32_t address
= 0x20000000;
while(fread(&temp_word
, 4, 1, input
)) {
ram_w_word(state
, address
, temp_word
);
/* Check for interrupt requests. */
/* Fetch new instruction word. */
iw
= fetch_instruction_word(state
);
/* Decode instruction word format and execute. */
if (iw
& (0b1 << 31)) { /* Instruction word is type A. */
wait_for_next_clock_cycle(state
);
stack_push(state
->active_thread
, (iw
<< 1));
state
->active_thread
->debug
->cycle_count
+= 1;
} else if ((iw
& (0b11 << 30)) == 0) { /* Instruction word is type C. */
for (state
->active_thread
->sc
= 0;
state
->active_thread
->sc
< SPW
;
state
->active_thread
->sc
++
wait_for_next_clock_cycle(state
);
uint8_t syllable
= extract_syllable_from_word(iw
, state
->active_thread
->sc
);
execute_syllable(state
, syllable
);
state
->active_thread
->debug
->cycle_count
+= 1;
fprintf(stderr
, "ERROR: Attempted to execute illegal instruction.\n");