Hello, I'm designing a basic flash-style cart to read games and demos from an SD card and write them to flash memory, as an exercise. I'd like to get Titan's OVERDRIVE demo working as I've met some of the makers.
I'm somewhat new to hardware engineering so I'd like a critical eye over my hardware and software design before I try to make it for real. Any advice would be appreciated.
(You might like my Ocelot Arcade System, PSCD32 controller adapter, my little Master System game or my YM2612 FM instrument editor.)
My hardware stuff up until now has been pretty basic through-hole stuff. I was thinking of getting the PCB and SMT assembly done by JLCPCB so I'm constrained by the choice of parts they have. Luckily, the ones they have seem to lend themselves to a good layout.
My cart will have a 64Mbit Flash (8 MByte, 22-bit address space A0-A21) which I'll consider as two 4MByte halves. The lower 4MByte will be the BOOT half and will be selected when the FLASH A21 is low, this will contain the menu software and so on. The upper half will be a flat 4MByte address space to write a cartridge ROM image into.
Here's my organisation for the cart: click to enlarge
Not shown is the 5V->3.3V regulator, a tank cap for the board, or the caps for each IC or the !CART grounded detection pin.
It'll boot up into a menu, read a list of files from the SD card, ask the user to pick one, then write the image to the upper 4MByte, then jump to it.
A basic summary of the steps:
1) !VRES is asserted, then negated. The cart starts connected to the 68000 address and data buses. The MCU sets up a rising edge interrupt on DETECTREGISTERCLK, then idles. The default state of !BOOT/GAME is to bankswitch in the lower 4MByte of the Flash. This will contain my menu code, etc. (I'm also going to use a 32Kbyte block of it to store the list of game names read from the SD card later.)
2) The Mega Drive performs the normal boot/TMSS sequence reading from the lower 4MByte of the Flash, and then boots into the set-up sequence of my cart's firmware.
3) The intro graphic is displayed, and a small code stub is copied to the Mega Drive RAM.
4) The stub is jumped to - the stub writes $0000 to the !TIME address $A13000, then NOPs for 0.5s, then continually reads $000006.w and loops continually while the LSB is set.
5) The write to the !TIME address $A13000 is decoded by the cart and both populates the content of the 16-bit register and triggers the MCU interrupt. The MCU NOPs briefly before acting.
6) The MCU raises MCU!68000 high, which isolates the cartridge from the 68000 bus by tristating all the transceivers in both directions. Since D0 is pulled up by the cart, any word reads to cart space will have their LSB set (other bits indeterminate). Since I know that the lowest bit of $000007 will be zero (since this is the entry point vector in the header, it must be a multiple of two), the 68000 stub will continually read 1 in the LSB of $000006.w while isolated and 0 when the cart is non-isolated.
7) While isolated, the MCU reads game filenames, titles and sizes from the SD card (or whatever data it needs) and uses FLASH!WE, the cart address and data buses to write the retrieved data to a 32 Kbyte block within the lower 4MByte of the Flash. (While isolated from the 68000 bus, the Flash chip's !C_CE will be pulled low.)
8) When finished, the MCU sets up another rising edge interrupt on DETECTREGISTERCLK and lowers MCU!68000 to reconnect the cart to the 68000 buses.
9) The 68000 can now exit the busy loop.
10) The Mega Drive jumps back into the lower 4Mbyte of the cart Flash to run a menu program, with all the available games data available within that 32Kbyte block.
11) The user selects a game - this is encoded as a 16-bit value (the index of the game in the order retrieved from the directory listing, or some other numeric identifier).
12) A second stub is jumped to in RAM, and the selected game value is written to the !TIME address $A13000, then the Mega Drive NOPs for 0.5s, then continually reads $000006.w and loops continually while the LSB is set as before.
13) The write to the !TIME address $A13000 is decoded by the cart and both populates the content of the 16-bit register and triggers the MCU interrupt. The MCU NOPs briefly before acting.
14) The MCU raises MCU!68000 high again to isolate the cart.
15) The value written to the register is retrieved into the MCU using REGISTER!OE and the cart's data bus.
16) The MCU raises !BOOT/GAME to switch in the upper 4MByte of the Flash.
17) The MCU can now open the indicated game file from the SD card and write it to the upper 4MByte of Flash.
18) When done, the MCU can lower MCU!68000 to reconnect the cart to the 68000 buses. The MCU enters a low-power idle state and takes no further action. All of its pins are inputs except !BOOT/GAME which is high.
19) The 68000 is released from its busy loop when the cart buses are reconnected.
20) The Mega Drive cleans up any RAM/VRAM it needs to using its stub (in case certain software cares?), zeroes out itself as much as it can, then jumps to ($000004).l to boot the game present in the upper 4MByte of the Flash. From this point on, the cart is completely dumb ROM. Writes to !TIME will populate the register, but it will never be read.
Does that sound like a good approach?
--
I'd like a second opinion on my glue logic and transceiver usage since I've never done this before.
My signal names are (usually) prefixed by their source (68000 bus or MCU).
I'm using transceivers to split the 5V 68000 world from the 3.3V internal world. The two 16-bit transceivers on the left should be considered as one 32-bit transceiver for the 'address and signals bus'. I'm using the !C_CE, !AS, R/!W, !TIME and !VRES signals from the cartridge slot to detect chip enables, reads, writes, strobes and reset conditions.
I'm using the 16-bit register because the MCU is way too slow to react to the 68000 quickly writing a word to the !TIME address space. With the register, I can store the value and use the rising edge of CLK to awaken the MCU, then it can isolate the cart and read the written register value at its own pace.
Reset logic:
FLASH!RST = 68000!VRES
MCU!RESET = 68000!VRES The PICkit is also connected to this pin when I'm programming the MCU, but you should never program the PIC while the cart is within the MD anyway. I'm going to have it so that you apply an external 5V source to the cart through header pins.
Address decoding logic:
FLASH!CE = 68000!C_CE (pulled low on isolate) The Flash should be selected on access to $000000-$3FFFFF, or always if the cart is isolated.
FLASH!OE_EXTERNAL = 68000!C_CE + 68000!AS + NOT(68000R/!W) This signal represents an !OE request from a 68000 request. This happens when !C_CE is low, !AS is low and R/!W is high. I shouldn't need !C_CE strictly, but I'm using this combination as an input below.
FLASH!OE = MCUFLASH!FORCEOE * FLASH!OE_EXTERNAL I want the Flash to output a value if the MCU is forcing it to, or if the 68000 signals ask it to.
REGISTER!WE = 68000!TIME + 68000!AS + 68000R/!W I'm going to have the register respond to any write in the $A130xx range, since that's simple. The register writes on the ascending edge of this signal, so it's also listed as REGISTERCLK.
MCUDETECTREGISTERCLK = REGISTERCLK On the rising edge of the register clock, the MCU awakes at the same time as the register is written.
Transceiver logic:
74ALVC164245 logic:
A bus = 3.3V
B bus = 5.0V
DIR L = 3.3V output <- 5V input (value into cart from 68000 - DATA: a write operation, ADDRESS: 68000 Ax enters cart address bus.)
DIR H = 3.3V input -> 5V output (value from cart into 68000 - DATA: a read operation, ADDRESS: cart address bus leaves onto 68000 Ax, do not allow this.)
ADDRESSTRANSCEIVERDIR = low Only valid direction is an address from the 5V port placed on 3.3V port if ADDRESSTRANSCEIVER!OE is low.
ADDRESSTRANSCEIVER!OE = MCU!68000 When MCU is in !RESET or if this pin has not been raised, the 68000 address appears on the 3.3V address bus.
DATATRANSCEIVERDIR = R/!W I'm surprised I don't have to invert this, the 68000's R/!W signals match the direction of the 74ALVC164245.
DATATRANSCEIVER!OE = MCU!68000 + (FLASH!OE_EXTERNAL * REGISTER!WE) I want to connect the data bus if isolation is not in effect and we're valid reading the Flash or valid writing the register. (Notice my FLASH!OE requires FLASH!CE instead of being independent of it to allow the combination to be used here.)
When the !OEs are low, the address or data buses will be floating, so I'm going to connect each pin to ground through 6k8 to prevent any unusual behaviour during isolation.
I'm also pulling up !AS, R/!W, !TIME and !VRES high whenever the cart is isolated, to disable the Flash and register ICs unless the MCU activates them itself.
MCU Outputs:
While the MCU is in the reset state, all its pins are tristated. My pull- resistors need to make this a good safe state for the cart to be in before the menu begin, and while the real game is running and the MCU is idle:
MCU!BOOT/GAME, low default, if low select the boot menu half of Flash, if high select the game half of Flash.
MCUFLASH!WE, high default, allows the MCU to program the Flash
MCU!68000, low default, if high the cartridge transceivers tristate the cart from the 68000 buses.
MCUFLASH!FORCEOE, high default, if low the MCU is reading the Flash (to verify a write)
MCUREGISTER!OE, high default, if low the MCU is reading the last value written to a !TIME address
MCU Inputs:
DETECTREGISTERCLK, a rising edge wakes the cart for either the 'ready to read games' state or the 'please play game REGISTER' state.
!RESET, standard reset.
The Flash indicates a busy state by toggling a Qx pin on consecutive reads during busy, so there's no busy pin to poll.
I haven't yet fully specified the SD card interface yet. I think I'm going to use the 4-wire SPI mode, with all the lines pulled to 3.3V through 3k3, and a 10R resistor on the +3.3V line to prevent high inrush current.
Please let me know what you think.
Datasheets:
Transceiver
Flash
Register
PIC MCU
Regards,
Matt C.
I'm somewhat new to hardware engineering so I'd like a critical eye over my hardware and software design before I try to make it for real. Any advice would be appreciated.
(You might like my Ocelot Arcade System, PSCD32 controller adapter, my little Master System game or my YM2612 FM instrument editor.)
My hardware stuff up until now has been pretty basic through-hole stuff. I was thinking of getting the PCB and SMT assembly done by JLCPCB so I'm constrained by the choice of parts they have. Luckily, the ones they have seem to lend themselves to a good layout.
My cart will have a 64Mbit Flash (8 MByte, 22-bit address space A0-A21) which I'll consider as two 4MByte halves. The lower 4MByte will be the BOOT half and will be selected when the FLASH A21 is low, this will contain the menu software and so on. The upper half will be a flat 4MByte address space to write a cartridge ROM image into.
Here's my organisation for the cart: click to enlarge
Not shown is the 5V->3.3V regulator, a tank cap for the board, or the caps for each IC or the !CART grounded detection pin.
It'll boot up into a menu, read a list of files from the SD card, ask the user to pick one, then write the image to the upper 4MByte, then jump to it.
A basic summary of the steps:
1) !VRES is asserted, then negated. The cart starts connected to the 68000 address and data buses. The MCU sets up a rising edge interrupt on DETECTREGISTERCLK, then idles. The default state of !BOOT/GAME is to bankswitch in the lower 4MByte of the Flash. This will contain my menu code, etc. (I'm also going to use a 32Kbyte block of it to store the list of game names read from the SD card later.)
2) The Mega Drive performs the normal boot/TMSS sequence reading from the lower 4MByte of the Flash, and then boots into the set-up sequence of my cart's firmware.
3) The intro graphic is displayed, and a small code stub is copied to the Mega Drive RAM.
4) The stub is jumped to - the stub writes $0000 to the !TIME address $A13000, then NOPs for 0.5s, then continually reads $000006.w and loops continually while the LSB is set.
5) The write to the !TIME address $A13000 is decoded by the cart and both populates the content of the 16-bit register and triggers the MCU interrupt. The MCU NOPs briefly before acting.
6) The MCU raises MCU!68000 high, which isolates the cartridge from the 68000 bus by tristating all the transceivers in both directions. Since D0 is pulled up by the cart, any word reads to cart space will have their LSB set (other bits indeterminate). Since I know that the lowest bit of $000007 will be zero (since this is the entry point vector in the header, it must be a multiple of two), the 68000 stub will continually read 1 in the LSB of $000006.w while isolated and 0 when the cart is non-isolated.
7) While isolated, the MCU reads game filenames, titles and sizes from the SD card (or whatever data it needs) and uses FLASH!WE, the cart address and data buses to write the retrieved data to a 32 Kbyte block within the lower 4MByte of the Flash. (While isolated from the 68000 bus, the Flash chip's !C_CE will be pulled low.)
8) When finished, the MCU sets up another rising edge interrupt on DETECTREGISTERCLK and lowers MCU!68000 to reconnect the cart to the 68000 buses.
9) The 68000 can now exit the busy loop.
10) The Mega Drive jumps back into the lower 4Mbyte of the cart Flash to run a menu program, with all the available games data available within that 32Kbyte block.
11) The user selects a game - this is encoded as a 16-bit value (the index of the game in the order retrieved from the directory listing, or some other numeric identifier).
12) A second stub is jumped to in RAM, and the selected game value is written to the !TIME address $A13000, then the Mega Drive NOPs for 0.5s, then continually reads $000006.w and loops continually while the LSB is set as before.
13) The write to the !TIME address $A13000 is decoded by the cart and both populates the content of the 16-bit register and triggers the MCU interrupt. The MCU NOPs briefly before acting.
14) The MCU raises MCU!68000 high again to isolate the cart.
15) The value written to the register is retrieved into the MCU using REGISTER!OE and the cart's data bus.
16) The MCU raises !BOOT/GAME to switch in the upper 4MByte of the Flash.
17) The MCU can now open the indicated game file from the SD card and write it to the upper 4MByte of Flash.
18) When done, the MCU can lower MCU!68000 to reconnect the cart to the 68000 buses. The MCU enters a low-power idle state and takes no further action. All of its pins are inputs except !BOOT/GAME which is high.
19) The 68000 is released from its busy loop when the cart buses are reconnected.
20) The Mega Drive cleans up any RAM/VRAM it needs to using its stub (in case certain software cares?), zeroes out itself as much as it can, then jumps to ($000004).l to boot the game present in the upper 4MByte of the Flash. From this point on, the cart is completely dumb ROM. Writes to !TIME will populate the register, but it will never be read.
Does that sound like a good approach?
--
I'd like a second opinion on my glue logic and transceiver usage since I've never done this before.
My signal names are (usually) prefixed by their source (68000 bus or MCU).
I'm using transceivers to split the 5V 68000 world from the 3.3V internal world. The two 16-bit transceivers on the left should be considered as one 32-bit transceiver for the 'address and signals bus'. I'm using the !C_CE, !AS, R/!W, !TIME and !VRES signals from the cartridge slot to detect chip enables, reads, writes, strobes and reset conditions.
I'm using the 16-bit register because the MCU is way too slow to react to the 68000 quickly writing a word to the !TIME address space. With the register, I can store the value and use the rising edge of CLK to awaken the MCU, then it can isolate the cart and read the written register value at its own pace.
Reset logic:
FLASH!RST = 68000!VRES
MCU!RESET = 68000!VRES The PICkit is also connected to this pin when I'm programming the MCU, but you should never program the PIC while the cart is within the MD anyway. I'm going to have it so that you apply an external 5V source to the cart through header pins.
Address decoding logic:
FLASH!CE = 68000!C_CE (pulled low on isolate) The Flash should be selected on access to $000000-$3FFFFF, or always if the cart is isolated.
FLASH!OE_EXTERNAL = 68000!C_CE + 68000!AS + NOT(68000R/!W) This signal represents an !OE request from a 68000 request. This happens when !C_CE is low, !AS is low and R/!W is high. I shouldn't need !C_CE strictly, but I'm using this combination as an input below.
FLASH!OE = MCUFLASH!FORCEOE * FLASH!OE_EXTERNAL I want the Flash to output a value if the MCU is forcing it to, or if the 68000 signals ask it to.
REGISTER!WE = 68000!TIME + 68000!AS + 68000R/!W I'm going to have the register respond to any write in the $A130xx range, since that's simple. The register writes on the ascending edge of this signal, so it's also listed as REGISTERCLK.
MCUDETECTREGISTERCLK = REGISTERCLK On the rising edge of the register clock, the MCU awakes at the same time as the register is written.
Transceiver logic:
74ALVC164245 logic:
A bus = 3.3V
B bus = 5.0V
DIR L = 3.3V output <- 5V input (value into cart from 68000 - DATA: a write operation, ADDRESS: 68000 Ax enters cart address bus.)
DIR H = 3.3V input -> 5V output (value from cart into 68000 - DATA: a read operation, ADDRESS: cart address bus leaves onto 68000 Ax, do not allow this.)
ADDRESSTRANSCEIVERDIR = low Only valid direction is an address from the 5V port placed on 3.3V port if ADDRESSTRANSCEIVER!OE is low.
ADDRESSTRANSCEIVER!OE = MCU!68000 When MCU is in !RESET or if this pin has not been raised, the 68000 address appears on the 3.3V address bus.
DATATRANSCEIVERDIR = R/!W I'm surprised I don't have to invert this, the 68000's R/!W signals match the direction of the 74ALVC164245.
DATATRANSCEIVER!OE = MCU!68000 + (FLASH!OE_EXTERNAL * REGISTER!WE) I want to connect the data bus if isolation is not in effect and we're valid reading the Flash or valid writing the register. (Notice my FLASH!OE requires FLASH!CE instead of being independent of it to allow the combination to be used here.)
When the !OEs are low, the address or data buses will be floating, so I'm going to connect each pin to ground through 6k8 to prevent any unusual behaviour during isolation.
I'm also pulling up !AS, R/!W, !TIME and !VRES high whenever the cart is isolated, to disable the Flash and register ICs unless the MCU activates them itself.
MCU Outputs:
While the MCU is in the reset state, all its pins are tristated. My pull- resistors need to make this a good safe state for the cart to be in before the menu begin, and while the real game is running and the MCU is idle:
MCU!BOOT/GAME, low default, if low select the boot menu half of Flash, if high select the game half of Flash.
MCUFLASH!WE, high default, allows the MCU to program the Flash
MCU!68000, low default, if high the cartridge transceivers tristate the cart from the 68000 buses.
MCUFLASH!FORCEOE, high default, if low the MCU is reading the Flash (to verify a write)
MCUREGISTER!OE, high default, if low the MCU is reading the last value written to a !TIME address
MCU Inputs:
DETECTREGISTERCLK, a rising edge wakes the cart for either the 'ready to read games' state or the 'please play game REGISTER' state.
!RESET, standard reset.
The Flash indicates a busy state by toggling a Qx pin on consecutive reads during busy, so there's no busy pin to poll.
I haven't yet fully specified the SD card interface yet. I think I'm going to use the 4-wire SPI mode, with all the lines pulled to 3.3V through 3k3, and a 10R resistor on the +3.3V line to prevent high inrush current.
Please let me know what you think.
Datasheets:
Transceiver
Flash
Register
PIC MCU
Regards,
Matt C.
Last edited: