Commit | Line | Data |
---|---|---|
84b74595 AT |
1 | /* (c) 2021 Aaron Taylor <ataylor at subgeniuskitty dot com> */ |
2 | /* See LICENSE.txt file for copyright and license details. */ | |
3 | ||
4 | // TODO: | |
5 | // - Write a brief description of the machine being simulated. Only one thread, reduced RAM, no meaningful console when running as screensaver, etc. | |
6 | ||
7 | // CLI Flags: | |
8 | // -path-to-aout-binary | |
9 | // -path-to-font-file | |
10 | // -speed | |
11 | ||
12 | // Ideas for sample programs to include: | |
13 | // - Build list of integers on the stack (DUP, IM_1, ADD). | |
14 | // - Calculate prime numbers, placing them on the stack. | |
15 | // - Lights sliding back and forth, like PDP-11 front panel with some OSes. | |
16 | ||
17 | #include "screenhack.h" | |
18 | #include "simulator.h" | |
19 | ||
20 | /* Keep this source code C89 compliant per XScreensaver's instructions. */ | |
21 | ||
22 | /* -------------------------------------------------------------------------- */ | |
23 | /* Data Structures */ | |
24 | /* -------------------------------------------------------------------------- */ | |
25 | ||
26 | struct NEDsim { | |
27 | /* Various X resources */ | |
28 | Display * dpy; | |
29 | Window win; | |
30 | GC gc; | |
31 | ||
32 | // TODO: Explain these | |
33 | int dpy_width, dpy_height; | |
34 | ||
35 | // TODO: Explain that this is created during init, then lights are populated/overwritten on each frame. | |
36 | Pixmap panel; | |
37 | ||
38 | /* Delay (in microseconds) between clock cycles in the NED CPU. */ | |
39 | size_t delay; | |
40 | ||
41 | int cell_size; | |
42 | int border_size; | |
43 | int origin_x_offset, origin_y_offset; | |
44 | int num_data_rows; | |
45 | ||
46 | size_t color_index; | |
47 | ||
48 | Bool suitable_display; | |
49 | ||
50 | char * current_font; | |
51 | ||
52 | // TODO: Explain that this contains all the actual state of the NED machine being simulated. | |
53 | struct NEDstate * nedstate; | |
54 | }; | |
55 | ||
56 | struct color_rgb { | |
57 | // TODO: Explain why this is an unsigned short. Copy from WolframAutomata. | |
58 | unsigned short red, green, blue; | |
59 | }; | |
60 | ||
61 | struct color_scheme { | |
62 | // TODO: Explain all this. | |
63 | struct color_rgb | |
64 | panel_bg, | |
65 | panel_fg, | |
66 | light_on, | |
67 | light_off, | |
68 | error_on, | |
69 | error_off, | |
70 | primary, | |
71 | secondary, | |
72 | tertiary, | |
73 | border, | |
74 | text; | |
75 | }; | |
76 | ||
77 | //static struct color_scheme color_list[] = { | |
78 | // // TODO: Explain all this. | |
79 | // // TODO: Add other color schemes, like purple PDP-11/70. | |
80 | // // TODO: http://www.chdickman.com/pdp8/DECcolors/ | |
81 | // { | |
82 | // {63479,63479,63479}, // 092-XXXX-123 | |
83 | // { 5140, 2056, 5654}, // 092-XXXX-152 | |
84 | // {40092,11051,15677}, // 092-XXXX-139 | |
85 | // {30326,13107,12850}, // 092-XXXX-154 | |
86 | // {65535,13107,12850}, // homemade | |
87 | // {30326,13107,12850}, // 092-XXXX-154 | |
88 | // { 4112,12850,20046}, // 092-XXXX-145 | |
89 | // {12336,29555,37008}, // 092-XXXX-151 | |
90 | // {30326,24158, 6425}, // 092-XXXX-157 | |
91 | // {63479,63479,63479}, // 092-XXXX-123 | |
92 | // {63479,63479,63479} // 092-XXXX-123 | |
93 | // } | |
94 | //}; | |
95 | ||
96 | static struct color_scheme color_list[] = { | |
97 | // TODO: Explain all this. | |
98 | // TODO: Add other color schemes, like purple PDP-11/70. | |
99 | // TODO: http://www.chdickman.com/pdp8/DECcolors/ | |
100 | { | |
101 | {63479,63479,63479}, // 092-XXXX-123 | |
102 | { 5140, 2056, 5654}, // 092-XXXX-152 | |
103 | {65535,11051,15677}, // 092-XXXX-139 - edit | |
104 | {20000,13107,12850}, // 092-XXXX-154 - edit | |
105 | {65535,13107,12850}, // homemade | |
106 | {20000,13107,12850}, // 092-XXXX-154 - edit | |
107 | { 4112,12850,20046}, // 092-XXXX-145 | |
108 | {12336,29555,37008}, // 092-XXXX-151 | |
109 | {30326,24158, 6425}, // 092-XXXX-157 | |
110 | {63479,63479,63479}, // 092-XXXX-123 | |
111 | {63479,63479,63479} // 092-XXXX-123 | |
112 | } | |
113 | }; | |
114 | ||
115 | /* -------------------------------------------------------------------------- */ | |
116 | /* Helper Functions */ | |
117 | /* -------------------------------------------------------------------------- */ | |
118 | ||
119 | // TODO: Explain | |
120 | static void | |
121 | set_color(struct NEDsim * nedsim, struct color_rgb * color) | |
122 | { | |
123 | XColor temp; | |
124 | XWindowAttributes xgwa; | |
125 | ||
126 | XGetWindowAttributes(nedsim->dpy, nedsim->win, &xgwa); | |
127 | ||
128 | temp.red = color->red; | |
129 | temp.green = color->green; | |
130 | temp.blue = color->blue; | |
131 | ||
132 | XAllocColor(nedsim->dpy, xgwa.colormap, &temp); | |
133 | XSetForeground(nedsim->dpy, nedsim->gc, temp.pixel); | |
134 | } | |
135 | ||
136 | // TODO: Explain | |
137 | // TODO: Make this a lot faster. | |
138 | // Input: size in 'cells', and sets font to fill that size, minus border and padding room. | |
139 | static void | |
140 | set_font_size(struct NEDsim * nedsim, int size) | |
141 | { | |
142 | // vvv--- for border ---vvv vvv--- for padding ---vvv | |
143 | int desired_height_in_pixels = (size * nedsim->cell_size) - (2 * nedsim->border_size) - (8 * nedsim->border_size); | |
144 | ||
145 | const char * font_size_prefix = "-*-helvetica-*-r-*-*-"; | |
146 | const char * font_size_suffix = "-*-*-*-*-*-*-*"; | |
147 | ||
148 | 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. | |
149 | char * font_full_name = malloc(buffer_size); | |
150 | int font_size_in_points = 2; // Start with a 2 pt font and work our way up. | |
151 | ||
152 | while (1) { | |
153 | // Load the font. | |
154 | snprintf(font_full_name, buffer_size, "%s%d%s", font_size_prefix, font_size_in_points, font_size_suffix); | |
155 | XFontStruct * font = XLoadQueryFont(nedsim->dpy, font_full_name); | |
156 | if (!font) { | |
157 | printf("WARNING: Unable to load font %s. Probably gonna look wonky.\n", font_full_name); | |
158 | font = XLoadQueryFont(nedsim->dpy, "fixed"); | |
159 | break; | |
160 | } | |
161 | XSetFont(nedsim->dpy, nedsim->gc, font->fid); | |
162 | ||
163 | // Get the height. | |
164 | int direction, ascent, descent; | |
165 | XCharStruct overall; | |
166 | XTextExtents(font, "X", 1, &direction, &ascent, &descent, &overall); | |
167 | ||
168 | // Compare the height. | |
169 | int height = overall.ascent - overall.descent; | |
170 | if (height == desired_height_in_pixels) { | |
171 | break; | |
172 | } else if (height > desired_height_in_pixels) { | |
173 | font_size_in_points--; | |
174 | snprintf(font_full_name, buffer_size, "%s%d%s", font_size_prefix, font_size_in_points, font_size_suffix); | |
175 | XFontStruct * font = XLoadQueryFont(nedsim->dpy, font_full_name); | |
176 | if (!font) { | |
177 | printf("WARNING: Unable to load font %s. Probably gonna look wonky.\n", font_full_name); | |
178 | font = XLoadQueryFont(nedsim->dpy, "fixed"); | |
179 | break; | |
180 | } | |
181 | XSetFont(nedsim->dpy, nedsim->gc, font->fid); | |
182 | break; | |
183 | } else { | |
184 | font_size_in_points++; | |
185 | } | |
186 | } | |
187 | ||
188 | free(nedsim->current_font); | |
189 | nedsim->current_font = font_full_name; | |
190 | } | |
191 | ||
192 | // TODO: Explain | |
193 | // TODO: Explain that this returns result in pixels so we can track fractional cell usage. | |
194 | static void | |
195 | get_text_size(struct NEDsim * nedsim, const char * text, int * x_size, int * y_size) | |
196 | { | |
197 | int direction, ascent, descent; | |
198 | XCharStruct overall; | |
199 | XFontStruct * font = XLoadQueryFont(nedsim->dpy, nedsim->current_font); | |
200 | XTextExtents(font, text, strlen(text), &direction, &ascent, &descent, &overall); | |
201 | *x_size = overall.width; | |
202 | *y_size = overall.ascent - overall.descent; | |
203 | } | |
204 | ||
205 | // TODO: Explain | |
206 | // TODO: Note that this might leave the foreground color changed. | |
207 | // Argument coordinates are in 'cells', not pixels. | |
208 | static void | |
209 | draw_rect_area(struct NEDsim * nedsim, size_t x_origin, size_t y_origin, size_t x_size, size_t y_size, | |
210 | Bool bord_top, Bool bord_bottom, Bool bord_left, Bool bord_right) | |
211 | { | |
212 | x_origin *= nedsim->cell_size; | |
213 | x_origin += nedsim->origin_x_offset; | |
214 | y_origin *= nedsim->cell_size; | |
215 | y_origin += nedsim->origin_y_offset; | |
216 | x_size *= nedsim->cell_size; | |
217 | y_size *= nedsim->cell_size; | |
218 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, y_origin, x_size, y_size); | |
219 | ||
220 | set_color(nedsim, &color_list[nedsim->color_index].border); | |
221 | if (bord_top) { | |
222 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, y_origin, x_size, nedsim->border_size); | |
223 | } | |
224 | if (bord_bottom) { | |
225 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, (y_origin + y_size - nedsim->border_size), x_size, nedsim->border_size); | |
226 | } | |
227 | if (bord_left) { | |
228 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, y_origin, nedsim->border_size, y_size); | |
229 | } | |
230 | if (bord_right) { | |
231 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, (x_origin + x_size - nedsim->border_size), y_origin, nedsim->border_size, y_size); | |
232 | } | |
233 | } | |
234 | ||
235 | // TODO: Explain | |
236 | // Arguments are in units of 'cells', not pixels. | |
237 | // Will leave foreground color changed. | |
238 | // Draws filled circle with upper left corner at x,y. | |
239 | static void | |
240 | draw_circular_area(struct NEDsim * nedsim, size_t x, size_t y, double diameter) | |
241 | { | |
242 | // First, convert the function argument units from 'cells' to 'pixels' | |
243 | x *= nedsim->cell_size; | |
244 | y *= nedsim->cell_size; | |
245 | diameter *= nedsim->cell_size; | |
246 | ||
247 | // Add the panel's absolute x,y offset to the requested coordinates. | |
248 | x += nedsim->origin_x_offset; | |
249 | y += nedsim->origin_y_offset; | |
250 | ||
251 | // Shrink the circle to be a bit smaller than the bounding box allows. Note | |
252 | // that the three adjustment values must sum to 1.0. | |
253 | // For example, 0.7 + 0.15 + 0.15 = 1.0. | |
254 | x += (0.15 * diameter); | |
255 | y += (0.15 * diameter); | |
256 | diameter *= 0.7; | |
257 | ||
258 | // Because we only draw the bottom border on repeated rows (e.g. draw_wordline()), | |
259 | // we need to offset vertically by half a border height. | |
260 | y -= (0.5 * nedsim->border_size); | |
261 | ||
262 | // Start angle 0 and ending angle 360*64 is one full circle in Xlib units. | |
263 | XFillArc(nedsim->dpy, nedsim->win, nedsim->gc, x, y, diameter, diameter, 0, 360*64); | |
264 | } | |
265 | ||
266 | // TODO: Explain | |
267 | static void | |
268 | draw_panel(struct NEDsim * nedsim) | |
269 | { | |
270 | // TODO: Collect all relevant #defines somewhere. | |
271 | #define OVERALL_WIDTH_IN_CELLS 70 | |
272 | #define HEADER_HEIGHT_IN_CELLS 14 | |
273 | #define FOOTER_HEIGHT_IN_CELLS 2 | |
274 | ||
275 | // Draw background color over entire window. | |
276 | set_color(nedsim, &color_list[nedsim->color_index].panel_bg); | |
277 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, 0, 0, nedsim->dpy_width, nedsim->dpy_height); | |
278 | ||
279 | // Draw NED panel in foreground color. | |
280 | set_color(nedsim, &color_list[nedsim->color_index].panel_fg); | |
281 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, | |
282 | nedsim->origin_x_offset, | |
283 | nedsim->origin_y_offset, | |
284 | nedsim->cell_size * OVERALL_WIDTH_IN_CELLS, | |
285 | nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + nedsim->num_data_rows + FOOTER_HEIGHT_IN_CELLS) | |
286 | ); | |
287 | } | |
288 | ||
289 | // TODO: Explain | |
290 | static void | |
291 | draw_logo(struct NEDsim * nedsim) | |
292 | { | |
293 | #define LOGO_X_OFFSET 2 | |
294 | #define LOGO_Y_OFFSET 2 | |
295 | #define LOGO_WIDTH 20 | |
296 | #define LOGO_NAME_HEIGHT 6 | |
297 | #define LOGO_WEBSITE_HEIGHT 2 | |
298 | ||
299 | // First draw the two colored boxes that comprise the logo area. | |
300 | set_color(nedsim, &color_list[nedsim->color_index].primary); | |
301 | draw_rect_area(nedsim, LOGO_X_OFFSET, LOGO_Y_OFFSET, LOGO_WIDTH, LOGO_NAME_HEIGHT, True, True, False, False); | |
302 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
303 | draw_rect_area(nedsim, LOGO_X_OFFSET, LOGO_Y_OFFSET+LOGO_NAME_HEIGHT, LOGO_WIDTH, LOGO_WEBSITE_HEIGHT, False, True, False, False); | |
304 | ||
305 | // Now draw the 'NED' text in the top box. | |
306 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
307 | set_font_size(nedsim, LOGO_NAME_HEIGHT); | |
308 | int text_x_size, text_y_size; | |
309 | get_text_size(nedsim, "NED", &text_x_size, &text_y_size); | |
310 | int local_x_offset = ((LOGO_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
311 | int local_y_offset = ((LOGO_NAME_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
312 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (LOGO_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
313 | ((LOGO_Y_OFFSET+LOGO_NAME_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "NED", 3); | |
314 | ||
315 | // And draw the 'subgeniuskitty.com' text in the bottom box. | |
316 | set_font_size(nedsim, LOGO_WEBSITE_HEIGHT); | |
317 | get_text_size(nedsim, "subgeniuskitty.com", &text_x_size, &text_y_size); | |
318 | local_x_offset = ((LOGO_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
319 | local_y_offset = ((LOGO_WEBSITE_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
320 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (LOGO_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
321 | ((LOGO_Y_OFFSET+LOGO_NAME_HEIGHT+LOGO_WEBSITE_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "subgeniuskitty.com", 18); | |
322 | } | |
323 | ||
324 | // TODO: Explain | |
325 | static void | |
326 | draw_halt(struct NEDsim * nedsim) | |
327 | { | |
328 | #define HALT_X_OFFSET 26 | |
329 | #define HALT_Y_OFFSET 2 | |
330 | #define HALT_WIDTH 6 | |
331 | #define HALT_LIGHT_HEIGHT 6 | |
332 | #define HALT_LABEL_HEIGHT 2 | |
333 | ||
334 | // First draw the two colored boxes that comprise the halt area. | |
335 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
336 | draw_rect_area(nedsim, HALT_X_OFFSET, HALT_Y_OFFSET, HALT_WIDTH, HALT_LIGHT_HEIGHT, True, True, False, False); | |
337 | set_color(nedsim, &color_list[nedsim->color_index].secondary); | |
338 | draw_rect_area(nedsim, HALT_X_OFFSET, HALT_Y_OFFSET+HALT_LIGHT_HEIGHT, HALT_WIDTH, HALT_LABEL_HEIGHT, False, True, False, False); | |
339 | ||
340 | // And finally, draw the label. | |
341 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
342 | int text_x_size, text_y_size; | |
343 | get_text_size(nedsim, "HALT", &text_x_size, &text_y_size); | |
344 | int local_x_offset = ((HALT_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
345 | int local_y_offset = ((HALT_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
346 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (HALT_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
347 | ((HALT_Y_OFFSET+HALT_LIGHT_HEIGHT+HALT_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "HALT", 4); | |
348 | } | |
349 | ||
350 | // TODO: Explain | |
351 | static void | |
352 | draw_wordline_lights(struct NEDsim * nedsim, uint32_t word, int x, int y) | |
353 | { | |
354 | #define WORDLINE_BITS_PER_STRIPE 4 | |
355 | #define WORDLINE_WIDTH 32 | |
356 | #define WORDLINE_HEIGHT 1 | |
357 | ||
358 | for (int i = 0; i < WORDLINE_WIDTH; i++) { | |
359 | if (word & (1<<(WORDLINE_WIDTH-1-i))) { | |
360 | set_color(nedsim, &color_list[nedsim->color_index].light_on); | |
361 | } else { | |
362 | set_color(nedsim, &color_list[nedsim->color_index].light_off); | |
363 | } | |
364 | draw_circular_area(nedsim, x+i, y, WORDLINE_HEIGHT); | |
365 | } | |
366 | } | |
367 | ||
368 | // TODO: Explain | |
369 | static void | |
370 | draw_wordline(struct NEDsim * nedsim, int x, int y) | |
371 | { | |
372 | // First, draw a solid box in the primary color over the entire wordline area. | |
373 | set_color(nedsim, &color_list[nedsim->color_index].primary); | |
374 | draw_rect_area(nedsim, x, y, WORDLINE_WIDTH, WORDLINE_HEIGHT, False, True, False, False); | |
375 | ||
376 | // Now, draw stripes in the secondary color. | |
377 | int i; | |
378 | for (i = 0; i < (WORDLINE_WIDTH/(2*WORDLINE_BITS_PER_STRIPE)); i++) { | |
379 | set_color(nedsim, &color_list[nedsim->color_index].secondary); | |
380 | draw_rect_area(nedsim, (x+(i*(WORDLINE_WIDTH/WORDLINE_BITS_PER_STRIPE))), y, | |
381 | WORDLINE_BITS_PER_STRIPE, WORDLINE_HEIGHT, False, True, False, False); | |
382 | } | |
383 | ||
384 | // Finally, draw the lights. | |
385 | draw_wordline_lights(nedsim, 0, x, y); | |
386 | } | |
387 | ||
388 | // TODO: Explain | |
389 | static void | |
390 | draw_pc(struct NEDsim * nedsim) | |
391 | { | |
392 | // TODO: Note that all #defines use units of 'cells', not 'pixels', etc. | |
393 | #define PC_X_OFFSET 36 | |
394 | #define PC_Y_OFFSET 7 | |
395 | #define PC_WIDTH 32 | |
396 | #define PC_LABEL_HEIGHT 2 | |
397 | #define PC_LIGHT_HEIGHT 1 | |
398 | ||
399 | // First draw the two colored boxes that comprise the PC area. | |
400 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
401 | draw_rect_area(nedsim, PC_X_OFFSET, PC_Y_OFFSET, PC_WIDTH, PC_LABEL_HEIGHT, True, True, False, False); | |
402 | draw_wordline(nedsim, PC_X_OFFSET, PC_Y_OFFSET+PC_LABEL_HEIGHT); | |
403 | ||
404 | // Now draw the label text "PC". | |
405 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
406 | int text_x_size, text_y_size; | |
407 | get_text_size(nedsim, "PC", &text_x_size, &text_y_size); | |
408 | int local_x_offset = ((PC_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
409 | int local_y_offset = ((PC_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
410 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (PC_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
411 | ((PC_Y_OFFSET+PC_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "PC", 2); | |
412 | } | |
413 | ||
414 | // TODO: Explain | |
415 | static void | |
416 | draw_sc(struct NEDsim * nedsim) | |
417 | { | |
418 | #define SC_X_OFFSET 42 | |
419 | #define SC_Y_OFFSET 2 | |
420 | #define SC_WIDTH 5 | |
421 | #define SC_LABEL_HEIGHT 2 | |
422 | #define SC_LIGHT_HEIGHT 1 | |
423 | ||
424 | // First draw the two colored boxes that comprise the SC area. | |
425 | set_color(nedsim, &color_list[nedsim->color_index].secondary); | |
426 | draw_rect_area(nedsim, SC_X_OFFSET, SC_Y_OFFSET, SC_WIDTH, SC_LABEL_HEIGHT, True, True, False, False); | |
427 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
428 | draw_rect_area(nedsim, SC_X_OFFSET, SC_Y_OFFSET+SC_LABEL_HEIGHT, SC_WIDTH, SC_LIGHT_HEIGHT, False, True, False, False); | |
429 | ||
430 | // Now draw the label text "SC". | |
431 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
432 | int text_x_size, text_y_size; | |
433 | get_text_size(nedsim, "SC", &text_x_size, &text_y_size); | |
434 | int local_x_offset = ((SC_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
435 | int local_y_offset = ((SC_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
436 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (SC_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
437 | ((SC_Y_OFFSET+SC_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "SC", 2); | |
438 | } | |
439 | ||
440 | // TODO: Explain | |
441 | static void | |
442 | draw_psw(struct NEDsim * nedsim) | |
443 | { | |
444 | #define PSW_N_X_OFFSET 51 | |
445 | #define PSW_Z_X_OFFSET 58 | |
446 | #define PSW_Y_OFFSET 2 | |
447 | #define PSW_LABEL_WIDTH 3 | |
448 | #define PSW_LABEL_HEIGHT 2 | |
449 | #define PSW_LIGHT_WIDTH 1 | |
450 | #define PSW_LIGHT_HEIGHT 1 | |
451 | ||
452 | // First draw the four colored boxes that comprise the two PSW areas. | |
453 | set_color(nedsim, &color_list[nedsim->color_index].secondary); | |
454 | draw_rect_area(nedsim, PSW_N_X_OFFSET, PSW_Y_OFFSET, PSW_LABEL_WIDTH, PSW_LABEL_HEIGHT, True, True, False, False); | |
455 | set_color(nedsim, &color_list[nedsim->color_index].secondary); | |
456 | draw_rect_area(nedsim, PSW_Z_X_OFFSET, PSW_Y_OFFSET, PSW_LABEL_WIDTH, PSW_LABEL_HEIGHT, True, True, False, False); | |
457 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
458 | 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); | |
459 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
460 | 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); | |
461 | ||
462 | // Now draw the label text "N". | |
463 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
464 | int text_x_size, text_y_size; | |
465 | get_text_size(nedsim, "N", &text_x_size, &text_y_size); | |
466 | int local_x_offset = ((PSW_LABEL_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
467 | int local_y_offset = ((PSW_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
468 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (PSW_N_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
469 | ((PSW_Y_OFFSET+PSW_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "N", 1); | |
470 | ||
471 | // Now draw the label text "Z". | |
472 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
473 | get_text_size(nedsim, "Z", &text_x_size, &text_y_size); | |
474 | local_x_offset = ((PSW_LABEL_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
475 | local_y_offset = ((PSW_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
476 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (PSW_Z_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
477 | ((PSW_Y_OFFSET+PSW_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "Z", 1); | |
478 | } | |
479 | ||
480 | // TODO: Explain | |
481 | static void | |
482 | draw_stack(struct NEDsim * nedsim) | |
483 | { | |
484 | #define STACK_X_OFFSET 2 | |
485 | #define STACK_Y_OFFSET 12 | |
486 | #define STACK_WIDTH 32 | |
487 | #define STACK_LABEL_HEIGHT 2 | |
488 | #define STACK_LIGHT_HEIGHT 1 | |
489 | ||
490 | // First draw the two colored boxes that comprise the stack area. | |
491 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
492 | draw_rect_area(nedsim, STACK_X_OFFSET, STACK_Y_OFFSET, STACK_WIDTH, STACK_LABEL_HEIGHT, True, True, False, False); | |
493 | for (int i = 0; i < nedsim->num_data_rows; i++) { | |
494 | draw_wordline(nedsim, STACK_X_OFFSET, STACK_Y_OFFSET+STACK_LABEL_HEIGHT+i); | |
495 | } | |
496 | ||
497 | // Now draw the label text "Stack Size:". | |
498 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
499 | int text_x_size, text_y_size; | |
500 | get_text_size(nedsim, "Stack Size:", &text_x_size, &text_y_size); | |
501 | int local_y_offset = ((STACK_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
502 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((STACK_X_OFFSET + 1) * nedsim->cell_size + nedsim->origin_x_offset), | |
503 | ((STACK_Y_OFFSET+STACK_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "Stack Size:", 11); | |
504 | } | |
505 | ||
506 | // TODO: Explain | |
507 | static void | |
508 | draw_heap(struct NEDsim * nedsim) | |
509 | { | |
510 | #define HEAP_X_OFFSET 36 | |
511 | #define HEAP_Y_OFFSET 12 | |
512 | #define HEAP_WIDTH 32 | |
513 | #define HEAP_LABEL_HEIGHT 2 | |
514 | #define HEAP_LIGHT_HEIGHT 1 | |
515 | // 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. | |
516 | #define HEAP_START_ADDRESS 0x20000000 | |
517 | ||
518 | // First draw the two colored boxes that comprise the heap area. | |
519 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
520 | draw_rect_area(nedsim, HEAP_X_OFFSET, HEAP_Y_OFFSET, HEAP_WIDTH, HEAP_LABEL_HEIGHT, True, True, False, False); | |
521 | for (int i = 0; i < nedsim->num_data_rows; i++) { | |
522 | draw_wordline(nedsim, HEAP_X_OFFSET, HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT+i); | |
523 | } | |
524 | ||
525 | // Now draw the label text "RAM Base:". | |
526 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
527 | int text_x_size, text_y_size; | |
528 | get_text_size(nedsim, "RAM Base:", &text_x_size, &text_y_size); | |
529 | int local_y_offset = ((HEAP_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
530 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((HEAP_X_OFFSET + 1) * nedsim->cell_size + nedsim->origin_x_offset), | |
531 | ((HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "RAM Base:", 9); | |
532 | ||
533 | // Now draw the address text. | |
534 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
535 | char address[11]; | |
536 | snprintf(address, sizeof(address), "0x%08X", HEAP_START_ADDRESS); | |
537 | get_text_size(nedsim, address, &text_x_size, &text_y_size); | |
538 | local_y_offset = ((HEAP_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
539 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((HEAP_X_OFFSET + 1 + (HEAP_WIDTH / 2)) * nedsim->cell_size + nedsim->origin_x_offset), | |
540 | ((HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), address, strlen(address)); | |
541 | } | |
542 | ||
543 | // TODO: Explain | |
544 | static void | |
545 | update_display(struct NEDsim * nedsim) | |
546 | { | |
547 | // Draw the halt indicator. | |
548 | if (nedsim->nedstate->halted) { | |
549 | set_color(nedsim, &color_list[nedsim->color_index].error_on); | |
550 | } else { | |
551 | set_color(nedsim, &color_list[nedsim->color_index].error_off); | |
552 | } | |
553 | draw_circular_area(nedsim, HALT_X_OFFSET, HALT_Y_OFFSET, HALT_WIDTH); | |
554 | ||
555 | // Draw the PSW "N" light. | |
556 | if (nedsim->nedstate->active_thread->psw->negative) { | |
557 | set_color(nedsim, &color_list[nedsim->color_index].light_on); | |
558 | } else { | |
559 | set_color(nedsim, &color_list[nedsim->color_index].light_off); | |
560 | } | |
561 | draw_circular_area(nedsim, PSW_N_X_OFFSET+1, PSW_Y_OFFSET+PSW_LABEL_HEIGHT, PSW_LIGHT_HEIGHT); | |
562 | ||
563 | // Draw the PSW "Z" light. | |
564 | if (nedsim->nedstate->active_thread->psw->zero) { | |
565 | set_color(nedsim, &color_list[nedsim->color_index].light_on); | |
566 | } else { | |
567 | set_color(nedsim, &color_list[nedsim->color_index].light_off); | |
568 | } | |
569 | draw_circular_area(nedsim, PSW_Z_X_OFFSET+1, PSW_Y_OFFSET+PSW_LABEL_HEIGHT, PSW_LIGHT_HEIGHT); | |
570 | ||
571 | ||
572 | // Draw the SC. | |
573 | int i; | |
574 | for (i = 0; i < SC_WIDTH; i++) { | |
575 | if ((SC_WIDTH-1-i) == nedsim->nedstate->active_thread->sc) { | |
576 | set_color(nedsim, &color_list[nedsim->color_index].light_on); | |
577 | } else { | |
578 | set_color(nedsim, &color_list[nedsim->color_index].light_off); | |
579 | } | |
580 | draw_circular_area(nedsim, SC_X_OFFSET+i, SC_Y_OFFSET+SC_LABEL_HEIGHT, SC_LIGHT_HEIGHT); | |
581 | } | |
582 | ||
583 | // Draw the PC. | |
584 | draw_wordline_lights(nedsim, nedsim->nedstate->active_thread->pc, PC_X_OFFSET, PC_Y_OFFSET+PC_LABEL_HEIGHT); | |
585 | ||
586 | // Draw the stack lights. | |
587 | int64_t top_of_stack = ((int64_t)nedsim->nedstate->active_thread->sp) - 1; | |
588 | for (i = 0; i < nedsim->num_data_rows; i++) { | |
589 | if ((top_of_stack-i) >= 0) { | |
590 | draw_wordline_lights(nedsim, nedsim->nedstate->active_thread->stack[top_of_stack-i], STACK_X_OFFSET, STACK_Y_OFFSET+STACK_LABEL_HEIGHT+i); | |
591 | } else { | |
592 | draw_wordline_lights(nedsim, 0, STACK_X_OFFSET, STACK_Y_OFFSET+STACK_LABEL_HEIGHT+i); | |
593 | } | |
594 | } | |
595 | ||
596 | // Draw the stack size in text. | |
597 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
598 | draw_rect_area(nedsim, STACK_X_OFFSET+(STACK_WIDTH/2), STACK_Y_OFFSET, STACK_WIDTH/2, STACK_LABEL_HEIGHT, True, True, False, False); | |
599 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
600 | char stack_size[11]; | |
601 | snprintf(stack_size, sizeof(stack_size), "0x%08X", nedsim->nedstate->active_thread->sp); | |
602 | int text_x_size, text_y_size; | |
603 | get_text_size(nedsim, stack_size, &text_x_size, &text_y_size); | |
604 | int local_y_offset = ((STACK_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
605 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((STACK_X_OFFSET + 1 + (STACK_WIDTH / 2)) * nedsim->cell_size + nedsim->origin_x_offset), | |
606 | ((STACK_Y_OFFSET+STACK_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), stack_size, strlen(stack_size)); | |
607 | ||
608 | // Draw the heap lights. | |
609 | for (i = 0; i < nedsim->num_data_rows; i++) { | |
610 | draw_wordline_lights(nedsim, ram_r_word(nedsim->nedstate, HEAP_START_ADDRESS+(i*BPW)), HEAP_X_OFFSET, HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT+i); | |
611 | } | |
612 | } | |
613 | ||
614 | /* -------------------------------------------------------------------------- */ | |
615 | /* Screenhack API Functions */ | |
616 | /* -------------------------------------------------------------------------- */ | |
617 | ||
618 | static Bool | |
619 | NEDsim_event(Display * dpy, Window win, void * closure, XEvent * event) | |
620 | { | |
621 | return False; | |
622 | } | |
623 | ||
624 | static void | |
625 | NEDsim_free(Display * dpy, Window win, void * closure) | |
626 | { | |
627 | // TODO: Replace all this with proper code to free everything. | |
628 | struct NEDsim * nedsim = closure; | |
629 | XFreeGC(nedsim->dpy, nedsim->gc); | |
630 | free(nedsim); | |
631 | } | |
632 | ||
633 | static void * | |
634 | NEDsim_init(Display * dpy, Window win) | |
635 | { | |
636 | struct NEDsim * nedsim; | |
637 | XGCValues gcv; | |
638 | XWindowAttributes xgwa; | |
639 | ||
640 | nedsim = calloc(1, sizeof(*nedsim)); | |
641 | if (!nedsim) { | |
642 | fprintf(stderr, "ERROR: Failed to calloc() for NEDsim struct in NEDsim_init().\n"); | |
643 | exit(EXIT_FAILURE); | |
644 | } | |
645 | ||
646 | nedsim->dpy = dpy; | |
647 | nedsim->win = win; | |
648 | ||
649 | XGetWindowAttributes(nedsim->dpy, nedsim->win, &xgwa); | |
650 | nedsim->dpy_width = xgwa.width; | |
651 | nedsim->dpy_height = xgwa.height; | |
652 | ||
653 | // TODO: Explain that this is the delay between each clock cycle of the simulated NED CPU. | |
654 | nedsim->delay = get_integer_resource(nedsim->dpy, "delay", "Integer"); | |
655 | nedsim->delay *= 1000; /* Turn milliseconds into microseconds. */ | |
656 | ||
657 | // TODO: Read in the a.out file. This should be done in the simulator's init function call? | |
658 | nedsim->nedstate = init_simulator(); | |
659 | ||
660 | nedsim->gc = XCreateGC(nedsim->dpy, nedsim->win, GCForeground, &gcv); | |
661 | ||
662 | // TODO: Do this properly. | |
663 | nedsim->color_index = 0; | |
664 | ||
665 | // 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. | |
666 | ||
667 | 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. | |
668 | // 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. | |
669 | // For now, we'll just make it 10 pixels? | |
670 | if (nedsim->cell_size < 10) { | |
671 | nedsim->suitable_display = False; | |
672 | return nedsim; | |
673 | } | |
674 | nedsim->origin_x_offset = (nedsim->dpy_width - (nedsim->cell_size * OVERALL_WIDTH_IN_CELLS)) / 2; // center panel horizontally | |
675 | ||
676 | // Determine how many rows for the stack and heap displays. Make it the largest power of two that fits on the display. | |
677 | int available_space_for_data_rows = nedsim->dpy_height - (nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + FOOTER_HEIGHT_IN_CELLS)); | |
678 | for (int i = 0; ; i++) { | |
679 | if ((nedsim->cell_size * (1 << i)) > available_space_for_data_rows) { | |
680 | nedsim->num_data_rows = (1 << --i); | |
681 | break; | |
682 | } | |
683 | } | |
684 | if (nedsim->num_data_rows < 4) { | |
685 | nedsim->suitable_display = False; | |
686 | return nedsim; | |
687 | } | |
688 | 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 | |
689 | ||
690 | // Scale border relative to cell_size in a 1:10 relationship. | |
691 | nedsim->border_size = nedsim->cell_size / 10; | |
692 | ||
693 | draw_panel(nedsim); | |
694 | draw_logo(nedsim); | |
695 | draw_halt(nedsim); | |
696 | draw_pc(nedsim); | |
697 | draw_sc(nedsim); | |
698 | draw_psw(nedsim); | |
699 | draw_stack(nedsim); | |
700 | draw_heap(nedsim); | |
701 | ||
702 | nedsim->suitable_display = True; | |
703 | ||
704 | return nedsim; | |
705 | } | |
706 | ||
707 | static unsigned long | |
708 | NEDsim_draw(Display * dpy, Window win, void * closure) | |
709 | { | |
710 | struct NEDsim * nedsim = closure; | |
711 | ||
712 | if (nedsim->suitable_display) { | |
713 | nedsim->nedstate = run_simulator(nedsim->nedstate); | |
714 | update_display(nedsim); | |
715 | } else { | |
716 | set_color(nedsim, &color_list[nedsim->color_index].error_on); | |
717 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, 0, 0, nedsim->dpy_width, nedsim->dpy_height); | |
718 | } | |
719 | ||
720 | return nedsim->delay; | |
721 | } | |
722 | ||
723 | static void | |
724 | NEDsim_reshape(Display * dpy, Window win, void * closure, unsigned int w, unsigned int h) | |
725 | { | |
726 | struct NEDsim * nedsim = closure; | |
727 | XWindowAttributes xgwa; | |
728 | XGetWindowAttributes(nedsim->dpy, nedsim->win, &xgwa); | |
729 | /* Only restart the simulation if the window changed size. */ | |
730 | if (nedsim->dpy_width != xgwa.width || nedsim->dpy_height != xgwa.height) { | |
731 | NEDsim_free(dpy, win, closure); | |
732 | closure = NEDsim_init(dpy, win); | |
733 | } | |
734 | } | |
735 | ||
736 | static const char * NEDsim_defaults[] = { | |
737 | "*delay: 250", | |
738 | 0 | |
739 | }; | |
740 | ||
741 | static XrmOptionDescRec NEDsim_options[] = { | |
742 | { "-delay", ".delay", XrmoptionSepArg, 0 }, | |
743 | { 0, 0, 0, 0 } | |
744 | }; | |
745 | ||
746 | XSCREENSAVER_MODULE ("Blinken-lights simulator for NED1 CPU architecture.", NEDsim) |