File: vram-addressing.txt

package info (click to toggle)
openmsx 0.8.2-2.1
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 15,796 kB
  • sloc: cpp: 124,615; xml: 22,614; tcl: 7,336; python: 3,789; asm: 1,154; sh: 69; makefile: 25
file content (367 lines) | stat: -rw-r--r-- 12,480 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
$Id: vram-addressing.txt 11986 2011-03-06 21:02:35Z manuelbi $

VRAM Addressing
===============

This document describes a model for the way the V9938 VDP calculates
addresses in VRAM using base registers and indices.

General Formula
---------------

General formula for VRAM addressing:
  vram_address = index1 & mask
where:
  vram_address is the index in VRAM
  index1 is the index in the particular table, such as the name table or
    color table. The unused bits of index are all ones.
  mask is the value in the table's base register. The unused bits of mask
    are all ones.

Examples:

The name table base register is VDP R#2.
It contains A16-A10 of the mask, the other bits are 1.
If R#2 would contain 24h, the mask would be:
  (24h << 10) | ~(-1 << 10) = 093FFh

In SCREEN1 (Graphic 1) there are usually 24 lines of 32 characters each.
However, the actual screen height is 32 lines, as can be seen when the
vertical adjust register R#23 is used. So the number of characters per
screen is 32*32 = 1024 = 2^10. With one byte per character, the index is
10 bits wide.

If the index would be 157h, this is extended with leading ones for A10 and
higher (1FC00h) to become 1FD57h. Then the final address is:
  vram_address = 1FD57h & 093FFh = 09157h

Note that in this case, the final address is equal to:
  R#2 * 2^10 + index = 09000h + 157h = 09157h
This is true for all display modes in which the used bits in the base
register (A16..A10 in this case) do not overlap the bits in the index
(A9..A0 in this case). This is also the way application programmers
typically use the table base address registers.

In SCREEN5 (Graphic 4) there are 256 lines of 128 bytes each. Only 212 lines
are visible at a time, but using the vertical adjust register R#23 all 256
lines can be seen. A line is 256 pixels wide, with 4 bits per pixel that
makes 128 bytes per line. So the total number of bytes per screen is
256 * 128 = 32768 = 2^15. Therefore the index is 15 bits wide.

If the index would be 4521h, this is extended with leading ones for A16 and
A15 (18000h) to become 1C521h. Using the same mask as before (093FFh), the
final address is:
  vram_address = 1C521h & 093FFh = 08121h

Note that in this case, the final address is NOT equal to:
  R#2 * 2^10 + index = 09000h + 4521h = 0D521h
The VDP data book states that in SCREEN5 bit4 to bit0 should be 1, in those
cases the general formula is equivalent to addition. But when some of those
bits are zeroes, the general formula gives the same result the real VDP
does, while addition does not. The many-scrollers part of Anma's "Source of
Power" demo uses this feature to display the same scrolling text many times
on the same screen.

Range Checking
--------------

Different VDP subsystems are interested in different tables. For example,
the sprite subsystem is only interested in the sprite name and pattern
tables. On reads and writes to tables, interested subsystems must be
informed so they can update their internal state. Therefore an algorithm is
needed which calculates in which tables (if any) a given address is located.

Here is the address calculation formula again:
  vram_address = index1 & mask
The lowest address within the range is when the index is 0, index1 then
contains only ones in unused bits. The highest address (inclusive) is when
the index is all ones, then index1 is also all ones and the address is equal
to the mask. However, not all addresses between the lowest and the highest
address actually occur if the mask has one or more zeroes in the bits that
overlap with the index.

Let "inside : Int -> Bool" be the function that tells whether an address is
inside a table:
  inside.addr = (Exist i : i in Indices : i & mask = addr)
In English: there is an index i, for which the VRAM address is addr.

A convenient formula for the inside function is this one:
  inside.addr =
    addr & (~mask | (-1 << index_width)) = mask & (-1 << index_width)
Note that "addr" only occurs once and all other values are constant for each
display mode, so the check takes two operations (one compare and one AND).
This is probably the cheapest you can get.

Index Width
-----------

Which address bits are stored in the table base address registers is
documented in the VDP data book, but which address bits are produced by the
index is not documented there. This section fills that gap.

- Name Table -

