leon912

Hamster's SDRAM_Controller

42 posts in this topic

Hi,

 

I'm trying to write data inside the sdram on the papilio pro board. To do so I downloaded the hamster's code where a SDRAM controller is implemented. However, I don't understand the input interface of that controller:

 

-- Interface to issue reads or write data
           cmd_ready         : out STD_LOGIC;                     -- '1' when a new command will be acted on
           cmd_enable        : in  STD_LOGIC;                     -- Set to '1' to issue new command (only acted on when cmd_read = '1')
           cmd_wr            : in  STD_LOGIC;                     -- Is this a write?
           cmd_address       : in  STD_LOGIC_VECTOR(sdram_address_width-2 downto 0); -- address to read/write
           cmd_byte_enable   : in  STD_LOGIC_VECTOR(3 downto 0);  -- byte masks for the write command
           cmd_data_in       : in  STD_LOGIC_VECTOR(31 downto 0); -- data for the write command
           
           data_out          : out STD_LOGIC_VECTOR(31 downto 0); -- word read from SDRAM
           data_out_ready    : out STD_LOGIC;                     -- is new data ready?
           
           -- SDRAM signals
           SDRAM_CLK     : out   STD_LOGIC;
           SDRAM_CKE     : out   STD_LOGIC;
           SDRAM_CS      : out   STD_LOGIC;
           SDRAM_RAS     : out   STD_LOGIC;
           SDRAM_CAS     : out   STD_LOGIC;
           SDRAM_WE      : out   STD_LOGIC;
           SDRAM_DQM     : out   STD_LOGIC_VECTOR( 1 downto 0);
           SDRAM_ADDR    : out   STD_LOGIC_VECTOR(12 downto 0);
           SDRAM_BA      : out   STD_LOGIC_VECTOR( 1 downto 0);
           SDRAM_DATA    : inout STD_LOGIC_VECTOR(15 downto 0));
 
 
As you can see, the SDRAM works with words of 16 bits but the controller needs 32 bits. I tryied to understand how it works but I wasn't able. Can u explain a little bit how simple operation like single read/write can be performed throug the controller?
 
Thanks

Share this post


Link to post
Share on other sites

Ok, so I have not actually tried to directly use Hamster's SDRAM controller but I have looked pretty closely at it with the intention of using it in the past. So take what I have to say with a grain of salt, I could be wrong and I'm going off the top of my head from what I remember reading over a year ago. :)

 

I think the most important thing to first understand is the difference between SRAM and SDRAM. With SRAM it is super simple to use because there are no special timing considerations. As long as you do a reads or writes at a speed slower then the highest rated clock frequency you will always put data into or get data out of the SRAM device. You don't have to wait for a bank to charge or anything like that. So SRAM is the most desirable technology from a usability standpoint, but that ease of use comes at a cost. SRAM is much more expensive then SDRAM or DDR memory.

 

With SDRAM they got the cost down and the density up by simplifying the internal circuitry, but this made controlling them more complicated. With SDRAM you can no longer just read and write when you want, you have to keep track of what column and bank you are writing too. There are certain published times that you have to wait if you change the column or the bank that you are accessing, and I think those times are even different for reads and writes. 

 

Hamster's SDRAM controller takes care of those details, all you really need to know is that you cannot expect to read and write whenever you want. If I understand correctly then basically cmd_ready is going to let you know when the appropriate wait times have been observed for the address you are trying to read or write to. So I guess you would set the address and data if you are going to do a write and then assert CMD_ENABLE. The controller will then let you know when that data was written by asserting cmd_ready which means it is safe to write the next 16 bits of data.

 

That is how I think it works, but like I said I have not tried it so I'd love to hear from someone with more experience.

 

Oh, also, you have seen his wiki pages right?

 

http://hamsterworks.co.nz/mediawiki/index.php/SDRAM_Memory_Controller

http://hamsterworks.co.nz/mediawiki/index.php/Simple_SDRAM_Controller

 

Jack

Share this post


Link to post
Share on other sites

