Here's something you don't see on my blog very often!
If anyone is curious, my favorite Switch games are Overcooked 1 and 2 (which are also available on other platforms).
Monday, May 25, 2020
Tuesday, April 14, 2020
Star Rider repair: closing coin door causes game reset!
(it was actually Apr 13th, I said 15th on the video by mistake)
Monday, April 6, 2020
Sunday, April 5, 2020
Latest Atari Dragon's Lair progress with Dexter
Here's the latest Dexter progress for Atari Dragon's Lair:
Fixes since last time include:
- no screen blanking at the beginning of scenes
- audio level increased
Fixes since last time include:
- no screen blanking at the beginning of scenes
- audio level increased
Thursday, January 2, 2020
My new Z80 emulator
So five years ago, I started speculating about how emulators could be improved and came up with several ideas. One of them was to emulate the clock pin on CPUs (and other devices such as flip-flops or the PIA6821) for maximum accuracy. I speculated that this would take a major performance hit but perhaps with multi-threading technology, this performance hit could be offset.
Well, I've been working on this secretly for several months now. I've emulated almost every TTL chip on the Dragon's Lair logic board as a standalone device and tied them all together. Performance at the moment is barely adequate on modern beefy hardware (75% CPU usage on a single thread on an AMD Ryzen) and not adequate on a Raspberry Pi 4.
I experimenting with using multi-threading solutions to speed this up, but the critical timing and the need to keep the whole system running in lock-step made me conclude that this must be run on a single thread. I'll keep looking at ways to use other threads to offload things like video and sound processing which I think will work fine.
So the good news is that I believe I'll be able to design the "perfect" Dragon's Lair emulator and it will be able to run at full speed on modern CPUs (such as the aforementioned Ryzen) and possibly even older CPUs like the Intel i5. When I say perfect, I mean that every single pin on every single IC in the system (including the Z80) will be emulated and have the exact timing of original hardware. Where a device is digital, there will be no shortcuts. (analog devices such as the sound chip will not be held to this same standard of perfection). I'm pretty excited about this because I sometimes study/repair original game board sets and not having the ability to study how the hardware is supposed to work via emulation has been quite inconvenient.
The bad news is that Dragon's Lair is the simplest of laserdisc games, so a game like Star Rider (which has three 6809E CPUs and a ton of TTL chips) probably won't able to run at full speed even on the newest of hardware using this approach. I'll be looking at thoughtful optimizations/compromises to make for these scenarios since obviously with enough compromises, any of these old games could be made to run at full speed.
Here's a brief description of how the Z80's pins work from an arbitrary instruction:
Here's every single pin of a Z80 captured via my logic analyzer. When I embarked on the journey to emulate this beast, I thought "this is insane. it will take so long to figure this stuff out." but.. this is the kind of emulator I want Daphne to be, so I did it anyway. now it's starting to make sense.
I had to do two separate captures because my logic analyzer can capture 32 pins at a time (which is a ton!) and the z80 has 40 pins. that alone almost made it give up.
I've put colored lines to show how this instruction (LD (HL), 00h) works.
First section ("M1") is loading the instruction's opcode from the ROM program. It sets the address to 1153h, lowers RD and MREQ lines, then the EPROM puts 36h on the data bus. Z80 then raises RD/MREQ, then lowers MREQ/REFRESH, putting the number 0005h on the address bus which is used for dynamic ram to refresh itself (I think dragon's lair ignores it because it uses static ram). That takes 4 clock cycles and is known in Z80 speak as "M1". Next section ("M2") it needs to read the rest of the instruction, so it sets address to 1154h, lowers RD/MREQ again and the EPROM puts 00h on the data bus. M2 is complete. Now it has the full instruction. It goes to the next section ("M3"). HL happens to have a value of A000h, so it sets the address bus to A000h, lowers MREQ but instead of lowering RD, it puts 00h on the data bus and lowers WR which tells the rest of the system that "Hey, I want to write a 00h to address A000h". The rest of the system's hardware maps that address to the RAM and the RAM wakes up and grabs the data from the data bus and stores it.
The amazing thing is... how did they design the Z80 in 1976 or whenever?
Well, I've been working on this secretly for several months now. I've emulated almost every TTL chip on the Dragon's Lair logic board as a standalone device and tied them all together. Performance at the moment is barely adequate on modern beefy hardware (75% CPU usage on a single thread on an AMD Ryzen) and not adequate on a Raspberry Pi 4.
I experimenting with using multi-threading solutions to speed this up, but the critical timing and the need to keep the whole system running in lock-step made me conclude that this must be run on a single thread. I'll keep looking at ways to use other threads to offload things like video and sound processing which I think will work fine.
So the good news is that I believe I'll be able to design the "perfect" Dragon's Lair emulator and it will be able to run at full speed on modern CPUs (such as the aforementioned Ryzen) and possibly even older CPUs like the Intel i5. When I say perfect, I mean that every single pin on every single IC in the system (including the Z80) will be emulated and have the exact timing of original hardware. Where a device is digital, there will be no shortcuts. (analog devices such as the sound chip will not be held to this same standard of perfection). I'm pretty excited about this because I sometimes study/repair original game board sets and not having the ability to study how the hardware is supposed to work via emulation has been quite inconvenient.
The bad news is that Dragon's Lair is the simplest of laserdisc games, so a game like Star Rider (which has three 6809E CPUs and a ton of TTL chips) probably won't able to run at full speed even on the newest of hardware using this approach. I'll be looking at thoughtful optimizations/compromises to make for these scenarios since obviously with enough compromises, any of these old games could be made to run at full speed.
Here's a brief description of how the Z80's pins work from an arbitrary instruction:
Here's every single pin of a Z80 captured via my logic analyzer. When I embarked on the journey to emulate this beast, I thought "this is insane. it will take so long to figure this stuff out." but.. this is the kind of emulator I want Daphne to be, so I did it anyway. now it's starting to make sense.
I had to do two separate captures because my logic analyzer can capture 32 pins at a time (which is a ton!) and the z80 has 40 pins. that alone almost made it give up.
I've put colored lines to show how this instruction (LD (HL), 00h) works.
First section ("M1") is loading the instruction's opcode from the ROM program. It sets the address to 1153h, lowers RD and MREQ lines, then the EPROM puts 36h on the data bus. Z80 then raises RD/MREQ, then lowers MREQ/REFRESH, putting the number 0005h on the address bus which is used for dynamic ram to refresh itself (I think dragon's lair ignores it because it uses static ram). That takes 4 clock cycles and is known in Z80 speak as "M1". Next section ("M2") it needs to read the rest of the instruction, so it sets address to 1154h, lowers RD/MREQ again and the EPROM puts 00h on the data bus. M2 is complete. Now it has the full instruction. It goes to the next section ("M3"). HL happens to have a value of A000h, so it sets the address bus to A000h, lowers MREQ but instead of lowering RD, it puts 00h on the data bus and lowers WR which tells the rest of the system that "Hey, I want to write a 00h to address A000h". The rest of the system's hardware maps that address to the RAM and the RAM wakes up and grabs the data from the data bus and stores it.
The amazing thing is... how did they design the Z80 in 1976 or whenever?
Saturday, September 28, 2019
DL Euro + Dexter running inside of a cabinet!
I realize that I haven't posted any updates on Dexter working with Dragon's Lair Euro in a while. Let me remedy by sharing this video and pictures from Matteo Marioni, running Dexter inside of his Dragon's Lair Euro cab. As you can see, there are still a few minor glitches to fix, but overall, it's looking great!
...
...
Saturday, August 17, 2019
How to modify MAME to shutdown if idle too long
So if you read my last post, you know that I am working on a MAME/Daphne cab that my kids may use from time to time. One problem I noticed is that some games (like Teenage Mutant Ninja Turtles) will sit on the start screen indefinitely if you insert a coin. I can't trust my kids to shut off the games while I'm at work, so I need a way to protect my CRT from being destroyed. The solution I came up with was to modify MAME to shutdown after no input has been received for 5 minutes.
I modified MAME v0.211 which as of this writing is almost the latest version (but not quite). As I am running MAME on a Raspberry Pi, I dug into the SDL code to add this hack. I tried to find a more universal solution that would also work on Windows but couldn't really figure it out after an hour or so of digging so just gave up.
I ended up using global variables which are normally a big no-no in my book, but since building MAME takes so long on the Pi, I wanted a solution that would avoid modifying any .h files (and thus force me to have to potentially rebuild MAME from scratch depending on which .h files I changed).
Here are the relevant files that I modified and the changes that I made:
src/emu/video.cpp:
// START MPO
extern u32 g_last_time_seconds_when_input_received;
// END MPO//-------------------------------------------------
// recompute_speed - recompute the current
// overall speed; we assume this is called only
// if we did not skip a frame
//-------------------------------------------------
void video_manager::recompute_speed(const attotime &emutime)
{
...
and a little ways further down
// if we're past the "time-to-execute" requested, signal an exit
if (m_seconds_to_run != 0 && emutime.seconds() >= m_seconds_to_run)
{
// create a final screenshot
emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CR$
osd_file::error filerr = file.open(machine().basename(), PATH_SEPARATOR "final.png");
if (filerr == osd_file::error::NONE)
save_snapshot(nullptr, file);
//printf("Scheduled exit at %f\n", emutime.as_double());
// schedule our demise
machine().schedule_exit();
}
// START MPO
u32 uTimeSeconds = machine().time().seconds();
// if we've just barely started up, then we need to seed the value
if (g_last_time_seconds_when_input_received == 0)
{
g_last_time_seconds_when_input_received = uTimeSeconds;
}
else
{
u32 diff = uTimeSeconds - g_last_time_seconds_when_input_received;
// If the system hasn't received input lately, then we need to shut down
// hard-coded since I am just doing a one-off feature for my own use
if (diff > 300)
{
machine().schedule_exit();
}
}
// END MPO
src/frontend/mame/ui/ui.cpp:
void mame_ui_manager::display_startup_screens(bool first_time)
{
const int maxstate = 3;
// int str = machine().options().seconds_to_run(); // MPO bool show_gameinfo = !machine().options().skip_gameinfo();
bool show_warnings = true, show_mandatory_fileman = true;
// disable everything if we are using -str for 300 or fewer seconds, or if we're the empty driver,
// or if we are debugging
// START MPO
// if (!first_time || (str > 0 && str < 60*5) || &machine().system() == &GAME_NAME(___empty) || (machine().debug_fla$
// suppress start-up dialogs
// END MPO show_gameinfo = show_warnings = show_mandatory_fileman = false;
src/osd/modules/input/input_sdl.cpp:
// START MPO, ugly global hack to avoid having to rebuild the entire code base (keeping changes inside of .cpp files only)
u32 g_last_time_seconds_when_input_received = 0;
// END MPO//============================================================
// sdl_keyboard_device
//============================================================
class sdl_keyboard_device : public sdl_device
{
public:
keyboard_state keyboard;
sdl_keyboard_device(running_machine &machine, const char *name, const char *id, input_module &module)
: sdl_device(machine, name, id, DEVICE_CLASS_KEYBOARD, module),
keyboard({{0}})
{
}
void process_event(SDL_Event &sdlevent) override
{
switch (sdlevent.type)
{
case SDL_KEYDOWN:
keyboard.state[sdlevent.key.keysym.scancode] = 0x80;
if (sdlevent.key.keysym.sym < 0x20)
machine().ui_input().push_char_event(osd_common_t::s_window_list.front()->target(), sdlev$
// START MPO
// We've received keyboard input (which is all we care about), so reset the timer
g_last_time_seconds_when_input_received = machine().time().seconds();
// END MPO break;
Subscribe to:
Comments (Atom)
