src/main.c

Fri, 12 Apr 2013 16:26:25 +0100

author
Philip Pemberton <philpem@philpem.me.uk>
date
Fri, 12 Apr 2013 16:26:25 +0100
branch
experimental_memory_mapper_v2
changeset 144
609707511166
parent 135
159f937af10d
permissions
-rw-r--r--

Don't set PS1 if there is a level-7 interrupt or bus error

PS1 should only be set if the page was originally present (PS1 or PS0 set). If
PS0 and PS1 are clear (page not present) then do NOT set PS1.

Once again the TRM is blatantly and spectacularly wrong...

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 }