src/main.c

Wed, 02 Mar 2011 07:16:32 +0000

author
Philip Pemberton <philpem@philpem.me.uk>
date
Wed, 02 Mar 2011 07:16:32 +0000
changeset 97
240e195e4bed
parent 95
6e01339b218d
child 107
566cfc70ef33
permissions
-rw-r--r--

Add 60Hz timer tick patch from Andrew Warkentin <andreww591 gmail com>

... I have also attached a patch that adds the 60Hz timer interrupt (I'm not sure if it's totally correct, though, since the cursor blinks rather slowly).

Received-From: Andrew Warkentin <andreww591 gmail com>
Signed-Off-By: Philip Pemberton <philpem@philpem.me.uk>

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