Cart Design Questions

Ask anything your want about Megadrive/Genesis programming.

Moderator: BigEvilCorporation

themrcul
Very interested
Posts: 116
Joined: Fri Apr 15, 2016 2:21 pm

Re: Cart Design Questions

Post by themrcul » Tue Jul 09, 2019 3:55 am

Thanks Tiido, that's a good idea.

themrcul
Very interested
Posts: 116
Joined: Fri Apr 15, 2016 2:21 pm

Re: Cart Design Questions

Post by themrcul » Mon Dec 20, 2021 12:51 pm

So it's been a long time.
In between my last post I had some issues with my first hardware build, had another son, and have had outside work programming projects that took all my time.

I have put off some other things to get some progress done on this project as it was killing me, not being able to do anything on it in two long years.

I have made a second hardware iteration (I really need a third, but I have patched up this build to get me by), soldered all components onto the board sans the audio section, as that is not needed yet.

I have coded up part of the ARM micro functionality and the basic stuff for the CPLD as well, including talking to SDRAM, talking between ARM and CPLD, writing a ROM to the CPLD from the ARM (which gets it from the computer via USB), and, what I thought should work for getting the Mega Drive to read from the cart and play a ROM.

I have diodes on board so I can run the board via USB power before I turn the Mega Drive on. That means I can send the ROM to SDRAM before I turn the Mega Drive on. I have read part of the ROM back to the computer from SDRAM -> ARM -> computer to confirm that the data looks ok.

However, when I turn on the Mega Drive, I get a few white bars/lines flashes (as usual when turning a Mega Drive on with this CRT), but no TMSS, and the screen is just black after the initial flashes.

I have gone back and forward with the code, and I don't know what's wrong. I thought that it should be relatively simple (considering other parts of the project) - when the cart CEO line goes low (which I have as an input called mdRead), I take what is on the address lines and send a read request to the SDRAM. Once the data comes through, I pipe that to the Mega Drive data lines, setting the bus transceiver direction to output to Mega Drive as well.

I have looked over the UMDK source code to see if I could glean anything but I don't see much help with it. My CPLD code follows a similar idea, although the UMDK uses a VHDL memory pipe (is that a FIFO?) to transition addresses and data from Mega Drive land to it's own CPLD world.

Can anyone have a quick look over the code (I have deleted most of it for readability, only kept the parts about the Mega Drive reading). If you see anything obviously wrong, can you give me any pointers? It seems weird to me that it just won't work.. I would be very grateful for any help at all, as I don't know where to go next with it - I've exhausted any ideas I have to try.

For your info the CPLD is clocked at 100mhtz. Since the Mega Drive is running at a completely different speed, I am flipping the inputs from the Mega Drive twice to avoid metastability issues.

I have also experimented with waiting 4 clocks after sensing CEO going low before I send the read request to the SDRAM but that hasn't helped. I have also tried sending the address through from the Mega Drive as is, as well as the meta stable version which is flipped twice but neither work sadly.

The code is below:

Code: Select all

module DebugCart(
	// SDRAM
	output[11:0] sdramAddr, 
	output[1:0]  sdramBankAddr, 
	inout[15:0]  sdramData, 
	output sdramClockEnable, 
	output sdramClk,
	output sdramCS, 
	output sdramRAS, 
	output sdramCAS, 
	output sdramWriteEnablePin,
	output sdramDataMaskLow, 
	output sdramDataMaskHigh,
	
	// MD BRIDGE
	input mdPresence,
	input[21:0] mdAddress,
	inout[15:0] mdData,
	input mdRead,
	
	// bus transceivers
	output mdAddressEnable,
	output mdDataEnable,
	output mdDataDirectionIsOutput);
		

		
/////////////////////////////////////////////////////////
// MD
/////////////////////////////////////////////////////////
reg mdPresence_m1, mdPresence_m2 = 0;
reg mdRead_m1, mdRead_m2 = 1'd1;
reg[21:0] mdAddress_m1, mdAddress_m2;
// assigns
assign mdAddressEnable = !mdPresence_m2; // low activate. so when presence is 3v (active), we set the bus transceiver LOW which activates it
assign mdDataEnable = !(mdPresence_m2 && !mdRead_m2);
assign mdDataDirectionIsOutput = 1'd1; // just keep simple for now
assign mdData = sdramReadResultForMD;


	
/////////////////////////////////////////////////////////
// SDRAM
/////////////////////////////////////////////////////////
wire[22:0]	sdramAddress;
reg [15:0]  sdramWriteData = 0;
wire 	sdramWriteEnable;

wire [15:0] sdramReadData;
wire 	sdramReadReady;
wire 	sdramReadEnable;

wire 	sdramBusy;

