Subject: Re: [gen] Generator 68000 emu bugs... testing against a real motorola cpu (long) |
From: sunder |
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.
Thanks :)
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.
The Idea:
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.
:)
Implementation Details:
(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