From 84b7459516288471d70a0bbddffef5c5a80efd18 Mon Sep 17 00:00:00 2001 From: Aaron Taylor Date: Thu, 8 Jul 2021 15:59:01 -0700 Subject: [PATCH] Initial commit of NED1 front panel code I've been hacking on. Thus far, it draws and XScreensaver compatible, dynamically sized front panel and glues it to a simplified NEDsim instance. The a.out format input file is hardcoded as "./test.out", and many other things are hardcoded/WIP at this time, but the basic simulator runs NED code and displays (with poor visual synchronization) the correct state. --- hacks/NEDsim/NEDsim.c | 746 +++++++++++++++++++++++++++++++++++++++ hacks/NEDsim/a.out.h | 385 ++++++++++++++++++++ hacks/NEDsim/simulator.c | 515 +++++++++++++++++++++++++++ hacks/NEDsim/simulator.h | 92 +++++ 4 files changed, 1738 insertions(+) create mode 100644 hacks/NEDsim/NEDsim.c create mode 100644 hacks/NEDsim/a.out.h create mode 100644 hacks/NEDsim/simulator.c create mode 100644 hacks/NEDsim/simulator.h diff --git a/hacks/NEDsim/NEDsim.c b/hacks/NEDsim/NEDsim.c new file mode 100644 index 0000000..bee5e3b --- /dev/null +++ b/hacks/NEDsim/NEDsim.c @@ -0,0 +1,746 @@ +/* (c) 2021 Aaron Taylor */ +/* See LICENSE.txt file for copyright and license details. */ + +// TODO: +// - Write a brief description of the machine being simulated. Only one thread, reduced RAM, no meaningful console when running as screensaver, etc. + +// CLI Flags: +// -path-to-aout-binary +// -path-to-font-file +// -speed + +// Ideas for sample programs to include: +// - Build list of integers on the stack (DUP, IM_1, ADD). +// - Calculate prime numbers, placing them on the stack. +// - Lights sliding back and forth, like PDP-11 front panel with some OSes. + +#include "screenhack.h" +#include "simulator.h" + +/* Keep this source code C89 compliant per XScreensaver's instructions. */ + +/* -------------------------------------------------------------------------- */ +/* Data Structures */ +/* -------------------------------------------------------------------------- */ + +struct NEDsim { + /* Various X resources */ + Display * dpy; + Window win; + GC gc; + + // TODO: Explain these + int dpy_width, dpy_height; + + // TODO: Explain that this is created during init, then lights are populated/overwritten on each frame. + Pixmap panel; + + /* Delay (in microseconds) between clock cycles in the NED CPU. */ + size_t delay; + + int cell_size; + int border_size; + int origin_x_offset, origin_y_offset; + int num_data_rows; + + size_t color_index; + + Bool suitable_display; + + char * current_font; + + // TODO: Explain that this contains all the actual state of the NED machine being simulated. + struct NEDstate * nedstate; +}; + +struct color_rgb { + // TODO: Explain why this is an unsigned short. Copy from WolframAutomata. + unsigned short red, green, blue; +}; + +struct color_scheme { + // TODO: Explain all this. + struct color_rgb + panel_bg, + panel_fg, + light_on, + light_off, + error_on, + error_off, + primary, + secondary, + tertiary, + border, + text; +}; + +//static struct color_scheme color_list[] = { +// // TODO: Explain all this. +// // TODO: Add other color schemes, like purple PDP-11/70. +// // TODO: http://www.chdickman.com/pdp8/DECcolors/ +// { +// {63479,63479,63479}, // 092-XXXX-123 +// { 5140, 2056, 5654}, // 092-XXXX-152 +// {40092,11051,15677}, // 092-XXXX-139 +// {30326,13107,12850}, // 092-XXXX-154 +// {65535,13107,12850}, // homemade +// {30326,13107,12850}, // 092-XXXX-154 +// { 4112,12850,20046}, // 092-XXXX-145 +// {12336,29555,37008}, // 092-XXXX-151 +// {30326,24158, 6425}, // 092-XXXX-157 +// {63479,63479,63479}, // 092-XXXX-123 +// {63479,63479,63479} // 092-XXXX-123 +// } +//}; + +static struct color_scheme color_list[] = { + // TODO: Explain all this. + // TODO: Add other color schemes, like purple PDP-11/70. + // TODO: http://www.chdickman.com/pdp8/DECcolors/ + { + {63479,63479,63479}, // 092-XXXX-123 + { 5140, 2056, 5654}, // 092-XXXX-152 + {65535,11051,15677}, // 092-XXXX-139 - edit + {20000,13107,12850}, // 092-XXXX-154 - edit + {65535,13107,12850}, // homemade + {20000,13107,12850}, // 092-XXXX-154 - edit + { 4112,12850,20046}, // 092-XXXX-145 + {12336,29555,37008}, // 092-XXXX-151 + {30326,24158, 6425}, // 092-XXXX-157 + {63479,63479,63479}, // 092-XXXX-123 + {63479,63479,63479} // 092-XXXX-123 + } +}; + +/* -------------------------------------------------------------------------- */ +/* Helper Functions */ +/* -------------------------------------------------------------------------- */ + +// TODO: Explain +static void +set_color(struct NEDsim * nedsim, struct color_rgb * color) +{ + XColor temp; + XWindowAttributes xgwa; + + XGetWindowAttributes(nedsim->dpy, nedsim->win, &xgwa); + + temp.red = color->red; + temp.green = color->green; + temp.blue = color->blue; + + XAllocColor(nedsim->dpy, xgwa.colormap, &temp); + XSetForeground(nedsim->dpy, nedsim->gc, temp.pixel); +} + +// TODO: Explain +// TODO: Make this a lot faster. +// Input: size in 'cells', and sets font to fill that size, minus border and padding room. +static void +set_font_size(struct NEDsim * nedsim, int size) +{ + // vvv--- for border ---vvv vvv--- for padding ---vvv + int desired_height_in_pixels = (size * nedsim->cell_size) - (2 * nedsim->border_size) - (8 * nedsim->border_size); + + const char * font_size_prefix = "-*-helvetica-*-r-*-*-"; + const char * font_size_suffix = "-*-*-*-*-*-*-*"; + + size_t buffer_size = strlen(font_size_prefix) + strlen(font_size_suffix) + 100; // '100' since nobody needs font with size in 'points' greater than 100 decimal digits long. + char * font_full_name = malloc(buffer_size); + int font_size_in_points = 2; // Start with a 2 pt font and work our way up. + + while (1) { + // Load the font. + snprintf(font_full_name, buffer_size, "%s%d%s", font_size_prefix, font_size_in_points, font_size_suffix); + XFontStruct * font = XLoadQueryFont(nedsim->dpy, font_full_name); + if (!font) { + printf("WARNING: Unable to load font %s. Probably gonna look wonky.\n", font_full_name); + font = XLoadQueryFont(nedsim->dpy, "fixed"); + break; + } + XSetFont(nedsim->dpy, nedsim->gc, font->fid); + + // Get the height. + int direction, ascent, descent; + XCharStruct overall; + XTextExtents(font, "X", 1, &direction, &ascent, &descent, &overall); + + // Compare the height. + int height = overall.ascent - overall.descent; + if (height == desired_height_in_pixels) { + break; + } else if (height > desired_height_in_pixels) { + font_size_in_points--; + snprintf(font_full_name, buffer_size, "%s%d%s", font_size_prefix, font_size_in_points, font_size_suffix); + XFontStruct * font = XLoadQueryFont(nedsim->dpy, font_full_name); + if (!font) { + printf("WARNING: Unable to load font %s. Probably gonna look wonky.\n", font_full_name); + font = XLoadQueryFont(nedsim->dpy, "fixed"); + break; + } + XSetFont(nedsim->dpy, nedsim->gc, font->fid); + break; + } else { + font_size_in_points++; + } + } + + free(nedsim->current_font); + nedsim->current_font = font_full_name; +} + +// TODO: Explain +// TODO: Explain that this returns result in pixels so we can track fractional cell usage. +static void +get_text_size(struct NEDsim * nedsim, const char * text, int * x_size, int * y_size) +{ + int direction, ascent, descent; + XCharStruct overall; + XFontStruct * font = XLoadQueryFont(nedsim->dpy, nedsim->current_font); + XTextExtents(font, text, strlen(text), &direction, &ascent, &descent, &overall); + *x_size = overall.width; + *y_size = overall.ascent - overall.descent; +} + +// TODO: Explain +// TODO: Note that this might leave the foreground color changed. +// Argument coordinates are in 'cells', not pixels. +static void +draw_rect_area(struct NEDsim * nedsim, size_t x_origin, size_t y_origin, size_t x_size, size_t y_size, + Bool bord_top, Bool bord_bottom, Bool bord_left, Bool bord_right) +{ + x_origin *= nedsim->cell_size; + x_origin += nedsim->origin_x_offset; + y_origin *= nedsim->cell_size; + y_origin += nedsim->origin_y_offset; + x_size *= nedsim->cell_size; + y_size *= nedsim->cell_size; + XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, y_origin, x_size, y_size); + + set_color(nedsim, &color_list[nedsim->color_index].border); + if (bord_top) { + XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, y_origin, x_size, nedsim->border_size); + } + if (bord_bottom) { + XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, (y_origin + y_size - nedsim->border_size), x_size, nedsim->border_size); + } + if (bord_left) { + XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, y_origin, nedsim->border_size, y_size); + } + if (bord_right) { + XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, (x_origin + x_size - nedsim->border_size), y_origin, nedsim->border_size, y_size); + } +} + +// TODO: Explain +// Arguments are in units of 'cells', not pixels. +// Will leave foreground color changed. +// Draws filled circle with upper left corner at x,y. +static void +draw_circular_area(struct NEDsim * nedsim, size_t x, size_t y, double diameter) +{ + // First, convert the function argument units from 'cells' to 'pixels' + x *= nedsim->cell_size; + y *= nedsim->cell_size; + diameter *= nedsim->cell_size; + + // Add the panel's absolute x,y offset to the requested coordinates. + x += nedsim->origin_x_offset; + y += nedsim->origin_y_offset; + + // Shrink the circle to be a bit smaller than the bounding box allows. Note + // that the three adjustment values must sum to 1.0. + // For example, 0.7 + 0.15 + 0.15 = 1.0. + x += (0.15 * diameter); + y += (0.15 * diameter); + diameter *= 0.7; + + // Because we only draw the bottom border on repeated rows (e.g. draw_wordline()), + // we need to offset vertically by half a border height. + y -= (0.5 * nedsim->border_size); + + // Start angle 0 and ending angle 360*64 is one full circle in Xlib units. + XFillArc(nedsim->dpy, nedsim->win, nedsim->gc, x, y, diameter, diameter, 0, 360*64); +} + +// TODO: Explain +static void +draw_panel(struct NEDsim * nedsim) +{ +// TODO: Collect all relevant #defines somewhere. +#define OVERALL_WIDTH_IN_CELLS 70 +#define HEADER_HEIGHT_IN_CELLS 14 +#define FOOTER_HEIGHT_IN_CELLS 2 + + // Draw background color over entire window. + set_color(nedsim, &color_list[nedsim->color_index].panel_bg); + XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, 0, 0, nedsim->dpy_width, nedsim->dpy_height); + + // Draw NED panel in foreground color. + set_color(nedsim, &color_list[nedsim->color_index].panel_fg); + XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, + nedsim->origin_x_offset, + nedsim->origin_y_offset, + nedsim->cell_size * OVERALL_WIDTH_IN_CELLS, + nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + nedsim->num_data_rows + FOOTER_HEIGHT_IN_CELLS) + ); +} + +// TODO: Explain +static void +draw_logo(struct NEDsim * nedsim) +{ +#define LOGO_X_OFFSET 2 +#define LOGO_Y_OFFSET 2 +#define LOGO_WIDTH 20 +#define LOGO_NAME_HEIGHT 6 +#define LOGO_WEBSITE_HEIGHT 2 + + // First draw the two colored boxes that comprise the logo area. + set_color(nedsim, &color_list[nedsim->color_index].primary); + draw_rect_area(nedsim, LOGO_X_OFFSET, LOGO_Y_OFFSET, LOGO_WIDTH, LOGO_NAME_HEIGHT, True, True, False, False); + set_color(nedsim, &color_list[nedsim->color_index].tertiary); + draw_rect_area(nedsim, LOGO_X_OFFSET, LOGO_Y_OFFSET+LOGO_NAME_HEIGHT, LOGO_WIDTH, LOGO_WEBSITE_HEIGHT, False, True, False, False); + + // Now draw the 'NED' text in the top box. + set_color(nedsim, &color_list[nedsim->color_index].text); + set_font_size(nedsim, LOGO_NAME_HEIGHT); + int text_x_size, text_y_size; + get_text_size(nedsim, "NED", &text_x_size, &text_y_size); + int local_x_offset = ((LOGO_WIDTH * nedsim->cell_size) - text_x_size) / 2; + int local_y_offset = ((LOGO_NAME_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (LOGO_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), + ((LOGO_Y_OFFSET+LOGO_NAME_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "NED", 3); + + // And draw the 'subgeniuskitty.com' text in the bottom box. + set_font_size(nedsim, LOGO_WEBSITE_HEIGHT); + get_text_size(nedsim, "subgeniuskitty.com", &text_x_size, &text_y_size); + local_x_offset = ((LOGO_WIDTH * nedsim->cell_size) - text_x_size) / 2; + local_y_offset = ((LOGO_WEBSITE_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (LOGO_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), + ((LOGO_Y_OFFSET+LOGO_NAME_HEIGHT+LOGO_WEBSITE_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "subgeniuskitty.com", 18); +} + +// TODO: Explain +static void +draw_halt(struct NEDsim * nedsim) +{ +#define HALT_X_OFFSET 26 +#define HALT_Y_OFFSET 2 +#define HALT_WIDTH 6 +#define HALT_LIGHT_HEIGHT 6 +#define HALT_LABEL_HEIGHT 2 + + // First draw the two colored boxes that comprise the halt area. + set_color(nedsim, &color_list[nedsim->color_index].tertiary); + draw_rect_area(nedsim, HALT_X_OFFSET, HALT_Y_OFFSET, HALT_WIDTH, HALT_LIGHT_HEIGHT, True, True, False, False); + set_color(nedsim, &color_list[nedsim->color_index].secondary); + draw_rect_area(nedsim, HALT_X_OFFSET, HALT_Y_OFFSET+HALT_LIGHT_HEIGHT, HALT_WIDTH, HALT_LABEL_HEIGHT, False, True, False, False); + + // And finally, draw the label. + set_color(nedsim, &color_list[nedsim->color_index].text); + int text_x_size, text_y_size; + get_text_size(nedsim, "HALT", &text_x_size, &text_y_size); + int local_x_offset = ((HALT_WIDTH * nedsim->cell_size) - text_x_size) / 2; + int local_y_offset = ((HALT_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (HALT_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), + ((HALT_Y_OFFSET+HALT_LIGHT_HEIGHT+HALT_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "HALT", 4); +} + +// TODO: Explain +static void +draw_wordline_lights(struct NEDsim * nedsim, uint32_t word, int x, int y) +{ +#define WORDLINE_BITS_PER_STRIPE 4 +#define WORDLINE_WIDTH 32 +#define WORDLINE_HEIGHT 1 + + for (int i = 0; i < WORDLINE_WIDTH; i++) { + if (word & (1<<(WORDLINE_WIDTH-1-i))) { + set_color(nedsim, &color_list[nedsim->color_index].light_on); + } else { + set_color(nedsim, &color_list[nedsim->color_index].light_off); + } + draw_circular_area(nedsim, x+i, y, WORDLINE_HEIGHT); + } +} + +// TODO: Explain +static void +draw_wordline(struct NEDsim * nedsim, int x, int y) +{ + // First, draw a solid box in the primary color over the entire wordline area. + set_color(nedsim, &color_list[nedsim->color_index].primary); + draw_rect_area(nedsim, x, y, WORDLINE_WIDTH, WORDLINE_HEIGHT, False, True, False, False); + + // Now, draw stripes in the secondary color. + int i; + for (i = 0; i < (WORDLINE_WIDTH/(2*WORDLINE_BITS_PER_STRIPE)); i++) { + set_color(nedsim, &color_list[nedsim->color_index].secondary); + draw_rect_area(nedsim, (x+(i*(WORDLINE_WIDTH/WORDLINE_BITS_PER_STRIPE))), y, + WORDLINE_BITS_PER_STRIPE, WORDLINE_HEIGHT, False, True, False, False); + } + + // Finally, draw the lights. + draw_wordline_lights(nedsim, 0, x, y); +} + +// TODO: Explain +static void +draw_pc(struct NEDsim * nedsim) +{ +// TODO: Note that all #defines use units of 'cells', not 'pixels', etc. +#define PC_X_OFFSET 36 +#define PC_Y_OFFSET 7 +#define PC_WIDTH 32 +#define PC_LABEL_HEIGHT 2 +#define PC_LIGHT_HEIGHT 1 + + // First draw the two colored boxes that comprise the PC area. + set_color(nedsim, &color_list[nedsim->color_index].tertiary); + draw_rect_area(nedsim, PC_X_OFFSET, PC_Y_OFFSET, PC_WIDTH, PC_LABEL_HEIGHT, True, True, False, False); + draw_wordline(nedsim, PC_X_OFFSET, PC_Y_OFFSET+PC_LABEL_HEIGHT); + + // Now draw the label text "PC". + set_color(nedsim, &color_list[nedsim->color_index].text); + int text_x_size, text_y_size; + get_text_size(nedsim, "PC", &text_x_size, &text_y_size); + int local_x_offset = ((PC_WIDTH * nedsim->cell_size) - text_x_size) / 2; + int local_y_offset = ((PC_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (PC_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), + ((PC_Y_OFFSET+PC_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "PC", 2); +} + +// TODO: Explain +static void +draw_sc(struct NEDsim * nedsim) +{ +#define SC_X_OFFSET 42 +#define SC_Y_OFFSET 2 +#define SC_WIDTH 5 +#define SC_LABEL_HEIGHT 2 +#define SC_LIGHT_HEIGHT 1 + + // First draw the two colored boxes that comprise the SC area. + set_color(nedsim, &color_list[nedsim->color_index].secondary); + draw_rect_area(nedsim, SC_X_OFFSET, SC_Y_OFFSET, SC_WIDTH, SC_LABEL_HEIGHT, True, True, False, False); + set_color(nedsim, &color_list[nedsim->color_index].tertiary); + draw_rect_area(nedsim, SC_X_OFFSET, SC_Y_OFFSET+SC_LABEL_HEIGHT, SC_WIDTH, SC_LIGHT_HEIGHT, False, True, False, False); + + // Now draw the label text "SC". + set_color(nedsim, &color_list[nedsim->color_index].text); + int text_x_size, text_y_size; + get_text_size(nedsim, "SC", &text_x_size, &text_y_size); + int local_x_offset = ((SC_WIDTH * nedsim->cell_size) - text_x_size) / 2; + int local_y_offset = ((SC_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (SC_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), + ((SC_Y_OFFSET+SC_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "SC", 2); +} + +// TODO: Explain +static void +draw_psw(struct NEDsim * nedsim) +{ +#define PSW_N_X_OFFSET 51 +#define PSW_Z_X_OFFSET 58 +#define PSW_Y_OFFSET 2 +#define PSW_LABEL_WIDTH 3 +#define PSW_LABEL_HEIGHT 2 +#define PSW_LIGHT_WIDTH 1 +#define PSW_LIGHT_HEIGHT 1 + + // First draw the four colored boxes that comprise the two PSW areas. + set_color(nedsim, &color_list[nedsim->color_index].secondary); + draw_rect_area(nedsim, PSW_N_X_OFFSET, PSW_Y_OFFSET, PSW_LABEL_WIDTH, PSW_LABEL_HEIGHT, True, True, False, False); + set_color(nedsim, &color_list[nedsim->color_index].secondary); + draw_rect_area(nedsim, PSW_Z_X_OFFSET, PSW_Y_OFFSET, PSW_LABEL_WIDTH, PSW_LABEL_HEIGHT, True, True, False, False); + set_color(nedsim, &color_list[nedsim->color_index].tertiary); + draw_rect_area(nedsim, (PSW_N_X_OFFSET + 1), PSW_Y_OFFSET+PSW_LABEL_HEIGHT, PSW_LIGHT_WIDTH, PSW_LIGHT_HEIGHT, False, True, False, False); + set_color(nedsim, &color_list[nedsim->color_index].tertiary); + draw_rect_area(nedsim, (PSW_Z_X_OFFSET + 1), PSW_Y_OFFSET+PSW_LABEL_HEIGHT, PSW_LIGHT_WIDTH, PSW_LIGHT_HEIGHT, False, True, False, False); + + // Now draw the label text "N". + set_color(nedsim, &color_list[nedsim->color_index].text); + int text_x_size, text_y_size; + get_text_size(nedsim, "N", &text_x_size, &text_y_size); + int local_x_offset = ((PSW_LABEL_WIDTH * nedsim->cell_size) - text_x_size) / 2; + int local_y_offset = ((PSW_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (PSW_N_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), + ((PSW_Y_OFFSET+PSW_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "N", 1); + + // Now draw the label text "Z". + set_color(nedsim, &color_list[nedsim->color_index].text); + get_text_size(nedsim, "Z", &text_x_size, &text_y_size); + local_x_offset = ((PSW_LABEL_WIDTH * nedsim->cell_size) - text_x_size) / 2; + local_y_offset = ((PSW_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (PSW_Z_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), + ((PSW_Y_OFFSET+PSW_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "Z", 1); +} + +// TODO: Explain +static void +draw_stack(struct NEDsim * nedsim) +{ +#define STACK_X_OFFSET 2 +#define STACK_Y_OFFSET 12 +#define STACK_WIDTH 32 +#define STACK_LABEL_HEIGHT 2 +#define STACK_LIGHT_HEIGHT 1 + + // First draw the two colored boxes that comprise the stack area. + set_color(nedsim, &color_list[nedsim->color_index].tertiary); + draw_rect_area(nedsim, STACK_X_OFFSET, STACK_Y_OFFSET, STACK_WIDTH, STACK_LABEL_HEIGHT, True, True, False, False); + for (int i = 0; i < nedsim->num_data_rows; i++) { + draw_wordline(nedsim, STACK_X_OFFSET, STACK_Y_OFFSET+STACK_LABEL_HEIGHT+i); + } + + // Now draw the label text "Stack Size:". + set_color(nedsim, &color_list[nedsim->color_index].text); + int text_x_size, text_y_size; + get_text_size(nedsim, "Stack Size:", &text_x_size, &text_y_size); + int local_y_offset = ((STACK_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((STACK_X_OFFSET + 1) * nedsim->cell_size + nedsim->origin_x_offset), + ((STACK_Y_OFFSET+STACK_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "Stack Size:", 11); +} + +// TODO: Explain +static void +draw_heap(struct NEDsim * nedsim) +{ +#define HEAP_X_OFFSET 36 +#define HEAP_Y_OFFSET 12 +#define HEAP_WIDTH 32 +#define HEAP_LABEL_HEIGHT 2 +#define HEAP_LIGHT_HEIGHT 1 +// TODO: What should I do about this define? I would like to be able to specify the address so I can do things like display the code itself if nothign interesting happens in RAM. +#define HEAP_START_ADDRESS 0x20000000 + + // First draw the two colored boxes that comprise the heap area. + set_color(nedsim, &color_list[nedsim->color_index].tertiary); + draw_rect_area(nedsim, HEAP_X_OFFSET, HEAP_Y_OFFSET, HEAP_WIDTH, HEAP_LABEL_HEIGHT, True, True, False, False); + for (int i = 0; i < nedsim->num_data_rows; i++) { + draw_wordline(nedsim, HEAP_X_OFFSET, HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT+i); + } + + // Now draw the label text "RAM Base:". + set_color(nedsim, &color_list[nedsim->color_index].text); + int text_x_size, text_y_size; + get_text_size(nedsim, "RAM Base:", &text_x_size, &text_y_size); + int local_y_offset = ((HEAP_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((HEAP_X_OFFSET + 1) * nedsim->cell_size + nedsim->origin_x_offset), + ((HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "RAM Base:", 9); + + // Now draw the address text. + set_color(nedsim, &color_list[nedsim->color_index].text); + char address[11]; + snprintf(address, sizeof(address), "0x%08X", HEAP_START_ADDRESS); + get_text_size(nedsim, address, &text_x_size, &text_y_size); + local_y_offset = ((HEAP_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((HEAP_X_OFFSET + 1 + (HEAP_WIDTH / 2)) * nedsim->cell_size + nedsim->origin_x_offset), + ((HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), address, strlen(address)); +} + +// TODO: Explain +static void +update_display(struct NEDsim * nedsim) +{ + // Draw the halt indicator. + if (nedsim->nedstate->halted) { + set_color(nedsim, &color_list[nedsim->color_index].error_on); + } else { + set_color(nedsim, &color_list[nedsim->color_index].error_off); + } + draw_circular_area(nedsim, HALT_X_OFFSET, HALT_Y_OFFSET, HALT_WIDTH); + + // Draw the PSW "N" light. + if (nedsim->nedstate->active_thread->psw->negative) { + set_color(nedsim, &color_list[nedsim->color_index].light_on); + } else { + set_color(nedsim, &color_list[nedsim->color_index].light_off); + } + draw_circular_area(nedsim, PSW_N_X_OFFSET+1, PSW_Y_OFFSET+PSW_LABEL_HEIGHT, PSW_LIGHT_HEIGHT); + + // Draw the PSW "Z" light. + if (nedsim->nedstate->active_thread->psw->zero) { + set_color(nedsim, &color_list[nedsim->color_index].light_on); + } else { + set_color(nedsim, &color_list[nedsim->color_index].light_off); + } + draw_circular_area(nedsim, PSW_Z_X_OFFSET+1, PSW_Y_OFFSET+PSW_LABEL_HEIGHT, PSW_LIGHT_HEIGHT); + + + // Draw the SC. + int i; + for (i = 0; i < SC_WIDTH; i++) { + if ((SC_WIDTH-1-i) == nedsim->nedstate->active_thread->sc) { + set_color(nedsim, &color_list[nedsim->color_index].light_on); + } else { + set_color(nedsim, &color_list[nedsim->color_index].light_off); + } + draw_circular_area(nedsim, SC_X_OFFSET+i, SC_Y_OFFSET+SC_LABEL_HEIGHT, SC_LIGHT_HEIGHT); + } + + // Draw the PC. + draw_wordline_lights(nedsim, nedsim->nedstate->active_thread->pc, PC_X_OFFSET, PC_Y_OFFSET+PC_LABEL_HEIGHT); + + // Draw the stack lights. + int64_t top_of_stack = ((int64_t)nedsim->nedstate->active_thread->sp) - 1; + for (i = 0; i < nedsim->num_data_rows; i++) { + if ((top_of_stack-i) >= 0) { + draw_wordline_lights(nedsim, nedsim->nedstate->active_thread->stack[top_of_stack-i], STACK_X_OFFSET, STACK_Y_OFFSET+STACK_LABEL_HEIGHT+i); + } else { + draw_wordline_lights(nedsim, 0, STACK_X_OFFSET, STACK_Y_OFFSET+STACK_LABEL_HEIGHT+i); + } + } + + // Draw the stack size in text. + set_color(nedsim, &color_list[nedsim->color_index].tertiary); + draw_rect_area(nedsim, STACK_X_OFFSET+(STACK_WIDTH/2), STACK_Y_OFFSET, STACK_WIDTH/2, STACK_LABEL_HEIGHT, True, True, False, False); + set_color(nedsim, &color_list[nedsim->color_index].text); + char stack_size[11]; + snprintf(stack_size, sizeof(stack_size), "0x%08X", nedsim->nedstate->active_thread->sp); + int text_x_size, text_y_size; + get_text_size(nedsim, stack_size, &text_x_size, &text_y_size); + int local_y_offset = ((STACK_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; + XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((STACK_X_OFFSET + 1 + (STACK_WIDTH / 2)) * nedsim->cell_size + nedsim->origin_x_offset), + ((STACK_Y_OFFSET+STACK_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), stack_size, strlen(stack_size)); + + // Draw the heap lights. + for (i = 0; i < nedsim->num_data_rows; i++) { + draw_wordline_lights(nedsim, ram_r_word(nedsim->nedstate, HEAP_START_ADDRESS+(i*BPW)), HEAP_X_OFFSET, HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT+i); + } +} + +/* -------------------------------------------------------------------------- */ +/* Screenhack API Functions */ +/* -------------------------------------------------------------------------- */ + +static Bool +NEDsim_event(Display * dpy, Window win, void * closure, XEvent * event) +{ + return False; +} + +static void +NEDsim_free(Display * dpy, Window win, void * closure) +{ + // TODO: Replace all this with proper code to free everything. + struct NEDsim * nedsim = closure; + XFreeGC(nedsim->dpy, nedsim->gc); + free(nedsim); +} + +static void * +NEDsim_init(Display * dpy, Window win) +{ + struct NEDsim * nedsim; + XGCValues gcv; + XWindowAttributes xgwa; + + nedsim = calloc(1, sizeof(*nedsim)); + if (!nedsim) { + fprintf(stderr, "ERROR: Failed to calloc() for NEDsim struct in NEDsim_init().\n"); + exit(EXIT_FAILURE); + } + + nedsim->dpy = dpy; + nedsim->win = win; + + XGetWindowAttributes(nedsim->dpy, nedsim->win, &xgwa); + nedsim->dpy_width = xgwa.width; + nedsim->dpy_height = xgwa.height; + + // TODO: Explain that this is the delay between each clock cycle of the simulated NED CPU. + nedsim->delay = get_integer_resource(nedsim->dpy, "delay", "Integer"); + nedsim->delay *= 1000; /* Turn milliseconds into microseconds. */ + + // TODO: Read in the a.out file. This should be done in the simulator's init function call? + nedsim->nedstate = init_simulator(); + + nedsim->gc = XCreateGC(nedsim->dpy, nedsim->win, GCForeground, &gcv); + + // TODO: Do this properly. + nedsim->color_index = 0; + +// TODO: Save the GIMP reference diagram somewhere, along with notes about the size/spacing, and that each cell is 10 pixels across in the reference image. + + nedsim->cell_size = nedsim->dpy_width / OVERALL_WIDTH_IN_CELLS ; // make panel as wide as it can be while keeping every cell an integer pixel size. + // TODO: What is my minimum cell_size? Below that, I should simply paint the window red and print an error in the console. Perform that check here, right after setting cell_size. + // For now, we'll just make it 10 pixels? + if (nedsim->cell_size < 10) { + nedsim->suitable_display = False; + return nedsim; + } + nedsim->origin_x_offset = (nedsim->dpy_width - (nedsim->cell_size * OVERALL_WIDTH_IN_CELLS)) / 2; // center panel horizontally + + // Determine how many rows for the stack and heap displays. Make it the largest power of two that fits on the display. + int available_space_for_data_rows = nedsim->dpy_height - (nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + FOOTER_HEIGHT_IN_CELLS)); + for (int i = 0; ; i++) { + if ((nedsim->cell_size * (1 << i)) > available_space_for_data_rows) { + nedsim->num_data_rows = (1 << --i); + break; + } + } + if (nedsim->num_data_rows < 4) { + nedsim->suitable_display = False; + return nedsim; + } + nedsim->origin_y_offset = (nedsim->dpy_height - (nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + nedsim->num_data_rows + FOOTER_HEIGHT_IN_CELLS))) / 2; // center panel vertically + + // Scale border relative to cell_size in a 1:10 relationship. + nedsim->border_size = nedsim->cell_size / 10; + + draw_panel(nedsim); + draw_logo(nedsim); + draw_halt(nedsim); + draw_pc(nedsim); + draw_sc(nedsim); + draw_psw(nedsim); + draw_stack(nedsim); + draw_heap(nedsim); + + nedsim->suitable_display = True; + + return nedsim; +} + +static unsigned long +NEDsim_draw(Display * dpy, Window win, void * closure) +{ + struct NEDsim * nedsim = closure; + + if (nedsim->suitable_display) { + nedsim->nedstate = run_simulator(nedsim->nedstate); + update_display(nedsim); + } else { + set_color(nedsim, &color_list[nedsim->color_index].error_on); + XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, 0, 0, nedsim->dpy_width, nedsim->dpy_height); + } + + return nedsim->delay; +} + +static void +NEDsim_reshape(Display * dpy, Window win, void * closure, unsigned int w, unsigned int h) +{ + struct NEDsim * nedsim = closure; + XWindowAttributes xgwa; + XGetWindowAttributes(nedsim->dpy, nedsim->win, &xgwa); + /* Only restart the simulation if the window changed size. */ + if (nedsim->dpy_width != xgwa.width || nedsim->dpy_height != xgwa.height) { + NEDsim_free(dpy, win, closure); + closure = NEDsim_init(dpy, win); + } +} + +static const char * NEDsim_defaults[] = { + "*delay: 250", + 0 +}; + +static XrmOptionDescRec NEDsim_options[] = { + { "-delay", ".delay", XrmoptionSepArg, 0 }, + { 0, 0, 0, 0 } +}; + +XSCREENSAVER_MODULE ("Blinken-lights simulator for NED1 CPU architecture.", NEDsim) diff --git a/hacks/NEDsim/a.out.h b/hacks/NEDsim/a.out.h new file mode 100644 index 0000000..d3b721e --- /dev/null +++ b/hacks/NEDsim/a.out.h @@ -0,0 +1,385 @@ +/* + * © 2018 Aaron Taylor + * See LICENSE.txt file for copyright and license details. + * + * Some parts of this file were imported from other projects. + * See `ned/misc/licenses/` and git history for details. + */ + +#include + +#ifndef NED_A_OUT_H +#define NED_A_OUT_H + +/* + * A binary file consists of up to 7 sections. In order, these sections are: + * + * exec header + * + * Contains parameters used by the kernel to load a binary file into + * memory and execute it, and by the link editor to combine a binary + * file with other binary files. This section is the only mandatory + * one. + * + * text segment + * + * Contains machine code and related data that are loaded into memory + * when a program executes. May be loaded read-only. + * + * data segment + * + * Contains initialized data; always loaded into writable memory. + * + * text relocations + * + * Contains records used by the link editor to update pointers in the + * text segment when combining binary files. + * + * data relocations + * + * Like the text relocation section, but for data segment pointers. + * + * symbol table + * + * Contains records used by the link editor to cross reference the + * addresses of named variables and functions (`symbols') between + * binary files. + * + * string table + * + * Contains the character strings corresponding to the symbol names. + * + * Every binary file begins with an exec structure: + */ + +struct exec { + uint32_t a_midmag; /* flags<<26 | mid<<16 | magic */ + uint32_t a_text; /* text segment size */ + uint32_t a_data; /* initialized data size */ + uint32_t a_bss; /* uninitialized data size */ + uint32_t a_syms; /* symbol table size */ + uint32_t a_entry; /* entry point */ + uint32_t a_trsize; /* text relocation size */ + uint32_t a_drsize; /* data relocation size */ +}; + +/* + * The exec fields have the following functions: + * + * a_midmag + * + * This field is stored in host byte-order. It has a number of + * sub-components accessed by the macros N_GETFLAG(), N_GETMID(), and + * N_GETMAGIC(), and set by the macro N_SETMAGIC(). + * + * The macro N_GETFLAG() returns a few flags: + * + * EX_DYNAMIC + * + * indicates that the executable requires the services of the + * run-time link editor. + * + * EX_PIC + * + * indicates that the object contains position independent code. + * + * If both EX_DYNAMIC and EX_PIC are set, the object file is a position + * independent executable image (e.g. a shared library), which is to be + * loaded into the process address space by the run-time link editor. + * + * The macro N_GETMID() returns the machine-id. This indicates which + * machine(s) the binary is intended to run on. + * + * N_GETMAGIC() specifies the magic number, which uniquely identifies + * binary files and distinguishes different loading conventions. The field + * must contain one of the following values: + * + * NED_MAGIC1 + * + * The text and data segments immediately follow the header and + * are contiguous. Both text and data segments are loaded into + * writable memory. + * + * a_text + * + * Contains the size of the text segment in bytes. + * + * a_data + * + * Contains the size of the data segment in bytes. + * + * a_bss + * + * Contains the size of the bss segment in bytes. + * + * a_syms + * + * Contains the size of the symbol table segment in bytes. + * + * a_entry + * + * Contains the address in memory of the entry point of the program. + * + * a_trsize + * + * Contains the size in bytes of the text relocation table. + * + * a_drsize + * + * Contains the size in bytes of the data relocation table. + * + * The include file defines several macros which use an exec + * structure to test consistency or to locate section offsets in the binary + * file. + * + * N_BADMAG(exec) + * + * Nonzero if the a_magic field does not contain a recognized value. + * + * N_TXTOFF(exec) + * + * The byte offset in the binary file of the beginning of the text + * segment. + * + * N_SYMOFF(exec) + * + * The byte offset of the beginning of the symbol table. + * + * N_STROFF(exec) + * + * The byte offset of the beginning of the string table. + */ + +#define N_GETMAGIC(ex) ((ex).a_midmag & 0xffff) +#define N_GETMID(ex) (((ex).a_midmag >> 16) & 0x03ff) +#define N_GETFLAG(ex) (((ex).a_midmag >> 26) & 0x3f) +#define N_SETMAGIC(ex,mag,mid,flag) \ + ((ex).a_midmag = (((flag) & 0x3f) <<26) | (((mid) & 0x03ff) << 16) | ((mag) & 0xffff)) + +#define N_BADMAG(ex) (N_GETMAGIC(ex) != NED_MAGIC1) + +#define N_TXTOFF(ex) (sizeof(struct exec)) +#define N_DATOFF(ex) (N_TXTOFF(ex) + (ex).a_text) +#define N_RELOFF(ex) (N_DATOFF(ex) + (ex).a_data) +#define N_SYMOFF(ex) (N_RELOFF(ex) + (ex).a_trsize + (ex).a_drsize) +#define N_STROFF(ex) (N_SYMOFF(ex) + (ex).a_syms) + +/* There doesn't appear to be any pattern to magic number assignments. */ +/* See: /usr/src/contrib/file/magic/Magdir/aout */ +#define NED_MAGIC1 0x107 + +/* There doesn't seem to be any pattern to Machine ID number assignments. */ +/* For now, I'm using the sum of the ASCII values for "NED". */ +#define MID_NED 0xD7 /* NED binary */ + +#define EX_PIC 0x10 /* contains position independent code */ +#define EX_DYNAMIC 0x20 /* contains run-time link-edit info */ +#define EX_DPMASK 0x30 /* mask for the above */ + +/* + * Relocation records have a standard format which is described by the + * relocation_info structure: + */ + +struct relocation_info { + uint32_t r_address; /* offset in text or data segment */ + uint32_t r_symbolnum : 24, /* ordinal number of add symbol */ + r_pcrel : 1, /* 1 if value should be pc-relative */ + r_length : 2, /* log base 2 of value's width */ + r_extern : 1, /* 1 if need to add symbol to value */ + r_baserel : 1, /* linkage table relative */ + r_jmptable : 1, /* relocate to jump table */ + r_relative : 1, /* load address relative */ + r_copy : 1; /* run time copy */ +}; + +/* + * The relocation_info fields are used as follows: + * + * r_address + * + * Contains the byte offset of a pointer that needs to be link-edited. + * Text relocation offsets are reckoned from the start of the text + * segment, and data relocation offsets from the start of the data + * segment. The link editor adds the value that is already stored at this + * offset into the new value that it computes using this relocation + * record. + * + * r_symbolnum + * + * Contains the ordinal number of a symbol structure in the symbol table + * (it is not a byte offset). After the link editor resolves the absolute + * address for this symbol, it adds that address to the pointer that is + * under going relocation. (If the r_extern bit is clear, the situation is + * different; see below.) + * + * r_pcrel + * + * If this is set, the link editor assumes that it is updating a pointer + * that is part of a machine code instruction using pc-relative + * addressing. The address of the relocated pointer is implicitly added to + * its value when the running program uses it. + * + * r_length + * + * Contains the log base2 of the length of the pointer in bytes; 0 for + * 1-byte displacements, 1 for 2-byte displacements, 2 for 4-byte + * displacements. + * + * r_extern + * + * Set if this relocation requires an external reference; the link editor + * must use a symbol address to update the pointer. When the r_extern bit + * is clear, the relocation is `local'; the link editor updates the + * pointer to reflect changes in the load addresses of the various + * segments, rather than changes in the value of a symbol (except when + * r_baserel is also set (see below). In this case, the content of the + * r_symbolnum field is an n_type value (see below); this type field tells + * the link editor what segment the relocated pointer points into. + * + * r_baserel + * + * If set, the symbol, as identified by the r_symbolnum field, is to be + * relocated to an offset into the Global Offset Table. At runtime, the + * entry in the Global Offset Table at this offset is set to be the + * address of the symbol. + * + * r_jmptable + * + * If set, the symbol, as identified by the r_symbolnum field, is to be + * relocated to an offset into the Procedure Linkage Table. + * + * r_relative + * + * If set, this relocation is relative to the (run-time) load address of + * the image this object file is going to be a part of. This type of + * relocation only occurs in shared objects. + * + * r_copy + * + * If set, this relocation record identifies a symbol whose contents + * should be copied to the location given in r_address. The copying is + * done by the runtime link-editor from a suitable data item in a shared + * object. + * + * Symbols map names to addresses (or more generally, strings to values). + * Since the link-editor adjusts addresses, a symbol's name must be used to + * stand for its address until an absolute value has been assigned. Symbols + * consist of a fixed-length record in the symbol table and a variable-length + * name in the string table. The symbol table is an array of nlist structures: + */ + +struct nlist { + union { + char * n_name; + uint32_t n_strx; + } n_un; + uint32_t n_type; + uint32_t n_other; + uint32_t n_desc; + uint32_t n_value; +}; + +/* + * The fields are used as follows: + * + * n_un.n_strx + * + * Contains a byte offset into the string table for the name of this + * symbol. + * + * n_un.n_name + * + * Used by the runtime link editor. Contains a pointer to the string in + * memory. + * + * n_type + * + * Used by the link editor to determine how to update the symbol's value. + * The n_type field is broken down into three sub-fields using bitmasks. + * The link editor treats symbols with the N_EXT type bit set as + * `external' symbols and permits references to them from other binary + * files. The N_TYPE mask selects bits of interest to the link editor: + * + * N_UNDF + * + * An undefined symbol. The link editor must locate an external + * symbol with the same name in another binary file to determine + * the absolute value of this symbol. As a special case, if the + * n_value field is nonzero and no binary file in the link-edit + * defines this symbol, the link-editor will resolve this symbol + * to an address in the bss segment, reserving an amount of bytes + * equal to n_value. If this symbol is undefined in more than one + * binary file and the binary files do not agree on the size, the + * link editor chooses the greatest size found across all + * binaries. + * + * N_ABS + * + * An absolute symbol. The link editor does not update an absolute + * symbol. + * + * N_TEXT + * + * A text symbol. This symbol's value is a text address and the + * link editor will update it when it merges binary files. + * + * N_DATA + * + * A data symbol; similar to N_TEXT but for data addresses. The + * values for text and data symbols are not file offsets but + * addresses; to recover the file offsets, it is necessary to + * identify the loaded address of the beginning of the + * corresponding section and subtract it, then add the offset of + * the section. + * + * N_BSS + * + * A bss symbol; like text or data symbols but has no + * corresponding offset in the binary file. + * + * The N_STAB mask selects bits of interest to symbolic debuggers. + * + * n_other + * + * This field provides information on the nature of the symbol independent + * of the symbol's location in terms of segments as determined by the + * n_type field. Currently, the lower 4 bits of the n_other field hold one + * of two values: AUX_FUNC and AUX_OBJECT. AUX_FUNC associates the symbol + * with a callable function, while AUX_OBJECT associates the symbol with + * data, irrespective of their locations in either the text or the data + * segment. + * + * n_desc + * + * Reserved for use by debuggers; passed untouched by the link editor. + * Different debuggers use this field for different purposes. + * + * n_value + * + * Contains the value of the symbol. For text, data and bss symbols, this + * is an address; for other symbols (such as debugger symbols), the value + * may be arbitrary. + * + * The string table consists of a 32-bit length followed by null-terminated + * symbol strings. The length represents the sizeof the entire table in bytes, + * so its minimum value (or the offset of the first string) is always 4 on + * 32-bit machines. + */ + +/* Used in nlist.n_type. */ +#define N_UNDF 0x00 /* undefined */ +#define N_ABS 0x02 /* absolute address */ +#define N_TEXT 0x04 /* text segment */ +#define N_DATA 0x08 /* data segment */ +#define N_BSS 0x10 /* bss segment */ + +#define N_EXT 0x01 /* external (global) bit, OR'ed in */ +#define N_TYPE 0xff /* mask for all the type bits */ +#define N_STAB 0xffffff00 /* mask for debugger symbols -- stab(5) */ + +/* Used in nlist.n_other */ +#define AUX_FUNC 1 /* Function */ +#define AUX_OBJECT 2 /* Data */ + +#endif diff --git a/hacks/NEDsim/simulator.c b/hacks/NEDsim/simulator.c new file mode 100644 index 0000000..4857946 --- /dev/null +++ b/hacks/NEDsim/simulator.c @@ -0,0 +1,515 @@ +/* (c) 2021 Aaron Taylor */ +/* See LICENSE.txt file for copyright and license details. */ + +/* -------------------------------------------------------------------------- */ +/* NED1 Simulator */ +/* -------------------------------------------------------------------------- */ + +// TODO: Make a bunch of functions private in this file. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "a.out.h" +#include "simulator.h" + +int +is_stdin_nonempty(void) +{ + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(STDIN_FILENO, &read_fds); + + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + + int retval = select(1, &read_fds, NULL, NULL, &timeout); + + if (retval == -1) { + /* TODO: How do I want to handle this error? */ + } + + return retval; +} + +uint32_t +generate_binary_psw(struct NEDstate * state) +{ + uint32_t psw = 0; + if (state->active_thread->psw->zero) psw |= 0b1; + if (state->active_thread->psw->negative) psw |= 0b10; + return psw; +} + +void +ram_w_byte(struct NEDstate * state, uint32_t address, uint8_t data) +{ + state->ram[address] = data; +} + +uint8_t +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. */ + +void +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 */ + printf("%c", data); + fflush(stdout); + } 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); + } + } +} + +uint32_t +ram_r_word(struct NEDstate * state, uint32_t address) +{ + if (address == 0x0) { /* Zero register */ + return 0b0; + } else if (address == 0x4) { /* 0x80000000 register */ + return 0x80000000; + } 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. */ + return 0b1; + } else if (address == 0x8000008) { /* SLU: RBUF */ + if (is_stdin_nonempty()) { + return getchar(); + } else { + return (uint8_t)rand(); + } + } else if (address == 0x800000C) { /* SLU: RCSR */ + if (is_stdin_nonempty()) { + return 0b1; + } else { + return 0b0; + } + } else if (address >= 0x20000000) { /* RAM */ + uint32_t word = 0; + for (int i=0; i<4; i++) word |= (ram_r_byte(state,address+i)) << (8*(3-i)); + return word; + } + return 0b0; +} + +uint32_t +fetch_instruction_word(struct NEDstate * state) +{ + uint32_t word = ram_r_word(state, state->active_thread->pc); + state->active_thread->pc += BPW; + return word; +} + +void +stack_w(struct NEDthread * thread, uint32_t value, uint8_t offset) +{ + thread->stack[thread->sp - (offset + 1)] = value; +} + +uint32_t +stack_r(struct NEDthread * thread, uint8_t offset) +{ + return thread->stack[thread->sp - (offset + 1)]; +} + +void +stack_push(struct NEDthread * thread, uint32_t value) +{ + thread->stack[thread->sp++] = value; +} + +uint32_t +stack_pop(struct NEDthread * thread) +{ + return thread->stack[--thread->sp]; +} + +void +set_psw_flags(uint32_t word, struct NEDstate * state) +{ + if (word == 0) { + state->active_thread->psw->zero = true; + } else { + state->active_thread->psw->zero = false; + } + if (word & 0x80000000) { + state->active_thread->psw->negative = true; + } else { + state->active_thread->psw->negative = false; + } +} + +void +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); +} + +void +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); +} + +void +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); +} + +void +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); +} + +void +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); +} + +void +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]; + } else { + printf("ERROR: Attempted MVSTCK to ID higher than THREAD_COUNT.\n"); + state->halted = true; + } +} + +void +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))); + } else { + stack_push(state->active_thread, (word >> shift_by)); + } + set_psw_flags(stack_r(state->active_thread,0), state); +} + +void +ned_instruction_test(struct NEDstate * state) +{ + uint32_t word = stack_pop(state->active_thread); + set_psw_flags(word, state); +} + +void +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. +} + +void +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); +} + +void +ned_instruction_brz(struct NEDstate * state) +{ + uint32_t new_pc = stack_pop(state->active_thread); + uint32_t test_word = stack_pop(state->active_thread); + if (test_word == 0) { + state->active_thread->pc = new_pc; + // The SC is caught and reset by the main loop since the PC changed. + } +} + +void +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); +} + +void +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); +} + +void +ned_instruction_halt(struct NEDstate * state) +{ + printf("Halting.\n"); + state->halted = true; +} + +void +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); + } else { /* STSP+x */ + stack_w(state->active_thread,stack_pop(state->active_thread),(syllable & 0b111)); + } + } else { + switch (syllable) { + 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; + default: + printf("ERROR: Attempted to execute illegal syllable: 0o%o\n", syllable); + state->halted = true; + break; + } + } +} + +uint8_t +extract_syllable_from_word(uint32_t word, uint8_t index) +{ + uint32_t mask = 0b111111 << 6*(4-index); + return (word & mask) >> 6*(4-index); +} + +void +parse_aout_file(FILE * input, struct exec * aout_exec, uint8_t * text_segment, + struct nlist ** symbol_table, uint32_t * symbol_count) +{ + uint32_t read_count = 0; + + /* Read in and check the a.out header. */ + for (uint32_t i=0; i<8; i++) { + switch (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; + } + if (read_count != 1) { + fprintf(stderr, "ERROR: Invalid a.out header.\n"); + exit(EXIT_FAILURE); + } + } + if (N_BADMAG(*aout_exec)) { + fprintf(stderr, "ERROR: Invalid magic number in a.out header.\n"); + exit(EXIT_FAILURE); + } else if (N_GETMID(*aout_exec) != MID_NED) { + fprintf(stderr, "ERROR: Executable not intended for NED Machine ID.\n"); + exit(EXIT_FAILURE); + } + + /* 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"); + exit(EXIT_FAILURE); + } + + /* Correct the byte order. */ + for (uint32_t i=0; i < (text_segment_size / 4); i++) { + uint8_t temp_word[4]; + 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++) { + switch (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; + } + if (read_count != 1) { + fprintf(stderr, "ERROR: Unable to read entire symbol table.\n"); + exit(EXIT_FAILURE); + } + } + } + + /* 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); + if (read_count != 1) { + fprintf(stderr, "ERROR: Failed to read string table size.\n"); + exit(EXIT_FAILURE); + } + for (uint32_t i=0; i < *symbol_count; i++) { + uint32_t len = 0; + if (i < ((*symbol_count)-1)) { + len = ((*symbol_table)[i+1].n_un.n_strx - (*symbol_table)[i].n_un.n_strx); + } else { + 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); + if (read_count != len) { + fprintf(stderr, "ERROR: Failed to read a string from the string table.\n"); + exit(EXIT_FAILURE); + } + } + +} + +struct NEDstate * +init_simulator(void) +{ + 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->halted = false; + 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 exec aout_exec; + struct nlist * symbol_table; + uint32_t symbol_count; + FILE * input = NULL; + if ((input = fopen(AOUT_PATH, "r")) == NULL) { + fprintf(stderr, "ERROR: %s: %s\n", AOUT_PATH, strerror(errno)); + state->halted = true; + } + parse_aout_file(input, &aout_exec, &(state->ram[address]), &symbol_table, &symbol_count); + fclose(input); + + return state; +} + +struct NEDstate * +run_simulator(struct NEDstate * state) +{ + if (state->halted) return state; + + /* Fetch instruction word. */ + uint32_t iw; + if (state->hack->resume_word) { + iw = state->hack->iw; + } else { + 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; + } else { + // More syllables remain to be executed in this instruction word. + state->hack->resume_word = true; + state->hack->iw = iw; + } + } else { + state->halted = true; + fprintf(stderr, "WARNING: Halting due to attempted execution of illegal instruction.\n"); + } + + return state; +} diff --git a/hacks/NEDsim/simulator.h b/hacks/NEDsim/simulator.h new file mode 100644 index 0000000..927f73b --- /dev/null +++ b/hacks/NEDsim/simulator.h @@ -0,0 +1,92 @@ +/* (c) 2021 Aaron Taylor */ +/* See LICENSE.txt file for copyright and license details. */ + +// TODO: Prune includes in both this header and the corresponding source file. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "./a.out.h" + +// TODO: Can get rid of this since I don't do a show_usage() anymore? +#define VERSION 5 + +/* Bytes per word. */ +#define BPW 4 + +/* Number of stack words. */ +#define STACK_LENGTH 1048576 + +/* Number of bytes of RAM. */ +#define RAM_LENGTH 1073741824 + +/* Number of hardware threads. */ +#define THREAD_COUNT 8 + +/* Number of syllables per word. */ +#define SPW 5 + +struct NEDpsw { + bool zero; + bool negative; +}; + +struct NEDthread { + uint32_t stack[STACK_LENGTH]; + uint32_t sp; + uint32_t pc; + uint8_t sc; + struct NEDpsw * psw; +}; + +struct NEDhack { + uint32_t iw; + bool resume_word; +}; + +// TODO: Make this a single thread before committing. Multi-thread is broken with my current main loop. +struct NEDstate { + bool halted; + uint8_t ram[RAM_LENGTH]; + struct NEDthread * thread[THREAD_COUNT]; + struct NEDthread * active_thread; + struct NEDhack * hack; +}; + +enum syllables { + MVSTCK = 0b00001111, + JMP = 0b00001110, + SWAP = 0b00001101, + ADD = 0b00001100, + XOR = 0b00001011, + NOT = 0b00001010, + OR = 0b00001001, + AND = 0b00001000, + BRZ = 0b00000111, + TEST = 0b00000110, + CMPSWP = 0b00000101, + SHIFT = 0b00000100, + STORE = 0b00000011, + LOAD = 0b00000010, + NOP = 0b00000001, + HALT = 0b00000000 +}; + +struct NEDstate * init_simulator(void); +struct NEDstate * run_simulator(struct NEDstate * state); +uint32_t ram_r_word(struct NEDstate * state, uint32_t address); + -- 2.20.1