Cart Design Questions

Ask anything your want about Megadrive/Genesis programming.

Moderator: BigEvilCorporation

themrcul
Very interested
Posts: 107
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: 107
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: 735
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: 107
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: 735
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: 2883
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: 107
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: 107
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: 107
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: 107
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

Post Reply