OXYGENHUB
◆ main openmsx / doc / internal / turbor-vdp-io.txt
13.7 KiB · Plain Text · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
Goal of the experiment
2
======================
3
4
On a MSX2+ machine or a turboR machine in Z80 mode, there are extra wait cycles
5
when accessing a VDP IO-port (0x98-0x9b) compared to other IO ports (these are
6
introduced by the T9769{A,B,C} chip).
7
8
In openMSX we also added those wait cycles in R800 mode. That's likely wrong.
9
We verify this hypothesis in this experiment. Spoiler: in R800 mode there are
10
indeed no extra wait cycles.
11
12
13
14
Sub-goals
15
=========
16
17
I don't have my real MSX computers permanently connected anymore. When I do set
18
them up, it's not too much extra effort to run a few more tests.
19
20
Emulation of Z80 timing is pretty straight forward. R800 is more complex. We
21
know about the following peculiarities:
22
- Align to even cycle on IO instructions (because R800, which runs at 7MHz,
23
  needs to access the IO bus, which runs at 3.5MHz).
24
- Doing two accesses to VDP IO-ports introduces a delay when those accesses are
25
  "too close together".
26
- Refresh behavior. Every so many cycles (current estimate: 210 cycles @7MHz),
27
  normal R800 instruction execution gets interrupted for a refresh period
28
  (current estimate: this period lasts 25.5 cycles. Best guess for that half
29
  cycle: because 50% chance it needs to align to an even cycle).
30
31
32
33
Design of the experiment
34
========================
35
36
I (repeatedly) run test programs with the following structure:
37
38
	DI