reg [15:0]  sdramReadResultForMD = 0;

reg 		sdramClkEnable = 1'd1;
reg 		sdramRefreshRequest = 1'd0;

sdram_controller2 sdram_controller(
  clk,
  sdramClkEnable,
  armReset,
  // host interface
  sdramAddress,
  sdramWriteEnable,
  sdramReadEnable,
  sdramRefreshRequest,
  sdramWriteData,
  sdramReadData,
  sdramBusy,
  // sdram interface
  sdramClk,
  sdramClockEnable,
  sdramDataMaskHigh,
  sdramDataMaskLow,
  sdramAddr,
  sdramBankAddr,
  sdramCS,
  sdramWriteEnablePin,
  sdramRAS,
  sdramCAS,
  sdramData);

assign sdramAddress[22] = 0;
assign sdramAddress[21:0] = mdAddress_m2;
assign sdramReadEnable = cpld_state == STATE_SDRAM_ReadForMD_ReadWord;
assign sdramWriteEnable = cpld_state == STATE_SDRAM_WriteForARM_BeginWriteFromARM; // don't worry about this


localparam  COMMAND_reset_sdram_read_counter = 3'd0,
				COMMAND_reset_sdram_write_counter = 3'd1,
				COMMAND_read_sdram = 3'd2,
				COMMAND_write_begin_sdram = 3'd3,
				COMMAND_write_end_sdram = 3'd4,
				COMMAND_change_to_sdram_mode = 3'd5;

localparam  STATE_CPLD_Idle = 6'd0,
			STATE_SDRAM_ReadForMD_WaitUntilReady = 6'd7,
			STATE_SDRAM_ReadForMD_ReadWord = 6'd8,
			STATE_SDRAM_ReadForMD_ReadWordComplete = 6'd9,
			STATE_SDRAM_ReadForMD_WaitForReadToFinish = 6'd10;
			
			// obviously there are other states here, for reading ROM data to be written to the SDRAM, but I have removed them for readability.
			
reg[5:0] cpld_state, cpld_state_next;

// combinational section - state machine core state
always @ (*) begin
	case (cpld_state)
		/*=============================================
			MAIN IDLE STATE
		=============================================*/
		STATE_CPLD_Idle: begin
			if (!mdRead_m2) begin
				// we need to read data for the MD
				cpld_state_next = sdramBusy ? STATE_SDRAM_ReadForMD_WaitUntilReady : STATE_SDRAM_ReadForMD_ReadWord;
			end
			else cpld_state_next = STATE_CPLD_Idle;
		end
		
		
		/*=============================================
			SDRAM READ FOR MD
		=============================================*/
		STATE_SDRAM_ReadForMD_WaitUntilReady: begin
			cpld_state_next = sdramBusy ? STATE_SDRAM_ReadForMD_WaitUntilReady : STATE_SDRAM_ReadForMD_ReadWord;
		end
		STATE_SDRAM_ReadForMD_ReadWord: begin
			cpld_state_next = sdramBusy ? STATE_SDRAM_ReadForMD_ReadWordComplete : STATE_SDRAM_ReadForMD_ReadWord;
		end
		STATE_SDRAM_ReadForMD_ReadWordComplete: begin
			cpld_state_next = STATE_SDRAM_ReadForMD_WaitForReadToFinish;
		end
		STATE_SDRAM_ReadForMD_WaitForReadToFinish: begin
			cpld_state_next = mdRead_m2 ? STATE_CPLD_Idle : STATE_SDRAM_ReadForMD_WaitForReadToFinish;
		end
		
		default: cpld_state_next = STATE_CPLD_Idle;
	endcase
end

// sequential section - state machine guts
always @ (posedge clk or posedge armReset) begin
	if (armReset) begin
		cpld_state <= STATE_CPLD_Idle;
		
		mdPresence_m1 <= !mdPresence;
		mdPresence_m2 <= !mdPresence_m1;
		
		mdRead_m2 <= 1'd1;
		armSendDataToARMPlz <= 0;
		
		// add more reset stuff here
		debug_sdram_read_counter <= 0;
		debug_sdram_write_counter <= 0;
	end
	else begin
		cpld_state <= cpld_state_next;
		
		mdPresence_m1 <= !mdPresence;
		mdPresence_m2 <= !mdPresence_m1;
		mdRead_m1 <= !mdRead;
		mdRead_m2 <= mdPresence_m2 ? !mdRead_m1 : 1'd1;
		mdAddress_m1 <= ~mdAddress;
		mdAddress_m2 <= ~mdAddress_m1;
		
		case (cpld_state)
			STATE_SDRAM_ReadForMD_ReadWordComplete: begin
				sdramReadResultForMD <= sdramReadData;
			end
		endcase
	end
