Using SDRAM like if it was SRAM, again


Recommended Posts

Hi all,


Please, apologies for my bad English.


I am building a clone of a ZX Spectrum computer. It was a Zilog Z80 based computer with some custom hardware for video displaying very popular here in Spain in the 80s.


I already have a basic working implementation using FPGA's BRAM. Using an ArcadeMegawing it displays a VGA video signal, reads a PS/2 keyboard, plays audio and also, with a simple adapter, can load game from tapes.


Since I have already used all available BRAM but I need more to implement some other things (DivMMX emulation, a way to load games from a SD card), I need to use the SDRAM chip onboard.


I have read about using SDRAM like if it was SRAM in other topic in this forum. There is said it is possible but only at slow speeds. No problem then because mi clone runs at 3.5 MHz.


I am figuring how to use Hamster's SDRAM controller but I am having no success on it. I have wrote this small module to test reading/writing to SDRAM.


It writes an incremental value and then reads it. I use the LEDs to see if it is working. I know that the module goes through all three states (send write command, send read command, wait to data is retrieved... then repeat for every memory address) because if I tie LEDs to address signals they display appropriate data (or at least I think they lit as they should, some kind of binary count) but if I tie the LEDs to the read data signal they are always on.


I don't know what is wrong with this. Any help is appreciated.

library ieee;	use ieee.std_logic_1164.all;	use ieee.std_logic_unsigned.all;entity top is    port	 (		netCLK    : in    std_logic;		--		netLED    : out   std_logic_vector( 3 downto 0);		--		sdramCLK  : out   std_logic;		sdramCKE  : out   std_logic;		sdramCS   : out   std_logic;		sdramRAS  : out   std_logic;		sdramCAS  : out   std_logic;		sdramWE   : out   std_logic;		sdramBA   : out   std_logic_vector( 1 downto 0);		sdramADDR : out   std_logic_vector(12 downto 0);		sdramDQM  : out   std_logic_vector( 1 downto 0);		sdramDATA : inout std_logic_vector(15 downto 0)	);end;architecture behavioral of top is	signal clock  : std_logic;	signal ready  : std_logic;	signal enable : std_logic := '0';	signal wr     : std_logic;	signal a      : std_logic_vector(20 downto 0);	signal di     : std_logic_vector(31 downto 0);	signal do     : std_logic_vector(31 downto 0);	signal dr     : std_logic;	signal addrwr : std_logic_vector(20 downto 0) := (others => '0');	signal addrrd : std_logic_vector(20 downto 0) := (others => '0');	signal datawr : std_logic_vector(31 downto 0) := (others => '0');	signal datard : std_logic_vector(31 downto 0);	type statetype is (sendwrite, sendread, waitread);	signal state  : statetype := sendwrite;begin	netLED <= datard(23 downto 20);--	netLED <= addrrd(20 downto 17);	process(clock)	begin		if rising_edge(clock) then			if enable = '1' then enable <= '0';			else				case state is					when sendwrite =>						if ready = '1' then							enable <= '1';							wr     <= '1';							a      <= addrwr;							di     <= datawr;							addrwr <= addrwr+1;							datawr <= datawr+1;							state  <= sendread;						end if;					when sendread =>						if ready = '1' then							enable <= '1';							wr     <= '0';							a      <= addrrd;							state  <= waitread;						end if;					when waitread =>						if dr = '1' then							datard <= do;							addrrd <= addrrd+1;							state  <= sendwrite;						end if;				end case;			end if;		end if;	end process;	Uclock : entity work.clock	port map	(		i => netCLK,		o => clock	);	Usdram : entity work.sdram_controller	generic map	(		sdram_address_width  => 22,		sdram_column_bits    => 8,		sdram_startup_cycles => 10100,		cycles_per_refresh   => (64000*100)/4196-1	)	port map	(		clk             => clock,		reset           => '0',	-- interface to issue reads or write data		cmd_ready       => ready,		cmd_enable      => enable,		cmd_wr          => wr,		cmd_address     => a,		cmd_byte_enable => "1111",		cmd_data_in     => di,		data_out        => do,		data_out_ready  => dr,		-- sdram signals		SDRAM_CLK       => sdramCLK,		SDRAM_CKE       => sdramCKE,		SDRAM_CS        => sdramCS,		SDRAM_RAS       => sdramRAS,		SDRAM_CAS       => sdramCAS,		SDRAM_WE        => sdramWE,		SDRAM_BA        => sdramBA,		SDRAM_DQM       => sdramDQM,		SDRAM_ADDR      => sdramADDR,		SDRAM_DATA      => sdramDATA	);end;
Link to comment
Share on other sites



