cycles now correctly accounted for
[dcpu16] / hw_lem1802.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <sys/time.h>
6
7 #ifdef HAVE_LIBPNG
8 #include <setjmp.h>
9 #include <png.h>
10 #endif /* HAVE_LIBPNG */
11
12 #ifdef HAVE_LIBVNCSERVER
13 #include <rfb/rfb.h>
14 #include <rfb/keysym.h>
15 #endif /* HAVE_LIBVNCSERVER */
16
17 #include "dcpu16.h"
18 #include "chargen-4x8.h"
19 #include "hw_lem1802.h"
20
21 #ifdef DEBUG
22 #define TRACE(...) do { printf("[debug] "); printf(__VA_ARGS__); printf("\n"); } while (0)
23 #else /* DEBUG */
24 #define TRACE(...) do {} while (0)
25 #endif /* DEBUG */
26
27 static dcpu16_hw_signal_t lem1802_reset_;
28 static dcpu16_hw_signal_t lem1802_cycle_;
29 static dcpu16_hw_signal_t lem1802_hwi_;
30 static struct dcpu16_hw hw_ = {
31 .name_ = "LEM1802 - Low Energy Monitor",
32 .id_l = 0xf615,
33 .id_h = 0x7349,
34 .ver = 0x1802,
35 .mfg_l = 0x8b36,
36 .mfg_h = 0x1c6c,
37 .hwi = lem1802_hwi_,
38 .cycle = lem1802_cycle_,
39 .reset = lem1802_reset_,
40 .data = (struct lem1802_ *)NULL
41 };
42
43 #define LEM1802_POWER_ON_CYCLES 100000 /* this should vary by, let us say, 10% */
44
45 #define PIX_X 160 /* pixels in display */
46 #define PIX_Y 128 /* pixels in display */
47 #define PIX_BORDER 16 /* border pixels from edge to first tile */
48 #define CELL_X_SZ 4
49 #define CELL_Y_SZ 8
50 #define CELL_X ((PIX_X - (2 * PIX_BORDER)) / CELL_X_SZ) /* tiles in display */
51 #define CELL_Y ((PIX_Y - (2 * PIX_BORDER)) / CELL_Y_SZ) /* tiles in display */
52
53 #define PALETTE_ENTRIES 16
54 static const DCPU16_WORD palette_default_[PALETTE_ENTRIES] = {
55 0x0000, /* black */
56 0x000a, /* blue */
57 0x00a0, /* green */
58 0x00aa, /* cyan */
59 0x0a05, /* red */
60 0x0a0f, /* magenta */
61 0x0aa5, /* yellow */
62 0x0aaf, /* pale blue */
63 0x0555, /* grey */
64 0x055f, /* light blue */
65 0x05f5, /* light green*/
66 0x05ff, /* light cyan */
67 0x0f55, /* light red */
68 0x0f5f, /* light magenta */
69 0x0ff5, /* light yellow */
70 0x0fff /* white */
71 };
72
73 struct pixel_ {
74 unsigned char r;
75 unsigned char g;
76 unsigned char b;
77 unsigned char a;
78 };
79
80 struct lem1802_ {
81 long long cycle_activated; /* running since */
82 long long cycles_until_active_; /* for tracking power-up delay */
83
84 DCPU16_WORD video_base;
85 DCPU16_WORD font_base;
86 DCPU16_WORD palette_base;
87 DCPU16_WORD border_color;
88 struct pixel_ *pixbuf;
89
90 unsigned int refresh_rate; /* redraw every n cycles */
91 unsigned int refresh_tally_; /* tick */
92
93 unsigned int blink_rate; /* toggle every n cycles? still figuring this out.. */
94 unsigned int blink_tally_; /* tick */
95 unsigned int blink_state;
96
97 enum cycle_state_ {
98 CYCLE_IDLE,
99 CYCLE_COPY_TO_RAM,
100 } cycle_state_;
101 const DCPU16_WORD *cycle_state_copy_src_ptr_;
102 DCPU16_WORD cycle_state_copy_dst_addr_;
103 size_t cycle_state_copy_words_;
104
105 int (*render)(void *, struct pixel_ *, size_t, size_t);
106 void *renderer_data;
107 };
108
109 static
110 long long power_on_cycles_(void) {
111 struct tv;
112 long long r = 0;
113
114 #if WANT_DELAY_START
115 gettimeofday(&tv, NULL);
116 r += LEM1802_POWER_ON_CYCLES - (LEM1802_POWER_ON_CYCLES / 10);
117 r += tv.tv_usec % (LEM1802_POWER_ON_CYCLES / 5);
118 #endif
119
120 return r;
121 }
122
123 static inline
124 void pixel_color_(struct pixel_ *pix, DCPU16_WORD color) {
125 unsigned char x;
126
127 x = (color >> 0) & 0x000f;
128 pix->r = x | (x << 4);
129
130 x = (color >> 4) & 0x000f;
131 pix->g = x | (x << 4);
132
133 x = (color >> 8) & 0x000f;
134 pix->b = x | (x << 4);
135
136 x = (color >> 12) & 0x000f;
137 pix->a = x | (x << 4);
138 }
139
140 static
141 void pixbuf_border_paint_(struct pixel_ *pixbuf, struct pixel_ *border) {
142 size_t x, y, i;
143
144 TRACE("%s>> painted border", __func__);
145
146 /* top */
147 for (y = 0; y < PIX_BORDER; y++) {
148 for (x = 0; x < PIX_X; x++) {
149 i = (y * PIX_X) + x;
150 pixbuf[i] = *border;
151 i = ((PIX_Y - y) * PIX_X) + x;
152 pixbuf[i] = *border;
153 }
154 }
155
156 /* sides */
157 for (y = PIX_BORDER; y < (PIX_Y - PIX_BORDER + 1); y++)
158 for (x = 0; x < PIX_BORDER; x++) {
159 i = (y * PIX_X) + x;
160 pixbuf[i] = *border;
161 pixbuf[i + (PIX_X - PIX_BORDER)] = *border;
162 }
163 }
164
165 static
166 void font_tile_paint_(struct pixel_ *p, struct pixel_ *fg, struct pixel_ *bg, DCPU16_WORD *tile) {
167 size_t pix_x, pix_y;
168 unsigned char *font_bitmap = (unsigned char *)tile;
169
170 #if 0
171 TRACE("%s>> fg:(%u,%u,%u) bg:(%u,%u,%u) font_bitmap:%02x %02x %02x %02x", __func__,
172 fg->r, fg->g, fg->b,
173 bg->r, bg->g, bg->b,
174 font_bitmap[0], font_bitmap[1], font_bitmap[2], font_bitmap[3]);
175 #endif
176
177 for (pix_x = 0; pix_x < CELL_X_SZ; pix_x++) {
178 for (pix_y = 0; pix_y < CELL_Y_SZ; pix_y++) {
179 if ((font_bitmap[pix_x] >> pix_y) & 0x01)
180 p[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = *fg;
181 else
182 p[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = *bg;
183 }
184 }
185 }
186
187 static
188 void pixbuf_addr_paint_(struct pixel_ *pixbuf, DCPU16_WORD *mem, DCPU16_WORD base, DCPU16_WORD addr, DCPU16_WORD *palette, DCPU16_WORD *tiles, unsigned int blink_state) {
189 struct pixel_ *tilestart = pixbuf; /* start of display */
190 unsigned int cell_x = addr % (PIX_X / CELL_X_SZ),
191 cell_y = addr / (PIX_X / CELL_X_SZ);
192 struct pixel_ fg, bg;
193 DCPU16_WORD *font_bitmap;
194 int blink;
195
196 cell_x = (addr - base) % CELL_X;
197 cell_y = (addr - base) / CELL_X;
198
199 #if 0
200 TRACE("%s>> addr:0x%04x col:%u row:%u v:%hu",
201 __func__,
202 addr,
203 cell_x, cell_y, mem[addr]);
204 #endif
205
206 blink = mem[addr] & 0x0080;
207
208 /* tiles take two words each */
209 font_bitmap = tiles + (2 * (mem[addr] & 0x7f));
210
211 pixel_color_(&bg, palette[(mem[addr] >> 8) & 0x0f]);
212 if (blink && blink_state)
213 pixel_color_(&fg, palette[(mem[addr] >> 8) & 0x0f]);
214 else
215 pixel_color_(&fg, palette[(mem[addr] >> 12) & 0x0f]);
216
217 tilestart += (PIX_X * PIX_BORDER); /* skip top border */
218 tilestart += (CELL_Y_SZ * PIX_X) * cell_y; /* skip down to row */
219
220 tilestart += PIX_BORDER; /* skip side border */
221 tilestart += (CELL_X_SZ) * cell_x; /* skip to column */
222
223 font_tile_paint_(tilestart, &fg, &bg, font_bitmap);
224 }
225
226 static
227 void lem1802_pixbuf_refresh_full_(struct lem1802_ *display, DCPU16_WORD *mem) {
228 struct pixel_ border;
229 size_t tile;
230
231 #if 0
232 TRACE("%s>> video_base:0x%04x", __func__, display->video_base);
233 #endif
234
235 if (display->cycles_until_active_) {
236 /* show cute power-up sequence.. */
237 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
238 return;
239 }
240
241 if (display->video_base == 0) {
242 /* disconnected, blank display */
243 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
244 return;
245 }
246
247 pixel_color_(&border, display->border_color);
248 pixbuf_border_paint_(display->pixbuf, &border);
249
250 for (tile = 0; tile < CELL_X * CELL_Y; tile++) {
251 pixbuf_addr_paint_(display->pixbuf,
252 mem,
253 display->video_base,
254 display->video_base + tile,
255 display->palette_base ? mem + display->palette_base : palette_default_,
256 display->font_base ? mem + display->font_base : (unsigned short *)chargen_4x8_glyphs,
257 display->blink_state);
258 }
259 }
260
261 static
262 int pixbuf_render_pnm_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
263 FILE *f = (FILE *)data;
264 size_t i;
265 fprintf(f, "P6\n"
266 "%lu %lu\n"
267 "255\n",
268 x, y);
269 for (i = 0; i < x * y; i++) {
270 fwrite(&pixbuf[i].r, 1, 1, f);
271 fwrite(&pixbuf[i].g, 1, 1, f);
272 fwrite(&pixbuf[i].b, 1, 1, f);
273 }
274 fclose(f);
275
276 return 0;
277 }
278
279 #ifdef HAVE_LIBPNG
280 static
281 int pixbuf_render_png_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
282 FILE *f = (FILE *)data;
283 int retval = 0;
284 png_structp png;
285 png_infop info;
286 size_t i;
287
288 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
289 if (png == NULL) {
290 goto f_done;
291 }
292
293 info = png_create_info_struct(png);
294 if (info == NULL) {
295 png_destroy_write_struct(&png, (png_infopp)NULL);
296 goto f_done;
297 }
298
299 if (setjmp(png_jmpbuf(png))) {
300 png_destroy_write_struct(&png, &info);
301 goto f_done;
302 }
303
304 png_init_io(png, f);
305 png_set_IHDR(png, info, x, y, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
306 png_set_invert_alpha(png);
307 png_write_info(png, info);
308 for (i = 0; i < y; i++) {
309 png_write_row(png, (unsigned char *)(pixbuf + (i * x)));
310 }
311 png_write_end(png, info);
312
313 png_destroy_write_struct(&png, &info);
314
315 f_done:
316 fclose(f);
317
318 return retval;
319 }
320 #endif /* HAVE_LIBPNG */
321
322 #ifdef HAVE_LIBVNCSERVER
323 /* create and return a new screen, easiest to do here because we know the screen dimensions */
324 rfbScreenInfoPtr lem1802_rfb_new(int argc, char *argv[]) {
325 rfbScreenInfoPtr s;
326 int paddedWidth = PIX_X + ( (PIX_X & 3) ? (4 - (PIX_X & 3)) : 0 );
327 int height = PIX_Y;
328 int bitsPerSample = 8;
329 int samplesPerPixel = 3;
330 int bytesPerPixel = 4;
331
332 s = rfbGetScreen(&argc, argv, paddedWidth, height, bitsPerSample, samplesPerPixel, bytesPerPixel);
333 if (s == NULL)
334 return NULL;
335
336 s->alwaysShared = TRUE;
337
338 #if 0
339 s->httpDir = "../classes";
340 #endif
341
342 TRACE("%s>> s:%p", __func__, s);
343 TRACE("%s>> s->kbdAddEvent:%p s->frameBuffer:%p", __func__, s->kbdAddEvent, s->frameBuffer);
344
345 return s;
346 }
347
348 /* set up a new screen to see our pixels */
349 void lem1802_vnc_associate(struct dcpu16_hw *hw, rfbScreenInfoPtr s) {
350 struct lem1802_ *display = (struct lem1802_ *)hw->data;
351
352 s->desktopName = "NYA ELEKTRISKA LEM1802";
353 s->frameBuffer = (char *)display->pixbuf;
354
355 TRACE("%s>> s:%p\n", __func__, s);
356 }
357
358 /* notify rfb server that pixels may have changed */
359 static
360 int pixbuf_render_vnc_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
361 rfbScreenInfoPtr s = (rfbScreenInfoPtr)data;
362 int retval = 0;
363
364 (void)pixbuf;
365
366 /* derp */
367 if (s)
368 rfbMarkRectAsModified(s, 0, 0, x, y);
369
370 TRACE("%s>>", __func__);
371
372 return retval;
373 }
374 #endif /* HAVE_LIBVNCSERVER */
375
376
377 static
378 void lem1802_reset_(struct dcpu16 *vm, void *data) {
379 struct lem1802_ *display = (struct lem1802_ *)data;
380
381 (void)vm;
382
383 display->cycle_activated = 0;
384 display->video_base = 0;
385 display->font_base = 0;
386 display->palette_base = 0;
387 display->border_color = 0;
388
389 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
390
391 display->refresh_tally_ = 0;
392 display->blink_tally_ = 0;
393 display->blink_state = 0;
394
395 display->cycle_state_ = 0;
396
397 #if DEBUG
398 vm->trace_cb_("%s>>", __func__);
399 #endif /* DEBUG */
400 }
401
402 static
403 void lem1802_cycle_(struct dcpu16 *vm, void *data) {
404 struct lem1802_ *display = (struct lem1802_ *)data;
405
406 (void)vm;
407 /*
408 for more cycle-accuracy, could step through video memory, if set,
409 one word per clock..
410 for now just count cycles and issue a full refresh/render
411 every so often
412 */
413
414 display->blink_tally_++;
415 if (display->blink_tally_ >= display->blink_rate) {
416 display->blink_tally_ = 0;
417 display->blink_state ^= 1;
418 TRACE("%s>> blink:%u (%u cycles)", __func__, display->blink_state, display->blink_rate);
419 }
420
421 display->refresh_tally_++;
422 if (display->refresh_tally_ >= display->refresh_rate) {
423 display->refresh_tally_ = 0;
424 if (display->render)
425 TRACE("%s>> refresh", __func__);
426 lem1802_pixbuf_refresh_full_(display, vm->ram);
427 display->render(display->renderer_data, display->pixbuf, PIX_X, PIX_Y);
428 }
429
430 switch (display->cycle_state_) {
431 case CYCLE_IDLE:
432 break;
433
434 case CYCLE_COPY_TO_RAM:
435 TRACE("%s>> copy_to_ram words:%zu src:%p dst_addr:0x%04x",
436 __func__,
437 display->cycle_state_copy_words_,
438 display->cycle_state_copy_src_ptr_,
439 display->cycle_state_copy_dst_addr_);
440 vm->ram[display->cycle_state_copy_dst_addr_] = *display->cycle_state_copy_src_ptr_;
441 display->cycle_state_copy_dst_addr_++;
442 display->cycle_state_copy_src_ptr_++;
443 display->cycle_state_copy_words_--;
444 if (display->cycle_state_copy_words_ == 0) {
445 display->cycle_state_ = CYCLE_IDLE;
446 }
447 break;
448 }
449
450 if (display->cycles_until_active_) {
451 display->cycles_until_active_--;
452 if (display->cycles_until_active_ == 0) {
453 TRACE("%s>> display now active", __func__);
454 }
455 }
456 }
457
458 static
459 void lem1802_hwi_(struct dcpu16 *vm, void *data) {
460 struct lem1802_ *display = (struct lem1802_ *)data;
461 DCPU16_WORD reg_a = vm->reg[DCPU16_REG_A];
462 DCPU16_WORD reg_b = vm->reg[DCPU16_REG_B];
463
464 TRACE("%s>> A:0x%04x B:0x%04x", __func__, reg_a, reg_b);
465
466 switch (reg_a) {
467 case 0: /* MEM_MAP_SCREEN */
468 if (display->cycle_activated == 0 && reg_b) {
469 display->cycle_activated = vm->cycle_;
470 display->cycles_until_active_ = power_on_cycles_();
471 }
472 display->video_base = reg_b;
473 if (reg_b == 0)
474 display->cycle_activated = 0;
475 break;
476
477 case 1: /* MEM_MAP_FONT */
478 display->font_base = reg_b;
479 break;
480
481 case 2: /* MEM_MAP_PALETTE */
482 display->palette_base = reg_b;
483 break;
484
485 case 3: /* SET_BORDER_COLOR */
486 display->border_color = reg_b & 0x000f;
487 break;
488
489 case 4: /* MEM_DUMP_FONT */
490 display->cycle_state_copy_src_ptr_ = (DCPU16_WORD *)chargen_4x8_glyphs;
491 display->cycle_state_copy_dst_addr_ = reg_b;
492 display->cycle_state_copy_words_ = 256;
493 display->cycle_state_ = CYCLE_COPY_TO_RAM;
494 dcpu16_cycle_inc(vm, 256);
495 break;
496
497 case 5: /* MEM_DUMP_PALETTE */
498 display->cycle_state_copy_src_ptr_ = palette_default_;
499 display->cycle_state_copy_dst_addr_ = reg_b;
500 display->cycle_state_copy_words_ = 16;
501 display->cycle_state_ = CYCLE_COPY_TO_RAM;
502 dcpu16_cycle_inc(vm, 16);
503 break;
504 }
505 }
506
507 static struct renderer_ {
508 char *name;
509 char *args;
510 int (*renderer)(void *, struct pixel_ *, size_t, size_t);
511 } lem1802_renderers_[] = {
512 { "pnm", "filename", pixbuf_render_pnm_ },
513 #ifdef HAVE_LIBPNG
514 { "png", "filename", pixbuf_render_png_ },
515 #endif /* HAVE_LIBPNG */
516 #ifdef HAVE_LIBVNCSERVER
517 { "vnc", "", pixbuf_render_vnc_ },
518 #endif /* HAVE_LIBVNCSERVER */
519 { "none", "", NULL },
520 { NULL, NULL, NULL }
521 };
522
523 int lem1802_renderer_set(struct dcpu16_hw *hw, const char *renderer, void *data) {
524 struct renderer_ *r;
525
526 for (r = lem1802_renderers_; r->renderer; r++) {
527 if (strcmp(renderer, r->name) == 0) {
528 ((struct lem1802_ *)(hw->data))->render = r->renderer;
529 ((struct lem1802_ *)(hw->data))->renderer_data = data;
530 TRACE("%s>> renderer set to %s", __func__, renderer);
531 return 0;
532 }
533 }
534
535 return -1;
536 }
537
538 char *lem1802_renderers_iter(void **iterp, char **name, char **args) {
539 struct renderer_ **r = (struct renderer_ **)iterp;
540
541 if (*r == NULL)
542 *r = lem1802_renderers_;
543 else
544 (*r)++;
545
546 if ((*r)->name == NULL) {
547 *r = NULL;
548 return NULL;
549 }
550
551 *name = (*r)->name;
552 *args = (*r)->args;
553
554 return (*r)->name;
555 }
556
557 /* instantitate a new display */
558 struct dcpu16_hw *lem1802_new(struct dcpu16 *vm) {
559 struct dcpu16_hw *hw;
560
561 hw = calloc(1, sizeof *hw);
562 if (hw == NULL) {
563 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
564 return NULL;
565 }
566
567 memcpy(hw, &hw_, sizeof *hw);
568
569 hw->data = calloc(1, sizeof(struct dcpu16_hw));
570 if (hw->data == NULL) {
571 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
572 free(hw);
573 return NULL;
574 }
575
576 ((struct lem1802_ *)(hw->data))->pixbuf = calloc(1, PIX_X * PIX_Y * sizeof *((struct lem1802_ *)(hw->data))->pixbuf);
577 if (((struct lem1802_ *)(hw->data))->pixbuf == NULL) {
578 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
579 free(hw->data);
580 free(hw);
581 return NULL;
582 }
583
584 ((struct lem1802_ *)(hw->data))->refresh_rate = 1666;
585 ((struct lem1802_ *)(hw->data))->blink_rate = 75000;
586
587 hw->vm = vm;
588
589 return hw;
590 }
591
592 void lem1802_del(struct dcpu16_hw **hw) {
593 if (hw) {
594 if (*hw) {
595 if ((*hw)->data) {
596 if (((struct lem1802_ *)(*hw)->data)->pixbuf) {
597 free(((struct lem1802_ *)(*hw)->data)->pixbuf);
598 ((struct lem1802_ *)(*hw)->data)->pixbuf = NULL;
599 }
600 free((*hw)->data);
601 (*hw)->data = NULL;
602 }
603 free(*hw);
604 }
605 *hw = NULL;
606 }
607 }