don't click here

Optimized Mega Drive Init for Sonic 1

Discussion in 'Engineering & Reverse Engineering' started by OrionNavattan, Nov 27, 2022.

  1. EDIT 3/22/2022: Added Sonic 3&K version made by RepellantMold.
    EDIT 3/3/2022: Added Sonic 2 versions, migrated to GitHub repository, and fixed bugs in Sonic 1 versions.
    EDIT 12/23/2022: Fixed the region variable being set on every boot (only needs to be done on a cold boot), and added a version for Sonic 1 AS.
    EDIT 12/7/2022: Fixed a couple of severe bugs in the code (VRAM was not being cleared properly and a Z80 register was missed), improved speed, and made use of more registers.

    Decided to tackle a small thing that has been bugging me for some time.

    The sheer amount of redundancy during Mega Drive/Genesis game initialization on cold boot is ridiculous: all memory except the Z80 RAM is cleared twice, and a bunch of dummy values are written to the VDP by the standard init library before being overwritten with real values later. This seems to have been a result of Sega requiring all games to use said init library (in the Sonic 1 AS disassembly, this is everything from EntryPoint to the tst.w (vdp_control_port).l under GameProgram; said library was apparently distributed as preassembled hexadecimal text per if the source code of Gargoyles is any indication), with game-specific values written afterwards. While this system does work, it is certainly not efficient, not to mention requiring duplicate code for clearing memory and configuring the VDP. But, what if, we instead write the real VDP register values right off the bat, and handle all initialization tasks in a streamlined fashion?

    Building on the standard Mega Drive/Genesis setup library, as well as this init code written by MarkeyJester, I put together a drop-in replacement for the init code in Sonic 1 that handles everything in one go with no redundancy and no changes in behavior from the original. The checksum check is still included, but it is easy to remove, and it, along with the TMSS check, setting the region, and clearing of RAM $FE00-$FFFF, are skipped on soft resets.


    While this code targets Sonic 1/2, it should be straightforward to adapt it to other games by changing variable names and values in SetupVDP accordingly.
    Last edited: Mar 23, 2023
    • Like Like x 4
    • Informative Informative x 1
    • Useful Useful x 1
    • List
  2. Brainulator


    Regular garden-variety member Member
    The actual original source code can be found in the Cliff Hanger and Ex-Mutants source code directories, with all the Engrish that entails.
  3. Quickman


    be attitude for gains Tech Member
    omg porjcet
    It's interesting to me that the original initialisation code (which I'm a little embarrassed to say I've always just copy-pasted into my own homebrew projects because I never did a really deep line-by-line investigation into what it actually does) doesn't follow the advice on Spritesmind you clearly did to poll the DMA busy flag.

    What would be a minimal initialisation routine, suitable for something like marsdev where you want to spend as little time working with assembly as possible, jump to compiled C and never look back?
  4. This issue is actually described in Genesis Technical Bulletin 12 (page 33 in this document) from September 1991, so apparently it wasn't something noticed until after the console had been out a couple years. In Sonic 2 Revisions 1 and 2 (Revision 0 lacks this check), this is done immediately after the standard init code:
    Code (ASM):
    2. GameProgram:            
    3.        tst.w   (vdp_control_port).l ; final line of standard init library
    5. CheckSumCheck:            
    6.        move.w   (vdp_control_port).l,d1           ; get status register
    7.        btst   #dma_status_bit,d1           ; was the console soft reset during a DMA operation?
    8.        bne.s   CheckSumCheck               ; if so, wait for it to finish

    I am planning to make a generic version of this code for homebrew (removing the checksum check and all game-specific behaviors such as preserving a section of RAM across resets). I don't have time to do it at this specific moment, but I will say that at the absolute minimum, you need to check for and fulfill the TMSS if required, set the VDP registers (even if it's just dummy values), and clear all memory. Basically, just get the hardware into a known state and ensure there's no garbage in any registers or memory (particularly important in the case of the Model 1 VA0, which has a hardware bug that can cause it to start with garbage in the 68k RAM). That's more or less the purpose of the standard init library: it doesn't set anything game-specific, it just prepares the hardware.
  5. MarkeyJester


    Nothing's Impossible Resident Jester
    I'm surprised anyone even looked at that, it was such a long time ago...

    It was only an experiment for curiosity to be fair, wanted to see how far one could really crunch down and push the init code into the vector table. I had made some significant improvements since then, in fact, there's one particular bug with the copy in that pastebin, the auto-increment was set to 2 before a DMA fill, and I see you fixed that issue in your version, nice one :)

    Not sure you need to disable the interrupts, I'm certain on power on the interrupts are all masked and the CPU is in supervisor mode, and I believe the same applies to soft reset too. I was trying to find verification on that, but the handful of pdf documents I scanned through, kept explaining the situation based on the "RESET" instruction, not the reset pin (with halt). I believe it to be true at least, or a lot of games would crash on reset due to interrupt during init, and they don't so... If I find confirmation I'll let you know.

    I don't think you'll need it, you seem to be on your own path now which is cool~ But just in-case you're curious, this is how far I managed to get.
  6. I found that Pastebin via an old post on SSRG. Funnily enough, the VDP increment bug was in the initial upload of my version. It took a few hours in Exodus to fully debug it.

    Disabling interrupts at the start of the code is more a precaution than anything else (granted, the original init code does it too, albeit at the end). The main thing that was on my mind there were flashcarts and how they hand off control to the game binary (do they reset the CPU, or do they do something like the TMSS bootrom? I don't know). It would also be of benefit if this code was ported to S&K, given the way that game jumps to the init code on any exceptions.

    And given that your code doesn't do it, I suspect clearing the Z80 registers is not strictly necessary either (the Z80 user's manual outright states that A, I, R, and PC are filled with zeros on reset, however I can't find any info on the state of the other registers on reset). It begs the question as to why Sega deemed that necessary, though if I had to guess, it may have been a precaution against developers not resetting the Z80 after loading code to its RAM.
  7. Brainulator


    Regular garden-variety member Member
    Something I'm curious about is how long to reset the Z80 for after loading a program into its memory. This forum post and this webpage both state that you have to wait at least 192 cycles between asserting (or turning on) Z80 reset ("move.w #0,$A11200") and releasing (or turning off) Z80 reset ("move.w #$100,$A11200"), with the latter webpage showing a loop that waits for this to happen. Sonic 1 has two separate places where the Z80 is reset as such, neither of which seem to last the supposedly-required 192 cycles: once during bootup, which has no wait time involved between writing and releasing reset, and another when loading the Z80 PCM sound driver, with a waiting period of 4 nop instructions, each of which last 4 cycles. I bring this up because if we're going to reset everything, then I want to make sure everything is done right, but not excessively.
  8. Clownacy


    Tech Member
    Both of those links mention that the delay is necessary for the YM2612 to initialise properly. 192 68k cycles is 32 YM2612 cycles, which is the longest that a YM2612 operation can take (or, at least, that's how long the BUSY flag is always set for - I've heard that the longest operation is actually 24 cycles).

    Looking at the YM2608 manual, the pin that's responsible for resetting the YM2612 is called "!IC". Toward the end of the manual, this comment is made:
    "When OPNA [the YM2608] is !IC (Initial clearness: 192 cycles or more of φM [the master clock, which is YM2612 cycles multiplied by 6]), the I/O port becomes an input mode."

    Assuming that the Z80 Reset flag really does reset the YM2612, then Yamaha's own documentation suggests that it might need to be held for 192 68k cycles. That's assuming that !IC actually needs to be held for 192 cycles, but it might just require waiting 192 cycles after pulling the pin low for a YM2612 cycle before doing any other YM2612 operations (*for a YM2612 cycle*, perhaps the reason why Sonic 1 only waits as long as it does). Additionally, that assumes that the Mega Drive's own circuitry doesn't hold the pin low for 192 cycles automatically. The lack of any mention of this requirement in the Genesis Software Manual and Sonic 1 not bothering to adhere to it casts a lot of doubt that the requirement actually exists.

    Curiously, Sonic 2's SoundDriverLoad function waits an absurd amount of time between pulling the reset bit low and releasing it, by looping with a dbf instruction for $FFE6 iterations. Sonic 2's SoundDriverLoad is exceptionally cautious compared to the one from Sonic 1: it preserves the data, address, and status registers, and makes sure that interrupts are disabled.

    Technically both of Sonic 1's Z80 resets have some delay: the one during bootup has a delay of 8 cycles due to the instruction that's placed between the instructions that pull the reset low and high.

    This is the kind of thing that would benefit from experiments on real hardware. We also have a copy of the YM3438's manual, which might hold some answers... if it were translated from Japanese.
  9. The bus arbiter's ZRES line is connected to both the Z80's RES pin and the YM2612's IC pin, so asserting Z80 reset should reset the 2612 as well. Given that Sonic 1's DACDriverLoad doesn't bother holding Z80 reset for more than 4 nops (which seems to line up with a remark in the Z80 user manual that "RESET must be active for a minimum of three full clock cycles before a reset operation is complete"), I'm starting to suspect that asserting ZRES for 192 68k cycles might not be necessary at all. I am planning to port my init code to Sonic 2, and when I do, I'll see if shortening the Z80 delay has any effect.

    Some relevant information from an old post on SpritesMind about the reset signals and behaviors of the console. On a soft reset (and I would assume also when the console is powered on), the bus arbiter asserts ZRES, and keeps it asserted until the 68k releases it by writing $100 to the Z80 reset register. Assuming you wait long enough before touching the Z80 in the init code, that would take care of properly resetting the 2612/3438.
    Last edited by a moderator: Mar 1, 2023
  10. Brainulator


    Regular garden-variety member Member
    That was definitely helpful. I was curious because I thought about merging the DAC driver loading subroutine into the rest of the initialization routine, seeing as the only other time it's called is during the "SONIC TEAM PRESENTS" screen, which is useless since there's only one Z80 driver in the game. (The Sonic 1 prototype we have does not do this.) Then again, the above-linked optimized routines initialize the controllers before loading the Z80 driver, rather than after, so that after the VDP register writes, one of the 68K registers will contain the value $40, which gets written to the controllers, and then the registers are all cleared. My thought was admittedly this: "Since the sound driver loading subroutine now only runs here, and the controller initialization routine only runs here, I can inline the two... and now I see that the latter routine stops the Z80 right after the DAC loader let it start up again. Seeing as there are a few nops involved in the previous routine, why not make it so that the controllers are initialized in place of those nops?"

    What's interesting to me is that before Sonic 2 Beta 5, said DBF loop ran for only $17 iterations. I wonder if this was done in an attempt to get the audio working on certain Mega Drive models; before Beta 5, Sonic 2 did not set the interrupt mode to 1, which seems to cause earlier Mega Drives to fail to produce sound (I've noticed it on one web browser emulator as well as Exodus, the latter of which is surmountable).
  11. Well, it indeed appears that that large wait loop that Sonic 2 enters while resetting the Z80 is completely unnecessary. Waiting the length of 4 nops like Sonic 1 does is sufficient to get things going. It also seems that the register-clearing Z80 binary isn't necessary either; although BC, DE, HL, IX, and IY are undefined on reset, Sonic 2's driver, and Sonic 1's DAC driver, seem to account for that. Requesting the Z80 bus after allowing time to reset the YM2612/3438, clearing the entire Z80 RAM, and retaining the bus until after the driver has been loaded seems to work without any issues on both my Model 1s (VA2 and VA6).

    Anyhow, new Sonic 2 versions, and in-lined DAC driver load plus a couple of major bugfixes for the Sonic 1 ones
    (the AS version wasn't initializing the joypads correctly, and neither were clearing the work RAM correctly due to a misunderstanding of mine about the lea instruction a few changes back).