What about using the ZPUino to handle all of those other tasks that you need done? How big is your ZX Spectrum core? The smallest ZPUino uses about 40% of the Spartan 6 if I remember correctly. You might be able to use both side by side and take advantage of the SDRAM controller and all of the easy to use SD card libraries that the ZPUino brings to the table.


Alternatively, the big difference between using SDRAM and SRAM is that with SDRAM you have to be able to make your core wait when you change to a different area of SDRAM memory. There is a signal provided by hamster's SDRAM controller that lets you know when your core needs to wait... Can you halt your core when that signal is asserted? I would test with that first...



Link to comment
Share on other sites

Hi Jack,


Thank you for your answer.

I don't think using the ZPUino is an option because the interface I want to emulate, the DivMMC, is just a ROM with the firmware (16K) to manage accesses to the SD card, some RAM to store data (256K) and some glue logic to interface with the ZX Spectrum hardware (basically to map ROM or RAM pages when necessary. All work is done by the ZX Spectrum itself.

I am aware about the need to wait after requesting a memory read until the data is available. Since the core runs at 3.5 MHz I hope think there is plenty of time since the CPU request a memory access until the data is read one T-state afterwards (for each CPU at 3.5 MHz cycle, the SDRAM have 28.5 cycles at 100MHz to do its work).


But first, I am trying to do some test reading/writing to the SDRAM but until now I am not having any success. I don't know what is wrong with my code above but, although I am pretty sure it runs through all three states (write, request read, wait for data) I am not reading what I wrote first.



Link to comment
Share on other sites

Thanks for the advice. I will try the simulator when I reach that point. By the moment I am just trying to write something in the SDRAM and trying to read it afterwards with no success. I know Hamster's controller works well. The problem is in my code buy I don't find the cause. I have tried several ways to the process but I always get the same, four LEDs on instead of a binary count.

Link to comment
Share on other sites

FWIW I don't see an obvious error in your code, but I will say I've written code that looked fine to me, but didn't generate the hardware I expected, pretty much the only way to know what your actually getting is looking at the simulator.

Coming from a software background when I first started with FPGA's I treated it like developing software, it wasn't until I started trying to build something none trivial I started to realize just how different it was, I've seen the tools remove what to me was obviously in use logic resulting in state machines that didn't cycle through states as I'd expected.

Now I start with a test bench and check it in the simulator before I even try it in hardware. If it doesn't work in the simulator it won't work in hardware.  

Link to comment
Share on other sites

OK. I'll try the simulator. As cannot simulate the SDRAM because it is real hardware, I have to write a module that simulates the behavior of the SDRAM, or much easier, simulate the SDRAM controller. Isn't it? Something that rises a ready signal some cycles after a command is received and stores or outputs some data depending on how was the write signal.

Link to comment
Share on other sites

  • 2 weeks later...



I've been working on this with little success. After a lot of testing with no success at all with Hamster's SDRAM controller I have switched to a different one.


Now I am using Matthew Hagerty's simple SDRAM controller:


It works pretty well in a basic design. I am able to read and write the SDRAM. But when I put this module in my ZX Spectrum project it only works half way.


I am using a test ROM designed to check the computer's onboard RAM. It runs four tests. After a bit of tweaking it passes the first two (I am not been able to pass the last two) but only if I run it at 112 MHz. Running it at 100 or 128 MHz does not work.


Any suggestion will be appreciated :D






Here is my interface module between the computer and the SDRAM:

