mirror of
https://github.com/ekeeke/Genesis-Plus-GX.git
synced 2024-11-13 22:35:10 +01:00
459 lines
17 KiB
Plaintext
459 lines
17 KiB
Plaintext
******************************************************
|
|
* Sega Genesis Technical Notes by Bart Trzynadlowski *
|
|
* and many others! *
|
|
******************************************************
|
|
|
|
|
|
Second Edition: February 7, 2000
|
|
- Current
|
|
- Made a table of contents
|
|
- Added tile information and Scroll layer name table information
|
|
- Added info on Genesis security system and "do nots" courtesy of
|
|
Flavio. He also found a mistake in the joypad notes.
|
|
- Added SMD ROM stuff
|
|
First Edition: February 3, 2000
|
|
- Initial release
|
|
|
|
|
|
This document is intended to be used as a reference alongside sega2.doc or any
|
|
other complete Sega Genesis technical documentation, it is not intended as a
|
|
standalone resource for learning about the Sega Genesis game console.
|
|
There is also some information here pertaining to specific situations
|
|
such as using the Starscream 68000 CPU core in an emulator project.
|
|
I wrote this document while working on a Genesis emulator. I felt some
|
|
things needed more description than was available. This document is intended
|
|
for emulator developers and Genesis programmers.
|
|
Feel free to pass this document around freely. If you use any parts of
|
|
it that were contributed by people other than me, it would be a good idea to
|
|
give them credit.
|
|
Any useful feedback is very much appreciated, especially corrections
|
|
and new entries. Do not ask about ROMs or anything stupid. trzy@powernet.net
|
|
Check my page at: http://www.powernet.net/~trzy as well.
|
|
Much thanks to Joe Groff, Steve Snake, nyef, ATani, and Flavio for
|
|
the invaluable help.
|
|
|
|
|
|
-- Table of Contents --
|
|
0. Control Port Write Modes
|
|
1. Auto-Increment
|
|
2. VRAM Address Decoding (for Writing)
|
|
3. Tiles
|
|
4. Scroll Layer Name Tables
|
|
5. Scroll Layers and Video Resolution
|
|
6. Joypads
|
|
7. Crash Course on the Genesis Security System and Common Don'ts
|
|
8. Emulating RAM and ROM w/ Starscream
|
|
9. SMD ROM Format
|
|
|
|
|
|
-- 0. Control Port Write Modes --
|
|
|
|
The VDP control port, 0xC00004, has two write modes: "Register Set" (write1)
|
|
and "Address Set" (write2). The VDP is able to distinguish between which mode
|
|
you want to use by looking at bits 15 and 14 of the word you write to the
|
|
control port.
|
|
|
|
10: Write1 Otherwise: Write2
|
|
|
|
For write2, bits 15 and 14 are CD1 and CD0 so the concern of conflict arises.
|
|
If you look at the possible access modes which are specified by the 6 CD bits
|
|
you will not find any that have 10 in CD1 and CD0.
|
|
|
|
|
|
-- 1. Auto-Increment --
|
|
|
|
The auto-increment value (in register #15) is apparently set to 2 by default.
|
|
|
|
|
|
-- 2. VRAM Address Decoding (for Writing) --
|
|
|
|
For words and longwords, the VRAM address decoding process is not as
|
|
straightforward as some would have you believe. The A0 address bit is not used
|
|
in decoding, what this apparently means is that it is treated as 0 (thus
|
|
preventing misaligned word writes).
|
|
A0 is used to test wether or not the bytes in a word should be swapped
|
|
or not. If A0 = 1, they are swapped, if it is 0, then bytes are not swapped.
|
|
A0 is also used when adding the auto-increment value to the VRAM address after
|
|
some data is written.
|
|
|
|
C example of emulating this:
|
|
|
|
if (addr & 1)
|
|
data = ByteSwap(data);
|
|
*((unsigned short int *) (vram + (addr & 0xfffe))) = data;
|
|
addr += auto_inc;
|
|
|
|
|
|
-- 3. Tiles --
|
|
|
|
The Genesis tile format is quite simple. Each pixel is represented by 4 bits
|
|
thus allowing 16 colors per every tile. Tiles are 8x8. The Genesis allows for
|
|
up to 64 colors to be displayed: 16 per palette, with 4 palettes. Palettes are
|
|
not specified in the tile bitmap, that information is elsewhere and beyond the
|
|
scope of this note.
|
|
|
|
Example of a bitmap for the letter "A". We use the color 0xa for each
|
|
of the pixels. Remember, color 0 is _always_ transparent in any
|
|
palette:
|
|
|
|
dc.l $00077000
|
|
dc.l $07700770
|
|
dc.l $07700770
|
|
dc.l $07777770
|
|
dc.l $07700770
|
|
dc.l $07700770
|
|
dc.l $00000000
|
|
dc.l $00000000
|
|
|
|
|
|
-- 4. Scroll Layer Name Tables --
|
|
|
|
Scroll layers (who's addresses and sizes are specified in VDP registers) are
|
|
"name tables" where each entry contains an index to a specific 8x8 tile. These
|
|
entries are arranged in a linear fashion.
|
|
Each entry is a word in size. Here is the format for an entry:
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|PRI|PL1|PL0|VFP|HFP|I10|I9 |I8 |I7 |I6 |I5 |I4 |I3 |I2 |I1 |I0 |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
PRI Priority (1 = highest, on top; 0 = lowest, on bottom)
|
|
PL1-PL0 Palette (0, 1, 2, or 3)
|
|
VFP Vertical flipping (1 = true)
|
|
HFP Horizontal flipping (1 = true)
|
|
I10-I0 Index of 8x8 tile in pattern area (VRAM $0000).
|
|
Multiply this by 32 to get offset in VRAM of the tile
|
|
bitmap
|
|
|
|
The information here applies to both Scroll A and Scroll B, please refer to
|
|
more complete documentation for information on how to set Scroll layer
|
|
addresses and what-not.
|
|
|
|
|
|
-- 5. Scroll Layers and Video Resolution --
|
|
|
|
The Genesis supports two video modes: 32x28 cell and 40x28 cell (which in
|
|
pixels are 256x224 and 320x224.) The video resolution is how many 8x8 tiles
|
|
are displayed on-screen. The vertical portion differs with PAL I believe, but
|
|
it that is not relevant.
|
|
Scroll A and Scroll B can have a number of different sizes which
|
|
obviously often cannot be all shown on-screen: 32x32, 32x64, 32x128, 64x32,
|
|
64x64, and 128x32. The Sega Programming FAQ says that 128x128 is possible, but
|
|
I don't think it is since that would take up 32KB of VRAM which would not
|
|
leave room for the other Scroll layer, the Window, or the patterns.
|
|
|
|
Only a portion of the Scroll layer is visible on the screen. How it is
|
|
displayed can be represented by the following diagram:
|
|
|
|
** Assuming the Scroll size is 64x64 and the screen 40x28, everything is
|
|
addressed in cell units (obviously not to scale) **
|
|
|
|
- = Scroll layer
|
|
* = Visible portion
|
|
|
|
H0 H39 H63
|
|
+****************************************--------------------+
|
|
V0 |* V0 * |
|
|
|* * |
|
|
|* * |
|
|
|* * |
|
|
|* * |
|
|
|* V27 * |
|
|
|**************************************** |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
V63 | |
|
|
+------------------------------------------------------------+
|
|
|
|
Thus you can see that there are tiles beyond the right limit of the visible
|
|
screen, and below as well. This method of having the layer larger than can be
|
|
displayed is for scrolling. The above is what would occur if one set the
|
|
Scroll size to 64x64 and the resolution to 40x28 without scrolling the screen
|
|
or anything.
|
|
If you are having problems emulating Scroll layers because everything
|
|
is shifted over you are probably forgetting to render the invisible parts of
|
|
the Scroll or to skip over them and on to the next visible row. I hope that
|
|
made sense.
|
|
|
|
|
|
-- 6. Joypads --
|
|
|
|
** Begin Email **
|
|
From: Joe Groff
|
|
To: Bart Trzynadlowski
|
|
Date: Sat, 15 Jan 2000 23:06:49 -0800 (PST)
|
|
Subject: Re: DGen/SDL
|
|
|
|
> is there any good info anywhere on control pad interfacing?
|
|
|
|
As far as I can tell, this is how the controller works. There are two one-byte
|
|
data sets:
|
|
v & 0x40: always set
|
|
v & 0x20: C
|
|
v & 0x10: B
|
|
v & 0x08: right
|
|
v & 0x04: left
|
|
v & 0x02: down
|
|
v & 0x01: up
|
|
and:
|
|
v & 0x40: always clear
|
|
v & 0x20: START
|
|
v & 0x10: A
|
|
v & 0x08: right
|
|
v & 0x04: left
|
|
v & 0x02: down
|
|
v & 0x01: up
|
|
By reading a word from 0xA10002 (for first controller) or 0xA10004 (for
|
|
second), the word will have one of the above bitmasks written to both bytes.
|
|
If you write a byte to 0xA10003(for 1st) or 0xA10005(for 2nd) with the 0x40
|
|
bit set, you'll get the first set, otherwise you'll get the second.
|
|
|
|
Example:
|
|
Player 1 is pressing left and the A and B buttons simultaneously. Player 2
|
|
is pressing B, C, and START. As the game, in order to probe controller 1:
|
|
- I send byte 0x00 to 0xA10003 (or word 0x0000 to 0xA10002 :)
|
|
- I read a word from 0xA10002, which gives me 0x1414. From the lower table,
|
|
I see A and left are being pressed.
|
|
- I send byte 0x40 to 0xA10003.
|
|
- I read another word from 0xA10002. This time I get 0x5454, which from the
|
|
upper table means B and left are being pressed.
|
|
Similarly, to probe controller 2:
|
|
- I send byte 0x00 to 0xA10005.
|
|
- I read from 0xA10004, and get 0x2020. So START is being depressed.
|
|
of the controller!
|
|
- I send byte 0x40 to 0xA10005.
|
|
- I read from 0xA10004 again, get 0x7070, so B and C are being pressed.
|
|
|
|
There's also a lot of odd code to handle 6-button controllers in DGen, but
|
|
as it's getting a bit late, I can't quite understand it. Hopefully this is
|
|
accurate and clear enough to at least get you started.
|
|
** End Email **
|
|
|
|
** Begin Email **
|
|
From: ATani
|
|
To: Bart Trzynadlowski
|
|
Date: Sat, 15 Jan 2000 23:06:43 -0800
|
|
Subject: Re: genesis tech question post
|
|
|
|
Ok, Well on the genesis the joysticks are read in by the z80 and passed to
|
|
the 68000 chip via memory addresses: A10003 & A10005.
|
|
|
|
If bit 6 of the Stored Controller 1 information is set then you return the
|
|
following information when reading Address: A10003 (Byte mode):
|
|
|
|
Bit: Description:
|
|
0 Up
|
|
1 Down
|
|
2 Left
|
|
3 Right
|
|
4 B
|
|
5 C
|
|
|
|
If Bit 6 is not set return the following:
|
|
Bit: Description:
|
|
4 A
|
|
5 Start
|
|
|
|
Address A10005 is the same except values returned are for joystick port 2.
|
|
|
|
The Stored data are in reference to the byte values written to A10003 and
|
|
A10005 (joystick port 1 and joystick port 2)
|
|
** End Email **
|
|
|
|
Flavio points out the information from ATani's email may be somewhat faulty:
|
|
|
|
"The Z80 has nothing to do with joypad reading. Actually, I believe the Z80
|
|
banker thingy will go wacko, should you attempt such a stunt. Gotta test
|
|
that."
|
|
|
|
|
|
-- 7. Crash Course on the Genesis Security System and Common Don'ts --
|
|
|
|
** The following information is courtesy of Flavio **
|
|
|
|
Crash course in Genesis security:
|
|
|
|
1) There must be either 'SEGA' or ' SEGA' in ASCII at offset 0x100 (256
|
|
decimal.)
|
|
|
|
2) If the four least significant bits of 0xA10001 are higher than zero, the
|
|
poor programmer must write 'SEGA' to 0xA14000.
|
|
Example:
|
|
MOVE.B $A10001, D0
|
|
ANDI.B #$F, D0
|
|
BEQ.S NO_VDP_LOCK
|
|
MOVE.L #'SEGA', $A14000.
|
|
NO_VDP_LOCK:
|
|
[Yer code here]
|
|
|
|
Yes, I know many of you can't read 68K ASM yet, but I don't know how to do
|
|
this on Paul Lee's C compiler (or any other for that matter.) :/
|
|
|
|
A list of common Caveat Gennyptor's follows:
|
|
|
|
- Be careful not to access forbidden addresses (see SEGA2.DOC), as the Genny
|
|
locks up instantly if you dare to touch them.
|
|
- Don't read from the VDP data port (C00000) if you have sent a
|
|
"VRAM/CRAM/VSRAM write" command (and vice versa.)
|
|
- The FM doesn't work if the Z80 is reset (their reset lines are wired
|
|
together.)
|
|
- Always have the Z80 busreq'ed before reading the joyports.
|
|
- Never attempt to read PSG ports.
|
|
- DMA transfers should be controlled by code placed at the Genny's work RAM
|
|
(0xFF0000-0xFFFFFF.)
|
|
- Don't attempt to transfer data to/from the VDP if a DMA is in progress.
|
|
- Z80 RAM _must_ be accessed in byte units.
|
|
- Don't toggle the joystick's select line more than four times per vertical
|
|
refresh, to ensure 6-button joypad compatibility.
|
|
- It takes at least 16 68K clock ticks for the joyport readouts to become
|
|
really stable, after you have toggled the aforementioned select line.
|
|
|
|
Flavio has also pointed out one more important thing not to attempt:
|
|
|
|
CLR, ST, and TAS cannot be used to access the C000xx range. (It's a
|
|
derivative of the "don't read with the VDP set to write" commandment.)
|
|
|
|
|
|
-- 8. Emulating RAM and ROM w/ Starscream --
|
|
|
|
Often times, Genesis games do some odd tricks that can be buggers to emulate.
|
|
One of these is jumping backwards (which causes the 24-bit address to wrap
|
|
around to 0xFFFFFF) into work RAM to execute code. Since Starscream uses
|
|
32-bit data to handle addresses, this sort of maneuver would wrap around to
|
|
0xFFFFFFFF (32-bit) which is out of the Genesis address space.
|
|
Below is part of a Starscream context for emulating the Genesis work
|
|
RAM and ROM. I have also included the code for the handlers (Joe Groff helped
|
|
with this -- thanks Joe!) The dummy handlers are for regions of the address
|
|
space where accesses are ignored (I have removed all references to VDP and I/O
|
|
stuff since it would just clutter the example.) In reality, the data items
|
|
rom and ram would be dynamically allocated and would have to be added to these
|
|
structures during initialization time. They would be seen here as (unsigned)
|
|
NULL or (void *) NULL.
|
|
|
|
struct STARSCREAM_PROGRAMREGION fetch_instructions[] =
|
|
{
|
|
{ 0x000000, 0x3fffff, (unsigned) rom },
|
|
{ 0xff0000, 0xffffff, (unsigned) ram - 0xff0000 },
|
|
{ 0xff000000, 0xff3fffff, (unsigned) ram - 0xff000000 },
|
|
{ 0xfff00000, 0xffffffff, (unsigned) ram - 0xfff00000 },
|
|
{ -1, -1, NULL }
|
|
};
|
|
struct STARSCREAM_DATAREGION read_data_byte[] =
|
|
{
|
|
{ 0x000000, 0x3fffff, NULL, (void *) rom },
|
|
{ 0x400000, 0xffffff, StarscreamReadRAMByte, NULL },
|
|
{ -1, -1, NULL, NULL }
|
|
};
|
|
struct STARSCREAM_DATAREGION read_data_word[] =
|
|
{
|
|
{ 0x000000, 0x3fffff, NULL, (void *) rom },
|
|
{ 0x400000, 0xdfffff, StarscreamFakeRead, NULL },
|
|
{ 0xe00000, 0xffffff, StarscreamReadRAMWord, NULL },
|
|
{ -1, -1, NULL, NULL }
|
|
};
|
|
struct STARSCREAM_DATAREGION write_data_byte[] =
|
|
{
|
|
{ 0x000000, 0xdfffff, StarscreamFakeWrite, NULL },
|
|
{ 0xe00000, 0xffffff, StarscreamWriteRAMByte, NULL },
|
|
{ -1, -1, NULL, NULL }
|
|
};
|
|
struct STARSCREAM_DATAREGION write_data_word[] =
|
|
{
|
|
{ 0x000000, 0xdfffff, StarscreamFakeWrite, NULL },
|
|
{ 0xe00000, 0xffffff, StarscreamWriteRAMWord, NULL },
|
|
{ -1, -1, NULL, NULL }
|
|
};
|
|
|
|
unsigned StarscreamReadRAMByte(unsigned address)
|
|
{
|
|
return ram[(address ^ 1) & 0xffff];
|
|
}
|
|
|
|
unsigned StarscreamReadRAMWord(unsigned address)
|
|
{
|
|
return *((unsigned short *) (ram + (address & 0xfffe)));
|
|
}
|
|
|
|
void StarscreamWriteRAMByte(unsigned address, unsigned data)
|
|
{
|
|
ram[(address ^ 1) & 0xffff] = data;
|
|
}
|
|
|
|
void StarscreamWriteRAMWord(unsigned address, unsigned data)
|
|
{
|
|
*((unsigned short *) (ram + (address & 0xfffe))) = data;
|
|
}
|
|
|
|
unsigned StarscreamFakeRead(unsigned address)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void StarscreamFakeWrite(unsigned address, unsigned data)
|
|
{
|
|
}
|
|
|
|
RAM is at 0xff0000-0xffffff and is mirrored every 64KB at 0xe00000-0xfeffff.
|
|
|
|
Please see the Starscream documentation for information on how the core works.
|
|
At the time of this writing, Neill Corlett's page is at:
|
|
http://www4.ncsu.edu/~nscorlet/
|
|
|
|
|
|
-- 9. SMD ROM Format --
|
|
|
|
The SMD ROM format consists of a 512 byte header and the actual ROM image in
|
|
16KB chunks with the odd bytes at the beginning, and the even bytes at the
|
|
end.
|
|
|
|
Header offsets:
|
|
0x00: Number of 16KB blocks. This number is often incorrect and it
|
|
is wise to calculate it manually: (sizeof(file) - 512) / 16384
|
|
0x02: If 0, the ROM is standalone or the last part of a series.
|
|
Otherwise it is part of a split ROM set.
|
|
0x08: 0xaa
|
|
0x09: 0xbb
|
|
|
|
Note: Most ROMs have 0xaa and 0xbb at 0x08 and 0x09 but a very small percentage
|
|
(about 1.28% by my calculations) do not conform to this. Those offsets are
|
|
useful for checking wether a ROM is in SMD format but be aware that they are
|
|
not always correct.
|
|
|
|
Decoding a 16KB SMD blocke: (thanks to XnaK and Kuwanger)
|
|
1. If the byte offset in the block is less than 8192, copy the byte
|
|
from the SMD block to the first unused odd offset in the decode
|
|
buffer.
|
|
2. Otherwise, put it in the first unused even offset in the decode
|
|
buffer.
|
|
|
|
Example block-decoding C function: (from my own GROM v0.75)
|
|
|
|
void smd_bin(unsigned char *bin_block, unsigned char *smd_block)
|
|
{
|
|
int i, o = 1, e = 0;
|
|
|
|
/* convert 16KB of SMD to BIN */
|
|
for (i = 0; i < 8192; i++)
|
|
{
|
|
bin_block[o] = smd_block[i];
|
|
bin_block[e] = smd_block[i + 8192];
|
|
o += 2;
|
|
e += 2;
|
|
}
|
|
}
|
|
|
|
Get GROM at:
|
|
http://www.powernet.net/~trzy
|
|
http://www.zophar.net
|
|
http://eidolon.psp.net
|
|
http://www.vintagegaming.com
|
|
...or...
|
|
If all else fails, email me.
|
|
|
|
|