Jump to content

Tursi's Blog

Sign in to follow this  
  • entries
  • comments
  • views

Genesis Cartridge Port (for real this time)



With the menu for my Thunder Force Multicart nearly ready to go, I needed to understand what sort of switching scheme was legal on the Genesis.


Research didn't actually help much.. even the pinout of the cartridge port is just the same pinout copied everywhere with little to no explanation of the pins. My original plan was to do it the same way as on the TI - just treat writes to ROM as a request to switch banks. Unfortunately I couldn't even determine for certain if that was safe. Likewise, attempts to determine with the debugger if any of the games write to ROM accidentally were thwarted when the debugger treated most accesses as a write.


I stumbled upon schematics here: http://emu-docs.org/?page=Genesis

and a description of internal signals here: http://code.google.com/p/genplus-gx/downloads/detail?name=gen_signals.doc&can=2&q=


With these, I was able to trace out and write notes on the actual pins of the cartridge port - all but one pin were explained by the time I was done:


GENESIS CARTRIDGE PORT PINOUT (from schematics - Tursi)A01	gndA02	Vcc1 -	+5vA03	A08A04	A11A05	A07A06	A12A07	A06A08	A13A09	A05A10	A14A11	A04A12	A15A13	A03A14	A16A15	A02A16	A17A17	A01A18	gndA19	D07A20	D00A21	D08A22	D06A23	D01A24	D09A25	D05A26	D02A27	D10A28	D04A29	D03A30	D11A31	Vcc1 - 	+5v, filter cap to neighboring groundA32	gndB01	SL1 - 	Left Audio (input? Likely. COULD be output too.) - Ties into the middle of an analog mixing 		circuit between YM2612 and CXA1034B02	!MRES 	(!HRES) - Master/Hard reset, resets BIOS ROM and allB03	SR1 - 	Right Audio (? see SL1)B04	A09B05	A10B06	A18B07	A19B08	A20B09	A21B10	A22B11	A23B12	!YS - 	Video output control bit - 0=transparent pixel, 1=opaque pixelB13	!VSYNC -Vertical sync outputB14	!HSYNC -Horizontal sync outputB15	EDCLK - External Dot Clock? MCLK/5 during HSYNC, MCLK/4 otherwise. Generated by Bus arbiter 		and used by VDP as pixel clock reference in 40-cell mode only.B16	!CAS0 -	(!C_OE) - Common output enable, asserted by the VDP for reads $000000-$DFFFFF B17	!CE0 -	Chip Enable for Cart - asserted for RW from $000000-$3FFFFF is !CART is asserted, 		or $400000-$7FFFFF when it is not (ie: a RAM cart).B18	!AS - 	68000 Valid Address Strobe - indicates that the address bus is validB19	VCLK -	Master 68k clock (MCLK/7) generated by the VDP (7MHz then?)B20	!DTAK -	(!DTACK) - 68000 Data Acknowledge (68k hangs if this or !BERR doesn't assert)		Generated by the VDP or Bus Arbiter for valid Genesis addresses B21	!CAS2 -	??? - generated (probably) by the Bus Arbiter, no documentation on what for. Runs only to cart and side ports.B22	D15B23	D14B24	D13B25	D12B26	!ASEL -	(!LO_MEM) - Asserted for reads to low memory ($000000-$7FFFFF)B27	!VRES -	(!RST) - Output! 68000 reset, output from bus arbiter when !SRES or !WRES are assertedB28	!LWR -	Lower byte write strobe, asserted by VDP when !R/W and !LDS are assertedB29	!UWR -	Upper byte write strobe, asserted by VDP when !R/W and !UDS are assertedB30	!M3 -	(SEL0) - System mode select input. 0 = normal, 1 = SMS mode. Not clear if there is a pullup/pulldown, but doesn't seem needed.B31	!TIME -	I/O select output, asserted when $A13000-$A130FF is accessed (used for cartridge-based hardware/bank switching/etc)B32	!CART -	4.7k pull up to VCC - tie to ground to indicate cart present

After reading around a while, it was specifically that I/O select, called !TIME on the schematic, that I was after. This provides an alternate I/O specific memory space, pre-decoded, that I should be able to use. Tying into a 74LS174 (as in a schematic for the Radica clone I noted) should allow me to easily set the upper address lines of a 4MB EPROM.


So with a scheme in mind, I needed to add just a bit of code to the menu to support it. Since I want four 1MB banks, I need just two bits to select them. Since I'm wary of how the 68k might access the cartridge port, and since I have lots of space, I will space my meaningful addresses by 4. The goal then, is that accessing these addresses should select these banks:


$A13000 - Bank 0 - $000000$A13004 - Bank 1 - $100000$A13008 - Bank 2 - $200000$A1300C - Bank 3 - $300000

Since I'll only be tying two pins off, that pattern will actually repeat through the whole 256 byte space listed above.


Next I needed to update the menu code, mainly because I don't have the 174s yet to test the hardware. ;) So first, a handler for the Start button in the original code:


	if old AND &h080 then		' start		for old = 1 to 3			gosub blackit			sleep 15			gosub lightit			sleep 15		next old		cls		' Launch appropriate bank here...		trampoline opt	endif

So this is pretty hacky, but it does the job. It sets up a loop to repeat 3 times - calls a new subroutine called 'blackit' which makes the current selection black. Then it delays for 15/60ths of a second. Then it calls 'lightit' which we previously had, to light it up in color again. Another sleep, and it repeats.


After the 3 flashes, it clears the screen with cls (this is why the code earlier set the text plane to the same as the graphics plane, even though it doesn't draw text. CLS works on the configured text plane). And it calls another new function called trampoline. We'll come back to that.


The function 'blackit' is just like 'greyit' and 'lightit', except it sets all the colors in a palette row to black. We could have done this with a loop, but in this case I just use palDat, which you'll recall is set to all black since it wasn't being used. Convenient! ;)


blackit:	valt	select case opt		case 0			palettes palDat,1,0,16			exit select		case 1			palettes palDat,2,0,16			exit select		case 2			palettes palDat,3,0,16			exit select	end select	return

Nothing interesting there, so lets talk about 'trampoline'. What the hell is a trampoline?


In programming, a trampoline is a function you call that sets up to make it possible to call another function. It's usually used in bank switch cases, just like this. Allow me to explain.


This system presents a pretty naïve bank switch - the entire memory space is switched out when the address is toggled. This INCLUDES the code that is currently running to perform the switch, if it's in ROM. Generally, this means that instead of executing the rest of the switch, you're now in the middle of someone else's random code -- crash. Now, there are a couple of ways to deal with this.


One way is to make sure the switching code is in exactly the same location in every bank. This way, when the bank switch happens, you're still executing the same code, and each bank can do what it needs to do after the switch. I actually looked into this, and it /might/ have been possible - empty blocks exist in all three Thunder Force games and there seemed to be some overlap, but I didn't know if there would be enough.


The other way is to copy a small amount of code into memory that does NOT switch, perform the switch there, then jump back. Usually, this is RAM. And that's a trampoline function.


There is another way to deal with this - to switch only part of the ROM at a time - many systems work like this, but it requires slightly more circuitry to do it.


So, I created a simple assembly function - it has three jobs. First, it copies the trampoline function to RAM, and jumps to it. Then, it calculates the address of the switch to toggle, and toggles it. Finally, since each bank is a full cartridge in this case, we simply load and execute the reset vector to start it up.


The cartridge then runs in its native space, and as long as it never touches that I/O space, it never knows that a switch took place.


' this function copies itself into RAM at $FFFF8000 and then jumps to it' the passed in parameter is which game to run (0,1,2) which is used to' set the correct bank - note: never returnsdeclare asm sub trampoline(d2.w)	move.l #@1,d0		; start address	movea.l d0,a0	move.l #@[email protected],d1	; number of bytes	lsr.l #$2,d1		; divide by 4 for words	addq #1,d1		; and account for potential partial words (probably unneeded but wont hurt)	movea.l #$ffff8000,a1	; target address	movea.l a1,a2		; save the [email protected]:	move.l (a0)+,(a1)+	; copy 32-bits	dbf d1,@3		; loop until done	jmp (a2)		; execute the code from RAM; the following code runs from RAM, d2.w has the program number	@1:		lsl.w #2,d2		; multiply by four for offset	moveq #0,d1		; prepare to zero	move.w d2,d1		; now we know its safe	add.l #$a13000,d1	; add the address of the IO space	movea.l d1,a0		; this is the address we will poke	move.w d1,(a0)		; do the poke (value irrelevant)	moveq #$0,d0        	; cart should be active, so prepare to jump	movea.l d0,a0       	; read from reset vector	movea.l (a0)+,sp    	; set stack pointer	movea.l (a0)+,a0    	; get boot address	jmp (a0)            	; and go do it	nop			; [email protected]:	nopEnd Sub	

So the first part of the function just copies the code between @1 and @2 to RAM at the fixed address of $8000. Actually it copies too much but I didn't really care. ;) It then jumps directly to that code.


After the jump, the argument is still in d2. First we multiply that by 4 for the offset I mentioned, then we just add the base of the IO space. The write to that address causes the bank switch (the data written is irrelevant).


Now that the correct bank is mapped in, we can just read in the reset vector into stack and an address register, and jump right to it.


Next article when I have the hardware to actually test with (although if I get time I might hack an emulator to do it first ;) ).



  • Like 1


Recommended Comments

There are no comments to display.

Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Create New...