Name table base address is [A16..A10] (R#2).

Display mode:   Index width in bits:
   Text 1         12 (*1)
   Text 2         12
 Multicolor       10 (*2)
  Graphic 1       10 (*2)
  Graphic 2       10 (*2)
  Graphic 3       10 (*2)
  Graphic 4       15
  Graphic 5       15
  Graphic 6       15 + plane (*3)
  Graphic 7       15 + plane (*3)
(*1) Text 1 index is calculated as 00C00h + position.
     On MSX1 it was 10 bits wide. For some reason, unifying address
     calculation with Text 2 must have been simpler than keeping it 10 bits
     wide, even though unification requires an add operation.
(*2) Maybe this is 12 bits and index = 00C00h + position just like Text 1.
     That's equivalent to 10 bits though, because there are 1024 characters
     per screen.
(*3) See "Planar Addressing" section for details.

- Pattern Table -

Pattern table base address is [A16..A11] (R#4).

Display mode:   Index width in bits:
   Text 1         11
   Text 2         11
 Multicolor       11
  Graphic 1       11
  Graphic 2       13
  Graphic 3       13
  Graphic 4       -
  Graphic 5       -
  Graphic 6       -
  Graphic 7       -

- Color Table -

Color table base address is [A16..A6] (R#3 and R#10).

Display mode:   Index width in bits:
   Text 1         -
   Text 2         9
 Multicolor       -
  Graphic 1       6 (*1)
  Graphic 2       13
  Graphic 3       13
  Graphic 4       -
  Graphic 5       -
  Graphic 6       -
  Graphic 7       -
(*1) Bit5 is fixed to zero.

- Sprite Attribute Table -

Sprite mode:    Index width in bits:
   mode 1         7
   mode 2         10 (*1)
(*1) Bit9 selects sprite color table (0) or sprite attribute table (1).
     Bit8 and Bit7 are fixed to zero for the attribute table.

- Sprite Pattern Table -

Sprite mode:    Index width in bits:
   mode 1         11
   mode 2         11

Index Calculation
-----------------

The following variables occur in every display mode:
  org_line: display line,
            0 <= org_line < 256
  line:     org_line after scroll adjustment (R#23 is applied),
            0 <= line < 256
  name:     value in VRAM at address (name_index & name_mask),
            0 <= name < 256

The "/" operation is integer division ("div").
The "%" operation is modulo/remainder ("mod").

- Text 1 -

Aka SCREEN0, WIDTH40.
0 <= x < 40
y = org_line / 8
name_index = 00C00h + y * 40 + x
pattern_index = name * 8 + line % 8

Adding 00C00h was probably done to make the index 12 bits wide, just like
in Text 2 mode. You can see this addition in action if you enable 212 lines
display and then play with different values of R#2. Pay attention to the
characters displayed in the lower few lines of the screen.

- Text 2 -

Aka SCREEN0, WIDTH80.
0 <= x < 80
y = org_line / 8
name_index = y * 80 + x
pattern_index = name * 8 + line % 8

- Multicolor -

Aka SCREEN3.
0 <= x < 32
y = line / 8
name_index = y * 32 + x
pattern_index = name * 8 + line % 4

- Graphic 1 -

Aka SCREEN1.
0 <= x < 32
y = line / 8
name_index = y * 32 + x
pattern_index = name * 8 + line % 8
color_index = name / 8

- Graphic 2 and 3 -

These modes are equivalent, except for the sprite mode.
Aka SCREEN2 and SCREEN4.
0 <= x < 32
y = line / 8
name_index = y * 32 + x
pattern_index = ((line / 64) * 256 + name) * 8 + line % 8
color_index = ((line / 64) * 256 + name) * 8 + line % 8

- Graphic 4 -

0 <= x < 256
name_index = line * 128 + x / 2

- Graphic 5 -

0 <= x < 512
name_index = line * 128 + x / 4

- Graphic 6 -

0 <= x < 512
name_index = line * 128 + x / 4
plane = (x / 2) % 2

- Graphic 7 -

0 <= x < 256
name_index = line * 128 + x / 2
plane = x % 2

- Sprite Mode 1 -

0 <= sprite_nr < 32
attrib_index = sprite_nr * 4

0 <= pattern_nr < 256
pattern_index = pattern_nr * 8

When sprite size is 16x16, Bit1 and Bit0 of the index are ignored.
Four patterns define the look of a single sprite, like this:
 0 2
 1 3

- Sprite Mode 2 -

0 <= sprite_nr < 32
attrib_index = 512 + sprite_nr * 4
color_index = sprite_nr * 16

0 <= pattern_nr < 256
pattern_index = pattern_nr * 8

When sprite size is 16x16, Bit1 and Bit0 of the index are ignored.
Four patterns define the look of a single sprite, like this:
 0 2
 1 3

Planar Addressing
-----------------

The V9938 has 3 pins that select a group of VRAM ICs each: CAS0, CAS1 and CASX.
CASX is used to select the expansion RAM, which can be accessed through the CPU
interface and through commands, but cannot be displayed directly.

Each VRAM group can be 0K, 16K or 64K. A group can consist of 8 chips of 1-bit
wide RAM or 2 chips of 4-bit wide RAM, but for the purpose of addressing we can
just pretend it is 1 chip with 8-bit wide RAM.

In real MSX machines, the following group combinations are used:

  CAS0:  CAS1:  CASX:   Description:
   16K     0K     0K    MSX1 with V9938 and 16K VRAM (*1)
   64K     0K     0K    MSX2 with 64K VRAM (*2)
   64K    64K     0K    MSX2 with 128K VRAM
   64K    64K    64K    MSX2 with 128K VRAM and 64K expansion (*3)

*1: Examples are the Spectravideo SVI-738 X'PRESS and the Yamaha CX5MII/128.
    The only V9938-specific display mode that they can fully display is
    80-columns text mode (TEXT2).
*2: Known 64K VRAM machines: Canon V-25, Hitachi MB-H3, Talent TPP-311 and 312,
    Toshiba HX-23.
*3: No known machines shipped with expansion VRAM. It was a popular amateur
    upgrade for users of FastCopy, to reduce the number of required disk swaps.

In most display modes addressing is linear, meaning the lower 64K of address
space uses the VRAM connected to CAS0 and the upper 64K of address space uses
the VRAM connected to CAS1.

In SCREEN7/8 (Graphic 6/7) the addressing is planar, meaning that even VRAM
addresses are mapped to CAS0 and odd addresses are mapped to CAS1. This means
that SCREEN 7/8 are only available on 128K VRAM machines. The most likely
reason for using planar addressing is getting a higher bandwidth: even though
the data lines are shared between all VRAM groups, the row select has to be
done only once.

VR=0 / VR=1 mode
----------------

The VDP chip has 10 pins that are connected to the VRAM subsystem to control
the read/write address. There are 8 address pins, these select the CAS/RAS
address. And there is a CAS0 and CAS1 pin, these select one of two ram chip
(groups). In total this allows for a 17-bit address space (=128kB). (I'm
ignoring the extended VRAM here).

When the VDP does a memory access, it's puts the 17-bit logical address on
these pins. The way how this is done depends on the VR bit (bit 3 of register
8), see table below.

In VR=1 mode, it first puts the bits A15-A8 on the address bus, followed by the
bits A7-A0. The whole time either pin CAS0 or CAS1 is selected, depending on
address bit A16. In VR=0 mode, .. well see table ;)


  VDP  |         VR=0         |         VR=1         |
address|----------------------|----------------------|
  pin  | RAS(high) | CAS(low) | RAS(high) | CAS(low) |
-------+-----------+----------|-----------+----------|
  AD0  |     A6  #2|     1  #1|     A8    |    A0    |
  AD1  |     A7    |    A0    |     A9    |    A1    |
  AD2  |     A8    |    A1    |    A10    |    A2    |
  AD3  |     A9    |    A2    |    A11    |    A3    |
  AD4  |    A10    |    A3    |    A12    |    A4    |
  AD5  |    A11    |    A4    |    A13    |    A5    |
  AD6  |    A12    |    A5    |    A14    |    A6    |
  AD7  |    A13    |    A6  #2|    A15    |    A7    |
-------+-----------+----------|-----------+----------|
CAS0/1 |          A14         |          A16         |

#1  this pin is always '1' in the CAS phase
#2  A6 is output both in CAS and RAS phase, this allows to use both 8/6 and 7/7
    RAS/CAS configurations (see VDP data sheet for details)


One important side-effect is that in VR=0 mode, the VDP can only address 32kB
memory. This is what makes the demo 'syntax infinity' work even though R14 is
programmed 'wrongly' (it writes to address 0x1????, but expects the data to end
up at address 0x0????).

Another side effect is that when switching between VR=0 and VR=1 modes, the
address space gets shuffled around, much like bit 7 in VDP register 1 for
TMS99x8 chips.