Xemu [doxygen]  hyppo 0a42be3a057156924bc1b626a687bd6e27349c45 @ Sat 19 Mar 02:15:11 CET 2022
vic6561.c
Go to the documentation of this file.
1 /* Test-case for a very simple and inaccurate Commodore VIC-20 emulator using SDL2 library.
2  Copyright (C)2016,2017 LGB (Gábor Lénárt) <lgblgblgb@gmail.com>
3 
4  This is the VIC-20 emulation. Note: the source is overcrowded with comments by intent :)
5  That it can useful for other people as well, or someone wants to contribute, etc ...
6 
7  This emulation of VIC-I chip is based on my own ideas, I can be easily wrong, so please
8  don't take anything here as the absolute truth :) Also, the emulation is scanline based,
9  which is not correct at all, to be precise.
10 
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
15 
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20 
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
24 
25 #include <stdio.h>
26 
27 #include <SDL.h>
28 
29 #include "xemu/emutools.h"
30 #include "vic6561.h"
31 
32 
33 #define SCREEN_COLOUR vic_cpal[0]
34 #define BORDER_COLOUR vic_cpal[1]
35 #define SRAM_COLOUR vic_cpal[2]
36 #define AUX_COLOUR vic_cpal[3]
37 
38 
39 /* Note about VIC-I memory accessing: this part of my emulator tries to emulate VIC-I in a non-VIC20-specific way.
40  That is: VIC-I sees 16K of address space with 12 bit (!) width data bus. Linearly.
41  The high 4 bits of data bus (D8-D11) is connected to a 4 bit wide SRAM in VIC-20 with 1K capacity.
42  However, in theory, you can have 16Kx4bit for colour SRAM ...
43  Also 6502 sees quite different situation for VIC-I addresses as VIC-I itself on the VIC-20.
44  To compensate the differences with keeping "some hacked VIC-20" emulations in the future simple, I've decided
45  to use a map to configure the exact mapping. vic_address_space_hi4 and vic_address_space_lo8 are the pointers
46  of the VIC-I memory space, at every Kbytes. Note: the init function expects 16 pointers, but in we use more
47  elements. The trick here: to avoid the need of checking "overflow" of the addressing space ;-)
48 */
49 
50 
51 Uint32 vic_palette[16]; // VIC palette with native SCREEN_FORMAT aware way. Must be initialized by the emulator
52 int scanline; // scanline counter, must be maintained by the emulator, as increment, checking for all scanlines done, etc, BUT it is zeroed here in vic_vsync()!
53 
54 static Uint8 *vic_address_space_hi4[16 + 3]; // VIC 16K address space pointers, one pointer per Kbytes [for VIC-I D8-D11, 4 higher bits], [+elements for "overflow"]
55 static Uint8 *vic_address_space_lo8[16 + 3]; // VIC 16K address space pointers, one pointer per Kbytes [for VIC-I D0-D7, 8 lower bits], [+elements for "overflow"]
56 static Uint8 vic_registers[16]; // VIC registers
57 static int charline; // current character line (0-7 for 8 pixels, 0-15 for 16 pixels height characters)
58 static Uint32 *pixels; // pointer to our work-texture (ie: the visible emulated VIC-20 screen, DWORD / pixel)
59 static int pixels_tail; // "tail" (in DWORDs) after each texture line (must be added to pixels pointer after rendering one scanline)
60 static int first_active_scanline; // first "active" (ie not top border) scanline
61 static int vic_vertical_area; // vertical "area" of VIC (1 = upper border, 0 = active display, 2 = bottom border)
62 static Uint32 vic_cpal[4]; // SDL pixel format related colours of multicolour colours, reverse mode swaps the colours here!! Also used to render everything on the screen!
63 static int char_height_minus_one; // 7 or 15 (for 8 and 16 pixels height characters)
64 static int char_height_shift; // 3 for 8 pixels height characters, 4 for 16
65 static int first_active_dotpos; // first dot within a scanline which belongs to the "active" (not top and bottom border) area
66 static int text_columns; // number of screen text columns
67 static int text_rows; // number of screen text rows
68 static int sram_colour_index; // index in the "multicolour" table for data read from colour SRAM (depends on reverse mode!)
69 static int vic_vid_addr; // memory address of screen (both of screen and colour SRAM to be precise, on lo8 and hi4 bus bits)
70 static int vic_vid_addr_bit9; // bit9 of screen address (not used directly but on address re-calculation only)
71 static int vic_chr_addr; // memory address of characer data (lo8 bus bits only)
72 static int vic_vid_counter; // video address counter (from zero) relative to the given video address (vic_vid_addr)
73 static int vic_row_counter; // counts the displayed (text) rows of VIC-I, if it's equal to text_rows it means end of actual active display, and start of the bottom border
74 
75 
76 /* Check constraints of the given parameters from some header file */
77 
78 #if ((SCREEN_ORIGIN_DOTPOS) & 1) != 0
79 # error "SCREEN_ORIGIN_DOTPOS must be an even number!"
80 #endif
81 #if (SCREEN_FIRST_VISIBLE_DOTPOS) & 1 != 0
82 # error "SCREEN_FIRST_VISIBLE_DOTPOS must be an even number!"
83 #endif
84 #if (SCREEN_LAST_VISIBLE_DOTPOS) & 1 != 1
85 # error "SCREEN_LAST_VISIBLE_DOTPOS must be an odd number!"
86 #endif
87 #if SCREEN_LAST_VISIBLE_SCANLINE > LAST_SCANLINE
88 # error "SCREEN_LAST_VISIBLE_SCANLINE cannot be greater than LAST_SCANLINE!"
89 #endif
90 
91 
92 
93 // Read VIC-I register by the CPU. "addr" must be 0 ... 15!
95 {
96  if (addr == 4)
97  return scanline >> 1; // scanline counter is read (bits 8-1)
98  else if (addr == 3)
99  return (vic_registers[3] & 0x7F) | ((scanline & 1) ? 0x80 : 0); // high byte of reg 3 is the bit0 of scanline counter!
100  else
101  return vic_registers[addr]; // otherwise, just read the "backend" (see cpu_vic_reg_write() for more information)
102 }
103 
104 
105 
106 // Write VIC-I register by the CPU. "addr" must be 0 ... 15!
108 {
109  vic_registers[addr] = data;
110  // Also update our variables, so access is faster/easier this way in our renderer.
111  switch (addr) {
112  case 0: // screen X origin (in 4 pixel units) on lower 7 bits, bit 7: interlace (only for NTSC, so it does not apply here, we emulate PAL)
113  first_active_dotpos = (data & 0x7F) * 4 + SCREEN_ORIGIN_DOTPOS;
114  break;
115  case 1: // screen Y orgin (in 2 lines units) for all the 8 bits
116  first_active_scanline = (data << 1) + SCREEN_ORIGIN_SCANLINE;
117  break;
118  case 2: // number of video columns (lower 7 bits), bit 7: bit 9 of screen memory
119  text_columns = data & 0x7F;
120  if (text_columns > 32) // FIXME: really 32? Not 31??
121  text_columns = 32;
122  vic_vid_addr_bit9 = ((data & 128) ? 0x200 : 0);
123  vic_vid_addr = (vic_vid_addr & 0xFDFF) | vic_vid_addr_bit9; // re-calculate the address, only bit9 may changed of the video address
124  break;
125  case 3: // Bits6-1: number of rows, bit 7: bit 0 of current scanline, bit 0: 8/16 height char
126  char_height_minus_one = (data & 1) ? 15 : 7;
127  char_height_shift = (data & 1) ? 4 : 3;
128  text_rows = (data >> 1) & 0x3F;
129  if (text_rows > 32) // FIXME: really 32? Not 31??
130  text_rows = 32;
131  break;
132  case 5:
133  vic_chr_addr = (data & 15) << 10;
134  vic_vid_addr = ((data & 0xF0) << 6) | vic_vid_addr_bit9;
135  break;
136  case 14:
137  AUX_COLOUR = vic_palette[data >> 4];
138  break;
139  case 15:
141  if (data & 8) { // normal mode
143  sram_colour_index = 2;
144  } else { // reverse mode
145  SRAM_COLOUR = vic_palette[data >> 4];
146  sram_colour_index = 0;
147  }
148  break;
149  }
150  //DEBUG("VIC-I: %02X -> [%01X]" NL, data, addr);
151 }
152 
153 
154 // Should be called before each new (half) frame
155 void vic_vsync ( int relock_texture )
156 {
157  if (relock_texture)
158  pixels = xemu_start_pixel_buffer_access(&pixels_tail); // get texture access stuffs for the new frame
159  scanline = 0; // current scanline (other than this, incrementation, calling vsync on last scanline etc, the emulator should manage ...)
160  charline = 0;
161  vic_vertical_area = 1;
162  vic_vid_counter = 0;
163  vic_row_counter = 0;
164 }
165 
166 
167 
168 void vic_init ( Uint8 **lo8_pointers, Uint8 **hi4_pointers )
169 {
170  int a;
171  vic_vsync(1); // we need once, since this is the first time, this will be called in update_emulator() later ....
172  // Some words about "overflow" (so the fact that loop is not from 0 to 15 only):
173  // overflow area (to avoid the checks of wrapping around 16K of VIC-I address space all the time)
174  // for screen/colour RAM: 32*32 rows/cols resolution in theory (1K), but start address can be at 0.5K steps, so 1.5K (->2) overflow can occur
175  // for chargen: for 16 pixel height chars, 4K, start can be given in 1K steps, so 3K overflow can occur
176  for (a = 0; a < 16 + 3; a++) {
177  cpu_vic_reg_write(a & 15, 0); // initialize VIC-I registers for _some_ value (just to avoid the danger of uninitialized emulator variables at startup)
178  // configure address space pointers, in a tricky way: we don't want to use 'and' on accesses, so offsets must be compensated!
179  vic_address_space_lo8[a] = lo8_pointers[a & 15] - (a << 10);
180  vic_address_space_hi4[a] = hi4_pointers[a & 15] - (a << 10);
181  }
182 }
183 
184 
185 static inline Uint8 vic_read_mem_lo8 ( Uint16 addr )
186 {
187  return vic_address_space_lo8[addr >> 10][addr];
188 }
189 
190 
191 
192 static inline Uint8 vic_read_mem_hi4 ( Uint16 addr )
193 {
194  return vic_address_space_hi4[addr >> 10][addr];
195 }
196 
197 
198 // Render a single scanline of VIC-I screen.
199 // It's not a correct solution to render a line in once, however it's only a sily emulator try from me, not an accurate one :-D
200 void vic_render_line ( void )
201 {
202  int v_columns, v_vid, dotpos, visible_scanline, mcm, bitp, chr;
203  // Check for start the active display (end of top border) and end of active display (start of bottom border)
204  if (vic_row_counter >= text_rows && vic_vertical_area == 0) // FIXME: the exact condition! Maybe not ">" like relation but equality is checked by VIC-I only?
205  vic_vertical_area = 2; // this will be the first scanline of bottom border
206  else if (scanline == first_active_scanline && vic_vertical_area == 1)
207  vic_vertical_area = 0; // this scanline will be the first non-border scanline
208  // Check if we're inside the top or bottom area, so full border colour lines should be rendered
210  if (vic_vertical_area) {
211  if (visible_scanline) {
213  while (v_columns--)
214  *(pixels++) = BORDER_COLOUR;
215  pixels += pixels_tail; // add texture "tail" (that is, pitch - text_width, in 4 bytes uints, ie Uint32 pointer ...)
216  }
217  return;
218  }
219  // So, we are at the "active" display area. But still, there are left and right borders ...
220  bitp = 128;
221  v_columns = text_columns;
222  v_vid = vic_vid_counter;
223  for (dotpos = 0; dotpos < CYCLES_PER_SCANLINE * 4; dotpos++) {
224  int visible_dotpos = (dotpos >= SCREEN_FIRST_VISIBLE_DOTPOS && dotpos <= SCREEN_LAST_VISIBLE_DOTPOS && visible_scanline);
225  if (dotpos < first_active_dotpos) {
226  if (visible_dotpos)
227  *(pixels++) = BORDER_COLOUR;
228  } else {
229  if (v_columns) {
230  if (bitp == 128) {
231  // NOTE! *AFAIK* VIC-I fetches colour info from the *VERY SAME* address as the video data! It's just matter of usage in VIC-20
232  // that only 1K of SRAM is connected for the upper 4 bits of the 12 bit wide data bus of VIC-I, but it can be otherwise too!
233  chr = vic_read_mem_lo8((vic_read_mem_lo8(vic_vid_addr + v_vid) << char_height_shift) + vic_chr_addr + charline);
234  mcm = vic_read_mem_hi4(vic_vid_addr + v_vid); // mcm here is simply the fetched colour SRAM byte (only lower 4 bits will be used)
235  v_vid++; // increment video address
236  vic_cpal[sram_colour_index] = vic_palette[mcm & 7]; // set text colour with the lower 3 bits fetched on the right place (depends on reverse mode: sram_colour_index)
237  mcm &= 8; // mcm: multi colour mode flag, bit 3 - so the true meaning of "mcm" ;-)
238  if (mcm)
239  bitp = 6; // in case of mcm, bitp is a shift counter needed to move bits to bitpos 1,0, otherwise bitp is a bit mask in hires mode
240  }
241  if (visible_dotpos) {
242  if (mcm) {
243  // Ugly enough! I have the assumption that visible dotpos condition only changes on even number in dotpos only ... :-/
244  // That is, you MUST define texture dimensions for width of even number!!
245  *pixels = *(pixels + 1) = vic_cpal[(chr >> bitp) & 3]; // "double width" pixel ...
246  pixels += 2;
247  dotpos++; // trick our "for" loop a bit here ...
248  } else
249  *(pixels++) = (chr & bitp) ? SRAM_COLOUR : SCREEN_COLOUR;
250  }
251  if (bitp <= 1) { // last pixel? handle both situation with "dual" meaning of bitp for multicolour and hires mode
252  v_columns--;
253  bitp = 128; // prepare for next byte fetch (video + colour)
254  } else {
255  if (mcm)
256  bitp -= 2;
257  else
258  bitp >>= 1;
259  }
260  } else if (visible_dotpos)
261  *(pixels++) = BORDER_COLOUR;
262  }
263  }
264  if (charline >= char_height_minus_one) {
265  charline = 0;
266  vic_vid_counter += text_columns; // FIXME: does VIC-I always use the text columns setting, even if picture wouldn't fit into the TV screen at all?!
267  vic_row_counter++;
268  } else {
269  charline++;
270  }
271  pixels += pixels_tail; // add texture "tail" (that is, pitch - text_width, in 4 bytes uints, ie Uint32 pointer ...)
272 }
cpu_vic_reg_write
void cpu_vic_reg_write(int addr, Uint8 data)
Definition: vic6561.c:107
SRAM_COLOUR
#define SRAM_COLOUR
Definition: vic6561.c:35
vic_init
void vic_init(Uint8 **lo8_pointers, Uint8 **hi4_pointers)
Definition: vic6561.c:168
emutools.h
CYCLES_PER_SCANLINE
#define CYCLES_PER_SCANLINE
Definition: vic6561.h:35
vic6561.h
BORDER_COLOUR
#define BORDER_COLOUR
Definition: vic6561.c:34
vic_render_line
void vic_render_line(void)
Definition: vic6561.c:200
SCREEN_ORIGIN_DOTPOS
#define SCREEN_ORIGIN_DOTPOS
Definition: vic6561.h:32
vic_vsync
void vic_vsync(int relock_texture)
Definition: vic6561.c:155
addr
int addr
Definition: dma65.c:81
SCREEN_LAST_VISIBLE_DOTPOS
#define SCREEN_LAST_VISIBLE_DOTPOS
Definition: vic6561.h:25
m65-memcontent-generator.data
data
Definition: m65-memcontent-generator.py:119
Uint32
uint32_t Uint32
Definition: fat32.c:49
AUX_COLOUR
#define AUX_COLOUR
Definition: vic6561.c:36
Uint8
uint8_t Uint8
Definition: fat32.c:51
SCREEN_COLOUR
#define SCREEN_COLOUR
Definition: vic6561.c:33
SCREEN_FIRST_VISIBLE_SCANLINE
#define SCREEN_FIRST_VISIBLE_SCANLINE
Definition: vic6561.h:22
SCREEN_FIRST_VISIBLE_DOTPOS
#define SCREEN_FIRST_VISIBLE_DOTPOS
Definition: vic6561.h:24
vic_palette
Uint32 vic_palette[16]
Definition: vic6561.c:51
SCREEN_LAST_VISIBLE_SCANLINE
#define SCREEN_LAST_VISIBLE_SCANLINE
Definition: vic6561.h:23
xemu_start_pixel_buffer_access
Uint32 * xemu_start_pixel_buffer_access(int *texture_tail)
Definition: emutools.c:1153
SCREEN_ORIGIN_SCANLINE
#define SCREEN_ORIGIN_SCANLINE
Definition: vic6561.h:31
Uint16
uint16_t Uint16
Definition: fat32.c:50
cpu_vic_reg_read
Uint8 cpu_vic_reg_read(int addr)
Definition: vic6561.c:94
scanline
int scanline
Definition: vic6561.c:52
pixels
Uint32 * pixels
Definition: osd.c:33