end

endmodule

HardWareMan
Very interested
Posts: 745
Joined: Sat Dec 15, 2007 7:49 am
Location: Kazakhstan, Pavlodar

Re: Cart Design Questions

Post by HardWareMan » Sat Dec 25, 2021 10:43 am

You should use "~" (tilde) to inversion, not exclamation sign. Because "~" is fo bitwise negation (inversion) and "!" is for logical not.

Oh. You are confused between logical and bitwise operators (&& and & for example). Combinatorial logic uses bitwise only.

themrcul
Very interested
Posts: 116
Joined: Fri Apr 15, 2016 2:21 pm

Re: Cart Design Questions

Post by themrcul » Mon Dec 27, 2021 12:20 am

Thank you very much HardwareMan for taking time to help me out. As you can tell I'm very much a newbie with this stuff.

Thanks for your tip about bitwise with combinatorial logic - nowhere online in the tutorials I read did it mention that, that is really helpful info.

In the meantime I did some more tests with the SDRAM and found that while individual reads and writes work well, when a lot of reads or writes happen it returns seemingly random results. So the SDRAM controller core I tried to retrofit into my project does not work for the timings needed for the particular IC I have.

So I have started to roll my own SDRAM core for the IC I have. So far it is simulating well, but needs some more work before I try deploying it.

If that still fails and I can't get the SDRAM to behave I will bite the bullet and redesign the PCB to use SRAM instead as I have spent so much time with this SDRAM without success and it's killing all the joy for me in this project!

Thanks again for your help, I'll update the main core with the pointers you've given me.

HardWareMan
Very interested
Posts: 745
Joined: Sat Dec 15, 2007 7:49 am
Location: Kazakhstan, Pavlodar

Re: Cart Design Questions

Post by HardWareMan » Mon Dec 27, 2021 2:42 pm

Maybe you just out of the timing for SDRAM refresh?

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Re: Cart Design Questions

Post by Chilly Willy » Wed Dec 29, 2021 11:55 pm

Maybe you should check some existing controllers to see how others do it.

ladybug.xs4all.nl/arlet/fpga/source/sdram.v
https://web.archive.org/web/20180117005 ... Controller

themrcul
Very interested
Posts: 116
Joined: Fri Apr 15, 2016 2:21 pm

Re: Cart Design Questions

Post by themrcul » Fri Dec 31, 2021 4:22 am

Thanks for the suggestions guys. I think it's now a combination of factors or failures. I'm pretty sure there is something wrong with the transport between the CPLD and the Arm, or the parent module which is requesting the data from the SDRAM, not just the SDRAM. Maybe the SDRAM is working fine, and the other things are broken..
I'll investigate the core module with dummy data next to try to track down the problems.

I am kinda regretting choosing SDRAM over SRAM now at this point though!

themrcul
Very interested
Posts: 116
Joined: Fri Apr 15, 2016 2:21 pm

Re: Cart Design Questions

Post by themrcul » Fri Jan 14, 2022 1:34 pm

Hi guys, so I have gotten SDRAM reading and writing reliably now.
My own attempt to write a controller for it failed - it just didn't work, even though it seemed like it should when I compared the simulator to the datasheet.

Regardless I got the previous controller working by changing the parameters to match the timing requirements of the particular chip I have and by tightening up the refresh periods compared with before.
Also, I fixed a bug I found where if data was streamed to the SDRAM it did not check if it needed to refresh and if too much data was streamed it would all be lost.

Anyway, I am now piping that data to the Mega Drive too and it still doesn't work.

Long story short, I thought there has to be something going wrong here, so I hooked up probes to the address lines and I found that VA7 has a broken trace somewhere between the gold fingers of the PCB and the level shifter - so data requests were not accurate.
I have tested all the other address lines on that side of the PCB and they are ok, and I tested another (unpopulated) PCB I got from the batch I ordered from JLCPCB and VA7 is ok there so I must have damaged the one on the board I soldered somehow/sometime... although I can't see how or when I would have done it.

Whatever, I can't change anything about that now other than try to repair (or bypass) the trace between the gold fingers and the level shifter for VA7.
There are data lines on that side of the board I have not tested and there are more address lines and data and signal lines on the other side of the board I have not tested at all but I am hoping VA7 is the only one - so I'll fix VA7 first and test again and if it still doesn't work I'll hunt for more broken traces.

themrcul
Very interested
Posts: 116
Joined: Fri Apr 15, 2016 2:21 pm

Re: Cart Design Questions

Post by themrcul » Sat Jan 15, 2022 3:05 am

