The test program, in short: Execute the same instruction (or short instruction sequence) number of times and use the E6 timer to measure how long it took. The E6 timer has a resolution of 255682Hz (3579545Hz / 14). R800 runs at 7159090Hz (2 * 3579545Hz). So the timer ticks once every 28 R800 ticks. ----------------------------------------------- org #c000 start equ #0100 ld de,start ld hl,begin ld bc,begin1-begin ldir push de ld hl,loop ld bc,loop1-loop ldir pop hl ld bc,(20000-1)*(loop1-loop) ; number varies depending on ldir ; length of the test-sequence ld hl,end ld bc,end1-end ldir jp start begin di out (#e6),a begin1 loop muluw hl,bc ; test sequence loop1 end in a,(#e6) ld l,a in a,(#e7) ld h,a ei ret end1 ----------------------------------------------- Below are the results, it's formatted like this ---------------------------------------------- [ prologue ] instruction-sequence [ repeat-count ] result: cycles: [ instruction break-down ] notes: ---------------------------------------------- Prologue are extra setup instructions, they are only executed once, only a few test have them Instruction-sequence is the actual instruction that's been tested. Mostly this is a single instruction, but sometimes there is a pair of instructions. repeat-count indicates how many times the instruction-sequence is repeated. I tried to repeat the instruction as much as possible. But to fit longer instruction sequences (longer in number of opcode bytes) in memory, the repeat count is sometimes reduced. raw-result: this is the value of the E6 timer at the end of the test. Note that you cannot directly compare this number to other test because other test might have a different repeat-count. cycles: here I calculated the number of cycles required per 'instruction-sequence'-iteration. I calculated this by comparing the result to the 'nop' instruction (a nop takes 1 cycle). Note that the duration of most instructions in almost never exactly an integer multiple of the duration of the nop instruction. A possible explanation might be the refresh (only happens at the end of an instruction, so for longer instructions the chance of a refresh is relatively less). But we need additional test to fully explain the refresh behaviour. Instruction break-down: Here I tried to split the instruction into micro-ops. I used the following notation: f opcode fetch, no page-break, takes 1 cycle r data read, no page-break, takes 1 cycle w data write, no page-break, takes 1 cycle F opcode fetch, page-break, takes 2 cycles R data read, page-break, takes 2 cycles W data write, page-break, takes 2 cycles x extra cycle (for various stuff) i IO --------------------------------------------------------------- * Instructions that don't do additional bus transactions (read, write, IO). nop [x 40000] result: 1629-1645 openmsx-rev11643: 1637-1638 cycles: 1 [f] add hl,bc [x 40000] result: 1633-1645 openmsx-rev11643: 1637-1638 cycles: 1 [f] cpl [x 40000] result: varies between 1628-1647, most often around 1638 openmsx-rev11643: 1637-1638 cycles: 1 [f] note: same result for cpl, daa, ccf, scf, exx, ex af,af', ld sp,hl, rrca, ... neg [x 20000] result: 1637 (most often 1637, sometimes 1638) openmsx-rev11643: 1643-1645 cycles: 2 [ff] note: - measurement on real HW is MUCH more stable for this test compared to previous tests - same result for ld {a,i,r},{a,i,r} - all results below for the real HW probably vary as well, though in my initial tests on the real HW I only noted the most often occuring value. I should re-test, but that's a lot of work. im 0/1/2 [x 20000] result: 2453-2360, most often 2456 openmsx-rev11643: 2459-2460 cycles: 2 [ffx] di [x 40000] result: 3267 openmsx-old: 3267 openmsx-rev11643: 3273-3274 cycles: 2 [fx] note: I added 'openmsx-old' tags where older openmsx revisions were better (possibly by accident) than the current revision. ei [x 40000] (VDP irq disabled) result: 1435 (!) openmsx-rev11643: 1434-1436 cycles: 1 [f] note: This is faster than nop!! Both instructions are one cycle, but it seems there's no refresh delay after a 'ei' instructions. At least with this assumption the theory matches very closely the measurment.... Implemented like this in openMSX and it matches the measurement very well. I verified that 'VDP irq disabled' has no influence on speed: I mean even with VDP irq disabled, the speed of 'nop' is the same as before. ld a,ixh [x 20000] result: 1637 openmsx-old: 1636 openmsx-rev11643: 1643-1545 cycles: 2 [ff] muluw hl,bc [x 20000] result: 29354 openmsx-rev11643: 29360-29361 cycles: 36 [ffx34] mulub a,b [x 20000] result: 11419 openmsx-rev11643: 11425-11427 cycles: 14 [ffx12] * Instructions that do a single read/write operation ld (#d000),a [x 10000] result: 2449 openmsx-rev11643: 2446-2447 cycles: 6 [FffW] note: page-break when switching from opcode fetching to data writes and again when switching from data write to opcode fetching ld a,(#d000) [x 10000] result: 2449 openmsx-rev11643: 2445-2447 cycles: 6 [FffR] note: same as above but for read ld a,(#0100) [x 80(!)] result: 20 openmsx-rev11643: 20 cycles: 6 [FffR] note: opcode fetch and read are in the same 256-byte page, but still there is a page-break when switching from opcode reading to data reading [ ld hl,#d000 ] ld a,(hl) [x 40000] result: 6522 openmsx-rev11643: 6522-6523 cycles: 4 [FR] [ ld hl,#d000 ; ld bc,0 ] ld a,(hl) ; add hl,bc [x 20000] result: 4054 openmsx-rev11643: 4054-4055 cycles: 3 + 2 [fR F] [ ld hl,start+XX ; ld bc,2 ] ld a,(hl) ; add hl,bc [x 20000] result: 4055 openmsx-rev11643: 4054-4055 cycles: 3 + 2 [fR F] note: same test as above, but now the data is in the same page as the instructions. There is still a page-break. [ ld hl,start+XX ; ld bc,2 ] ld (hl),a ; add hl,bc [x 20000] result: 4054 openmsx-rev11643: 4054-4055 cycles: 3 + 2 [fW F] note: same as above but for writes ld r,(ix+o) [x 10000] result: 2854 openmsx-rev11643: 2848-2849 cycles: 7 [FffxR] note: between opcode fetching (3 bytes) and read there is one additional cycle to calculate the result of IX+o ld (ix+o),r [x 10000] result: 2854 openmsx-rev11643: 2848-2849 cycles: 7 [FffxW] note: same as above, but for write ld (ix+o),n [x 10000] result: 2861 openmsx-rev11643: 2848-2849 cycles: 7 [FfffW] note: Has the same number of cycles as 'ld (ix+o),r' even though this instruction is one byte longer. The IX+o calculation is done in parallel with fetching the last opcode byte. Z80 shows the same parallelism. note: Even though it has the same number of cycles as 'ld (ix+o),r', it is consistently slightly slower. A possible explanation is that there are page-breaks on opcode fetches in this test, since this instruction has length 4 instead of 3. bit 0,a [x 20000] result: 1637 openmsx-old: 1636 openmsx-rev11643: 1643-1645 cycles: 2 [ff] bit 0,(hl) [x 20000] result: 4055 openmsx-rev11643: 4065-4066 cycles: 5 [FfR] bit 0,(ix+o) [x 10000] result: 2861 openmsx-rev11643: 2848-2849 cycles: 7 [FfffR] note: I always found it strange that the format of these instructions is <0xDD> <0xCB> (offset before function) But this allows to hide the latency of calculating IX+o. The same happens on a Z80. * Instructions that read/write 2 bytes ld (#d000),hl [x 10000] result: 2854 openmsx-rev11643: 2848-2849 cycles: 7 [FffWw] note: no page-break for the second data write ld (#d0ff),hl [x 10000] result: 3264 openmsx-rev11643: 3260-3262 cycles: 8 [FffWW] note: of course here there must be a page-break ld hl,(#d000) [x 10000] result: 2854 openmsx-rev11643: 2848-2849 cycles: 7 [FffRr] note: similar for read ld hl,(#d0ff) [x 10000] result: 3264 openmsx-rev11643: 3260-3262 cycles: 8 [FffRR] push hl ; pop hl [x 20000] result: 8956 openmsx-old: 8950 openmsx-rev11643: 8928-8930 cycles: 6 + 5 [FxWw FRr] note: push is once cycle slower than pop, probably because push first has to decrease SP, while pop can directly put the value of SP on the bus * Read-modify-write instructions (single byte) inc a [x 40000] result: 1628-1642 openmsx-rev11643: 1637-1638 cycles: 1 [f] inc (hl) [x 40000] result: 11393 openmsx-rev11643: 11392-11393 cycles: 7 [FRxW] note: This instructions is slower than I expected, but it can be explained like this: - there is one cycle between data read and data write to get the time to actually increase the value - when switching from data-read to data-write there is a page-break (even though the address is identical) But see also the 'ex (sp),hl' instruction below inc (ix+o) [x 10000] result: 4080 openmsx-rev11643: 4076-4077 cycles: 10 [FffxRxW] note: similar as above, but 3 cycles longer: - need to fetch 2 more opcode bytes - need to calculate IX+o before data read can be executed res 0,a [x 20000] result: 1637 openmsx-old: 1637 openmsx-rev11643: 1643-1645 cycles: 2 [ff] res 0,(hl) [x 20000] result: 6523 openmsx-rev11643: 6522-6523 cycles: 8 [FfRxW] note: similar to 'inc (hl)' but opcode is one byte longer res 0,(ix+o) [x 10000] result: 4083 openmsx-rev11643: 4076-4077 cycles: 10 [FfffRxW] note: similar to 'inc (ix+o)'. Opcode is one byte longer, but cost of IX+o can be hidden rld [x 20000] result: 6529 openmsx-old: 6528 openmsx-rev11643: 6521-6523 cycles: 8 cycles [FfRxW] note: similar to 'res 0,(hl)' ld hl,#0000 ; ld de,#0100 ; ld bc,40000 ; ldir [no repeat] (src != dst) result: 11393 openmsx-rev11643: 11393-11394 cycles: 7 [FfRW] note: - as expected, 3 page-breaks are needed - there's no extra cycle needed to repeat the instruction TODO make a specific test for this ldi <-> ldir ld hl,#0100 ; ld de,#0100 ; ld bc,40000 ; ldir [no repeat] (src == dst) result: 11393 openmsx-rev11643: 11393-11394 cycles: 7 [FfRW] note: same timing as above, even though the read and write addresses are the same, there is a page-break in between * Read-modify-write 2 bytes ex (sp),hl [x 40000] result: 11393 openmsx-rev11643: 11392-11393 cycles: 7 [FRrww] note: - This result was unexpected to me: based on the results in the previous section, I expected a page-break between the read and writes. - 7 cycles is really the minimum. There are 5 bus transactions (1 opcode fetch, 2 data reads and 2 data writes). Stack and instructions are in different pages so there must be at least 2 page-breaks. - I've confirmed the [frrww] order, see exsphl.txt for details. [ ld sp,#xxFF ] ex (sp),hl [x 40000] result: 14660 openmsx-rev11643: 14634-14635 cycles: 9 [FRRwW] note: - Now we forced a page-break between reading/writing of the word. So two additional cycles. - This confirms that first two bytes are read and only then two byte are written: the order [FRwRw] would only require 8 cycles [ ld sp,#01F0 ] ex (sp),hl [x 200(!)] result: 58 openmsx-rev11643: 56-58 cycles: 7 [FRrww] note: Stack pointer in same page as instructions. But there still seems to be a pagebreak between instructions and data. ex (sp),ix [x 20000] result: 6529 openmsx-rev11643: 6521-6523 cycles: 8 [FfRrww] [ ld sp,#xxFF ] ex (sp),ix [x 20000] result: 8153 openmsx-rev11643: 8152-8153 cycles: 10 [FfRRwW] * Jump instructions jr $+2 [x 20000] result: 2455 openmsx-rev11643: 2459-2460 cycles: 3 [ffx] note: one additional cycle to calculate PC+offset jr $+3 ; nop [x 10000] <- nop is skipped result: 1229 openmsx-rev11643: 1229-1230 cycles: 3 [ffx] note: - even if opcode fetching is not contiguous there is no extra page-break - slightly slower than test above, because code is less dense and thus overall there are more opcode-fetch page-breaks jr $+2 ; nop [x 10000] result: 1636 openmsx-rev11643: 1637-1638 cycles: 3 + 1 [ffx f] [ ld a,0 ; or a ] jr z,$+2 [x 20000] result: 2454 openmsx-rev11643: 2459-2460 cycles: 3 [ffx] note: condition is true, jump is taken [ ld a,1 ; or a ] jr z,$+2 [x 20000] result: 1637 openmsx-old: 1636 openmsx-rev11643: 1641-1642 cycles: 2 [ff] note: condition is false, jump not taken PC+offset does not need to be calculated, so no extra cycle djnz $+2 [x 20000] result: 2454 openmsx-rev11643: 2457-2459 cycles: 3 [ffx] note: - whether the jump is taken or not does not matter for the program flow - 1/256 of the times the jump is not taken, this can explain why this test is slightly faster than 'jr $+2' (but it's well within measure error margin) [ ld b,0 ] djnz $+0 [x 2000(!)] result: i 63014 openmsx-rev11643: 62707-62708 (1) openmsx-rev11643: 63013-63014 (2) cycles: 3 [ffx] note: - Only repeated instruction 2000 times (iso 20000) because otherwise timer would overflow. - The difference between (1) and (2) is that in (1) the djnz instruction is 2-bytes aligned, while in (2) it's mis-aligned. This matters because it will cause a lot more page-breaks. Maybe some of the regression in the other tests could also be explained like this? Till now I didn't pay much attention to instruction alignment. [ ld b,1 ] djnz $ ; inc b [x 10000] result: 1228 openmsx-rev11643: 1229-1230 cycles: 3 [ff f] note: djnz only takes 2 cycles when it doesn't jump, same result as for jr jp $+3 [x 10000] result: 2028 openmsx-old: 2040 openmsx-rev11643: not yet retested cycles: 5 [Fffx] note: Two additional cycles are unexpected. It can either be explained as two 'x' cycles or as one 'x' cycles plus a forced page-break. See also tests below. [ ld a,0 ; or a ] jp z,$+3 [x 10000] result: 2029 openmsx: 2041 openmsx-rev11643: not yet retested cycles: 5 [Fffx] note: condition is true, jump is taken [ ld a,1 ; or a ] jp z,$+3 [x 10000] result: 1229 openmsx: 1227 openmsx-rev11643: not yet retested cycles: 3 [fff] note: condition is false, jump not taken extra 2 cycles are gone [ ld hl,start+XX ; ld bc,2 ] add hl,bc ; jp (hl) [x 20000] result: 3260 openmsx-rev11643: 3266-3268 cycles: 4 [F fx] note: Similar as above, 2 extra cycles [ ld hl,start+XX ; ld bc,#0102 ; ld de,#FF02 ] add hl,bc ; jp (hl) ; add hl,de ; jp (hl) [x 10000] result: 3241 openmsx-old: 3240 openmsx-rev11643: 3201 TODO retest on real machine, I have the feeling I'm not doing the exact same test now and before cycles: 8 cycles [F fx F fx] note: In this test every jump has a destination in a different 256-byte page. A single 'jp (hl)' instruction is still responsible for 3 cycles (though the cost of the forced page-break is pushed to the next instruction). This confirms that the two extra cycles in a jump are one 'x' cycle plus one forced page-break (in case of two 'x' cycles, this test would show yet an additional cost for the page-break). note: The 64 backward jumps in the first 256-byte page and the 64 forward jumps in the last page in this test are not executed. call $+3 ; pop hl [x 10000] result: 4880 openmsx-old: 4891 openmsx-rev11643: not yet retested cycles: 7 + 5 [FffWw FRr] note: This is again as expected, though the two extra cycles can be hidden by the data-writes. [ ld a,0 ; or a ] call z,$+3 ; pop hl [x 10000] result: 4880 openmsx-old: 4891 openmsx-rev11643: not yet retested cycles: 7 + 5 [FffWw FRr] note: condition is true, call is executed as expected, same result as unconditional call [ ld a,1 ; or a ] call z,$+3 [x 10000] result: 1229 openmsx-old: 1228 openmsx-rev11643: not yet retested cycles: 3 [fff] note: condition is false, call not executed [ ld hl,start+XX ; ld bc,3 ] add hl,bc ; push hl ; ret [x 10000] result: 4884-4887 openmsx-rev11643: 4885-4886 cycles: 12 [F fxWw FRr] note: 'RET' probably behaves as a 'JP (HL)' (two extra cycles), but these cycles can be hidden by the two data reads [ ld hl,start+XX ; ld bc,3 ; ld a,0 ; or a ] add hl,bc ; push hl ; ret z [x 10000] result: 4884-4887 openmsx-rev11643: 4885-4886 cycles: 12 [F fxWw FRr] note: Same test as above, but with a conditional ret that is always executed. As expected same result as above. [ ld a,1 ; or a ] ret z [x 40000] result: 1644 openmsx-rev11643: 1637-1638 cycles: 1 [f] * IO instructions in a,(0) [x 20000] result: 8066 openmsx-old: 8126 openmsx-rev11643: 8152-8153 cycles: 10 [ffI8] note: - 8 cycles for IO seems a lot, but this makes it the same speed as Z80 Z80: 4 cycles @ 3.5MHz R800: 8 cycles @ 7MHz This is needed to keep the timing on the external cartridge slots the same. - Besides IO port 0, i checked a lot of other ports, but not all. I did check some random unused port numbers and at least one input port from each IO-device in the turbor. Only the VDP ports are different (see below) in a,(#98) [x 20000] result: 44286 openmsx-rev11643: 40713 cycles: +/- 54 note: - Can be explained by the 'intelligent' VDP-delay added by the S1990 Need additional tests to accurately measure this. - Only IO ports 0x98-0x9B show this behaviour - TODO this still differs a lot between emulation and real HW [ ld b,0 ] in a,(0) ; djnz $-2 [x 1] [no page-break between in/djnz] result: 126 openmsx-old: 132 136 openmsx-rev11643: not yet retested cycles: 13 [ffI8 ffx] [ ld b,0 ] in a,(0) ; djnz $-2 [x 1] [page-break between in/djnz] result: 145 openmsx-old: 156 openmsx-rev11643: not yet retested cycles: 15 [FfI8 Ffx] note: - we forced a page-break between the in and the djnz instruction by properly aligning the start address - 2 extra cycles compared to test above, this confirms the decomposition of the in instruction is [ffI8] (as opposed to [ffI7 + page-break]) out (0),a [x 20000] result: 8065 openmsx-old: 8125 openmsx-rev11643: 8152-8153 cycles: 10 [ffI8] note: similar to 'in a,(0)' out (#98),a [x 20000] result: 44285 openmsx-rev11643: 40713 cycles: +/- 54 note: similar to 'in a,(#98)' Theory: R800 is connected to a 7MHz bus, but IO is done over a 3.5MHz bus. If the start of IO is at an odd clock cycle (so not at a clock edge at the slower bus) there is an extra wait cycle required. in a,(0) ; [nop] x A [x 8000] result: A=0 -> 3226 A=1 -> 3274 A=2 -> 3872 A=3 -> 3932 A=4 -> 4525 openmsx-old: A=0 -> 3248 A=1 -> 3255 A=2 -> 3893 A=3 -> 3913 A=4 -> 4557 openmsx-rev11643: A=0 -> 3261 A=1 -> 3269 A=2 -> 3914 A=3 -> 3926 A=4 -> 4565 cycles: A=0 -> 10 [ffxoI6] A=1 -> 10 [f ffxI6] A=2 -> 12 [f f ffxoI6] A=3 -> 12 [f f f ffxI6] A=4 -> 14 [f f f f ffxoI6] note: When there are an odd number of NOP instructions between two OUT instructions, one of the NOPs can be executed seemingly 'for free'. Or in other words when there's an even number of NOP instructions, the OUT instruction has an extra penality cycle. These results could be explained by assuming the following structure for the out command: 3 cycles [ffx..] followed by possibly one extra cycle to align to the slow 3.5MHz bus, followed by 6 cycles (= 3 cycles on the slow bus) for the actual IO. * Internal-ROM ld hl,(#xxyy) [x 10000] with yy=#00 -> no page break with xx in normal RAM result: 2855 cycles: 7 [FffRr] with xx in ROM, DRAM mode result: 2855 cycles: 7 [FffRr] with xx in ROM result: 4080 cycles: 10 [FffRmRm] (m -> memory wait cycle) with yy=#ff -> page break during read with xx in normal RAM result: 3265 cycles: 8 [FffRR] with xx in ROM, DRAM mode result: 3264 cycles: 8 [FffRR] with xx in ROM result: 4080 cycles: 10 [FffRmRm] note: - RAM or ROM in DRAM mode seems to have the same speed, including page break optimization - reads from ROM take 3 cycles, they don't become slower in case of a page break. So it seem there are always 2 cycles to set the address plus one wait cycle. - I did the same test with the 'ld (#xxyy),hl' instruction (even though writing to ROM doesn't make sense). I got the same results. [ ld a,128 ; or a ] [ BIOS ROM contains nop (x 11) ; ret m at address #1EB2 ] call #1EB2 [x 10000] result: [(D)RAM] 9763-9770 [ROM] 18757 cycles: [(D)RAM] 24 [FffWw F f f f f f f f f f f fRr ] (<- 23 !!) [ROM] 46 [FffWw Fm Fm Fm Fm Fm Fm Fm Fm Fm Fm Fm FmRr ] note: - this confirm 3 cycles per memory read from ROM - we measured 24 cycles for (D)RAM while there should only be 23, this might be a measurement error though: to calculate the number of cycles (in long code sequences) I compare it with the duration of a NOP instruction, for long durations differences this may be inaccurate * External-slot ld hl,(#4000) [x 5000] result: 2857 cycles: 14 [FffRmmmRmmm] ?? note: 5 cycles per memory access, close to 3 cycles (@ 3.5MHz) on Z80 but I'd expected it to match exactly. It's strange to have halve cycles on the external 3.5MHz cartridge slots. ld (#4000),hl [x 5000] result: 2857 cycles: 14 [FffRmmmRmmm] ?? ld a,(#4000) [x 5000] result: 1634 cycles: 8 [FffRmm] ?? ld (#4000),a [x 5000] result: 1634 cycles: 8 [FffWmm] ?? [ ld hl,#4000 ] ld a,(hl) [x 15000] result: 3669 cycles: 6 [FRmm] ?? [ ld hl,#4000 ] ld (hl),a [x 15000] result: 3669 cycles: 6 [FWmm] ?? [ ld hl,#4000 ] inc (hl) [x 15000] result: 7337 cycles: 12 [FRmmxWmmm] ?? TODO: RETN, RST CPI(R), LDI(R), INI(R), OUTI(R) HALT (how to test?) * refresh org #c000 di ld b,0 ld c,0 out (#e6),a loop nop ; variable number of nops djnz loop dec c jr nz,loop in a,(#e6) ld l,a in a,(#e7) ld h,a ret nops | real | openmsx-old -----+-------+--------- 0 | 8088 | 8036 1 | 10717 | 10716 2 | 13336 | 13382 3 | 16079 | 16042 4 | 18740 | 18726 10 | 34778 | 34753 cycles (without refresh) = 65536 * nops + 197375