OXYGENHUB
◆ main openmsx / doc / internal / r800-djnz.txt
6.0 KiB · Plain Text · last changed chore(): initial commit 1 month ago source blame history raw
1
It seems in some cases on R800 a djnz or jr instruction take one cycle longer
2
than expected. I discovered this purely by accident: I was doing another timing
3
test in which I varied the number of instructions inside a loop. For a specific
4
amount of instructions I got an expected result. This happened when a djnz
5
instruction was the very last instruction in a 256-byte memory page. In the
6
following test I'll investigate this in some more detail.
7
8
9
10
Test-program:
11
12
	org	#c000
13
14
	di
15
	ld	b,0
16
17
	ds      248         ; vary between 248-251
18
19
	out     (#e6),a
20
loop	djnz    loop
21
	in      a,(#e6)
22
	ld      l,a
23
	in      a,(#e7)
24
	ld      h,a
25
	ret
26
27
By varying the value of the 'ds' directive between 248 and 251, the start
28
address of the djnz instruction varies from 0xc0fd to 0xc100. For each position
29
we measure how long the test takes using the E6-timer (this timer ticks once
30
every 28 R800 clock cycles, there's also approx 18% overhead for refresh)
31
32
djnz-start-address  | real turborGT | openmsx-rev11821 | openmsx-fixed
33
--------------------+---------------+------------------+--------------
34
             0xC0FD | 31 - 33       | 31 - 32          | 31 - 32
35
             0xC0FE | 41 - 42       | 31 - 32 (!!!)    | 41 - 42
36
             0xC0FF | 52 - 53       | 52 - 53          | 52 - 53
37
             0xC100 | 31 - 32       | 31 - 32          | 31 - 32
38
 (there's some variation in the measured E6-ticks, we list the range of all
39
  measured values in this table)
40
41
Translated to clock ticks per iteration:
42
43
address | real | rev11821 | fixed
44
--------+------+----------+------
45
 0xC0FD |  3   |    3     |   3
46
 0xC0FE |  4   |    3     |   4
47
 0xC0FF |  5   |    5     |   5
48
 0xC100 |  3   |    3     |   3
49
50
There are 3 different cases in this test:
51
 0xC0FD and 0xC100:
52
   djnz instruction takes 3 cycles, this is the normal case for a djnz
53
   instruction which does execute a jump. There are no page-breaks: all
54
   opcode bytes are fetched from the same 256-byte memory page.
55
 0xC0FE:
56
   This is the unexpected case, for some yet unknown reason the instruction
57
   takes 1 cycle extra. Note that all opcode bytes are fetched from the
58
   same 256-byte memory page. See below for a possible explanation.
59
 0xC0FF:
60
   Here both opcode bytes of the djnz instruction are located in a different
61
   memory page, so per iteration there are two extra cycles for two page break,
62
   thus 5 cycles in total.
63
64
65
66
I did a similar test for the jr instruction:
67
68
	org	#c000
69
70
	di
71
	ld	b,0
72
73
	ds	242	; vary between 239-242
74
75
	out	(#e6),a
76
test2a	djnz	test2b
77
	in	a,(#e6)
78
	ld	l,a
79
	in	a,(#e7)
80
	ld	h,a
81
	ret
82
test2b	jr	test2a
83
84
jr-start-address  | real turborGT | openmsx-rev11821 | openmsx-fixed
85
------------------+---------------+------------------+--------------
86
           0xC0FD | 63 - 64       | 62 - 63          | 62 - 63
87
           0xC0FE | 73 - 74       | 62 - 64 (!!!)    | 73 - 74
88
           0xC0FF | 83 - 84       | 83 - 84          | 83 - 84
89
           0xC100 | 83 - 84       | 83 - 85          | 83 - 84
90
91
address | real | rev11821 | fixed
92
--------+------+----------+------
93
 0xC0FD |  6   |    6     |   6
94
 0xC0FE |  7   |    6     |   7
95
 0xC0FF |  8   |    8     |   8
96
 0xC100 |  8   |    8     |   8
97
98
Again 3 different cases in this test:
99
 0xC0FD:
100
   Both jr and djnz instruction take 3 cycles (when they do jump) there are no
101
   page break or no extra penalty cycles.
102
 0xC0FE:
103
   Now the jr instruction is the last in a memory page, this shows the same
104
   penalty cycle as a djnz instruction. Again there is no 'real' page break in
105
   this test.
106
 0xC0FF and 0xC100:
107
   In both these cases there are two page breaks per iteration (but at different
108
   locations for the two cases). So 8 cycles per iteration in total.
109
110
111
112
113
114
Possible explanation (this is just a guess but it does explain the
115
measurements):
116
117
This behaviour could be explained by some (very limited) pipeline behaviour in
118
the R800: it seems that the decision to cause a page-break on the next
119
instruction is already made during the execution of the current instruction,
120
after the last opcode byte is fetched, but before the destination address of
121
the jump is calculated. (Though when the jump does actually go to another page,
122
this is also a reason for a page-break).
123
124
It's possible all R800 instructions have this pipeline behaviour, but it's only
125
visible in the djnz and the jr instructions. All other instructions either don't
126
jump (the PC simply increments) and then it's not possible to externally detect
127
when exactly the 'must-fetch-opcode-using-page-break-decision' is made. Or the
128
instruction does jump but it already causes a page-break for another reason, so
129
the possible extra page-break is always masked. The list of instructions that
130
cause a 'jump' is:
131
132
   djnz, jr, jr-conditional
133
     These show the extra page-break behaviour
134
135
   jp, jp-conditional, jp (hl/ix/iy)
136
     There's _always_ a forced page-break after this instruction. (The exact
137
     reason why is not clear to me.)
138
139
   call, call-conditional, ret, ret-conditional, rst, accept-an-IRQ
140
     All these instructions (or events) push/pop on/from the stack. When
141
     switching between data read/write and opcode fetch there's always a
142
     page-break;
143
144
   otir, otdr, inir, indr, ldir, lddr, cpir, cpdr
145
     (When these are repeated PC is decremented by 2.) All these instructions
146
     do a data read or write and thus there already is a page-break when
147
     switching back to opcode fetching (even if opcode and data are in the
148
     same memory page).
149
150
   halt
151
     I'm not sure about this instruction: it's possible this instruction
152
     constantly decrements PC by one (after it's been increased). At least
153
     this is how Z80 does it. So it might be the case that a halt instruction
154
     as the last instruction in a memory page 'loops' a bit slower. Though
155
     this effect will be hard to notice. At the moment I don't know how to
156
     write a test to detect this. Suggestions are welcome.
157
158
159
160
161
As you've already seen in the measurements above: I implemented a forced
162
page-break in the djnz and jr instructions when they are located at address
163
0x..FE. After this fix the measurements in openMSX match the real hardware.
164
165
166
167
Wouter