Subject: Re: [gen] Generator 68000 emu bugs... testing against a real motorola cpu (long)
Date: Mon, 23 Feb 2004 09:59:04 -0500
To: Generator development list
James Ponder wrote:
Ray (Lisa em) recently emailed me about this. I'd forgotten you'd emailed
and I had not got around to checking your comments. I now have, and umm.
Yes. You appear to be correct on all counts. Whoops.
Yup. :) To clarify, I'm in the process of building an opcode tester by using a real Motorola processor.
So if you've got any reservations about certain opcodes, please let me know so that I can test them. With this framework, I can't do certain things - like test branching or memory accesses, but I can test thing like rotates, shifts, and other bit/arithmetic operations.
I've got a ton of bugs in my Lisa emulator (otherwise I would have released it), so I want to verify that the Generator core is doing the right thing for all opcodes so as to eliminate it out of the pool of possible bugs. Otherwise, I'm not playing with a full deck. :)
The idea is to get the CPU core compiled on a m68k, then execute a single opcode with the three inputs (two register values + the CCR) on both the native 68k chip, and the Generator function, then compare the results in the output register and the CCR. If there's any difference, complain, then change the input values and try again, etc... :)
[I suppose, if I have enough energy/free time, I might do a NeXTStep port of Generator, but I'm not promising anything!]
A long time ago, I suggested that James test the Generator core against the UAE core. (I believe v0.3 or some another ancient ver. had both cores.)
The testing was done by running against Genesis game ROMs on both cores and checking for differences. I'm sure this located a lot of core bugs. But, this test didn't account for two important cases:
1. it can only test the cpu core against another emulator core - what if there are CPU bugs in UAE? So testing against a real 68k processor is important.
2. Genesis games are not going to use every possible 68000 opcode - their authors would optimize them for speed and size - and the games are not operating systems. With the Lisa, this is a bigger issue as this machine needs to run several OS's (LisaOS, Xenix, MacWorks), which will use opcodes that games are almost guaranteed not to. [i.e. backing out of opcodes when MMU exceptions occur, supervisor vs user mode opcodes, etc. A lot of this is of course covered by Generator and now works, I'm just presenting it as an example for this argument.]
History of this sub-project (incase you're curious about the setup):
(You can skip this part if your eyes have already glazed over.)
In order to do these tests, I needed a 68k machine that could run Generator and let me write assembly code. That implied that I needed something that was (relatively) fast, had enough disk, RAM, and could compile Generator. This also implied requiring GCC and therefore, to make my life easier, some form of Unix. :)
I originally tried to install OpenBSD on an old Mac IIsi - because it is a real Motorola 68k chip, but it lacks an FPU, so OBSD won't boot. (SoftFPU let me run the installer - which took 13+ hours, but the OS wouldn't boot without a real FPU.)
I looked around for 68882 sources/FPU cards for the IIsi, but gave up. There are plenty of dirt cheap 040 Macs on ebay, but I'm not about to spend $30 to ship a $5 computer - which I wouldn't have too much use for afterward.
Rather than continue with NetBSD (which also needs an FPU) or Linux, I remembered that my NeXT slab has a very nice 68040, and a lot more RAM than the IIsi, plus it's already got the NeXTStep OS on it. It was also a good use of the slab - so far, it's been just a museum piece for me. :-D
Dumbass me, I hadn't turned on the NeXT in years and had forgotten its password... Had to do the single user Command-~ trick to get in, then found, that oh, yeah, changing the passwd file isn't enough since once NetInfo runs, it's not going to use /etc/passwd... So I had to launch NetInfo service by service until I could run the nu command. After, I raided the peanuts archive to get a recent GCC. I then had to struggle to get ld, as, etc. to work: it turns out, they're on the NeXT Developer CD...
After a few minutes of beating up on as (GNU assembler) and trying to figure out it's syntax (it's neither what's in the Motorola 68k programmer's ref, nor what Generator's disassembler displays) I cheated and used gcc -S on a small C program to figure it out. :)
(Skip this if you don't care about 68k code.)
I'm passing two pointers to uint32 which get loaded into the d0,d1 registers as longs, and pointers to two uint16's which act as the CCR before the opcode and after. (The opcodes can be tested against word and byte operands of course - this is so that I don't have to build 3 different functions for the same test.)
I'll also probably limit the inputs to a few special cases to save some time - I don't intend to run the tests for months at a time. :) For bytes, the values could be: 0,1,2,4,8,16,32,64,128, 0x55, 0xaa, 0xff (and expansions thereof to words/longs, of course.)
For things like rotate/shifts, I could test all the values to shift by, especially those exceeding the size I'm shifting/rotating (0,16,32), to make sure they're handled properly... i.e. 0-35 (as long as they're legal as opcodes.)
Another set of permutations is the Condition Code Register input. (i.e. N,Z,V,X,C flags) which adds another 32 more permutations.
This method is currently also limited to testing one opcode per assemble/link cycle...
Making a more efficient tester:
I suppose that I could pad the opcode area I'm testing with say, 4 NOP's to provide enough padding for all opcodes, then get a pointer to the NOP area so that I could fill it with a new opcode to test. But, this causes cache coherency issues... Not an problem on the original 68000's, but anything above an 030 (maybe even 020?) will, according to the Motorola manuals, break.
That implementation might be this: I could put a label right before the NOP's, then add another pointer parameter which returns a pointer to the label. Then, the higher level C code could just overwrite the tested opcode and pad the remaining bytes with NOP's.
If you've got any ideas as how to do this on an '040 safely, i.e. force flush the instruction cache when there's an opcode change from user mode - (I'm not in the microkernel's context after all!), let me know.
It might not even be at all possible as it depends on whether NeXTStep allows text pages to be modified. i.e. the OS might mark them as read only. (This is more likely of modern security conscious OS's, but possible for simplifying page/vm management.)
Another problem might be this: if the page containing asmtest() gets swapped out to disk without the OS noticing the write and marking it as dirty - it would discard the change and load back the original.
Here's the actual asm test function:
[blackhole:ray:~/lisaem/68000-tester:29]$ more asmtest.s /**************************************************************************************\ * Generator Meter * * * * Copyright (C) 2004 Ray A. Arachelian * * All Rights Reserved * * * * * * MC68000 Assembly Opcode Tester Routines. These MC68000 instructions are to * * be executed on a real Motorola 68040 machine (NeXTStep 3.3) at the same time * * time that Generator CPU core code is executed with the same parameters. The * * condition code register and output registers are then compared in order to * * detect emulation errors. * * * \**************************************************************************************/ /*----------------------------------------------------------- C Prototype for this function call is: extern void asmtest(uint16 *ccrin, uint32 *reg1, uint32 *reg2, uint16 *ccrout); word: &ccrin =a6@(8) - condition code register before (in) long: ®1 =a6@(12) - d0 register (in/out) long: ®2 =a6@(16) - d1 register (in/out) long: &ccrout=a6@(20) - condition code register after (out) \*----------------------------------------------------------*/ .text .align 1 .globl _asmtest _asmtest: pea a6@ /* Setup stack frame */ movel sp,a6 movel a2,sp@- movel a6@(8),a2 /* Get pointer to ccrin */ movel a6@(12),a0 /* Get pointer to reg1 */ movel a6@(16),a1 /* Get pointer to reg2 */ movel a6@(20),a3 /* Get pointer to ccrout*/ movel a0@,d0 /* Get reg1 into d0 */ movel a1@,d1 /* Get reg2 into d1 */ movew a2@,d2 /* Get CCRin */ movew d2,ccr /* copy it to CCR */ /*---------------------------------------------------*/ MYOPCODE: nop /* Will overwrite NOP's */ nop /* with opcode to test */ nop nop nop nop nop nop /*---------------------------------------------------*/ ENDMYOPC: movew ccr,d2 /* Get new CCR value */ movel d0,a0@ /* Save d0 into reg1 */ movel d1,a1@ /* Save d1 into reg2 */ movew d2,a3@ /* Save CCRout */ jra L2 /* Return to C land */ .align 1 L2: movel sp@+,a2 unlk a6 rts