Ok so I repaired VA7. It took me to the limits of my soldering ability to do so. It turns out there was a solder bridge formed behind the VA7 pin and an adjacent pin on the level shifter as well. I had to bend the pins on the level shifter to fix it among other things and thankfully nothing snapped.
So now all the address lines on the B side of PCB work correctly.

It still doesn't work on the Mega Drive though. So something else must still be stuffed.
I'll test the data lines and then the other side of the board next.

themrcul
Very interested
Posts: 116
Joined: Fri Apr 15, 2016 2:21 pm

Re: Cart Design Questions

Post by themrcul » Wed Jan 19, 2022 1:44 pm

Hi guys,
So I have gone ahead and tested each address line on the cart PCB using Intel's Debug Probes feature of Quartus, along with the data lines in both directions with a multimeter and all the lines are on the correct pins and working.

But I still can't get the Mega Drive to boot. I start the PCB up, send a ROM across byte by byte, then write to the SDRAM word by word until the ROM is fully transferred. After which I turn on the Mega Drive and I just get a black screen. I don't get the TMSS screen or anything else..

I have written a small C# application to send ROM data to the SDRAM. It works by simply using fileStream.Read(<bytes>); of the ROM .bin file. These bytes are then sent to the MD cartridge via USB and written as words to the SDRAM in order of receipt. I am mainly testing the ROM file for Arrow Flash, which is 512KB in size.

I have confirmed reading back the ROM from the SDRAM to the CPLD, then from the CPLD to the ARM and then from the ARM to the computer via USB. The bytes are read back correctly and match the ROM data sent.

In my code when the CAS0 line goes low and the reset line is still high it will request a word from the SDRAM at the address incoming from the Mega Drive.
It reads from the SDRAM the same way it does when the ARM micro requests data, only the result is piped to the MD data lines while CAS0 is low. Yet it is still not working. I have experimented with between 0 and 4 NOPs upon the read request from the Mega Drive, in case it takes a few cycles for the address lines to settle, to no avail, along with maintaining data output on the data lines an additional cycle after CAS0 goes high. That hasn't helped either.

Can anyone offer some advice as to what I may be doing wrong? My CPLD code is below. Most has been removed for easier reading, but only parts that are not necessary to seeing how it is responding to Mega Drive read requests. I can confirm that it stays in IDLE state unless it is refreshing the SDRAM or serving a request from the Mega Drive.

Again a sincere thanks to anyone who offers advice or help. I'm afraid this project is dead in the water if I can't get the Mega Drive booting!

Code: Select all

module AlcartDebug(
	// ARM
	//<ARM inputs, outputs>,
	
	// SDRAM
	//<SDRAM inputs, outputs>,
	
	// MD BRIDGE
	input mdPresence, // taken from 5V fingers on cart through reduction resistors to CPLD pin to sense if MD is running
	input mdReset,
	input[21:0] mdAddress,
	inout[15:0] mdData,
	input mdReadCAS0,
	output mdAddressDisable, // goes to address and control level shifters
	output mdDataDisable, // goes to data level shifter buffer
	output mdDataDirectionIsOutput // changes data direction on data level shifter buffer
);
		
//<SDRAM, ARM, definitions go here, removed for easy reading and because they aren't necessary)
		
/////////////////////////////////////////////////////////
// MD
/////////////////////////////////////////////////////////
reg mdPresence_sync = 0;
reg mdReset_sync = 1'b1;
reg mdReadCAS0_sync = 1'b1;
reg[21:0] mdAddress_sync;
reg mdDataIsOutput = 1'd1;
reg mdDataDisable_sync = 1'b1;
// assigns
assign mdAddressDisable = ~mdPresence_sync;
assign mdDataDisable = mdDataDisable_sync;
assign mdDataDirectionIsOutput = mdDataIsOutput;
assign mdData = mdDataDirectionIsOutput ? sdramReadDataCached : 16'bz;

