[gen] Generator 68000 emu bugs... testing against a real motorola cpu (long)

sunder sunder at sunder.net
Mon Feb 23 14:59:04 GMT 2004

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 

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:

*                                  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: &reg1  =a6@(12) - d0 register (in/out)
    long: &reg2  =a6@(16) - d1 register (in/out)
    long: &ccrout=a6@(20) - condition code register after (out)


         .align 1
.globl _asmtest

         pea a6@                      /*  Setup stack frame   */
         movel sp,a6
         movel a2,sp at -

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


         lsll #7,d1                   /* execute test opcode  */


         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
         movel sp at +,a2
         unlk a6

More information about the gen mailing list