src/main.c

Wed, 13 Mar 2013 01:10:34 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Wed, 13 Mar 2013 01:10:34 +0000
branch
experimental_memory_mapper_v2
changeset 135
159f937af10d
parent 128
3246b74d96bc
parent 134
b826697f411a
permissions
-rw-r--r--

merge changes from default

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@111 22 static int load_fd()
philpem@111 23 {
philpem@111 24
philpem@111 25 int writeable = 1;
philpem@111 26 state.fdc_disc = fopen("discim", "r+b");
philpem@111 27 if (!state.fdc_disc){
philpem@111 28 writeable = 0;
philpem@111 29 state.fdc_disc = fopen("discim", "rb");
philpem@111 30 }
philpem@111 31 if (!state.fdc_disc){
philpem@111 32 fprintf(stderr, "ERROR loading disc image 'discim'.\n");
philpem@111 33 state.fdc_disc = NULL;
philpem@111 34 return (0);
philpem@111 35 }else{
philpem@111 36 wd2797_load(&state.fdc_ctx, state.fdc_disc, 512, 10, 2, writeable);
philpem@111 37 fprintf(stderr, "Disc image loaded.\n");
philpem@111 38 return (1);
philpem@111 39 }
philpem@111 40 }
philpem@111 41
philpem@112 42 static int load_hd()
philpem@112 43 {
philpem@112 44
philpem@112 45 state.hdc_disc0 = fopen("hd.img", "r+b");
philpem@112 46 if (!state.hdc_disc0){
philpem@112 47 fprintf(stderr, "ERROR loading disc image 'hd.img'.\n");
philpem@112 48 state.hdc_disc0 = NULL;
philpem@112 49 return (0);
philpem@112 50 }else{
philpem@134 51 wd2010_init(&state.hdc_ctx, state.hdc_disc0, 512, 16, 8);
philpem@112 52 fprintf(stderr, "Disc image loaded.\n");
philpem@112 53 return (1);
philpem@112 54 }
philpem@112 55 }
philpem@112 56
philpem@112 57
philpem@112 58
philpem@41 59 /**
philpem@41 60 * @brief Set the pixel at (x, y) to the given value
philpem@41 61 * @note The surface must be locked before calling this!
philpem@41 62 * @param surface SDL surface upon which to draw
philpem@41 63 * @param x X co-ordinate
philpem@41 64 * @param y Y co-ordinate
philpem@41 65 * @param pixel Pixel value (from SDL_MapRGB)
philpem@41 66 */
philpem@41 67 void putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
philpem@41 68 {
philpem@41 69 int bpp = surface->format->BytesPerPixel;
philpem@41 70 /* Here p is the address to the pixel we want to set */
philpem@41 71 Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
philpem@41 72
philpem@41 73 switch (bpp) {
philpem@41 74 case 1:
philpem@41 75 *p = pixel;
philpem@41 76 break;
philpem@41 77
philpem@41 78 case 2:
philpem@41 79 *(Uint16 *)p = pixel;
philpem@41 80 break;
philpem@41 81
philpem@41 82 case 3:
philpem@41 83 if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
philpem@41 84 p[0] = (pixel >> 16) & 0xff;
philpem@41 85 p[1] = (pixel >> 8) & 0xff;
philpem@41 86 p[2] = pixel & 0xff;
philpem@41 87 }
philpem@41 88 else {
philpem@41 89 p[0] = pixel & 0xff;
philpem@41 90 p[1] = (pixel >> 8) & 0xff;
philpem@41 91 p[2] = (pixel >> 16) & 0xff;
philpem@41 92 }
philpem@41 93 break;
philpem@41 94
philpem@41 95 case 4:
philpem@41 96 *(Uint32 *)p = pixel;
philpem@41 97 break;
philpem@41 98
philpem@41 99 default:
philpem@41 100 break; /* shouldn't happen, but avoids warnings */
philpem@41 101 } // switch
philpem@41 102 }
philpem@41 103
philpem@41 104
philpem@41 105 /**
philpem@41 106 * @brief Refresh the screen.
philpem@41 107 * @param surface SDL surface upon which to draw.
philpem@41 108 */
philpem@41 109 void refreshScreen(SDL_Surface *s)
philpem@41 110 {
philpem@41 111 // Lock the screen surface (if necessary)
philpem@41 112 if (SDL_MUSTLOCK(s)) {
philpem@41 113 if (SDL_LockSurface(s) < 0) {
philpem@41 114 fprintf(stderr, "ERROR: Unable to lock screen!\n");
philpem@41 115 exit(EXIT_FAILURE);
philpem@41 116 }
philpem@41 117 }
philpem@41 118
philpem@41 119 // Map the foreground and background colours
philpem@42 120 Uint32 fg = SDL_MapRGB(s->format, 0x00, 0xFF, 0x00); // green foreground
philpem@42 121 // Uint32 fg = SDL_MapRGB(s->format, 0xFF, 0xC1, 0x06); // amber foreground
philpem@42 122 // Uint32 fg = SDL_MapRGB(s->format, 0xFF, 0xFF, 0xFF); // white foreground
philpem@42 123 Uint32 bg = SDL_MapRGB(s->format, 0x00, 0x00, 0x00); // black background
philpem@41 124
philpem@41 125 // Refresh the 3B1 screen area first. TODO: only do this if VRAM has actually changed!
philpem@41 126 uint32_t vram_address = 0;
philpem@41 127 for (int y=0; y<348; y++) {
philpem@41 128 for (int x=0; x<720; x+=16) { // 720 pixels, monochrome, packed into 16bit words
philpem@41 129 // Get the pixel
philpem@41 130 uint16_t val = RD16(state.vram, vram_address, sizeof(state.vram)-1);
philpem@41 131 vram_address += 2;
philpem@41 132 // Now copy it to the video buffer
philpem@41 133 for (int px=0; px<16; px++) {
philpem@41 134 if (val & 1)
philpem@41 135 putpixel(s, x+px, y, fg);
philpem@41 136 else
philpem@41 137 putpixel(s, x+px, y, bg);
philpem@45 138 val >>= 1;
philpem@41 139 }
philpem@41 140 }
philpem@41 141 }
philpem@41 142
philpem@41 143 // TODO: blit LEDs and status info
philpem@41 144
philpem@41 145 // Unlock the screen surface
philpem@41 146 if (SDL_MUSTLOCK(s)) {
philpem@41 147 SDL_UnlockSurface(s);
philpem@41 148 }
philpem@41 149
philpem@41 150 // Trigger a refresh -- TODO: partial refresh depending on whether we
philpem@41 151 // refreshed the screen area, status area, both, or none. Use SDL_UpdateRect() for this.
philpem@41 152 SDL_Flip(s);
philpem@41 153 }
philpem@41 154
philpem@47 155 /**
philpem@47 156 * @brief Handle events posted by SDL.
philpem@47 157 */
philpem@47 158 bool HandleSDLEvents(SDL_Surface *screen)
philpem@47 159 {
philpem@47 160 SDL_Event event;
philpem@47 161 while (SDL_PollEvent(&event))
philpem@47 162 {
philpem@80 163 if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) {
philpem@80 164 keyboard_event(&state.kbd, &event);
philpem@80 165 }
philpem@80 166
philpem@47 167 switch (event.type) {
philpem@47 168 case SDL_QUIT:
philpem@47 169 // Quit button tagged. Exit.
philpem@47 170 return true;
philpem@47 171 case SDL_KEYDOWN:
philpem@47 172 switch (event.key.keysym.sym) {
philpem@95 173 case SDLK_F11:
philpem@95 174 if (state.fdc_disc) {
philpem@95 175 wd2797_unload(&state.fdc_ctx);
philpem@95 176 fclose(state.fdc_disc);
philpem@95 177 state.fdc_disc = NULL;
philpem@95 178 fprintf(stderr, "Disc image unloaded.\n");
philpem@95 179 } else {
philpem@111 180 load_fd();
philpem@95 181 }
philpem@95 182 break;
philpem@48 183 case SDLK_F12:
philpem@47 184 if (event.key.keysym.mod & (KMOD_LALT | KMOD_RALT))
philpem@48 185 // ALT-F12 pressed; exit emulator
philpem@47 186 return true;
philpem@47 187 break;
philpem@47 188 default:
philpem@47 189 break;
philpem@47 190 }
philpem@47 191 break;
philpem@47 192 default:
philpem@47 193 break;
philpem@47 194 }
philpem@47 195 }
philpem@47 196
philpem@47 197 return false;
philpem@47 198 }
philpem@47 199
philpem@47 200
philpem@27 201 /****************************
philpem@27 202 * blessed be thy main()...
philpem@27 203 ****************************/
philpem@27 204
philpem@0 205 int main(void)
philpem@0 206 {
philpem@7 207 // copyright banner
philpem@16 208 printf("FreeBee: A Quick-and-Dirty AT&T 3B1 Emulator. Version %s, %s mode.\n", VER_FULLSTR, VER_BUILD_TYPE);
philpem@17 209 printf("Copyright (C) 2010 P. A. Pemberton. All rights reserved.\nLicensed under the Apache License Version 2.0.\n");
philpem@17 210 printf("Musashi M680x0 emulator engine developed by Karl Stenerud <kstenerud@gmail.com>\n");
philpem@16 211 printf("Built %s by %s@%s.\n", VER_COMPILE_DATETIME, VER_COMPILE_BY, VER_COMPILE_HOST);
philpem@16 212 printf("Compiler: %s\n", VER_COMPILER);
philpem@16 213 printf("CFLAGS: %s\n", VER_CFLAGS);
philpem@17 214 printf("\n");
philpem@7 215
philpem@7 216 // set up system state
philpem@7 217 // 512K of RAM
philpem@55 218 int i;
philpem@120 219 if ((i = state_init(2048*1024, 2048*1024)) != STATE_E_OK) {
philpem@55 220 fprintf(stderr, "ERROR: Emulator initialisation failed. Error code %d.\n", i);
philpem@55 221 return i;
philpem@55 222 }
philpem@7 223
philpem@20 224 // set up musashi and reset the CPU
philpem@7 225 m68k_set_cpu_type(M68K_CPU_TYPE_68010);
philpem@7 226 m68k_pulse_reset();
philpem@9 227
philpem@28 228 // Set up SDL
philpem@20 229 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) {
philpem@20 230 printf("Could not initialise SDL: %s.\n", SDL_GetError());
philpem@28 231 exit(EXIT_FAILURE);
philpem@20 232 }
philpem@7 233
philpem@28 234 // Make sure SDL cleans up after itself
philpem@28 235 atexit(SDL_Quit);
philpem@28 236
philpem@28 237 // Set up the video display
philpem@28 238 SDL_Surface *screen = NULL;
philpem@28 239 if ((screen = SDL_SetVideoMode(720, 384, 8, SDL_SWSURFACE | SDL_ANYFORMAT)) == NULL) {
philpem@28 240 printf("Could not find a suitable video mode: %s.\n", SDL_GetError());
philpem@28 241 exit(EXIT_FAILURE);
philpem@28 242 }
philpem@32 243 printf("Set %dx%d at %d bits-per-pixel mode\n\n", screen->w, screen->h, screen->format->BitsPerPixel);
philpem@28 244 SDL_WM_SetCaption("FreeBee 3B1 emulator", "FreeBee");
philpem@28 245
philpem@52 246 // Load a disc image
philpem@111 247 load_fd();
philpem@52 248
philpem@112 249 load_hd();
philpem@112 250
philpem@20 251 /***
philpem@20 252 * The 3B1 CPU runs at 10MHz, with DMA running at 1MHz and video refreshing at
philpem@20 253 * around 60Hz (???), with a 60Hz periodic interrupt.
philpem@20 254 */
philpem@92 255 const uint32_t SYSTEM_CLOCK = 10e6; // Hz
philpem@76 256 const uint32_t TIMESLOT_FREQUENCY = 1000;//240; // Hz
philpem@20 257 const uint32_t MILLISECS_PER_TIMESLOT = 1e3 / TIMESLOT_FREQUENCY;
philpem@92 258 const uint32_t CLOCKS_PER_60HZ = (SYSTEM_CLOCK / 60);
philpem@20 259 uint32_t next_timeslot = SDL_GetTicks() + MILLISECS_PER_TIMESLOT;
philpem@92 260 uint32_t clock_cycles = 0, tmp;
philpem@16 261 bool exitEmu = false;
philpem@112 262
philpem@112 263 /*bool lastirq_fdc = false;*/
philpem@16 264 for (;;) {
philpem@20 265 // Run the CPU for however many cycles we need to. CPU core clock is
philpem@20 266 // 10MHz, and we're running at 240Hz/timeslot. Thus: 10e6/240 or
philpem@20 267 // 41667 cycles per timeslot.
philpem@92 268 tmp = m68k_execute(SYSTEM_CLOCK/TIMESLOT_FREQUENCY);
philpem@91 269 clock_cycles += tmp;
philpem@20 270
philpem@53 271 // Run the DMA engine
philpem@76 272 if (state.dmaen) {
philpem@53 273 // DMA ready to go -- so do it.
philpem@53 274 size_t num = 0;
philpem@53 275 while (state.dma_count < 0x4000) {
philpem@53 276 uint16_t d = 0;
philpem@53 277 // num tells us how many words we've copied. If this is greater than the per-timeslot DMA maximum, bail out!
philpem@53 278 if (num > (1e6/TIMESLOT_FREQUENCY)) break;
philpem@53 279
philpem@53 280 // Evidently we have more words to copy. Copy them.
philpem@112 281 if (state.fd_selected){
philpem@112 282 if (!wd2797_get_drq(&state.fdc_ctx)) {
philpem@112 283 // Bail out, no data available. Try again later.
philpem@112 284 break;
philpem@112 285 }
philpem@112 286 }else if (state.hd_selected){
philpem@112 287 if (!wd2010_get_drq(&state.hdc_ctx)) {
philpem@112 288 // Bail out, no data available. Try again later.
philpem@112 289 break;
philpem@112 290 }
philpem@112 291 }else{
philpem@112 292 printf("ERROR: DMA attempt with no drive selected!\n");
philpem@112 293 }
philpem@128 294
philpem@128 295 if (access_check_dma()) {
philpem@128 296
philpem@128 297 uint32_t newAddr;
philpem@128 298 // Map logical address to a physical RAM address
philpem@128 299 newAddr = MAP_ADDR(state.dma_address);
philpem@67 300
philpem@128 301 if (!state.dma_reading) {
philpem@128 302 // Data available. Get it from the FDC or HDC.
philpem@128 303 if (state.fd_selected) {
philpem@128 304 d = wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
philpem@128 305 d <<= 8;
philpem@128 306 d += wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
philpem@128 307 }else if (state.hd_selected) {
philpem@128 308 d = wd2010_read_data(&state.hdc_ctx);
philpem@128 309 d <<= 8;
philpem@128 310 d += wd2010_read_data(&state.hdc_ctx);
philpem@128 311 }
philpem@128 312 if (newAddr <= 0x1FFFFF) {
philpem@128 313 WR16(state.base_ram, newAddr, state.base_ram_size - 1, d);
philpem@128 314 } else if (newAddr >= 0x200000) {
philpem@128 315 WR16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1, d);
philpem@128 316 }
philpem@128 317 } else {
philpem@128 318 // Data write to FDC or HDC.
philpem@53 319
philpem@128 320 // Get the data from RAM
philpem@128 321 if (newAddr <= 0x1fffff) {
philpem@128 322 d = RD16(state.base_ram, newAddr, state.base_ram_size - 1);
philpem@128 323 } else {
philpem@128 324 if (newAddr <= (state.exp_ram_size + 0x200000 - 1))
philpem@128 325 d = RD16(state.exp_ram, newAddr - 0x200000, state.exp_ram_size - 1);
philpem@128 326 else
philpem@128 327 d = 0xffff;
philpem@128 328 }
philpem@67 329
philpem@128 330 // Send the data to the FDD or HDD
philpem@128 331 if (state.fd_selected){
philpem@128 332 wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d >> 8));
philpem@128 333 wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d & 0xff));
philpem@128 334 }else if (state.hd_selected){
philpem@128 335 wd2010_write_data(&state.hdc_ctx, (d >> 8));
philpem@128 336 wd2010_write_data(&state.hdc_ctx, (d & 0xff));
philpem@128 337 }
philpem@112 338 }
philpem@53 339 }
philpem@53 340
philpem@53 341 // Increment DMA address
philpem@57 342 state.dma_address+=2;
philpem@53 343 // Increment number of words transferred
philpem@53 344 num++; state.dma_count++;
philpem@53 345 }
philpem@53 346
philpem@53 347 // Turn off DMA engine if we finished this cycle
philpem@53 348 if (state.dma_count >= 0x4000) {
philpem@67 349 // FIXME? apparently this isn't required... or is it?
philpem@112 350 state.dma_count = 0x3fff;
philpem@112 351 /*state.dmaen = false;*/
philpem@53 352 }
philpem@112 353 }else if (wd2010_get_drq(&state.hdc_ctx)){
philpem@112 354 wd2010_dma_miss(&state.hdc_ctx);
philpem@111 355 }else if (wd2797_get_drq(&state.fdc_ctx)){
philpem@111 356 wd2797_dma_miss(&state.fdc_ctx);
philpem@53 357 }
philpem@53 358
philpem@111 359
philpem@56 360 // Any interrupts? --> TODO: masking
philpem@78 361 /* if (!lastirq_fdc) {
philpem@76 362 if (wd2797_get_irq(&state.fdc_ctx)) {
philpem@76 363 lastirq_fdc = true;
philpem@76 364 m68k_set_irq(2);
philpem@76 365 }
philpem@112 366 }
philpem@92 367 */
philpem@112 368 if (wd2797_get_irq(&state.fdc_ctx) || wd2010_get_irq(&state.hdc_ctx)) {
philpem@111 369 m68k_set_irq(2);
philpem@111 370 }else if (keyboard_get_irq(&state.kbd)) {
philpem@92 371 m68k_set_irq(3);
philpem@53 372 } else {
philpem@107 373 // if (!state.timer_asserted){
philpem@111 374 m68k_set_irq(0);
philpem@107 375 // }
philpem@53 376 }
philpem@85 377
philpem@20 378 // Is it time to run the 60Hz periodic interrupt yet?
philpem@20 379 if (clock_cycles > CLOCKS_PER_60HZ) {
philpem@41 380 // Refresh the screen
philpem@41 381 refreshScreen(screen);
philpem@97 382 if (state.timer_enabled){
philpem@97 383 m68k_set_irq(6);
philpem@97 384 state.timer_asserted = true;
philpem@97 385 }
philpem@92 386 // scan the keyboard
philpem@92 387 keyboard_scan(&state.kbd);
philpem@91 388 // decrement clock cycle counter, we've handled the intr.
philpem@91 389 clock_cycles -= CLOCKS_PER_60HZ;
philpem@91 390 }
philpem@91 391
philpem@47 392 // handle SDL events -- returns true if we need to exit
philpem@47 393 if (HandleSDLEvents(screen))
philpem@47 394 exitEmu = true;
philpem@47 395
philpem@20 396 // make sure frame rate is equal to real time
philpem@20 397 uint32_t now = SDL_GetTicks();
philpem@20 398 if (now < next_timeslot) {
philpem@20 399 // timeslot finished early -- eat up some time
philpem@20 400 SDL_Delay(next_timeslot - now);
philpem@20 401 } else {
philpem@20 402 // timeslot finished late -- skip ahead to gain time
philpem@20 403 // TODO: if this happens a lot, we should let the user know
philpem@20 404 // that their PC might not be fast enough...
philpem@20 405 next_timeslot = now;
philpem@20 406 }
philpem@20 407 // advance to the next timeslot
philpem@20 408 next_timeslot += MILLISECS_PER_TIMESLOT;
philpem@20 409
philpem@20 410 // if we've been asked to exit the emulator, then do so.
philpem@16 411 if (exitEmu) break;
philpem@16 412 }
philpem@7 413
philpem@52 414 // Release the disc image
philpem@52 415 wd2797_unload(&state.fdc_ctx);
philpem@95 416 fclose(state.fdc_disc);
philpem@52 417
philpem@0 418 return 0;
philpem@0 419 }