assign sdramAddress = (cpld_state == STATE_SDRAM_ReadForARM_WaitUntilReady || cpld_state == STATE_SDRAM_ReadForARM_ReadWord ||
							  cpld_state == STATE_SDRAM_ReadForArm_WaitForReadToFinish) ? debug_sdram_read_counter :
								(cpld_state == STATE_SDRAM_WriteForARM_WaitUntilReady ||
								 cpld_state == STATE_SDRAM_WriteForARM_BeginWriteFromARM ||
								 cpld_state == STATE_SDRAM_WriteForARM_WriteFromARM_WaitUntilFinished ? debug_sdram_write_counter : { 1'b0, mdAddress_sync });
assign sdramReadEnable = cpld_state == STATE_SDRAM_ReadForMD_ReadWord || cpld_state == STATE_SDRAM_ReadForARM_ReadWord;
assign sdramWriteEnable = cpld_state == STATE_SDRAM_WriteForARM_BeginWriteFromARM;


/////////////////////////////////////////////////////////
// STATES
/////////////////////////////////////////////////////////
localparam  COMMAND_reset_sdram_read_counter = 3'd0,
				COMMAND_reset_sdram_write_counter = 3'd1,
				COMMAND_read_sdram = 3'd2,
				COMMAND_write_begin_sdram = 3'd3,
				COMMAND_write_end_sdram = 3'd4,
				COMMAND_change_to_sdram_mode = 3'd5;

localparam  STATE_CPLD_Init = 6'd0,
				STATE_CPLD_Idle = 6'd1,

				STATE_SDRAM_ReadForMD_WaitUntilReady = 6'd8,
				STATE_SDRAM_ReadForMD_ReadWord = 6'd9,
				STATE_SDRAM_ReadForMD_WaitForReadToFinish = 6'd11,
				
				STATE_SDRAM_ReadForARM_WaitUntilReady = 6'd12,
				STATE_SDRAM_ReadForARM_ReadWord = 6'd13,
				STATE_SDRAM_ReadForArm_WaitForReadToFinish = 6'd14,
				STATE_SDRAM_ReadForARM_WaitFor1stReceipt = 6'd15,
				STATE_SDRAM_ReadForARM_WaitFor2ndReceipt = 6'd16,
				STATE_SDRAM_ReadForARM_FinishARMConversation = 6'd17,
				
				STATE_SDRAM_WriteForARM_WaitForFirstByte = 6'd18,
				STATE_SDRAM_WriteForARM_CollectFirstByte = 6'd19,
				STATE_SDRAM_WriteForARM_WaitForSecondByte = 6'd20,
				STATE_SDRAM_WriteForARM_CollectSecondByte = 6'd21,
				STATE_SDRAM_WriteForARM_WaitUntilReady    = 6'd22,
				STATE_SDRAM_WriteForARM_BeginWriteFromARM = 6'd23,
				STATE_SDRAM_WriteForARM_WriteFromARM_WaitUntilFinished = 6'd24,
				STATE_SDRAM_WriteForARM_WriteFromARM_FINISH = 6'd25,
				
				STATE_CPLD_AllowNextARMMsgToComeThrough = 6'd31,
				
				STATE_CPLD_Reset_Debug_Read_Counter = 6'd32,
				STATE_CPLD_Reset_Debug_Write_Counter = 6'd33,
				
				STATE_SDRAM_DoAutoRefresh = 6'd34,
				STATE_SDRAM_DoAutoRefreshForSDRAMWrite = 6'd35
				;
reg[5:0] cpld_state = STATE_CPLD_Init, cpld_state_next;

// combinational section - state machine core state
always @ (*) begin
	case (cpld_state)
		/*=============================================
			MAIN IDLE STATE
		=============================================*/
		STATE_CPLD_Idle: begin
			if (mdReset_sync & ~mdReadCAS0_sync) begin
				// we need to read data for the MD
				cpld_state_next = STATE_SDRAM_ReadForMD_WaitUntilReady;
			end
			else if (sdramRefreshCounter > 11'd1024)
				cpld_state_next = STATE_SDRAM_DoAutoRefresh;
			else if (armDataPacketIsAwaitingProcessing) begin
				// <removed>
			end
			else
				cpld_state_next = STATE_CPLD_Idle;
		end
		
		/*=============================================
			SDRAM READ FOR MD
		=============================================*/
		STATE_SDRAM_ReadForMD_WaitUntilReady: begin
			cpld_state_next = sdramBusy ? STATE_SDRAM_ReadForMD_WaitUntilReady : STATE_SDRAM_ReadForMD_ReadWord;
		end
		STATE_SDRAM_ReadForMD_ReadWord: begin
			cpld_state_next = sdramBusy ? STATE_SDRAM_ReadForMD_WaitForReadToFinish : STATE_SDRAM_ReadForMD_ReadWord;
		end
		STATE_SDRAM_ReadForMD_WaitForReadToFinish: begin
			cpld_state_next = mdReadCAS0_sync ? STATE_CPLD_Idle : STATE_SDRAM_ReadForMD_WaitForReadToFinish;
		end
		
		// < other states removed here as they aren't necessary for reading this problem>
		
		default: cpld_state_next = STATE_CPLD_Idle;
	endcase
end

always @ (posedge clk) begin
	if (cpld_state == STATE_SDRAM_DoAutoRefresh | cpld_state == STATE_SDRAM_DoAutoRefreshForSDRAMWrite) begin
		sdramRefreshEnable <= 1'b1;
		sdramRefreshCounter <= 0;
	end
	else begin
		sdramRefreshEnable <= 0;
		sdramRefreshCounter <= sdramRefreshCounter + 1'b1;
	end
end

// sequential section - state machine guts
always @ (posedge clk or posedge armReset) begin
	if (armReset) begin
		cpld_state <= STATE_CPLD_Init;
		
		mdPresence_sync <= mdPresence;
		mdReset_sync <= mdReset;
		mdReadCAS0_sync <= 1'b1;
		mdDataDisable_sync <= 1'b1;
		
		armSendDataToARMPlz <= 0;
		sdramReset <= 1'd1;
		mdDataIsOutput <= 0;
		
		// add more reset stuff here
		debug_sdram_read_counter <= 0;
		debug_sdram_write_counter <= 0;
	end
	else begin
		cpld_state <= cpld_state_next;
		
		// sync md signals to our clock
		mdPresence_sync <= mdPresence;
		mdReset_sync <= mdReset;
		mdReadCAS0_sync <= mdReadCAS0;
		mdAddress_sync <= mdAddress;
		mdDataDisable_sync <= 1'b1;
		
		armSendDataToARMPlz <= 0;
		sdramReset <= 0;
		mdDataIsOutput <= 1'b1;
		
		case (cpld_state)	

			STATE_SDRAM_ReadForMD_WaitForReadToFinish: begin
				if (sdramReadValid) begin
					sdramReadDataCached <= sdramReadData;
				end
				mdDataDisable_sync <= 0;
			end
			
			// <other states removed here as they aren't applicable to the probem>
		endcase
	end
end

endmodule

Charles MacDonald
Very interested
Posts: 292
Joined: Sat Apr 21, 2007 1:14 am

Re: Cart Design Questions

Post by Charles MacDonald » Sun Apr 03, 2022 4:32 am

I had a similar kind of experience on a past project and it was helpful to reduce the complexity and just try a simple test case: ignore the SDRAM entirely and output fixed ROM data to run a small program that is only a few words long so it can fit within the CPLD. For example something like:

Code: Select all

dc.l 0,8
loop: reset
bra.s loop 
You can your logic analyzer to verify this program is being executed by monitoring the 68K bus. If it works, depending on how much ROM you can synthesize in the CPLD you could make some short diagnostic programs to interact with the SDRAM.

Consider a program like this:

Code: Select all

dc.l 0,8
repeat:
lea $0100.w, a0
movem.l (a0)+, d0-d3
bra.s repeat
If your CPLD code outputs this ROM data for addresses less than 0x100 and outputs SDRAM for addresses over 0x100, you should see the program run successfully and then read SDRAM data from offset 0x100 through 0x10F. Depending on what data you loaded into the SDRAM you can verify if it is reading the correct data consistently from those locations. If the SDRAM isn't working, the ROM part of the program still works correctly so it's in a debuggable state.

themrcul
Very interested
Posts: 116
Joined: Fri Apr 15, 2016 2:21 pm

Re: Cart Design Questions

Post by themrcul » Tue Apr 05, 2022 12:36 pm

Thanks very much Charles for taking the time to reply to me.

I'm afraid I don't have a logic analyzer. I was hoping to get by without one, but it may be the case that I need to bite the bullet and purchase one. That being said, I don't know how I would use one to monitor the 68K bus - I'm very much a noob with hardware stuff as you can probably tell!

The other thing is, I only have TMSS Mega Drives, would a test program not need to have TMSS handling to work?

Regardless in the meantime I have designed a test PCB for my cartridge that connects an ARM 3 micro up to the data, address and control lines of a MD cartridge port.
I'll be able to use it to test if each data, control and address line successfully reach the CPLD - if not there may be something wrong with my voltage translator lines, like a solder bridge or something.
If that all works ok I'll code up a test program to report back simple reads from the MD cartridge port to the host computer. Then I'll plug my cartridge up to it and see what happens when I "read" data from it.

I've designed and had the PCB fabricated and shipped to me, as well as soldering it up. What remains is writing the first test program for it.

I've had life and other projects get in the way of being able to do that, but hopefully I'll be able to continue this project soon and I'll post back my results when I do.

Thanks again Charles I really appreciate you posting to help me.

Charles MacDonald
Very interested
Posts: 292
Joined: Sat Apr 21, 2007 1:14 am

Re: Cart Design Questions

Post by Charles MacDonald » Tue Apr 05, 2022 9:06 pm

TMSS isn't too bad to deal with, it just takes a little more complexity on your end:

1. Your ROM should contain the ASCII text "SEGA" at offset 0x100. (e.g. the CPLD outputs that hardcoded data when reading that memory range)

2. If your diagnostic software needs to write to the VDP, it has to satisfy TMSS by executing "move.l #'SEGA', $A14000". If you aren't touching the VDP I think you can skip this.

About monitoring the bus the idea here is that executing the reset instruction pulses the reset line low for something like 100+ clock cycles. Normally reset doesn't pulse and stays high after going low for ~300ms after power up. So if you connected a logic analyzer or oscilloscope probe to the reset line and ran that little diagnostic program from the previous post you should see reset pulse continuously and that's an indicator the program is really running correctly and the CPLD is outputting ROM data properly. It verifies that at least the CPLD interface to the data bus, address bus, and some of the control lines is working as expected.

That way you have a starting point to do larger tests and exercise more things incrementally.

I like the idea of the ARM chip as a cartridge port tester, I'm sure that will give you a lot of insight to what's going on. Sounds like a handy way to dump games too. :)

themrcul
Very interested
Posts: 116
Joined: Fri Apr 15, 2016 2:21 pm

Re: Cart Design Questions

Post by themrcul » Tue Apr 12, 2022 7:04 am

Thanks again Charles,
Your posts inspired me to get my test pcb working.

I programmed the code for the SAM3U microcontroller to receive commands from the computer via USB to set address, control and data lines manually, along with a dummy "read from cart" command sequence. I'll be able to give it more features later.

I also coded up a simple console application for Windows that talks to the SAM3U through USB to send the commands to.

From my initial tests I can confirm that, while all data lines (and most likely all the control lines) are working correctly, several of the address lines are stuffed.

It could be a problem with my test pcb, so I need to investigate further, but on my initial "set address to be 1 or 0" test, I have found that:
A7 is always at a true state.
A13 and A15 are always false.

So, if the test PCB is all working correctly (which is the next thing to investigate with a multimeter), then that implies that A7 is shorted somewhere, and A13 and A15 have a broken trace somewhere.

Anyway, A7 always being 1 would definitely stuff the booting of any game up. I'll post more when I find out more or can repair it.

Thanks again!

themrcul
Very interested
Posts: 116
Joined: Fri Apr 15, 2016 2:21 pm

Re: Cart Design Questions

Post by themrcul » Thu Jul 14, 2022 7:30 am

Hi everyone,
So I've been going back and forward with this thing, and while I have made some baby steps to make it reliable this time (at least within it's own world), I simply can't get it to work on the real Mega Drive.

As I wrote above, I made a test PCB to send commands through the edge connector of the cartridge PCB to perform reads to the cart -and that works really well. It can read retail Mega Drive carts and it can read mine too.

Retail carts return a valid data result very quickly, faster than my cart can. But I have verified byte for byte that a ROM read from my cart matches a ROM read from a retail one, in this example my PAL copy of Sonic the Hedgehog.

For a little while there I thought that my SDRAM read routine was too slow, as I have calculated I need around 150ns to get a valid read from my cart from the CAS0 line assertion to de-assertion. This calculation is from needing 14x NOPs on my test cart reader between taking CAS0 low and reading the data GPIOs.
But even if I hard-code the TMSS 'SEGA' code at 0x100 and 0x101 in my CPLD read routine, which returns a result within 60ns, my cart still does not pass TMSS, I still only have a black screen on my monitor when I turn the power on the Mega Drive.

I don't have any logic analyzing tools, and perhaps I was extremely naive going into this project thinking I wouldn't need one (as getting a read from memory and returning it on the data lines can't be that complicated, right???). Also, 64 line logic analyzers are really expensive (at least $600AUD), and I would probably need to redesign and resolder another test cart with test points which I am really hesitant to do. That would cost another $70 for the PCB and I only have one CPLD IC left in my stock, with none available to buy any time soon due to the chip crisis!

I have spent so much time spinning wheels on this simple stage that it has kinda made me feel like a bit of an idiot.. why can't I get such a simple operation to work? Receive read, request data at address from SDRAM, feed result to data lines until read line de-asserts. How hard could that be? Too hard for me it seems...

I've been strongly considering commissioning someone to at least help me get this base part of the cart working, but I don't know if anyone would be interested or how much that would cost... Also, there's always been just one more thing I can test to see if it makes this project work - but nothing so far has..

--

Anyhow, whine over. I do have some questions that may help me in some areas.

1) The first one is on addresses.
The MD cart bus is missing A0, and A1 onwards is routed to A0-A21 on my CPLD side.
So when someone says that 'SEGA' for TMSS starts at address 0x100, the actual address I am checking for at the CPLD is 0x80, as A0 from the 68000 is not connected to the bus. That means that a 32 megabit game will only address up to 2 mega bytes on the address lines (A1-A21), as the bus is 16bits wide. Is this right?

2) Clocking.
I am clocking my ARM Micro at 100MHz. I am also using a Programmable Clock Output line from the ARM Micro to clock my CPLD, at the same 100MHz, as the global clock for the CPLD.
When I testbenched my SDRAM, to my horror I noticed that the clock passed on from the CPLD to the SDRAM is halved. I am such a beginner with these things that I didn't know that would happen... So in the end the SDRAM appears to be clocked at 50MHz.
I'm pretty sure this is why reads take 140ns rather than the advertised 70ns from the datasheet of the SDRAM.
I can't send any faster clock from the ARM to the CPLD, so if SDRAM speed is the reason the whole thing is failing I will need to generate a faster clock for the CPLD and SDRAM, more like 166MHz for both ICs.

