Showcasing some of the audio debug features available for the Mega Drive
Active disassembly analyses code behaviour at runtime, and uses that information to build a more accurate disassembly than is possible using traditional "static" disassemblers.
I thought for awhile there that I'd never get this place online, but here we are. This is the official website for Exodus, the soon to be released emulator. The release date for Exodus is the 1st of May 2013. I'm making what I hope to be the final, release-ready build right now as I write this article.
It's been a long road getting to this point. I first started working on Exodus in late November 2006, with a focus on the Sega Mega Drive. About 3 months later, I had an emulator capable of playing quite a few Mega Drive games, minus support for sound. I've released preview versions of this emulator several times in a limited capacity, but it's always been far from what I wanted it to reach, until now. Almost 7 and a half years after I began work on Exodus, I'm finally ready to make the first full release.
Welcome to the official website for Exodus, a unique and powerful emulation platform. Please use the main menu above to learn more about Exodus, and to gain access to support and downloads.
Exodus is a software program designed to allow real physical hardware to be emulated in software. This is not a revolutionary idea. There are many other emulators out there for a wide variety of tasks and systems. What makes Exodus a little different is what goals it tries to achieve, what it does that other emulators do not, and what it doesn't do that other emulators do. More detail is given about the goals of Exodus in the "Design Philosophy" section. In this section, you'll get a quick overview of what makes Exodus different.
So, what does make Exodus different from other emulators? There are a few key points:
More detail about each of these items will be given below.
The first and most significant thing to understand about Exodus, is that it is not actually an emulator. Exodus is a generic emulation platform, which allows systems to be assembled from individual components at runtime. Plugins are used to add actual emulation support for real devices. A device may be an individual discrete component, such as a processor, sound, or video chip. This plugin model itself isn't as interesting as how Exodus builds systems from these devices. With Exodus, nothing that makes up a "system" is hardcoded. Exodus uses XML files, called modules, to build an actual system from a set of discrete components. The current system is simply defined as the current set of loaded modules, and any selected settings for those modules.
Perhaps more significantly than the plugin model itself, Exodus completely handles all the communication and interaction between each device. Exodus emulates the idea of the "system bus", and supports mapping devices in almost any way imaginable. Devices themselves can be written to simply emulate themselves in isolation, without knowing or caring about the system they are used in, or how they are physically connected to that system. When devices are emulated in this manner, they are inherently reusable in any system, under any situation in which the real device could be placed. This means that you only have to emulate one device, once, and there are no system-specific hacks that have to be applied in order to make that device work in a given system. Support can be added for new systems simply by writing an XML file to describe the physical devices in that system and the connection between those devices, and loading the file in the Exodus platform, provided emulation cores exist for all the required devices.
With this plugin model, and the XML-based method of defining system components, Exodus is probably the most generic, flexible, and scalable emulation platform ever written. The long-term goal of Exodus is to provide support for as many systems as possible, even to rival other projects such as MESS and MAME. The project goals, architecture, and design philosophy are setup to attempt to make this emulator easier to maintain and more flexible over time than other large-scale emulation projects however, as well as providing several key advantages they currently do not offer.
So, apart from this modular architecture, what's interesting about Exodus? The most important thing is accuracy, in particular, timing accuracy. Most emulators are extremely inaccurate when it comes to timing. The reason for that is that there's often a huge performance tradeoff involved. In a real system, you might have half a dozen or more discrete devices all doing things at the same time. Each device might run at its own rate with its own performance characteristics. In a real system, those devices are not completely isolated, that wouldn't be a very useful system, those devices need to interact. When you're emulating that system, how do you ensure that every single device access occurs in the correct order though? The simple fact is, most emulators do not. Most emulators aim for a certain degree of timing accuracy, which is usually just enough to emulate some particular set of existing programs that run on that platform, and that's as accurate as they get. Timing related bugs often become the biggest roadblock to increasing emulation accuracy as an emulator advances, and increasingly become more and more difficult to solve. So, how do you solve timing problems with 100% accuracy? Well, the first, most obvious way, is to run every device in what's called "lock-step". This is simply to advance every device in the system one by one, advancing each device by a single "step", ensuring that no device gets ahead of another. This works with 100% reliability. It's also very, very, very slow. The biggest problem this approach has is that you reduce your emulator to a single-threaded model, because everything always has to wait for everything else, so you can't really do anything in parallel. Since we now live in the age of parallel computing, this effectively cripples the performance of your emulator now and into the future. That leads into the next topic.
Exodus was built from the ground up to solve the unsolvable timing problems, and has a unique approach to timing accuracy. It adopts what I call the optimistic execution model. This idea isn't new either. The concept is simple, and it goes something like this: "Most of the time, a timing problem is not going to occur. Given that assumption, I want to execute my devices unsynchronized for as long as possible. If something ends up happening in the wrong order, I want to roll back to the previous point, and repeat the operation, this time with fore-knowledge about the timing requirements". This is the execution model Exodus uses. By executing in parallel for as long as possible, we can make use of multiple cores. The idea of state rollback is implemented as a core part of the platform itself, and is heavily optimized to be as fast as possible where a rollback is not required. Devices can assist the emulation by giving advance notice about significant events, such as interrupt generation, which are likely to affect other devices. System XML definitions can also give additional timing hints, such as forcing particular devices to always remain behind the current execution point of other devices. A combination of these techniques allows Exodus to achieve 100% timing accuracy, while making effective use of multiple cores to execute devices in parallel for as long as possible.
Another key point that makes Exodus different is simply what kind of users it targets. Exodus is not designed to allow you to play commercial games for mainstream systems. That may be a side-effect of what it does, but that is not its focus. There are many other emulators out there for a large number of systems. If you simply want to play games, one of those may be more suitable. Exodus is aimed at users who have a technical interest in a particular platform. A large number of debugging and diagnostic features are provided to assist in better understanding the internals of the hardware, and to assist in debugging problems, and developing code, where it may be difficult to gather information on the real hardware. To this end, Exodus has a strong focus on accuracy in all areas, with the goal that any given piece of code will behave exactly the same way in Exodus as it would on the real hardware. Debugging features are exposed to provide as much transparency as possible, so that at any given point, it can be understood what state each device is in, and to allow that state to be modified in real time through the debugger.
These points summarise the main things that make Exodus a little different from other projects. Hopefully this is enough to spark your interest. For more detailed information about the design of Exodus, as well as the current level of support and future plans of Exodus, please refer to other sections of this website.
A very specific philosophy governs this project. It is this philosophy that has shaped what Exodus has become thus far, and will continue to shape what it will become in the future. That philosophy can be broken down into the following key ideas:
A system is simply the combination of a set of discrete components. A Motorola 68000 chip in a Mega Drive is the same as a 68000 in an Amiga, an arcade system, etc. One core should exist for a given device, which emulates all the features of that device, so that the device can then be used in any system, without it being aware of any external details of the system that uses it. Each core should serve to make its emulation accurate and "feature complete", whether a particular feature or level of accuracy is required in any particular target system or not.
A good, clean architecture for a core is critical to ensure readability of the code, which directly affects how maintainable that core will be, and also how well it serves to document the device it emulates. All cores must be free from assembly code. This is critical for portability and future preservation of the core. In addition, all cores should make use of object and class structures where appropriate, and the code itself should be well written and structured.
Using less lines doesn't necessarily make your code better. You might be able to sum up a complex operation on one line using 10 bit shifts, half a dozen AND operators, and a few OR's. Consider carefully first if the resulting code is the cleanest, most readable way to express that operation. In most cases, a cleaner way exists. Use the "Data" class where appropriate, rather than directly performing bit manipulation. Avoid "magic numbers". Where constants exist, define them all in one place, then refer to them by name in code. This not only serves to make code more readable, it also helps to avoid bugs when writing code, especially when performing bit manipulation.
All cores should serve as a form of documentation on the device they emulate. To this end, the code must be well structured, readable, and extremely well commented. Cores need to be maintainable by future developers, not just the original author. Leave a 500 line comment if that's what you need to explain what something is doing. Where you use an external source of information to implement something, add it to the list of references for the project, so that other people can see why something is done a particular way. Someone should be able to understand everything they need to know about how your core works by reading through the code itself and the list of references. If they can't, you're missing a reference or a comment.
Every core should be as accurate as possible, and honestly report on its accuracy. If there is any point at which the real behaviour of the device is unclear, or where the core doesn't emulate a behaviour of the real device, it should be documented, so the limitations of a given core can be well understood to others, not just the original developer. Any "hacks", where something is written in a way that is known to be incorrect, just to get the correct apparent result in a given situation, should be avoided at all costs. These kind of hacks only serve to hide underlying problems, and make them harder to detect and debug later on. They ultimately hurt the goals of emulation and preservation rather than helping them.
Accurate emulation of timing is critical in a lot of systems. All cores should emulate the timing of the original hardware as much as is possible, in any case where that timing information can be observed by an external device. Cases where timing is known to not be respected, or where correct timing information is simply unknown, should be clearly documented.
It should be a goal of every emulation core to expose as many debugging features to the user as possible. Not only does this open the emulation platform up to people wanting to do development or testing, it also greatly assists in debugging the emulator itself, improving existing emulation cores, and developing new cores.
Performance is important, but only in as much as it does not infringe on good architecture, readable code, generic implementation, or accuracy. Computers get faster. Compilers get better. Inefficient code can be optimized later. Ultimately, slow cores will run full speed one day. On the other hand, unmaintainable cores will languish and die, no matter how fast they are. We're trying to preserve systems for the coming decades, not the next 2 years. We need to write cores that will stand the test of time.
If you understand the above philosophy, you will understand why Exodus has been designed the way it has, and what sets it apart from other emulators.