Xemu [doxygen]  hyppo 0a42be3a057156924bc1b626a687bd6e27349c45 @ Sat 19 Mar 02:15:11 CET 2022
spectrum.c
Go to the documentation of this file.
1 /* Learning material to get to know ZX Spectrum (I never had that machine ...)
2  by trying to write an emulator :-O
3  Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu
4  Copyright (C)2017,2021 LGB (Gábor Lénárt) <lgblgblgb@gmail.com>
5 
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
19 
20 #include "xemu/emutools.h"
21 #include "xemu/emutools_files.h"
22 #include "xemu/emutools_hid.h"
23 #include "xemu/emutools_config.h"
24 #include "xemu/z80.h"
25 #include "spectrum.h"
26 
27 
28 
30 
31 static Uint8 memory[0x10000];
32 static const Uint8 init_ula_palette_rgb[16 * 3] = {
33  0x00, 0x00, 0x00, // 0: black
34  0x00, 0x00, 0xD7, // 1: blue
35  0xD7, 0x00, 0x00, // 2: red
36  0xD7, 0x00, 0xD7, // 3: magenta
37  0x00, 0xD7, 0x00, // 4: green
38  0x00, 0xD7, 0xD7, // 5: cyan
39  0xD7, 0xD7, 0x00, // 6: yellow
40  0xD7, 0xD7, 0xD7, // 7: white
41  0x00, 0x00, 0x00, // 8: "bright black" ~ same as "black"
42  0x00, 0x00, 0xFF, // 9: bright blue
43  0xFF, 0x00, 0x00, // A: bright red
44  0xFF, 0x00, 0xFF, // B: bright magenta
45  0x00, 0xFF, 0x00, // C: bright green
46  0x00, 0xFF, 0xFF, // D: bright cyan
47  0xFF, 0xFF, 0x00, // E: bright yellow
48  0xFF, 0xFF, 0xFF // F: bright white
49 };
50 static Uint32 ula_palette[16];
51 static Uint32 ulaplus_palette[256];
52 static int vsync_int;
53 
54 static int contended_memory_t_states;
55 
56 static int tstate_x;
57 static int scanline;
58 static Uint8 *pix_mem_p[192];
59 static Uint8 *atr_mem_p[192];
60 static unsigned int frame_counter;
61 static int frame_done;
62 static int flash_state;
63 static Uint32 paper_colours[256], ink_colours[256], border_colour;
64 static Uint32 ulaplus_colours[64];
65 static Uint8 ulaplus_ink_index[256], ulaplus_paper_index[256];
66 
67 static Uint32 *pix;
68 
69 
70 
71 static void init_ula_tables ( void )
72 {
73  int a;
74  // Look-up tables with memory pointers for each of the 192 visible lines for both of pixel and attribute mode
75  // So we can use an indexed value to get the pointer and increment it through one line, then load the next etc ...
76  for (a = 0; a < 192; a++) {
77  pix_mem_p[a] = memory + 0x4000 + (((((a >> 3) & 0x18) | (a & 7)) << 8) | (((a >> 3) & 7) << 5));
78  atr_mem_p[a] = memory + 0x5800 + ((a >> 3) << 5);
79  }
80  // For standard ULA mode: Look-up tables for quicker paper/ink decoding, based on the attribute byte as index
81  // (with AND'ing the FLASH state, changed in every 16 frames)
82  // So first 128 entries are the normal, the next 128 are the swapped ink/paper.
83  for (a = 0; a < 128; a++ ) {
84  paper_colours[a] = ink_colours [a + 128] = ula_palette[(a & 120) >> 3];
85  ink_colours [a] = paper_colours[a + 128] = ula_palette[(a & 7) | ((a & 64) >> 3)];
86  }
87  // ULAplus palette
88  // I'm not so much familiar even with that ULA, but I try to follow this here for ULAplus:
89  // http://faqwiki.zxnet.co.uk/wiki/ULAplus
90  // ULAplus part is not used currently at all ...
91  for (a = 0; a < 256; a++) {
92  int red = (a >> 2) & 7;
93  int green = (a >> 5) & 7;
94  int blue = ((a & 3) << 1) | ((a & 3) ? 1 : 0);
95  ulaplus_palette[a] = SDL_MapRGBA(
97  (red << 5) | (red << 2) | (red >> 1),
98  (green << 5) | (green << 2) | (green >> 1),
99  (blue << 5) | (blue << 2) | (blue >> 1),
100  0xFF
101  );
102  ulaplus_ink_index[a] = ((a & 0xC0) >> 2) + (a & 7);
103  ulaplus_paper_index[a] = ((a & 0xC0) >> 2) + ((a >> 3) & 7) + 8;
104  ulaplus_colours[a & 63] = ulaplus_palette[0]; // all black initial state
105  }
106 }
107 
108 
109 // Z80ex is instructed to call this function at every T-states
110 // we use it then to emulate ULA functions: rendering screen
111 // at memory read/write (and I/O) we can use inserting extra T-states
112 // for contended memory emulation, which ends here too, so everything seems to be OK
113 void z80ex_tstate_cb ( void )
114 {
115  if (scanline >= 16) {
116  if (tstate_x < 128) { // visible area (if in the range vertically, I mean ...)
117  if (scanline >= 64 && scanline < 64 + 192) {
118  Uint8 pd = *(pix_mem_p[scanline - 64] + (tstate_x >> 2));
119  Uint8 ad = *(atr_mem_p[scanline - 64] + (tstate_x >> 2));
120  Uint32 fg = ink_colours[ad & flash_state];
121  Uint32 bg = paper_colours[ad & flash_state];
122  switch (tstate_x & 3) {
123  case 0:
124  *pix++ = pd & 0x80 ? fg : bg;
125  *pix++ = pd & 0x40 ? fg : bg;
126  break;
127  case 1:
128  *pix++ = pd & 0x20 ? fg : bg;
129  *pix++ = pd & 0x10 ? fg : bg;
130  break;
131  case 2:
132  *pix++ = pd & 0x08 ? fg : bg;
133  *pix++ = pd & 0x04 ? fg : bg;
134  break;
135  case 3:
136  *pix++ = pd & 0x02 ? fg : bg;
137  *pix++ = pd & 0x01 ? fg : bg;
138  break;
139  }
140  } else {
141  *pix++ = border_colour;
142  *pix++ = border_colour;
143  }
144  } else if (tstate_x < 128 + 24) { // right border
145  *pix++ = border_colour;
146  *pix++ = border_colour;
147  } else if (tstate_x < 128 + 24 + 48) { // HSYNC
148  } else { // left border
149  *pix++ = border_colour;
150  *pix++ = border_colour;
151  }
152  }
153  // Next tstate in this line, or next line, or next frame ...
154  if (XEMU_UNLIKELY(tstate_x == 223)) {
155  if (XEMU_UNLIKELY(scanline == 311)) {
156  vsync_int = 1;
157  frame_counter++;
158  flash_state = (frame_counter & 16) ? 0xFF : 0x7F;
159  frame_done = 1; // signal main loop that we're ready. We use this, as it's problematic to update emu in-opcode maybe (eg: RESET function in HID, etc called from here ...)
160  scanline = 0;
161  } else
162  scanline++;
163  tstate_x = 0;
164  } else
165  tstate_x++;
166 }
167 
168 
170 {
171  if (contended_memory_t_states & ((addr & 0xC000) == 0x4000)) // contended memory area
172  z80ex_w_states(contended_memory_t_states);
173  return memory[addr];
174 }
175 
176 
178 {
179  if (XEMU_UNLIKELY(addr < 0x4000))
180  return; // ROM is not writable
181  if (addr >= 0x8000)
182  memory[addr] = value; // no contended memory, simply do the write
183  else {
184  // contended memory area!
185  if (contended_memory_t_states)
186  z80ex_w_states(contended_memory_t_states);
187  memory[addr] = value;
188  }
189 }
190 
191 
193 {
194  if (XEMU_UNLIKELY(port16 & 1))
195  return 0xFF;
196  // The ULA port: every even addresses ...
197  return (
198  ((port16 & 0x0100) ? 0xFF : kbd_matrix[0]) &
199  ((port16 & 0x0200) ? 0xFF : kbd_matrix[1]) &
200  ((port16 & 0x0400) ? 0xFF : kbd_matrix[2]) &
201  ((port16 & 0x0800) ? 0xFF : kbd_matrix[3]) &
202  ((port16 & 0x1000) ? 0xFF : kbd_matrix[4]) &
203  ((port16 & 0x2000) ? 0xFF : kbd_matrix[5]) &
204  ((port16 & 0x4000) ? 0xFF : kbd_matrix[6]) &
205  ((port16 & 0x8000) ? 0xFF : kbd_matrix[7])
206  ) | 224;
207 }
208 
209 
211 {
212  if (XEMU_UNLIKELY(port16 & 1))
213  return;
214  // The ULA port: every even addresses ...
215  border_colour = ula_palette[value & 7];
216 }
217 
218 
220 {
221  return 0xFF;
222 }
223 
224 
225 void z80ex_reti_cb ( void )
226 {
227 }
228 
229 
230 
231 #define VIRTUAL_SHIFT_POS 0x00
232 
233 
234 /* Primo for real does not have the notion if "keyboard matrix", well or we
235  can say it has 1*64 matrix (not like eg C64 with 8*8). Since the current
236  Xemu HID structure is more about a "real" matrix with "sane" dimensions,
237  I didn't want to hack it over, instead we use a more-or-less artificial
238  matrix, and we map that to the Primo I/O port request on port reading.
239  Since, HID assumes the high nibble of the "position" is the "row" and
240  low nibble can be only 0-7 we have values like:
241  $00 - $07, $10 - $17, $20 - $27, ...
242  ALSO: Primo uses bit '1' for pressed, so we also invert value in
243  the port read function above.
244 */
245 static const struct KeyMappingDefault speccy_key_map[] = {
246  { SDL_SCANCODE_LSHIFT, 0x00 }, { SDL_SCANCODE_RSHIFT, 0x00},
247  { SDL_SCANCODE_Z, 0x01 },
248  { SDL_SCANCODE_X, 0x02 },
249  { SDL_SCANCODE_C, 0x03 },
250  { SDL_SCANCODE_V, 0x04 },
251  { SDL_SCANCODE_A, 0x10 },
252  { SDL_SCANCODE_S, 0x11 },
253  { SDL_SCANCODE_D, 0x12 },
254  { SDL_SCANCODE_F, 0x13 },
255  { SDL_SCANCODE_G, 0x14 },
256  { SDL_SCANCODE_Q, 0x20 },
257  { SDL_SCANCODE_W, 0x21 },
258  { SDL_SCANCODE_E, 0x22 },
259  { SDL_SCANCODE_R, 0x23 },
260  { SDL_SCANCODE_T, 0x24 },
261  { SDL_SCANCODE_1, 0x30 },
262  { SDL_SCANCODE_2, 0x31 },
263  { SDL_SCANCODE_3, 0x32 },
264  { SDL_SCANCODE_4, 0x33 },
265  { SDL_SCANCODE_5, 0x34 },
266  { SDL_SCANCODE_0, 0x40 },
267  { SDL_SCANCODE_9, 0x41 },
268  { SDL_SCANCODE_8, 0x42 },
269  { SDL_SCANCODE_7, 0x43 },
270  { SDL_SCANCODE_6, 0x44 },
271  { SDL_SCANCODE_P, 0x50 },
272  { SDL_SCANCODE_O, 0x51 },
273  { SDL_SCANCODE_I, 0x52 },
274  { SDL_SCANCODE_U, 0x53 },
275  { SDL_SCANCODE_Y, 0x54 },
276  { SDL_SCANCODE_RETURN, 0x60 },
277  { SDL_SCANCODE_L, 0x61 },
278  { SDL_SCANCODE_K, 0x62 },
279  { SDL_SCANCODE_J, 0x63 },
280  { SDL_SCANCODE_H, 0x64 },
281  { SDL_SCANCODE_SPACE, 0x70 },
282  { SDL_SCANCODE_LCTRL, 0x71 }, // SYM.SHIFT
283  { SDL_SCANCODE_M, 0x72 },
284  { SDL_SCANCODE_N, 0x73 },
285  { SDL_SCANCODE_B, 0x74 },
287  // **** this must be the last line: end of mapping table ****
288  { 0, -1 }
289 };
290 
291 
292 
293 void clear_emu_events ( void )
294 {
295  hid_reset_events(1);
296 }
297 
298 
299 
300 // HID needs this to be defined, it's up to the emulator if it uses or not ...
301 int emu_callback_key ( int pos, SDL_Scancode key, int pressed, int handled )
302 {
303  return 0;
304 }
305 
306 
307 
308 static void open_new_frame ( void )
309 {
310  int tail;
311  frame_done = 0;
312  pix = xemu_start_pixel_buffer_access(&tail);
313  if (XEMU_UNLIKELY(tail))
314  FATAL("FATAL: Xemu texture tail is not zero, but %d", tail);
315 }
316 
317 
318 static struct {
320  char *rom;
322 } configdb;
323 
324 
325 
326 int main ( int argc, char **argv )
327 {
328  xemu_pre_init(APP_ORG, TARGET_NAME, "The learner's ZX Spectrum emulator from LGB (the learner)");
329  xemucfg_define_str_option("rom", ROM_NAME, "Path and filename for ROM to be loaded", &configdb.rom);
330  xemucfg_define_switch_option("fullscreen", "Start in fullscreen mode", &configdb.fullscreen);
331  xemucfg_define_switch_option("syscon", "Keep system console open (Windows-specific effect only)", &configdb.syscon);
332  xemucfg_define_num_option("sdlrenderquality", RENDER_SCALE_QUALITY, "Setting SDL hint for scaling method/quality on rendering (0, 1, 2)", &configdb.sdlrenderquality, 0, 2);
333  if (xemucfg_parse_all(argc, argv))
334  return 1;
335  /* Initiailize SDL - note, it must be before loading ROMs, as it depends on path info from SDL! */
336  if (xemu_post_init(
337  TARGET_DESC APP_DESC_APPEND, // window title
338  1, // resizable window
339  SCREEN_WIDTH, SCREEN_HEIGHT, // texture sizes
340  SCREEN_WIDTH, SCREEN_HEIGHT, // logical sizes
341  SCREEN_WIDTH * 3, SCREEN_HEIGHT * 3, // window size (tripled in size, original would be too small)
342  SCREEN_FORMAT, // pixel format
343  16, // we have 16 colours
344  init_ula_palette_rgb, // initialize palette from this constant array
345  ula_palette, // initialize palette into this stuff
346  configdb.sdlrenderquality, // render scaling quality
347  USE_LOCKED_TEXTURE, // 1 = locked texture access
348  NULL // no emulator specific shutdown function
349  ))
350  return 1;
351  hid_init(
352  speccy_key_map,
354  SDL_DISABLE // no joystick HID events
355  );
356  init_ula_tables();
357  /* Intialize memory and load ROMs */
358  memset(memory, 0xFF, sizeof memory);
359  if (xemu_load_file(configdb.rom, memory, 0x4000, 0x4000, "Selected ROM image cannot be loaded. Without it, Xemu won't work.\nPlease install it, or use the CLI switch -rom to specify one.") < 0)
360  return 1;
361  // Continue with initializing ...
362  clear_emu_events(); // also resets the keyboard
363  z80ex_init();
364  vsync_int = 0;
365  contended_memory_t_states = 0;
366  tstate_x = 0;
367  scanline = 0;
368  flash_state = 0xFF;
369  frame_counter = 0;
370  if (!configdb.syscon)
371  sysconsole_close(NULL);
373  open_new_frame(); // open new frame at the very first time, the rest of frames handled below, during the emulation
374  xemu_timekeeping_start(); // we must call this once, right before the start of the emulation
375  for (;;) { // our emulation loop ...
376  if (XEMU_UNLIKELY(vsync_int)) {
377  if (z80ex_int())
378  vsync_int = 0;
379  else
380  z80ex_step();
381  } else
382  z80ex_step();
383  if (XEMU_UNLIKELY(frame_done)) {
384  if (frame_counter & 1) // currently Xemu framework assumes about ~25Hz for calling this, so we do this at every second frames
386  xemu_update_screen(); // updates screen (Xemu framework), also closes the access to the buffer
387  xemu_timekeeping_delay(19968); // FIXME: better accuracy, as some T-states over it is now ....
388  open_new_frame(); // open new frame
389  }
390  }
391  return 0;
392 }
xemu_pre_init
void xemu_pre_init(const char *app_organization, const char *app_name, const char *slogan)
Definition: emutools.c:651
z80ex_init
void z80ex_init(void)
Definition: z80ex.c:175
xemucfg_define_str_option
void xemucfg_define_str_option(const char *optname, const char *defval, const char *help, char **storage)
USE_LOCKED_TEXTURE
#define USE_LOCKED_TEXTURE
Definition: commodore_65.h:26
TARGET_DESC
#define TARGET_DESC
Definition: xemu-target.h:2
main
int main(int argc, char **argv)
Definition: spectrum.c:326
z80ex_w_states
void z80ex_w_states(unsigned w_states)
Definition: z80ex.c:307
z80ex_mwrite_cb
void z80ex_mwrite_cb(Z80EX_WORD addr, Z80EX_BYTE value)
Definition: spectrum.c:177
xemu_timekeeping_delay
void xemu_timekeeping_delay(int td_em)
Definition: emutools.c:405
emutools.h
sysconsole_close
void sysconsole_close(const char *waitmsg)
Definition: emutools.c:1393
z80ex_step
int z80ex_step(void)
Definition: z80ex.c:47
SCREEN_WIDTH
#define SCREEN_WIDTH
Definition: vic3.h:29
z80ex
Z80EX_CONTEXT z80ex
Definition: spectrum.c:29
xemucfg_define_num_option
void xemucfg_define_num_option(const char *optname, const int defval, const char *help, int *storage, int min, int max)
xemu_update_screen
void xemu_update_screen(void)
Definition: emutools.c:1184
z80ex_pread_cb
Z80EX_BYTE z80ex_pread_cb(Z80EX_WORD port16)
Definition: spectrum.c:192
z80ex_pwrite_cb
void z80ex_pwrite_cb(Z80EX_WORD port16, Z80EX_BYTE value)
Definition: spectrum.c:210
configdb_st::rom
char * rom
Definition: configdb.h:32
addr
int addr
Definition: dma65.c:81
sdl_pix_fmt
SDL_PixelFormat * sdl_pix_fmt
Definition: emutools.c:80
hid_handle_all_sdl_events
void hid_handle_all_sdl_events(void)
Definition: emutools_hid.c:613
xemucfg_parse_all
int xemucfg_parse_all(int argc, char **argv)
z80ex_int
int z80ex_int(void)
Definition: z80ex.c:225
Uint32
uint32_t Uint32
Definition: fat32.c:49
Z80EX_WORD
unsigned short Z80EX_WORD
Definition: z80ex.h:51
hid_init
void hid_init(const struct KeyMappingDefault *key_map_in, Uint8 virtual_shift_pos_in, int joy_enable)
Definition: emutools_hid.c:300
hid_reset_events
void hid_reset_events(int burn)
Definition: emutools_hid.c:170
Uint8
uint8_t Uint8
Definition: fat32.c:51
xemu_set_full_screen
void xemu_set_full_screen(int setting)
Definition: emutools.c:311
_z80_cpu_context
Definition: z80ex.h:143
configdb_st::sdlrenderquality
int sdlrenderquality
Definition: configdb.h:35
TARGET_NAME
#define TARGET_NAME
Definition: xemu-target.h:1
emutools_files.h
z80.h
VIRTUAL_SHIFT_POS
#define VIRTUAL_SHIFT_POS
Definition: spectrum.c:231
APP_ORG
#define APP_ORG
Definition: emutools.h:50
STD_XEMU_SPECIAL_KEYS
#define STD_XEMU_SPECIAL_KEYS
Definition: emutools_hid.h:108
ROM_NAME
#define ROM_NAME
Definition: commander_x16.h:32
xemu_start_pixel_buffer_access
Uint32 * xemu_start_pixel_buffer_access(int *texture_tail)
Definition: emutools.c:1153
Z80EX_BYTE
unsigned char Z80EX_BYTE
Definition: z80ex.h:49
sdlrenderquality
int sdlrenderquality
Definition: spectrum.c:321
fullscreen
int fullscreen
Definition: spectrum.c:319
emutools_config.h
configdb
struct configdb_st configdb
Definition: configdb.c:34
z80ex_reti_cb
void z80ex_reti_cb(void)
Definition: spectrum.c:225
KeyMappingDefault
Definition: emutools_hid.h:24
xemu_post_init
int xemu_post_init(const char *window_title, int is_resizable, int texture_x_size, int texture_y_size, int logical_x_size, int logical_y_size, int win_x_size, int win_y_size, Uint32 pixel_format, int n_colours, const Uint8 *colours, Uint32 *store_palette, int render_scale_quality, int locked_texture_update, void(*shutdown_callback)(void))
Definition: emutools.c:908
memory
Uint8 memory[0x100000]
Definition: commodore_65.c:43
xemu_load_file
int xemu_load_file(const char *filename, void *store_to, int min_size, int max_size, const char *cry)
Definition: emutools_files.c:674
xemu_timekeeping_start
void xemu_timekeeping_start(void)
Definition: emutools.c:1122
configdb_st::syscon
int syscon
Definition: configdb.h:34
kbd_matrix
Uint8 kbd_matrix[16]
Definition: dave.c:30
configdb_st::fullscreen
int fullscreen
Definition: configdb.h:34
SCREEN_HEIGHT
#define SCREEN_HEIGHT
Definition: vic3.h:30
SCREEN_FORMAT
#define SCREEN_FORMAT
Definition: commodore_65.h:25
RENDER_SCALE_QUALITY
#define RENDER_SCALE_QUALITY
Definition: commodore_65.h:27
syscon
int syscon
Definition: spectrum.c:319
z80ex_mread_cb
Z80EX_BYTE z80ex_mread_cb(Z80EX_WORD addr, int m1_state)
Definition: spectrum.c:169
value
int value
Definition: dma65.c:90
APP_DESC_APPEND
#define APP_DESC_APPEND
Definition: emutools.h:52
emu_callback_key
int emu_callback_key(int pos, SDL_Scancode key, int pressed, int handled)
Definition: spectrum.c:301
emutools_hid.h
xemucfg_define_switch_option
void xemucfg_define_switch_option(const char *optname, const char *help, int *storage)
clear_emu_events
void clear_emu_events(void)
Definition: spectrum.c:293
z80ex_intread_cb
Z80EX_BYTE z80ex_intread_cb(void)
Definition: spectrum.c:219
FATAL
#define FATAL(...)
Definition: xep128.h:117
KeyMappingDefault::pos
int pos
Definition: emutools_hid.h:26
spectrum.h
XEMU_UNLIKELY
#define XEMU_UNLIKELY(__x__)
Definition: emutools_basicdefs.h:125
z80ex_tstate_cb
void z80ex_tstate_cb(void)
Definition: spectrum.c:113
scanline
int scanline
Definition: vic6561.c:52
rom
char * rom
Definition: spectrum.c:320