> > And to add to that: Emulate instructions and features as you encounter them.
> > your emulator until you hit an unimplemented opcode, add it in, rinse, lather,
> > repeat.
> Lemme just back up Bart here - everyone's first mistake when writing a CPU
> emulator is just to implement all the opcodes and *then* test it. Don't ever do
> that, it makes it nearly impossible to debug :-)
I don't think its strictly necessary that the test is done before all instructions are implemented and rather the important part is that the instruction set emulation is independently tested to some extent prior to debugging large applications.
My preference is to create a reasonably exhaustive test program which exercises all instruction forms and associated flag settings. This is a laborious task, to be sure, but with judicious use of macros and a structured test procedure, most of the work is reduced to calculating initial and expected values. This is a pretty boring task, so I don't expect it will be the only testing method used but such a program comes in extremely handy for regression testing.
This is my prefered method for implementing instruction handlers and testing functionality:
1) Implement load immediate and store immediate. Use a custom test jig to test all the instruction forms.
2) Implement jumps and branches. Again, implement all forms and step through custom test programs to make sure they work
3) Implement memory loads and stores. At this point, I'd ignore control registers that have side effects or otherwise behave differently from RAM.
4) Start implementing all of the other instructions, using custom test programs that are easily understood and have easily determined initial conditions at the point of test. The benefit of writing small test programs is that you don't have to spend time stepping through massive chunks of code that don't have anything to do with the test being performed.
When writing an exhaustive test program, you generally want to test that all instruction forms calculate the expected results for several operand combinations distributed throughout the valid operand domain. Boundary conditions and exceptional combinations are particularly important. The goal should be to obtain maximum coverage of the instruction handler code. You also want to catch "stuck-at" errors, particularly for flag calculations. This is usually done by setting initial values to the inverse of the expected results. A more difficult matter is testing that the final results get written to the desired location without modifying other parts of the processor state. Exhaustive testing would be a pain, but at least some testing should be done when chances for screw-ups are easily identified.
It would be interesting to hear how other people test their emulators. It seemed to me that many emulators are tested only when someone else reports a particular problem with a game or application. The stance often seems to be "we'll fix bugs when we find out about them" rather than doing the upfront work to test for correctness early on so that these situations occur less in the future.