| 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 |