Another comment is that SDRAM is optimized for burst access - once you have opened a "row" you can read or write data sequential from/to that row in a burst (one word per clock), making this kind of memory very good for a frame buffer where you typically access data sequentially.  However, this needs a memory controller written specifically for this use case.  I think hamsters SDRAM controller is written with a 32-bit CPU in mind and do not provide burst access.

 

BTW, the MiST FPGA board have the same SDRAM chip as Papilio Pro and they wrote a memory controller that makes the SDRAM look like SRAM that can be accessed at 8 MHz (i.e. 16 MB/s).  I ported their Macintosh PlusToo project to Papilio Pro and wrote a post about it here: http://forum.gadgetfactory.net/index.php?/topic/2485-plustoo-macintosh-plus-clone-running-on-papilio-pro/

You can download the project and look at the sdram controller module.  It might not be fast enough for your project but you might be able to modify it to do burst access.

 

Magnus

Share this post


Link to post
Share on other sites

Indeed at least one of Hamster's controller does support bursts.

 

I use a variant of it in ZPUino. It supports full 32-bit wide-bursts (yes, I also do not support 16-bit, but that can be changed) and almost-full 32-bit writes (there's a dummy waitstate there I need to remove one of these days).

 

See this post for some details on bursting. http://forum.gadgetfactory.net/index.php?/topic/1894-overview-of-an-sdram-transfer/

 

Alvie

Share this post


Link to post
Share on other sites

That's good.  I based my comment about bursting based on the interface described at the top of the post - no burst count etc., just single word read/write.

 

Magnus

Share this post


Link to post
Share on other sites

I read the VHDL code of Hamster but what I do not understant is its physical implementation: as i said, i would like to know why the controller requires a data bus and address bus that are twice the ones required by the memory.

 

Can somebody explain it ?

Share this post


Link to post
Share on other sites

The memory controller is written so that a CPU with 32-bit data bus can access the memory.  The memory controller is creating the illusion of a 32-bit memory system by reading or writing two 16-bit words for each 32-bit access.  You can modify his controller so that it's only reading or writing one 16-bit word for each access if that's what you need.  However, that would reduce the data rate by almost a factor of 2.  A better way would be to actually increase the data bus width to 64-bits so that each access is a burst of four 16-bit words, that would almost double the data rate and hopefully give you enough bandwidth so that you can support a VGA framebuffer (25 MB/s).

 

Magnus

Share this post


Link to post
Share on other sites

Ok thanks!

 

There is any manual or commented code that explains how to use the memory controller (how to enable signals, etc..)?

Share this post


Link to post
Share on other sites

I'm not sure what part is confusing to you, the interface to the memory controller is quite simple.

 

clk: 100 MHz clock signal to the controller.  All interface inputs and outputs are synchronous to this clock

reset: reset signal to the controller (like power-on reset)

cmd_ready: output from the controller, indicating if it's ready to accept a command (ready if '1')

cmd_enable: input to the controller, '1' means that you are issuing a new command (the command is only accepted if cmd_ready is '1')

cmd_wr: input to the controller, specifies if the command is a write command (cmd_wr = '1') or read command (cmd_wr = '0')

cmd_address: input to the controller, specifies the memory address for the command (note that this is a 32-bit word address)

cmd_byte_enable: 4-bit input to the controller used when writing (one bit for each byte). '1' means that the corresponding byte is written, '0' means that it will not be written

cmd_data_in: 32-bit input to the controller with the data that you want to write

data_out: 32-bit output from the controller with the data from the last read command (only valid when data_out_ready == '1')

data_out_ready: data_out from the last read command is available

 

For example, to write all four bytes in a 32-bit data word to a memory location you set cmd_wr = '1', cmd_address = <your memory address>, cmd_byte enable = '1111', cmd_data_in = <your data>, and cmd_enable ='1'.  The controller will respond with changing cmd_ready from '1' to '0', at which point you can set cmd_enable = '0'.  The next command can be issued when cmd_ready is changed from '0' to '1'.

 

To read a 32-bit data word from a memory location you set cmd_wr = '0', cmd_address = <your memory address>, cmd_byte_enable = '0000' (or whatever), cmd_data_in = 32-bit 0 (or whatever) and cmd_enable = '1'.  The controller will respond with changing cmd_ready from '1' to '0', at which point you can set cmd_enable = '0'.  The 32-bit read data is available at data_out when data_out_ready = '1'

 

Note that cmd_wr, cmd_address, cmd_byte_enable and cmd_data_in are "don't care" if cmd_enable is '0', they only matter when cmd_enable = '1' and cmd_ready = '1'.

 

Magnus

Share this post


Link to post
Share on other sites

Ok but there is something still not clear:

 

constant sdram_address_width : natural := 22;
   constant sdram_column_bits   : natural := 8;
   constant sdram_startup_cycles: natural := 10100; -- 100us, plus a little more
   constant cycles_per_refresh  : natural := (64000*100)/4196-1;
   
   constant test_width          : natural := sdram_address_width-1; -- each 32-bit word is two 16-bit SDRAM addresses 
 
 
this is a piece of code from the top_level entity. I know that the memory has words of 16 bits, so if i need to read/write 32 bits i need to specify 2 addresses, right? In addition I read that each address is composed of 8 bits to specify the column (A0-A7), A8,A9,A11 nothing and A10 to enable/disable precharge; plus there are B0-B1 to specify the bank address.
 
I would like to know if i have to deal with those things of if I have to act like i have a 22bit address memory.
 
Thanks!

Share this post


Link to post
Share on other sites
   constant sdram_address_width : natural := 22;
   constant sdram_column_bits   : natural := 8;
   constant sdram_startup_cycles: natural := 10100; -- 100us, plus a little more
   constant cycles_per_refresh  : natural := (64000*100)/4196-1;

 

The constants are there so you can adapt the memory controller to different kind of memory chips.  The constants used above are valid for the memory chip on Papilio Pro.

 

I know that the memory has words of 16 bits, so if i need to read/write 32 bits i need to specify 2 addresses, right?

 

No, just one address.  The 32-bits use up two consecutive memory locations in the 16-bit SDRAM.  The width of cmd_address is one less than the address width of the SDRAM (21 vs.22 bits).  One way to look at it is to say that cmd_address is SDRAM address bits 21-1, and that the controller is internally appending SDRAM address bit 0 ('0' for the first 16 bits and '1' for the second set of 16-bits).

 

In addition I read that each address is composed of 8 bits to specify the column (A0-A7), A8,A9,A11 nothing and A10 to enable/disable precharge; plus there are B0-B1 to specify the bank address.

I would like to know if i have to deal with those things of if I have to act like i have a 22bit address memory.
 
The whole purpose of the memory controller is to hide all the nasty details of the memory chip from the user.  From the users perspective it looks like a 32-bit wide memory with 21 address bits (i.e. 8MB) and with individual byte-write enables.
 
 
This is what hamster writes about how to use the memory controller:

The use of the SDRAM controller is pretty simple. Any time that CMD_READY is asserted by the memory controller, and CMD_ENABLE is asserted by your logic the address, data, byte enable flags and write flag will be registered by the controller, and the command will be carried out as soon as possible. Should you want to write to memory, that is all that you have to do.

To perform reads the value of the cmd_data_in cmd_byte_enable inputs are ignored. As soon as the data has been read it will be presented on data_out, and data_out_ready will be asserted for one cycle. This may happen many cycles after the command is issued (depending on when a refresh cycle is due), but sometime during the command execution CMD_READY will be asserted so you can queue up the next transaction.

 

Magnus

Share this post


Link to post
Share on other sites

Ok perfect!

Thanks!

 

One last thing: if i write in the memory and then i want to check that the writes have been execute correctely, is there any vhdl component that allows me to pick up the data and show them, for instance, on the computer monitor using putty (so serial transaction?).

 

I found some vhdl code that implements a serial comunication through the USB but nothing suites me and I have to adapt the code to my architecture and this takes a lot of time. I was wondering if there was a time-saving solution for testing purposes

 

leon

Share this post


Link to post
Share on other sites

Unfortunately, any such component is going to have to interface to the memory controller, to read from RAM, and perhaps share it with whatever component is doing the writing.  I agree, that's a pain, but it's unavoidable.

 

The one aspect you don't have to deal with is USB.  The Papilio boards have a USB-to-serial converter already built-in on a separate chip (the FTDI chip).  So what you need to find VHDL code for is serial communication, period.  You connect that to the correct two pins, to go to the FTDI chip; and you interface the code to your architecture; and that's that.

 

If there is, or could be, a soft-CPU somewhere involved in your design, that makes serial interfacing a bit easier, though it has its own complexities.  If not, have a look at http://opencores.org/project,uart2bus.  I haven't tried it, because I haven't needed it, but it provides a memory-oriented interface accessed over a serial line.  The fact that it's designed for 16 bits address and 8 bits data might be annoying, but not insurmountable.

 

A small CPU, using a few kilobytes of on-chip RAM, and with a serial interface and a memory interface, is the route I would take.  Start with a simple working system that's already been created, and which has a serial interface (I expect most of them do); then adding the "glue" logic to connect it with your design, and writing a program to talk over the serial port and perform whatever test actions you have in mind.  Best would be one that doesn't need to use the SDRAM itself, and can operate entirely from the on-chip BRAM.

 

http://forum.gadgetfactory.net/index.php?/topic/2511-papilio-pro-without-sdram/, a recent thread on this forum, might be of interest in that light.

Share this post


Link to post
Share on other sites

HI,

 

thanks for your answer!

 

I'll follow the path that you said and i'm planning of doing it entirely in VHDL without using any of the tools related with the papilio pro board. Do you think that using one of those can help me in speed up the design/test phase?

Share this post


Link to post
Share on other sites

By tools do you mean DesignLab?  I think it could go either way.  It gives you a lot of help, but also imposes its own way of doing things.  I was able to do plenty without it (until starting my current project, which depends on DesignLab for unavoidable and non-technical reasons).

 

Soft-CPU and SoC (system on a chip) designs, in Verilog and VHDL, are plentiful.

Share this post


Link to post
Share on other sites

Yes, I was talking about designLab. My sistem should be able to take data from OV7670 camera, store them in to the SDRAM and plot them on the monitor via VGA cable. In addition I need a component that works as a scope and prints on my pc monitor the value of data flowing in whichever bus I choose (I already implemented this component).

 

My question is if designLab can help me doing all this complex system or if it's better to design everything with simple VHDL.

 

 

Leon

Share this post


Link to post
Share on other sites

Hi all,

 

thanks to your suggestions I was able to read and write to/from the sdram. However i found a mistake inside the description of the sdram_controller by hamster: when i write a 32 bit word like x"44434241" which is DCBA and then I read it, i get BADC.

Reading Hamster's code i found the possible mistake which is the line :

 

if data_ready_delay(0) = '1' then
            data_out       <= captured_data & captured_data_last;
            data_out_ready <= '1';
         end if;
 
 
I swapped the two captured data and captured_data_last and I read correctly DCBA.
 
Has anyone encountered this error? Or it's me doing something wrong?
 
 
 
 
 
Leon

Share this post


Link to post
Share on other sites

Yup, i had to swap out captured_data & captured_data_last to get the right order of reads for me as well.

But right now, im running a microblaze MCS to the SDRAM controller, and im doing 3 consecutive writes for testing. But when i read. i only get the most recently written data, regardless of what address i put for the read function. Any idea what could be going wrong?

 

SDK CODE:

 

#include <xparameters.h>
#include <xiomodule.h>
#include <stdbool.h>

#define DELAY 2000000

/*---------------------------------------------------------------
 * data_in = GPO2
 * cmd_addr = GPO1
 * cmd_wr = GPO3(7)
 * cmd_enable = GPO3(6)
 * cmd_byte_enable = GPO3(5 downto 2)
 * cmd_ready = GPI1(7)
 * data_out_ready = GPI1(6)

 * data_out = GPI2
 *---------------------------------------------------------------*/


int main()
{
    //Instantiate IOs
    XIOModule gpi;
    XIOModule gpo;
    u8 flags;
    u32 flags2;
    //Initialize IOs
    XIOModule_Initialize(&gpi, XPAR_IOMODULE_0_DEVICE_ID);
    XIOModule_Start(&gpi);
    XIOModule_Initialize(&gpo, XPAR_IOMODULE_0_DEVICE_ID);
    XIOModule_Start(&gpo);

    XIOModule_DiscreteWrite(&gpo, 3, 0);

    while(1)
    {

        while((flags & 0x80) != 0x80) {flags = XIOModule_DiscreteRead(&gpi, 1);}; //loop until cmd_ready = '1';
        {
        XIOModule_DiscreteWrite(&gpo, 3, 0xFC);
        XIOModule_DiscreteWrite(&gpo, 1, 0b0000000000000100000000);
        XIOModule_DiscreteWrite(&gpo, 2, 0xAAAAFFFF);
        }


        while((flags & 0x80) != 0x80) {flags = XIOModule_DiscreteRead(&gpi, 1);};
        {XIOModule_DiscreteWrite(&gpo, 3, 0xFC);
        XIOModule_DiscreteWrite(&gpo, 1, 0b0000000000000100000010);
        XIOModule_DiscreteWrite(&gpo, 2, 0x1234ABCD);
        }


        while((flags & 0x80) != 0x80) {flags = XIOModule_DiscreteRead(&gpi, 1);};
        {
        XIOModule_DiscreteWrite(&gpo, 3, 0xFC);
        XIOModule_DiscreteWrite(&gpo, 1, 0b0000000000000100000100);
        XIOModule_DiscreteWrite(&gpo, 2, 0xA0A0F0F0);
        }

        for (u32 j=0; j<DELAY;++j);

        while((flags & 0x80) != 0x80) {flags = XIOModule_DiscreteRead(&gpi, 1);};
        {
            XIOModule_DiscreteWrite(&gpo, 1, 0b1000000000000100000000);
            XIOModule_DiscreteWrite(&gpo, 3, 0x40);

            flags2 = XIOModule_DiscreteRead(&gpi, 2);
            xil_printf("-|%x|-\t",flags2);
        }

        for (u32 j=0; j<DELAY;++j);


    };

}

Share this post


Link to post
Share on other sites

Hi,

 

tomorrow I will test the controller performing many consecutive writes and then reading what i've written. I'll tell u if i get any error

Share this post


Link to post
Share on other sites

Yup, i had to swap out captured_data & captured_data_last to get the right order of reads for me as well.

But right now, im running a microblaze MCS to the SDRAM controller, and im doing 3 consecutive writes for testing. But when i read. i only get the most recently written data, regardless of what address i put for the read function. Any idea what could be going wrong?

 

SDK CODE:

 

#include <xparameters.h>

#include <xiomodule.h>

#include <stdbool.h>

#define DELAY 2000000

/*---------------------------------------------------------------

 * data_in = GPO2

 * cmd_addr = GPO1

 * cmd_wr = GPO3(7)

 * cmd_enable = GPO3(6)

 * cmd_byte_enable = GPO3(5 downto 2)

 * cmd_ready = GPI1(7)

 * data_out_ready = GPI1(6)

 * data_out = GPI2

 *---------------------------------------------------------------*/

int main()

{

    //Instantiate IOs

    XIOModule gpi;

    XIOModule gpo;

    u8 flags;

    u32 flags2;

    //Initialize IOs

    XIOModule_Initialize(&gpi, XPAR_IOMODULE_0_DEVICE_ID);

    XIOModule_Start(&gpi);

    XIOModule_Initialize(&gpo, XPAR_IOMODULE_0_DEVICE_ID);

    XIOModule_Start(&gpo);

    XIOModule_DiscreteWrite(&gpo, 3, 0);

    while(1)

    {

        while((flags & 0x80) != 0x80) {flags = XIOModule_DiscreteRead(&gpi, 1);}; //loop until cmd_ready = '1';

        {

        XIOModule_DiscreteWrite(&gpo, 3, 0xFC);

        XIOModule_DiscreteWrite(&gpo, 1, 0b0000000000000100000000);

        XIOModule_DiscreteWrite(&gpo, 2, 0xAAAAFFFF);

        }

        while((flags & 0x80) != 0x80) {flags = XIOModule_DiscreteRead(&gpi, 1);};

        {XIOModule_DiscreteWrite(&gpo, 3, 0xFC);

        XIOModule_DiscreteWrite(&gpo, 1, 0b0000000000000100000010);

        XIOModule_DiscreteWrite(&gpo, 2, 0x1234ABCD);

        }

        while((flags & 0x80) != 0x80) {flags = XIOModule_DiscreteRead(&gpi, 1);};

        {

        XIOModule_DiscreteWrite(&gpo, 3, 0xFC);

        XIOModule_DiscreteWrite(&gpo, 1, 0b0000000000000100000100);

        XIOModule_DiscreteWrite(&gpo, 2, 0xA0A0F0F0);

        }

        for (u32 j=0; j<DELAY;++j);

        while((flags & 0x80) != 0x80) {flags = XIOModule_DiscreteRead(&gpi, 1);};

        {

            XIOModule_DiscreteWrite(&gpo, 1, 0b1000000000000100000000);

            XIOModule_DiscreteWrite(&gpo, 3, 0x40);

            flags2 = XIOModule_DiscreteRead(&gpi, 2);

            xil_printf("-|%x|-\t",flags2);

        }

        for (u32 j=0; j<DELAY;++j);

    };

}

 

I don't think you are dealing with data_out_ready correctly.  This signal is only high for one clock when the read data is available, so you have to capture the read data in a register using this signal as a strobe and at the same time set a flag that the processor can read.  The flag needs to be reset when the processor reads the captured data register.

 

Magnus

Share this post


Link to post
Share on other sites

Another problem in your code is where you are waiting for cmd_ready.  You need to unconditionally read the cmd_ready bit into the flag register before you start checking it.  In the current code once bit 7 in the flag register is set then it will never read the cmd_ready bit again since while((flags & 0x80) != 0x80) will always be false and the code block for the while instructions (i.e. the reading of the cmd_ready bit) will never be executed.

 

I think you need to do something like this:

 

while(1)
    {
        flags = XIOModule_DiscreteRead(&gpi, 1);
        while((flags & 0x80) != 0x80) {flags = XIOModule_DiscreteRead(&gpi, 1);}; //loop until cmd_ready = '1';
        {
        XIOModule_DiscreteWrite(&gpo, 3, 0xFC);
        XIOModule_DiscreteWrite(&gpo, 1, 0b0000000000000100000000);
        XIOModule_DiscreteWrite(&gpo, 2, 0xAAAAFFFF);
        }

        flags = XIOModule_DiscreteRead(&gpi, 1);
        while((flags & 0x80) != 0x80) {flags = XIOModule_DiscreteRead(&gpi, 1);};
        {XIOModule_DiscreteWrite(&gpo, 3, 0xFC);
        XIOModule_DiscreteWrite(&gpo, 1, 0b0000000000000100000010);
        XIOModule_DiscreteWrite(&gpo, 2, 0x1234ABCD);
        }

 

etc.

 

Magnus

Share this post


Link to post
Share on other sites

Hi,

 

i write and read a burst without problem! The best way is to put a register at the output of the controller to sample the data_output and using the data_output_ready as a load enable for the register

Share this post


Link to post
Share on other sites

Hi all,

 

Thanks for your inputs! The problem was very silly. I was changing the read address BEFORE giving the read command. Once i set the read address after the read command, the problem was solved. I'm tempted to backtrack the problem and see how the address is handled in the state machine for the SDRAM controller, but im too lazy to do so :P

Share this post


Link to post
Share on other sites

Ok, I got a problem. I'm still testing the SDRAM and I got the following error:

 

I write the following 32-bit words:

 

"DCBA" in addr 54

"HGFE" in addr 1024

"LKJI"   in addr 1048576

"PONM" in addr 2097151

 

When I try to read from addr 54 I get "POBA"; if i read from addr 1024 i get "POFE".

 

So it seems like that the most significant 16-bit word remains stuck at the last writing whereas the least significant 16-bit word is read correctely.

 

I didn't change hamster's code (except for the swap of "captured data" that i mentioned few posts before), so where do u think is the error?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now