39
	OUT (#20),A	; reset counter
40
41
	<body>
42
43
	IN A,(#20)      ; latch counter, and read 32-bit value in [DE:HL]
44
	LD L,A
45
	IN A,(#21)
46
	LD H,A
47
	IN A,(#22)
48
	LD E,A
49
	IN A,(#23)
50
	LD D,A
51
	EI
52
	RET
53
54
Where <body> varies from test-to-test.
55
56
Alex Wulms gave me a FPGA test board that implements a 32-bit counter which
57
runs at 3.5MHz and is accessed via IO-ports 0x20-0x23. In openMSX this device
58
is emulated as the 'hirestimer' extension.
59
- Writing (any) value to port 0x20 resets the counter to zero.
60
- That counter increments at a frequency of 3.5MHz (because the MSX IO-bus runs
61
  at this frequency).
62
- Reading IO port 0x20 latches the value of the counter and returns the 8 LSB
63
  bits.
64
- Reading IO ports 0x21-0x23 retrieves the other bits of the latched value.
65
66
So in Z80 mode (runs at 3.5MHz) this board allows to make cycle-accurate
67
measurements. But the R800 runs at 7MHz, so we can only measure up-to 2 cycles
68
accurate.
69
70
The test code is located at the start of a 256-byte block (specifically at
71
address 0x0100 in my case). This ensures that none of the instruction fetches
72
take an extra cycle for the "page-break".
73
74
I ran all tests on a turbor-GT machine, both in Z80 and R800 mode.
75
76
(Not important) I ran all tests in compass, because that allows me to easily:
77
- Edit the test program
78
- Re-assemble (CTRL-A)
79
- Execute (CTRL-G)
80
- Look at the result: the value in registers DE:HL (F4)
81
82
83
84
The actual tests
85
================
86
87
In this section I'll present _processed_ test results. Because often the
88
details are not important for our main goal and too detailed results would only
89
add confusion. However these details could be important to (in the future)
90
investigate the sub-goals. Therefor I still included the raw results in an
91
appendix.
92
93
94
1) Establish a base-line
95
------------------------
96
97
Replace <body> with a sequence of n NOP instructions.
98
99
     n | Z80 | R800
100
    ---+-----+-----
101
     0 | 12  | 5
102
     1 | 17  | 5
103
     2 | 22  | 6
104
     3 | 27  | 6
105
     4 | 32  | 7
106
     5 | 37  | 7
107
108
The case n=0 (an empty <body>) is important. It measures the time between
109
resetting the counter and immediately after reading it back. Thus
110
	OUT (#20),A	; reset counter
111
	IN A,(#20)      ; read it back
112
113
This means that for the actual duration of the <body>, we must subtract this
114
n=0 result.
115
116
On a Z80 both an 'OUT (n),A' and 'IN A,(n)' instruction take 12 cycles. So the
117
value 12 in the above table makes sense. (Actually it also depends where
118
exactly within the OUT instruction the counter gets reset and where exactly the
119
counter gets latched, but apparently that happens at the same relative offsets
120
in the OUT/IN instructions).
121
122
On Z80, for the n>=1 cases we then get the, totally as expected, result that a
123
NOP instruction takes 5 cycles.
124
125
For R800 n=0 we measure 5 cycles at 3.5MHz, this could be either 9 or 10 cycles
126
at 7MHz. In our current R800 emulation model an IN or OUT instruction takes 9
127
cycles plus an optional cycle to 'align' the 7MHz R800-bus with the 3.5MHz
128
IO-bus. So that would be 10 cycles @7MHz in this case which matches the
129
measured value.
130
131
The case R800 n=1 also measures 5, that's because the cost of the NOP
132
instruction is "absorbed" by no longer having to wait for 1 cycle to align both
133
buses. And also for n>=2, the measured value only goes up every 2nd increment
134
of n.
135
136
137
2a) Measure a single OUT instruction, non-VDP port
138
--------------------------------------------------
139
140
In these group of tests the body of our test program is
141
	NOP, repeated m times
142
	OUT (#30),A
143
	NOP, repeated n times
144
145
I arbitrarily picked IO port 0x30 because it has no function and because (I
146
hoped) it introduces no extra wait cycles when accessing it. I put a variable
147
amount (possibly zero) of NOP instruction both in front and after the OUT
148
instruction.
149
150
Here are the results:
151
152
    m | n | Z80 | R800
153
   ---+---+-----+------------
154
    0 | 0 | 24  | 10  (sometimes 22)
155
    0 | 1 | 29  | 10  (sometimes 23)
156
    0 | 2 | 34  | 11  (sometimes 23)
157
    0 | 3 | 39  | 11  (sometimes 24)
158
    0 | 4 | 44  | 12  (sometimes 24)
159
    0 | 5 | 49  | 12  (sometimes 25)
160
    --+---+-----+------------
161
    1 | 0 | 29  | 10  (*)
162
    1 | 1 | 34  | 10  (*)
163
    1 | 2 | 39  | 11  (sometimes 23,24,25)
164
    --+---+-----+------------
165
    2 | 0 | 34  | 11  (sometimes 23)
166
    2 | 1 | 39  | 11  (sometimes 24)
167
    2 | 2 | 44  | 12  (sometimes 24,25)
168
169
  (*) The lack of a 'sometimes' annotation does not mean it never occurs,
170
      instead it probably means I didn't measure long enough to observe it.
171
172
Z80 is completely as expected. Here's the formula that predicts the result:
173
   12 + 12 + 5*(m+n)
174
175
The 'normal' R800 values (ignoring the 'sometimes' annotations) can also be
176
explained. There are now two IO instructions that can 'absorb' the cost of a
177
NOP. Thus again the measured value only goes up by 1 for every 2nd increment of
178
'm' or 'n' independently.
179
180
Now for the 'sometimes' values. For example for R800 n=0 we mostly measured
181
value 11, but from time time instead we measured the value 23. Very roughly
182
speaking, the chance of measuring the alternative value went up with the
183
duration of the test (so mostly visible in test 3b with high 'n' values).
184
185
This phenomenon can be explained by the R800 refresh stuff. At regular
186
intervals the R800 gets interrupted from normal instruction execution for
187
refresh. If such an interruption falls in the test then we'll measure a higher
188
duration. The value of this extra duration is 12 or 13 (glossing over the m=1,
189
n=2 result). This corresponds with ~25 R800 cycles. (This matches the current
190
emulation model).
191
192
193
2b) Measure a single OUT instruction, VDP port
194
----------------------------------------------
195
196
This is the most important test in this experiment (from the point of view of
197
the original goal). It's the same test as in 2a) but with port 0x30 replaced
198
with 0x98.
199
200
    m | n | Z80 | R800
201
   ---|---+-----+------------
202
    0 | 0 | 25  | 10  (sometimes 22)
203
    0 | 1 | 30  | 10  (sometimes 23)
204
    0 | 2 | 35  | 11  (sometimes 23)
205
    0 | 3 | 40  | 11  (sometimes 24)
206
    0 | 4 | 45  | 12  (sometimes 24)
207
    0 | 5 | 50  | 12  (sometimes 25)
208
    --+---+-----+------------
209
    1 | 0 | 30  | 10  (sometimes 22)
210
    1 | 1 | 35  | 10  (sometimes 23)
211
    1 | 2 | 40  | 11  (sometimes 23,25)
212
    --+---+-----+------------
213
    2 | 0 | 35  | 11  (sometimes 23)
214
    2 | 1 | 40  | 11  (*)
215
    2 | 2 | 45  | 12  (*)
216
217
Comparing 2a) with 2b) gives:
218
- Identical results for R800.
219
- For Z80 2b) is exactly 1 cycle higher than 2a).
220
221
Thus we can confirm our hypothesis:
222
- On a turboR in Z80 mode, accessing IO-port 0x98-9b introduces 1 wait cycle.
223
- But doing the same in R800 mode does not.
224
225
226
227
3a) Two OUT instructions, VDP-port followed by non-VDP port
228
-----------------------------------------------------------
229
230
Ok, so in the previous test we've already accomplished our goal. But we know
231
that there's something else going on in R800 (but not in Z80) mode, namely wait
232
cycles between VDP accesses that are "too close together". While we're add it,
233
let's also do some measurements on those.
234
235
To get a new base-line, we first measure access to a VDP-port followed by
236
access to a non-VDP port. More specifically, the new test body is:
237
	OUT (#98),A
238
	NOP, repeated n times
239
	OUT (#30),A
240
241
Which results in:
242
    n | Z80 | R800
243
   ---+-----+------------------
244
    0 | 37  | 15  (sometimes 27)
245
    1 | 42  | 15  (sometimes 28)
246
    2 | 47  | 16  (sometimes 28)
247
248
249
3b) Two VDP-OUT instructions
250
----------------------------
251
252
Now the more interesting test, we make the test body:
253
	OUT (#98),A
254
	NOP, repeated n times
255
	OUT (#98),A
256
257
And we get:
258
259
     n | Z80 | R800
260
    ---+-----+-------------------------
261
     0 |  38 | 41  (sometimes 53)
262
     1 |  43 | 41  (sometimes 53)
263
     2 |  48 | 41  (sometimes 53)
264
     5 |  63 | 41  (sometimes 53)
265
    10 |  88 | 41  (sometimes 53)
266
    20 | 138 | 41  (sometimes 53)
267
    30 | 188 | 41  (sometimes 42,53)
268
    40 | 238 | 41  (sometimes 47,48,53)
269
    50 | 288 | 41  (sometimes 52,53)
270
    51 | 293 | 41  (sometimes 53)
271
    52 | 298 | 41  (sometimes 53,54)
272
    53 | 303 | 41  (sometimes 54,55)
273
    54 | 308 | 42  (sometimes 54,55)
274
    55 | 313 | 42  (sometimes 55,56)
275
    56 | 318 | 43  (sometimes 55,56)
276
    57 | 323 | 43  (sometimes 56,57)
277
    58 | 328 | 44  (sometimes 56,57)
278
    59 | 333 | 44  (sometimes 56,57,58)
279
    60 | 338 | 45  (sometimes 57,58)
280
    65 | 363 | 47  (sometimes 59,60,61)
281
    70 | 388 | 50  (sometimes 62,63)
282
  (*) for higher values of 'n' the qualification 'sometimes' becomes more and
283
      more frequent
284
285
The trend is clear. For n<=53 the output remains constant. For n>53 the output
286
is again linearly rising, and in fact following the same curve as for non-VDP
287
ports.
288
289
If you work out the details of how many wait cycles are added, you'll find that
290
it matches pretty well with the current openMSX emulation model: openMSX
291
requires at least 62 cycles @7MHz between two consecutive VDP IO access. (TODO
292
see next chapter.
293
294
295
296
TODO Comparison with openMSX
297
============================
298
299
TODO repeat these experiments in openMSX and compare.
300
 [I plan to work on this soon, but need to take a break and want already to
301
  publish this.]
302
303
304
305
306
Appendix: raw test measurements
307
===============================
308
309
Notes:
310
- Test results are written as hex values (all other values are decimal)
311
- Comma separated values within one cell are repeated measurements of the same
312
  test.
313
- In some cases I did not repeated a test, in other cases many times. This is
314
  based on when I 'guessed' the variation in the result was interesting. Or
315
  when I was expecting some variation, but didn't get it in the initial
316
  measurements, I kept measuring until I did see a different result.
317
- Though that does mean there is some bias in the results. E.g. you cannot
318
  use these results to get an accurate estimate of the frequency at which the
319
  different values occur.
320
321
322
1) No (extra) OUT instructions
323
<body> =
324
	NOP, repeated n times
325
326
n | Z80   | R800
327
--+-------+-----
328
0 | c     | 5
329
1 | 11,11 | 5,5
330
2 | 16,16 | 6,6
331
3 | 1b,1b | 6,6
332
4 | 20,20 | 7,7
333
5 | 25,25 | 7,7
334
335
336
337
2a) Single OUT instruction, non-VDP port
338
<body> =
339
	NOP, repeated m times
340
	OUT (#30),A
341
	NOP, repeated n times
342
343
m | n | Z80   | R800
344
--+---+-------+------------
345
0 | 0 | 18,18 | 16,a,a,a,16
346
0 | 1 | 1d    | a,a,17,a,a
347
0 | 2 | 22    | b,b,b,b,b,17
348
0 | 3 | 27,27 | b,b,b,b,18
349
0 | 4 | 2c    | 18,c,c,c
350
0 | 5 | 31,31 | c,19,19,c,c
351
--+---+-------+------------
352
1 | 0 | 1d,1d | a,a,a,a,a,a,a ... (46 measurements)
353
1 | 1 | 22    | a,a,a,a,a,a,a ... (11 measurements)
354
1 | 2 | 27    | b,b,b,b,b,19,18,17,17
355
--+---+-------+------------
356
2 | 0 | 22    | b,b,b,b,17
357
2 | 1 | 27    | b,b,b,b,b,b,b,18
358
2 | 2 | 2c    | 19,c,c,c,18,c
359
360
361
2b) Single OUT instruction, VDP port
362
<body> =
363
	NOP, repeated m times
364
	OUT (#98),A
365
	NOP, repeated n times
366
367
m | n | Z80   | R800
368
--|---+-------+------------
369
0 | 0 | 19,19 | a,a,a,a,16
370
0 | 1 | 1e    | a,a,a,17
371
0 | 2 | 23    | b,b,b,17
372
0 | 3 | 28    | b,b,b,b,b,18
373
0 | 4 | 2d    | c,18,c
374
0 | 5 | 32    | c,19,c,c
375
--+---+-------+------------
376
1 | 0 | 1e    | a,a,a,16,a,a
377
1 | 1 | 23    | a,17,a,a,17
378
1 | 2 | 28    | b,b,b,b,b,b,b,b,19,17,b,b
379
--+---+-------+------------
380
2 | 0 | 23    | b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,17,17,b
381
2 | 1 | 28    | b,b,b,b,b,b,b
382
2 | 2 | 2d    | c,c,c,c,c,c,c,c,c,c
383
384
385
386
3a) Two OUT instructions, VDP-port followed by non-VDP port
387
<body> =
388
	OUT (#98),A
389
	NOP, repeated n times
390
	OUT (#30),A
391
392
n | Z80   | R800
393
--+-------+------------------
394
0 | 25,25 | f,1b,f,f,f,f
395
1 | 2a    | 1c,f,f,f,f
396
2 | 2f    | 1c,10,10,1c,10,10
397
398
399
3b) Two OUT instructions, VDP-port followed by another VDP port
400
<body> =
401
	OUT (#98),A
402
	NOP, repeated n times
403
	OUT (#98),A
404
405
 n | Z80    | R800
406
---+--------+-------------------------
407
 0 | 26,26  | 29,29,29,35,29,29,29
408
 1 | 2b     | 29,29,35,29,29,29,35
409
 2 | 30     | 29,35,29,35,29,29,29
410
 5 | 3f     | 35,29,29,29,29
411
10 | 58     | 29,29,35,29,29,29,35
412
20 | 8a     | 29,29,29,29,35
413
30 | bc,bc  | 2a,29,35,29,29,29,29,29
414
40 | ee,ee  | 29,29,30,35,29,29,2f,30,29,29,29,29,2f    (30 not a typo!)
415
50 | 120,120| 35,29,34,29,29,29,29,34,29,29,29,25       (34 not a typo!)
416
51 | 125    | 29,35,29,29,29,29,29,35,35,29,29
417
52 | 12a    | 36,29,35,29,29,29,36,29,36,29,36,29,36,35
418
53 | 12f,12f| 29,29,29,36,29,37,29,29,29,29,36,36,29,29
419
54 | 134    | 2a,2a,2a,2a,2a,2a,37,2a,2a,2a,36,2a,2a,2a,37,2a,2a,2a,36,2a,37
420
55 | 139    | 2a,38,36,38,2a,38,37,2a,37,2a,38,36,2a,37,2a,38
421
56 | 13e    | 2b,2b,2b,2b,2b,38,37,2b,2b,37,2b,2b,38
422
57 | 143    | 39,37,37,38,2b,39,38,2b,2b,2b,38,2b,2b,2b,38
423
58 | 148    | 38,39,2c,38,2c,2c,2c,2c,39,38,38,2c,2c,2c,2c,39
424
59 | 14d    | 39,2c,39,39,39,2c,38,3a,39,2c,2c,39,2c,2c,2c
425
60 | 152,152| 39,2d,39,2d,3a,2d,3a,39,2d,39,2d
426
65 | 16b    | 3d,3b,3c,3b,2f,2f,3d,3c,3b,2f,2f,2f,3c,2f
427
70 | 184,184| 3e,3e,3f,32,3e,3e,32,32,3f,32,3f,32,3e,3f,32