Mon, 06 Dec 2010 01:43:04 +0000
fix side-select bug in WDC FDC driver, was causing all reads to occur on side0... now the Loader boots!
Loader will boot, but immediately gives up on the floppy drive... Not sure why.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <stdint.h>
4 #include <stdbool.h>
5 #include <malloc.h>
6 #include <string.h>
8 #include "SDL.h"
10 #include "musashi/m68k.h"
11 #include "version.h"
12 #include "state.h"
13 #include "memory.h"
15 void FAIL(char *err)
16 {
17 state_done();
18 fprintf(stderr, "ERROR: %s\nExiting...\n", err);
19 exit(EXIT_FAILURE);
20 }
22 /**
23 * @brief Set the pixel at (x, y) to the given value
24 * @note The surface must be locked before calling this!
25 * @param surface SDL surface upon which to draw
26 * @param x X co-ordinate
27 * @param y Y co-ordinate
28 * @param pixel Pixel value (from SDL_MapRGB)
29 */
30 void putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
31 {
32 int bpp = surface->format->BytesPerPixel;
33 /* Here p is the address to the pixel we want to set */
34 Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;
36 switch (bpp) {
37 case 1:
38 *p = pixel;
39 break;
41 case 2:
42 *(Uint16 *)p = pixel;
43 break;
45 case 3:
46 if (SDL_BYTEORDER == SDL_BIG_ENDIAN) {
47 p[0] = (pixel >> 16) & 0xff;
48 p[1] = (pixel >> 8) & 0xff;
49 p[2] = pixel & 0xff;
50 }
51 else {
52 p[0] = pixel & 0xff;
53 p[1] = (pixel >> 8) & 0xff;
54 p[2] = (pixel >> 16) & 0xff;
55 }
56 break;
58 case 4:
59 *(Uint32 *)p = pixel;
60 break;
62 default:
63 break; /* shouldn't happen, but avoids warnings */
64 } // switch
65 }
68 /**
69 * @brief Refresh the screen.
70 * @param surface SDL surface upon which to draw.
71 */
72 void refreshScreen(SDL_Surface *s)
73 {
74 // Lock the screen surface (if necessary)
75 if (SDL_MUSTLOCK(s)) {
76 if (SDL_LockSurface(s) < 0) {
77 fprintf(stderr, "ERROR: Unable to lock screen!\n");
78 exit(EXIT_FAILURE);
79 }
80 }
82 // Map the foreground and background colours
83 Uint32 fg = SDL_MapRGB(s->format, 0x00, 0xFF, 0x00); // green foreground
84 // Uint32 fg = SDL_MapRGB(s->format, 0xFF, 0xC1, 0x06); // amber foreground
85 // Uint32 fg = SDL_MapRGB(s->format, 0xFF, 0xFF, 0xFF); // white foreground
86 Uint32 bg = SDL_MapRGB(s->format, 0x00, 0x00, 0x00); // black background
88 // Refresh the 3B1 screen area first. TODO: only do this if VRAM has actually changed!
89 uint32_t vram_address = 0;
90 for (int y=0; y<348; y++) {
91 for (int x=0; x<720; x+=16) { // 720 pixels, monochrome, packed into 16bit words
92 // Get the pixel
93 uint16_t val = RD16(state.vram, vram_address, sizeof(state.vram)-1);
94 vram_address += 2;
95 // Now copy it to the video buffer
96 for (int px=0; px<16; px++) {
97 if (val & 1)
98 putpixel(s, x+px, y, fg);
99 else
100 putpixel(s, x+px, y, bg);
101 val >>= 1;
102 }
103 }
104 }
106 // TODO: blit LEDs and status info
108 // Unlock the screen surface
109 if (SDL_MUSTLOCK(s)) {
110 SDL_UnlockSurface(s);
111 }
113 // Trigger a refresh -- TODO: partial refresh depending on whether we
114 // refreshed the screen area, status area, both, or none. Use SDL_UpdateRect() for this.
115 SDL_Flip(s);
116 }
118 /**
119 * @brief Handle events posted by SDL.
120 */
121 bool HandleSDLEvents(SDL_Surface *screen)
122 {
123 SDL_Event event;
124 while (SDL_PollEvent(&event))
125 {
126 switch (event.type) {
127 case SDL_QUIT:
128 // Quit button tagged. Exit.
129 return true;
130 case SDL_KEYDOWN:
131 switch (event.key.keysym.sym) {
132 case SDLK_F12:
133 if (event.key.keysym.mod & (KMOD_LALT | KMOD_RALT))
134 // ALT-F12 pressed; exit emulator
135 return true;
136 break;
137 default:
138 break;
139 }
140 break;
141 default:
142 break;
143 }
144 }
146 return false;
147 }
150 /****************************
151 * blessed be thy main()...
152 ****************************/
154 int main(void)
155 {
156 // copyright banner
157 printf("FreeBee: A Quick-and-Dirty AT&T 3B1 Emulator. Version %s, %s mode.\n", VER_FULLSTR, VER_BUILD_TYPE);
158 printf("Copyright (C) 2010 P. A. Pemberton. All rights reserved.\nLicensed under the Apache License Version 2.0.\n");
159 printf("Musashi M680x0 emulator engine developed by Karl Stenerud <kstenerud@gmail.com>\n");
160 printf("Built %s by %s@%s.\n", VER_COMPILE_DATETIME, VER_COMPILE_BY, VER_COMPILE_HOST);
161 printf("Compiler: %s\n", VER_COMPILER);
162 printf("CFLAGS: %s\n", VER_CFLAGS);
163 printf("\n");
165 // set up system state
166 // 512K of RAM
167 state_init(512*1024);
169 // set up musashi and reset the CPU
170 m68k_set_cpu_type(M68K_CPU_TYPE_68010);
171 m68k_pulse_reset();
173 // Set up SDL
174 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) {
175 printf("Could not initialise SDL: %s.\n", SDL_GetError());
176 exit(EXIT_FAILURE);
177 }
179 // Make sure SDL cleans up after itself
180 atexit(SDL_Quit);
182 // Set up the video display
183 SDL_Surface *screen = NULL;
184 if ((screen = SDL_SetVideoMode(720, 384, 8, SDL_SWSURFACE | SDL_ANYFORMAT)) == NULL) {
185 printf("Could not find a suitable video mode: %s.\n", SDL_GetError());
186 exit(EXIT_FAILURE);
187 }
188 printf("Set %dx%d at %d bits-per-pixel mode\n\n", screen->w, screen->h, screen->format->BitsPerPixel);
189 SDL_WM_SetCaption("FreeBee 3B1 emulator", "FreeBee");
191 // Load a disc image
192 FILE *disc = fopen("discim", "rb");
193 wd2797_load(&state.fdc_ctx, disc, 512, 10, 2);
195 /***
196 * The 3B1 CPU runs at 10MHz, with DMA running at 1MHz and video refreshing at
197 * around 60Hz (???), with a 60Hz periodic interrupt.
198 */
199 const uint32_t TIMESLOT_FREQUENCY = 240; // Hz
200 const uint32_t MILLISECS_PER_TIMESLOT = 1e3 / TIMESLOT_FREQUENCY;
201 const uint32_t CLOCKS_PER_60HZ = (10e6 / 60);
202 uint32_t next_timeslot = SDL_GetTicks() + MILLISECS_PER_TIMESLOT;
203 uint32_t clock_cycles = 0;
204 bool exitEmu = false;
205 for (;;) {
206 // Run the CPU for however many cycles we need to. CPU core clock is
207 // 10MHz, and we're running at 240Hz/timeslot. Thus: 10e6/240 or
208 // 41667 cycles per timeslot.
209 clock_cycles += m68k_execute(10e6/TIMESLOT_FREQUENCY);
211 // Run the DMA engine
212 //
213 if (state.dmaen) { //((state.dma_count < 0x3fff) && state.dmaen) {
214 printf("DMA: copy addr=%08X count=%08X idmarw=%d dmarw=%d\n", state.dma_address, state.dma_count, state.idmarw, state.dma_reading);
215 if (state.dmaenb) {
216 state.dmaenb = false;
217 // state.dma_address++;
218 state.dma_count++;
219 }
220 // DMA ready to go -- so do it.
221 size_t num = 0;
222 while (state.dma_count < 0x4000) {
223 uint16_t d = 0;
225 // num tells us how many words we've copied. If this is greater than the per-timeslot DMA maximum, bail out!
226 if (num > (1e6/TIMESLOT_FREQUENCY)) break;
228 // Evidently we have more words to copy. Copy them.
229 if (!wd2797_get_drq(&state.fdc_ctx)) {
230 printf("\tDMABAIL: no data! dmac=%04X dmaa=%04X\n", state.dma_count, state.dma_address);
231 // Bail out, no data available. Try again later.
232 // TODO: handle HDD controller too
233 break;
234 }
236 if (!state.dma_reading) {
237 // Data available. Get it from the FDC.
238 d = wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
239 d <<= 8;
240 d += wd2797_read_reg(&state.fdc_ctx, WD2797_REG_DATA);
242 // TODO: FIXME: if we get a pagefault, it NEEDS to be tagged as 'peripheral sourced'... this is a HACK!
243 m68k_write_memory_16(state.dma_address << 1, d);
244 } else {
245 // Data write to FDC
246 // TODO: FIXME: if we get a pagefault, it NEEDS to be tagged as 'peripheral sourced'... this is a HACK!
247 d = m68k_read_memory_16(state.dma_address << 1);
249 wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d >> 8));
250 wd2797_write_reg(&state.fdc_ctx, WD2797_REG_DATA, (d & 0xff));
251 }
253 // Increment DMA address
254 state.dma_address++;
255 // Increment number of words transferred
256 num++; state.dma_count++;
257 }
259 // Turn off DMA engine if we finished this cycle
260 if (state.dma_count >= 0x4000) {
261 printf("\tDMATRAN: transfer complete! dmaa=%06X, dmac=%04X\n", state.dma_address, state.dma_count);
262 state.dma_count = 0;
263 state.dmaen = false;
264 }
265 }
267 // Any interrupts?
268 if (wd2797_get_irq(&state.fdc_ctx)) {
269 m68k_set_irq(2);
270 } else {
271 m68k_set_irq(0);
272 }
274 // Is it time to run the 60Hz periodic interrupt yet?
275 if (clock_cycles > CLOCKS_PER_60HZ) {
276 // Refresh the screen
277 refreshScreen(screen);
278 // TODO: trigger periodic interrupt (if enabled)
279 // decrement clock cycle counter, we've handled the intr.
280 clock_cycles -= CLOCKS_PER_60HZ;
281 }
283 // handle SDL events -- returns true if we need to exit
284 if (HandleSDLEvents(screen))
285 exitEmu = true;
287 // make sure frame rate is equal to real time
288 uint32_t now = SDL_GetTicks();
289 if (now < next_timeslot) {
290 // timeslot finished early -- eat up some time
291 SDL_Delay(next_timeslot - now);
292 } else {
293 // timeslot finished late -- skip ahead to gain time
294 // TODO: if this happens a lot, we should let the user know
295 // that their PC might not be fast enough...
296 next_timeslot = now;
297 }
298 // advance to the next timeslot
299 next_timeslot += MILLISECS_PER_TIMESLOT;
301 // if we've been asked to exit the emulator, then do so.
302 if (exitEmu) break;
303 }
305 // Release the disc image
306 wd2797_unload(&state.fdc_ctx);
307 fclose(disc);
309 return 0;
310 }