15

I'm wondering how old-computer emulators work.

I can think of two possible approaches:

  • "hardware" approach - like a set of components that exactly act as low-level hardware elements. I mean a bus with its busy signals, CPU and fetch–decode–execute cycles, IRQs that must wait for CPU, etc... Or even set of logical gates and electric circuits to imitate the structure of the real machine.
  • "behavior" approach - do what we know the real machine would do and try to emulate all known side effects.

In other words: is there some kind of hardware reverse-engineering done or is it based on the programmers' experience?

I have found question What exactly is a cycle-accurate emulator?. Well, if there are cycle-accurate-emulators so there should also be no-cycle-accurate-emulators which would be examples of behavior approach as they wouldn't be as precise as the real machine.

On the other hand, the VICE emulator, for example, can deal with such complicated tasks as stealing cycles to display sprites described in How to avoid sprites confusing the raster timings on C64?. That, in my opinion, is only possible when you exactly duplicate hardware behavior. The proof would be to run any demo with sprites on side-borders effect.

Can you, please, shed some light on this? Or maybe there is someone involved in emulator development and can share his knowledge?


EDIT: I'm mainly interested how hardware quirks are dealt with like, for example, C64 graphic tricks.

  • 2
  • @GregHewgill thank you for the link. It's a great source of knowledge. But I am more interested how hardware-specific tricks are possible with emulator. I've edited my question to be more specific. – Adam Jun 5 '17 at 23:48
  • Emulators that aren't cycle-accurate tend not to be as perfect in their emulation of various "hardware tricks". For any given "trick", it can run the gamut from not supporting the trick at all, to supporting the trick as used by most applications, to supporting the trick in almost all circumstances. – Ross Ridge Jun 6 '17 at 1:40
  • 2
    Depends on what you want - In principle, emulators that are not cycle-accurate can generally be faster than the ones that need to wait for some T-states to pass to identically emulate the original hardware. So in case your goal is "I want to make an as-fast-as-possible replica of the original as possible and can live with a non-100% emulation of the original", you might not want a cycle-accurate emulation. I think there is justification for both approaches. – tofro Jun 6 '17 at 11:46
  • Depends also on what you want to emulate. Some late computers, like the Z80, have most of the quirks pretty well documented, and the hardware is simple enough by today standards for it to be pretty well emulated. – Rui F Ribeiro Jan 4 '18 at 16:40
20

I wrote my first emulators somewhere in the mid-to-late '90s, my first cycle-accurate emulator circa 2000 and have managed to outdo even that, writing a clock-sign-transition-accurate emulator — accurate to the half-cycle.

Two problems usually recur:

  • each component's proper implementation may be not only obtuse but possibly unknown; and
  • ensuring that the components act simultaneously is difficult.

Most official documentation provides a programming model for a component, not an exhaustive documentation of its internals. E.g. many sound chips have a noise channel. There's almost always an easy way to derive the pattern of noise that channel will produce. It's almost never documented. If it isn't documented, how are you going to figure it out?

If a CPU is running at 3 Mhz and it has a graphics chip that is also running at 3 Mhz, then both are doing work at the same time. If they can interact on any cycle, then you need a way of being able to figure out exactly what state each should have been in at each synchronisation point.

A simplification often made in the old days was that the CPU operates a whole opcode at a time. E.g. if it encounters PUSH HL then it puts HL onto the stack, adjusting the stack pointer and memory underneath, then magically and instantaneously warps forwards in time by the eleven cycles that should have taken.

Furthermore, if the graphics chip draws one line every 228 cycles then they can interleave by doing 228 cycles of work on the CPU (with a running error count to deal with not necessarily being able to stop exactly at 228), then drawing one more line of the display.

That sort of emulator is not cycle accurate. All changes the processor made within 228 cycles are seemingly batched up and performed instantaneously, immediately before the graphics chip managed to draw any of the line.

The densest scheme for cycle accuracy, in an example two-chip machine, is: run the CPU for one cycle. Then run the graphics chip for one cycle. Then repeat. Both components need to be written to be able to run a cycle at a time, and your computer's real CPU is going to do a lot of jumping around and cache thrashing.

An obvious improvement occurs for one-directional signalling — e.g. where a CPU may send commands to something like a sound chip, but doesn't receive anything back. Just keep a log of the signals, and then compute their effect later, or even asynchronously. It's producer-consumer, possibly cooperatively scheduled.

A related approach is not explicitly to keep a log, but just to write the dependency to be able to run for N cycles, allowing it to assume constant machine state throughout. E.g. suppose graphics are always drawn from the address range 0x4000–0x8000. Then keep a count of cycles since you last let the graphics processor run. Any time the CPU writes to the graphics range, run the graphics output for that number of cycles and zero the count. Then write the value and continue. You'll do better than lockstep cycle-by-cycle.

As to capturing quirks, that's usually just a matter of understanding the way in which the hardware operates. E.g. the Atari 2600 is very close in ideology to the discrete hardware that it replaced — there are no centralised counters, because propagating a number is a heavyweight operation, there are lots of individual local counters with a common clock and the ability to reset each other. You can't tell a sprite to appear at x=23 because the thing that draws the sprites never knows what the current x is. It just knows that it will receive 160 clocks per line (umm, simplified a little, but let me have it) so whatever it does, it must repeat every 160 cycles. And you can programmatically tell it to reset. It will always trigger sprite output when it overflows. So you can place sprites by telling it to reset at an appropriate moment.

If you model it as a bunch of distinct counters, you'll get much of the correct behaviour for free. If you try to get away with storing the x at which a sprite should appear, and correlating that with a global clock then you've attempted to change the problem and you'll probably end up getting it wrong.

  • Thank you for the answer. Can you, please, share an example of components' programming model (preferably for C64), link or scan maybe? That would be very interesting to see. – Adam Jun 6 '17 at 7:22
3

The answer is both ways, and combinations of the two. Sometimes logic diagrams of the original ASIC circuits are available to use, sometimes reverse engineering has to make guesses to match the known behaviors. Some emulators are designed to be more fast than accurate, but as host computers get faster, the accurate emulators run fast enough to be preferred.

Hardware "quirks" are best delt with by using the schematics to build logically equivalent cycle accurate software state machines. With no schematics, one waits to find bugs that deviate from vintage hardware, and fixes them one at a time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.