So if I am to do a redesign, I have three choices as I see it.
1) Linking a second programmable clock output from the ARM Micro, but this second one would go to the SDRAM directly. The issue I see for this one is the ICs are on separate sides of the board, and getting a clean clock line from the ARM Micro to the SDRAM might be a challenge - also the distance is high (8cm or so, at 100MHz that may be an issue).

2) Putting a clock generator with two outputs, both set to 166MHz, with one linked to a second global clock input in the CPLD and the other to the clock input of the SDRAM. The CPLD I have in this PCB has no PLL and so no way to multiply a clock line or forward a clock line onto another IC, so I would need a generator if I want something faster than 50MHz out of the CPLD.
The challenge with this one is that now the ARM and the SDRAM are on cross-clock domains, and that can be hard to get right from what I have read...

3) Dumping all that garbage and just shelling out for some good old fashioned SRAM instead which is what I should have done in the first place. That will cost $80AUD in SRAM parts but far out I have wasted so much energy and time on getting SDRAM working that if I knew in advance $80 would seem like nothing.


That being said, I don't know that that is the problem with the Mega Drive booting from my cart.
This is my basic read code:

Code: Select all

reg mdDataIsOutput = 1'b1;
assign mdDataTransceiverDataDirection = mdDataIsOutput;
assign mdDataTransceiverOutputEnable = ~mdCartPluggedIn | mdReadCAS0_sync; // active low, so a 0 result from this query means data is active, a 1 means transceiver is tri-stated
assign mdData = mdDataIsOutput ? sdramData : 16'bz;

