src/main.c

Thu, 02 Dec 2010 23:30:13 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Thu, 02 Dec 2010 23:30:13 +0000
changeset 41
75887b42d7e5
parent 40
239bc48590ba
child 42
1d55c8c7c1ac
permissions
-rw-r--r--

add video emulation

philpem@0 1 #include <stdio.h>
philpem@7 2 #include <stdlib.h>
philpem@4 3 #include <stdint.h>
philpem@7 4 #include <stdbool.h>
philpem@7 5 #include <malloc.h>
philpem@7 6 #include <string.h>
philpem@18 7
philpem@20 8 #include "SDL.h"
philpem@20 9
philpem@4 10 #include "musashi/m68k.h"
philpem@7 11 #include "version.h"
philpem@18 12 #include "state.h"
philpem@40 13 #include "memory.h"
philpem@7 14
philpem@7 15 void FAIL(char *err)
philpem@7 16 {
philpem@7 17 state_done();
philpem@7 18 fprintf(stderr, "ERROR: %s\nExiting...\n", err);
philpem@7 19 exit(EXIT_FAILURE);
philpem@7 20 }
philpem@7 21
philpem@41 22 /**
philpem@41 23 * @brief Set the pixel at (x, y) to the given value
philpem@41 24 * @note The surface must be locked before calling this!
philpem@41 25 * @param surface SDL surface upon which to draw
philpem@41 26 * @param x X co-ordinate
philpem@41 27 * @param y Y co-ordinate
philpem@41 28 * @param pixel Pixel value (from SDL_MapRGB)
philpem@41 29 */
philpem@41 30 void putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
philpem@41 31 {
philpem@41 32 int bpp = surface->format->BytesPerPixel;
philpem@41 33 /* Here p is the address to the pixel we want to set */
philpem@41 34 Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
philpem@41 35
philpem@41 36 switch (bpp) {
philpem@41 37 case 1:
philpem@41 38 *p = pixel;
philpem@41 39 break;
philpem@41 40
philpem@41 41 case 2:
philpem@41 42 *(Uint16 *)p = pixel;
philpem@41 43 break;
philpem@41 44
philpem@41 45 case 3:
philpem@41 46 if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
philpem@41 47 p[0] = (pixel >> 16) & 0xff;
philpem@41 48 p[1] = (pixel >> 8) & 0xff;
philpem@41 49 p[2] = pixel & 0xff;
philpem@41 50 }
philpem@41 51 else {
philpem@41 52 p[0] = pixel & 0xff;
philpem@41 53 p[1] = (pixel >> 8) & 0xff;
philpem@41 54 p[2] = (pixel >> 16) & 0xff;
philpem@41 55 }
philpem@41 56 break;
philpem@41 57
philpem@41 58 case 4:
philpem@41 59 *(Uint32 *)p = pixel;
philpem@41 60 break;
philpem@41 61
philpem@41 62 default:
philpem@41 63 break; /* shouldn't happen, but avoids warnings */
philpem@41 64 } // switch
philpem@41 65 }
philpem@41 66
philpem@41 67
philpem@41 68 /**
philpem@41 69 * @brief Refresh the screen.
philpem@41 70 * @param surface SDL surface upon which to draw.
philpem@41 71 */
philpem@41 72 void refreshScreen(SDL_Surface *s)
philpem@41 73 {
philpem@41 74 // Lock the screen surface (if necessary)
philpem@41 75 if (SDL_MUSTLOCK(s)) {
philpem@41 76 if (SDL_LockSurface(s) < 0) {
philpem@41 77 fprintf(stderr, "ERROR: Unable to lock screen!\n");
philpem@41 78 exit(EXIT_FAILURE);
philpem@41 79 }
philpem@41 80 }
philpem@41 81
philpem@41 82 // Map the foreground and background colours
philpem@41 83 Uint32 fg = SDL_MapRGB(s->format, 0, 255, 0); // green foreground
philpem@41 84 Uint32 bg = SDL_MapRGB(s->format, 0, 0, 0); // black background
philpem@41 85
philpem@41 86 // Refresh the 3B1 screen area first. TODO: only do this if VRAM has actually changed!
philpem@41 87 uint32_t vram_address = 0;
philpem@41 88 for (int y=0; y<348; y++) {
philpem@41 89 for (int x=0; x<720; x+=16) { // 720 pixels, monochrome, packed into 16bit words
philpem@41 90 // Get the pixel
philpem@41 91 uint16_t val = RD16(state.vram, vram_address, sizeof(state.vram)-1);
philpem@41 92 vram_address += 2;
philpem@41 93 // Now copy it to the video buffer
philpem@41 94 for (int px=0; px<16; px++) {
philpem@41 95 if (val & 1)
philpem@41 96 putpixel(s, x+px, y, fg);
philpem@41 97 else
philpem@41 98 putpixel(s, x+px, y, bg);
philpem@41 99 }
philpem@41 100 }
philpem@41 101 }
philpem@41 102
philpem@41 103 // TODO: blit LEDs and status info
philpem@41 104
philpem@41 105 // Unlock the screen surface
philpem@41 106 if (SDL_MUSTLOCK(s)) {
philpem@41 107 SDL_UnlockSurface(s);
philpem@41 108 }
philpem@41 109
philpem@41 110 // Trigger a refresh -- TODO: partial refresh depending on whether we
philpem@41 111 // refreshed the screen area, status area, both, or none. Use SDL_UpdateRect() for this.
philpem@41 112 SDL_Flip(s);
philpem@41 113 }
philpem@41 114
philpem@27 115 /****************************
philpem@27 116 * blessed be thy main()...
philpem@27 117 ****************************/
philpem@27 118
philpem@0 119 int main(void)
philpem@0 120 {
philpem@7 121 // copyright banner
philpem@16 122 printf("FreeBee: A Quick-and-Dirty AT&T 3B1 Emulator. Version %s, %s mode.\n", VER_FULLSTR, VER_BUILD_TYPE);
philpem@17 123 printf("Copyright (C) 2010 P. A. Pemberton. All rights reserved.\nLicensed under the Apache License Version 2.0.\n");
philpem@17 124 printf("Musashi M680x0 emulator engine developed by Karl Stenerud <kstenerud@gmail.com>\n");
philpem@16 125 printf("Built %s by %s@%s.\n", VER_COMPILE_DATETIME, VER_COMPILE_BY, VER_COMPILE_HOST);
philpem@16 126 printf("Compiler: %s\n", VER_COMPILER);
philpem@16 127 printf("CFLAGS: %s\n", VER_CFLAGS);
philpem@17 128 printf("\n");
philpem@7 129
philpem@7 130 // set up system state
philpem@7 131 // 512K of RAM
philpem@18 132 state_init(512*1024);
philpem@7 133
philpem@20 134 // set up musashi and reset the CPU
philpem@7 135 m68k_set_cpu_type(M68K_CPU_TYPE_68010);
philpem@7 136 m68k_pulse_reset();
philpem@9 137
philpem@28 138 // Set up SDL
philpem@20 139 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) {
philpem@20 140 printf("Could not initialise SDL: %s.\n", SDL_GetError());
philpem@28 141 exit(EXIT_FAILURE);
philpem@20 142 }
philpem@7 143
philpem@28 144 // Make sure SDL cleans up after itself
philpem@28 145 atexit(SDL_Quit);
philpem@28 146
philpem@28 147 // Set up the video display
philpem@28 148 SDL_Surface *screen = NULL;
philpem@28 149 if ((screen = SDL_SetVideoMode(720, 384, 8, SDL_SWSURFACE | SDL_ANYFORMAT)) == NULL) {
philpem@28 150 printf("Could not find a suitable video mode: %s.\n", SDL_GetError());
philpem@28 151 exit(EXIT_FAILURE);
philpem@28 152 }
philpem@32 153 printf("Set %dx%d at %d bits-per-pixel mode\n\n", screen->w, screen->h, screen->format->BitsPerPixel);
philpem@28 154 SDL_WM_SetCaption("FreeBee 3B1 emulator", "FreeBee");
philpem@28 155
philpem@20 156 /***
philpem@20 157 * The 3B1 CPU runs at 10MHz, with DMA running at 1MHz and video refreshing at
philpem@20 158 * around 60Hz (???), with a 60Hz periodic interrupt.
philpem@20 159 */
philpem@20 160 const uint32_t TIMESLOT_FREQUENCY = 240; // Hz
philpem@20 161 const uint32_t MILLISECS_PER_TIMESLOT = 1e3 / TIMESLOT_FREQUENCY;
philpem@20 162 const uint32_t CLOCKS_PER_60HZ = (10e6 / 60);
philpem@20 163 uint32_t next_timeslot = SDL_GetTicks() + MILLISECS_PER_TIMESLOT;
philpem@20 164 uint32_t clock_cycles = 0;
philpem@16 165 bool exitEmu = false;
philpem@16 166 for (;;) {
philpem@20 167 // Run the CPU for however many cycles we need to. CPU core clock is
philpem@20 168 // 10MHz, and we're running at 240Hz/timeslot. Thus: 10e6/240 or
philpem@20 169 // 41667 cycles per timeslot.
philpem@20 170 clock_cycles += m68k_execute(10e6/TIMESLOT_FREQUENCY);
philpem@20 171
philpem@20 172 // TODO: run DMA here
philpem@16 173
philpem@20 174 // Is it time to run the 60Hz periodic interrupt yet?
philpem@20 175 if (clock_cycles > CLOCKS_PER_60HZ) {
philpem@41 176 // Refresh the screen
philpem@41 177 refreshScreen(screen);
philpem@20 178 // TODO: trigger periodic interrupt (if enabled)
philpem@20 179 // decrement clock cycle counter, we've handled the intr.
philpem@20 180 clock_cycles -= CLOCKS_PER_60HZ;
philpem@16 181 }
philpem@16 182
philpem@20 183 // make sure frame rate is equal to real time
philpem@20 184 uint32_t now = SDL_GetTicks();
philpem@20 185 if (now < next_timeslot) {
philpem@20 186 // timeslot finished early -- eat up some time
philpem@20 187 SDL_Delay(next_timeslot - now);
philpem@20 188 } else {
philpem@20 189 // timeslot finished late -- skip ahead to gain time
philpem@20 190 // TODO: if this happens a lot, we should let the user know
philpem@20 191 // that their PC might not be fast enough...
philpem@20 192 next_timeslot = now;
philpem@20 193 }
philpem@20 194 // advance to the next timeslot
philpem@20 195 next_timeslot += MILLISECS_PER_TIMESLOT;
philpem@20 196
philpem@20 197 // if we've been asked to exit the emulator, then do so.
philpem@16 198 if (exitEmu) break;
philpem@16 199 }
philpem@7 200
philpem@7 201 // shut down and exit
philpem@20 202 SDL_Quit();
philpem@7 203
philpem@0 204 return 0;
philpem@0 205 }