don't click here

Devon Tries to Understand the Genesis and Make His Own Emulator

Discussion in 'Technical Discussion' started by Devon, Feb 24, 2024.

  1. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,246
    1,416
    93
    your mom
    May or may not be inspired by the development of clownmdemu... :ssh:

    So, I've taken a recent interest in figuring out how the components of a Sega Genesis operate, and I decided that I wanted to try my hand at making my own emulator. Now, this is not really me trying to compete with any other emulator out there, and it's more than likely not going to be the most accurate or optimized emulator. Basically, it's just a personal exercise to home in my knowledge and learn more about the hardware, just for pure fun. I decided that basically logging my journey here might be of some use/interest for others, and really just to use as an excuse to infodump. :V

    Anyways, I wanted to share some stuff I learned in preparation for this project. However, if I get anything incorrect, do let me know, and I will correct it. It would especially be valuable for this project.

    68000 Bus Interfacing

    I recently learned were the use and purpose of the 68000's AS (Address Strobe) and DTACK (DaTa ACKnowledge) pins, and how they are utilized to manage data read/writes on the bus, alongside the UDS (Upper Data Strobe), LDS (Lower Data Strobe) and R/W (Read/Write) pins. Here's a nice little page that illustrates it some. There's also this as well.

    The 68000 has 23 bit address bits. It excludes bit 0, so the addresses that it will request will always be even. It also has 16 data pins, which means, it can only handle bus data 16 bits at a time. Yes, this means longword reads/writes involve doing 2 bus cycles to handle the full 4 bytes, which is why longword operations on the bus take longer than byte/word operations.

    The general function of a bus cycle is that the 68000 will put the requested address on its address pins, set its R/W pin to indicate if a read or write should occur, and then set its AS pin to basically "announce" to the bus that it would like to perform an operation. If the 68000 is wanting to write data, then it will also put the data on its data pins. The address will then be decoded and whatever device on the bus responds will set the data if there's a read, or take in the data if there's a write, and then set DTACK to indicate that it has responded. After that, if there was a read, the 68000 will take in the data. Finally, the 68000 and the responding device will reset their flags and the bus cycle ends.

    In total, this takes at least 4 CPU cycles to execute, the first being the 68000 starting the bus cycle, then the second being a device responding to it, then the third being the 68000 taking in data if there was a read and resetting its flags, and then the fourth being the responding device resetting its flags. However, note that I said "at least 4 CPU cycles". This is because the responding device may not handle the data and set DTACK in time, and as such, additional CPU clock cycles may be introduced while the 68000 waits for the device to finish. An example of this happening on the Genesis is with Work RAM refreshes, which will cause any requests for read/writes to Work RAM to be delayed if it's in the middle of a refresh. This is why executing code from Work RAM is a bit slower. For more info about it, here's this thread. Hell, for some address ranges, no device will ever respond, so the 68000 will end up in a perpetual wait state, waiting for DTACK when it'll never be set. An easy example is attempting to read from PSG. PSG is write only, so it'll only ever respond to writes, not reads.

    How the 68000 reads an individual byte is that it will go to the word in which the byte is part of (again, no bit 0 pin), and it will utilize the UDS and LDS pins to individually select the byte. If the byte the 68000 wants is on an even address, it will select the upper byte. If it's on an odd address, it will select the lower byte. Of course, if the 68000 wants to read a whole word, it will set both pins to select both bytes. When doing a byte write, the 68000 will actually copy the byte into both halves of the data pins, and of course set UDS and LDS respectively. Attempting to perform a word operation from an odd address would not work, because that would require the 68000 to handle the lower byte from the word it's situated in, and then go to the next word and handle the upper byte, and the 68000 just doesn't do that at all, so an address error occurs instead.

    This is what I have for now. I'll be sure to update on the progress I make and anything I learn further down the line.

    68000 Bus Arbitration

    It is possible for a separate device to request access to the bus to whatever with it. This is done with 3 pins on the 68000: BR (Bus Request), BG (Bus Grant), and BGACK (Bus Grant ACKnowledge). The device begins a bus request by setting BR, and then the 68000 will respond by setting BG, indicating it will release the bus once it's ready to (basically, when the 68000 is not accessing the bus and when no other device is accessing the bus). Once the 68000 is ready, the device will set BGACK and become the Bus Master. Once it is finished, it will reset BR, and once it's complwtely finished, it will reset BGACK, and the 68000 will have access to the bus again.

    VDP DMAs from 68000 memory basically have the 68000 request the VDP to take over the bus so it can read data. The Z80 also uses this mechanism to read data from the bus via the bank window and I believe VDP/PSG. Of course, if the 68000 doesn't have control of the bus, it will be locked in place until it regains access. This is why VDP DMAs from the bus freeze the 68000, and why the 68000 pretty much slows down when the Z80 reads data from the bus. This also explains why the Z80 has to wait for VDP DMAs to finish, since the VDP has control of the bus at that point, and needs to wait its turn (and thus is why PCM playback from ROM via the Z80 tends to be so crusty).

    As mentioned, the 68000 won't release its bus until it has finished any bus cycle it is currently processing. This of course introduces further delays for any device requesting the bus, since it needs to wait for the 68000 to be ready. This can be another source of Z80 slowdown/lockup if it wants to access the bus, especially if the 68000 is dealing with a device that hasn't finished processing data in time or just no device responding period. An example of that besides Work RAM refreshes is the 68000 filling up the VDP's FIFO. Basically, when the 68000 sends data to the VDP, it gets queued in a (4 slot, I believe) FIFO queue. If the 68000 manages to fill it up and attempt to send more data, the VDP won't respond until the FIFO is cleared up. I believe this is easily done by manually copying data to VRAM during active display, particularly since the VRAM is accessed a byte at a time instead of a word at a time like CRAM and VSRAM do. So that's another source of slowdown for the Z80 if it wants to access the bus.

    You can see further info about this in Kabuto's hardware notes and here.
     
    Last edited by a moderator: Feb 24, 2024
    • Informative Informative x 6
    • Like Like x 3
    • List