assign sdramReadRequest = cpld_state == state_md_read;

// pseudo code for state machine
always @ (*) begin
	case (cpld_state)
		state_idle: begin
			state_next = mdCartPluggedIn & ~mdReadCAS0_sync ? state_md_read_wait_for_ready : state_idle;
		end
			
		state_md_read_wait_for_ready: begin
			state_next = sdramBusy ? state_md_read_wait_for_ready : state_md_read;
		end
			
		state_md_read: begin
			state_next = state_md_wait_for_finish;
		end
		
		state_md_wait_for_finish: begin
			state_next = ~mdCartPluggedIn | mdReadCAS0_sync ? state_idle : state_md_wait_for_finish;
		end
		
		default:
			state_next = state_idle;
		
	endcase
end

always @ (posedge clk) begin
	cpld_state <= state_next;
	
	if (reset) begin
		// blah
	end
	else begin
	
		// the following flip-flop copies are for metastability
		mdReadCAS0_sync <= mdReadCAS0;
		mdAddress_sync <= mdAddress;
		mdDataIsOutput <= 1'b1;
	
		case (cpld_state)
			state_md_read: begin
				sdramAddress <= mdAddress_sync;
			end
		endcase
	end // else reset
end


Charles, I know you said to compile a small ROM and hard-code that into the CPLD, but shouldn't a hacked return 0x5345 ('SE') at address 0x80 and 0x4741 ('GA') at address 0x81 at least pass TMSS, even if other things may be failing?

Thanks again to anyone willing to post some help.

Oh yeah I should mention that I have a retail cart reader as well. One feature of it is to 'get cart info'. I assume it reads part of the header and returns some potentially useful information.

What is interesting is that for the retail Sonic the Hedgehog cart it returns the name of the game, "SONIC THE HEDGEHOG (W)", and the size of the ROM, 512KB, and 0KB for save SRAM.
When I read the ROM written to my cart, it returns the name correctly, "SONIC THE HEDGEHOG (W)", but calculates the ROM size incorrectly to be 4096KB. I assume 4096KB is the hard-coded minimum size they have defined for a ROM, so perhaps it is getting zeros or an unknown result from my cart at the requested read locations.
I checked the header location for ROM size, and it is at 0x1A0 to 0x1A7. When I read those locations manually (or at least, the word location 0xD0-0xD3) through my test reader I get the correct data (0x0, 0x0, 0x0, 0x0, 0x0, 0x7 0xff, 0xff) - which means it is a 512KB game.
Weird... Again I'm too much of a novice with electronics to work out what could be going wrong there, and not having access to their source code doesn't help.

Post Reply