library ieee;	use ieee.std_logic_1164.all;	use ieee.std_logic_unsigned.all;entity sdram is	port	(		clock     : in  std_logic;		enable    : in  std_logic;		refresh   : in  std_logic;		rd        : in  std_logic;		wr        : in  std_logic;		a         : in  std_logic_vector(14 downto 0);		di        : in  std_logic_vector( 7 downto 0);		do        : out std_logic_vector( 7 downto 0);		--		sdramCLK  : out   std_logic;		sdramCKE  : out   std_logic;		sdramCS   : out   std_logic;		sdramRAS  : out   std_logic;		sdramCAS  : out   std_logic;		sdramWE   : out   std_logic;		sdramDQML : out   std_logic;		sdramDQMH : out   std_logic;		sdramBA   : out   std_logic_vector( 1 downto 0);		sdramADDR : out   std_logic_vector(12 downto 0);		sdramDATA : inout std_logic_vector(15 downto 0)	);end;architecture behavioral of sdram is	signal sdr   : std_logic;	signal sde   : std_logic;	signal sdw   : std_logic;	signal sdrf  : std_logic;	signal sddr  : std_logic;	signal sddi  : std_logic_vector(15 downto 0);	signal sddo  : std_logic_vector(15 downto 0);	signal sda   : std_logic_vector(23 downto 0);	type statetype is (waitCommand, waitRefresh, waitRead, waitWrite);	signal state  : statetype := waitCommand;begin	process(clock)		variable tmp : std_logic := '0';	begin		if rising_edge(clock) then			if enable = '0' then tmp := '0'; end if;			case state is			when waitCommand =>				if sdr = '1' and enable = '1' and tmp = '0' then					if refresh = '0' then						tmp   := '1';						sdrf  <= '1';						state <= waitRefresh;					elsif rd = '0' then						tmp   := '1';						sde   <= '1';						sdw   <= '1';						sda   <= "000000000"&a;						state <= waitRead;					elsif wr = '0' then						tmp   := '1';						sde   <= '1';						sdw   <= '0';						sda   <= "000000000"&a;						sddi  <= "00000000"&di;						state <= waitWrite;					end if;				end if;			when waitRefresh =>				if sddr = '1' then					sdrf  <= '0';					state <= waitCommand;				end if;			when waitRead=>				if sddr = '1' then					sde   <= '0';					do    <= sddo(7 downto 0);					state <= waitCommand;				end if;			when waitWrite=>				if sddr = '1' then					sde   <= '0';					state <= waitCommand;				end if;			end case;		end if;	end process;	Usdcontroller : entity work.sdcontroller	port map	(		clk_100m0_i => clock,		ready_o     => sdr,		refresh_i   => sdrf,		rw_i        => sde,		we_i        => sdw,		addr_i      => sda,		data_i      => sddi,		ub_i        => '0',		lb_i        => '0',		done_o      => sddr,		data_o      => sddo,		--		sdClk_o     => sdramCLK,		sdCke_o     => sdramCKE,		sdCe_bo     => sdramCS,		sdRas_bo    => sdramRAS,		sdCas_bo    => sdramCAS,		sdWe_bo     => sdramWE,		sdBs_o      => sdramBA,		sdAddr_o    => sdramADDR,		sdData_io   => sdramDATA,		sdDqml_o    => sdramDQML,		sdDqmh_o    => sdramDQMH	);end;

Some description of the tests:



Walk Test -pass-

This is a very simple test - all it does is set each bit and reset each bit in memory, checking the memory holds the desired value on each iteration. If the test fails here, it means the failed RAM chip simply isn't reliably (or at all) able to be set to a given value. This can be caused by a failed chip or a bad solder joint or broken PCB track. (When testing lower RAM, you can often see this once tests halt, by vertical lines 1 pixel wide running down the screen being visible)

This test will also show up failures in logic caused by data bus lines being shorted together.

Inversion Test -pass-

Many faulty chips can pass the first test sequence. This test looks for faults where setting or resetting a bit in memory causes another bit to erroneously be set or reset. The tests consist of setting all of the memory bank to zero, and then writing 1s in all even memory addresses, and then checking the pattern is as expected. The test is run again - memory is blanked, then odd addresses are written to, and the pattern checked. Then, all of the memory bank is set to 1, and even addresses are reset and tested. Finally, memory is all set to 1 again and odd addresses are reset and tested.

If the memory fails the inversion test, you can often get some insight into what is happening by watching the screen if it's lower RAM that has failed (and if your Spectrum won't boot normally at all, this is highly likely). You should see alternating black and white vertical stripes appear on the screen. If a set of stripes is anything other than black or white, setting the bit in the failed memory likely sets the entire contents of the chip to that value. It's likely the row/column select circuitry in the failed chip has a fault in that case. Other failures can be simply one or two adjacent bits getting flipped in the wrong place. This may not be visible by the screen even if it's happened in lower RAM (the portion of RAM that the frame buffer occupies might be working fine).

March Test -fail-

This test aims to shake out simple failures caused by addressing a memory location that causes adjacent locations to be written erroneously. The algorithm used works as follows:

    Step 1: Write 0 in ascending addressing order;
    Step 2: Read 0 and write 255, again with ascending addressing order;
    Step 3: Read 255 and write 0 with descending addressing order;
    Step 4: read 0 with descending addressing order.

Random Fill Test -fail-

There are some more subtle kinds of failure that the inversion or March test won't pick up, such as setting a memory bit in the faulty chip causing a bit to be set somewhere in the other half of the chip to be set. The random fill test tries to shake these out.

The routine uses a 16 bit pseudo random number generator to fill the memory bank with values, and then restarts the random number generator with the starting seed, and compares the output of the random number generator with what is held in memory. Memory is filled from bottom to top with one pattern, verified, then top to bottom with another pattern and verified again.

When lower RAM is tested, you should see a scrambled pattern of random pixels and attributes on the screen.

Memory failures in this test are most likely problems with the chip's row/column select circuitry. For example, if the most significant bit gets stuck on for column access, setting a bit at location 0 in the chip will also set location 8192 in the case of a 4116.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   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.