4
\$\begingroup\$

I am using the Spartan SP601 Evaluation Board, which includes 1 GB Elpida EDE1116ACBG-8E-E SDRAM. I would like to build a RAM controller, but have no experience in working with RAM before. I largely based my design on a RAM controller in a textbook for Spartan-3 devices. I provided my design below in case it is of help. The control signals for the SDRAM are cas_b,ras_b, and we_b.

I have the following questions:

  • Is there any way to debug my SDRAM controller? It seems to me like it either works or it doesn't work, and I can't really tell what interactions are going on between the controller and the SDRAM.

  • What are the most relevant timing parameters that I should look at for my SDRAM? The number of timing parameters listed on the datasheet for the Elpida is overwhelming to look at.

  • Do you have any other tips for developing a working SDRAM controller? Is my design below a good start?

Basic SDRAM Testing Circuit

library IEEE;
library UNISIM;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use UNISIM.vcomponents.all;
entity ram_ctrl_test is
    Port ( reset : in STD_LOGIC;
--         clk : in  STD_LOGIC;
           I, IB : in STD_LOGIC;
           btn : in  STD_LOGIC_VECTOR (2 downto 0);
           rx : in STD_LOGIC;
           tx : out STD_LOGIC;
           led : out  STD_LOGIC_VECTOR (7 downto 0);
           ad : out  STD_LOGIC_VECTOR (12 downto 0);
           dio_a : inout STD_LOGIC_VECTOR (15 downto 0);
           bank : out STD_LOGIC_VECTOR (2 downto 0);
           cke : out  STD_LOGIC;
           cas_b, ras_b, we_b : out  STD_LOGIC);
end ram_ctrl_test;

architecture arch of ram_ctrl_test is
    constant ADDR_W: integer := 13;
    constant DATA_W: integer := 16;
    signal clk, O: std_logic;
    signal addr: std_logic_vector(ADDR_W-1 downto 0);
    signal sw : std_logic_vector(7 downto 0);
    signal data_f2s, data_s2f: std_logic_vector(DATA_W-1 downto 0);
    signal mem, rw: std_logic;
    signal data_reg: std_logic_vector(7 downto 0);
    signal db_btn: std_logic_vector(2 downto 0);
    signal rx_empty, update: std_logic;
    signal in_data, out_data: std_logic_vector(7 downto 0);
begin
    IBUFGDS_inst : IBUFGDS
    generic map (
        DIFF_TERM => FALSE, -- Differential Termination 
        IBUF_LOW_PWR => FALSE, -- Low power (TRUE) vs. performance (FALSE) setting for referenced I/O standards
        IOSTANDARD => "DEFAULT")
    port map (
        O => O,  -- Clock buffer output
        I => I,  -- Diff_p clock buffer input (connect directly to top-level port)
        IB => IB -- Diff_n clock buffer input (connect directly to top-level port)
    );
    BUFG_inst : BUFG
    port map (
        O => clk, -- 1-bit output: Clock buffer output
        I => O  -- 1-bit input: Clock buffer input
    );
    ctrl_unit: entity work.sram_ctrl
        port map(clk=>clk,reset=>reset,mem=>mem,rw=>rw,addr=>addr,data_f2s=>data_f2s,ready=>open,data_s2f_r=>data_s2f,
        data_s2f_ur=>open,ad=>ad,dio_a=>dio_a,bank=>bank,cke=>cke,cas_b=>cas_b,ras_b=>ras_b,we_b=>we_b);
    debounce_unit0: entity work.debounce
        port map(clk=>clk,reset=>reset,sw=>btn(0),db_level=>open,db_tick=>db_btn(0));
    debounce_unit1: entity work.debounce
        port map(clk=>clk,reset=>reset,sw=>btn(1),db_level=>open,db_tick=>db_btn(1));
    debounce_unit2: entity work.debounce
        port map(clk=>clk,reset=>reset,sw=>btn(2),db_level=>open,db_tick=>db_btn(2));
    uart: entity work.uart(str_arch)
        port map(clk=>clk,reset=>reset,rx=>rx,rd_uart=>update,wr_uart=>update,w_data=>out_data,tx_full=>open,
        rx_empty=>rx_empty,r_data=>in_data,tx=>tx);
    enabler: entity work.enable(fsm_arch)
        port map(clk=>clk,reset=>reset,rx_empty=>rx_empty,en=>update);
    address: entity work.addr_controller(fsm_arch)
        port map(clk=>clk,reset=>reset,update=>update,in_data=>in_data,out_data=>out_data,sw=>sw);
    -- data registers
    process(clk)
    begin
        if rising_edge(clk) then
            if (db_btn(0) = '1') then
                data_reg <= sw;
            end if;
        end if;
    end process;
    -- address
    addr <= "00000" & sw;
    -- command
    process(db_btn,data_reg)
    begin
        data_f2s <= (others => '0');
        if db_btn(1) = '1' then -- write
            mem <= '1';
            rw <= '0';
            data_f2s <= "00000000" & data_reg;
        elsif db_btn(2) = '1' then -- read
            mem <= '1';
            rw <= '1';
        else
            mem <= '0';
            rw <= '1';
        end if;
    end process;
    -- output
    led <= data_s2f(7 downto 0);
end arch;

RAM Controller

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity sram_ctrl is
    Port ( clk, reset : in  STD_LOGIC;
              -- to/from main system
           mem : in  STD_LOGIC;
           rw : in STD_LOGIC;
           addr : in  STD_LOGIC_VECTOR (12 downto 0);
           data_f2s : in  STD_LOGIC_VECTOR (15 downto 0);
           ready : out  STD_LOGIC;
           data_s2f_r, data_s2f_ur : out  STD_LOGIC_VECTOR (15 downto 0);
              -- to/from chip
           ad : out  STD_LOGIC_VECTOR (12 downto 0);
           bank : out STD_LOGIC_VECTOR (2 downto 0);
           cke : out  STD_LOGIC;
           dio_a : inout  STD_LOGIC_VECTOR (15 downto 0);
           cas_b, ras_b, we_b : out  STD_LOGIC);
end sram_ctrl;

architecture arch of sram_ctrl is
    type state_type is (idle, rd1, rd2, wr1, wr2);
    signal state_reg, state_next: state_type;
    signal data_f2s_reg, data_f2s_next: std_logic_vector(15 downto 0);
    signal data_s2f_reg, data_s2f_next: std_logic_vector(15 downto 0);
    signal addr_reg, addr_next: std_logic_vector(12 downto 0);
    signal cas_buf, ras_buf, we_buf, tri_buf: std_logic;
    signal cas_reg, ras_reg, we_reg, tri_reg: std_logic;
begin
    -- state & data registers
    process(clk,reset)
    begin
        if (reset = '1') then
            state_reg <= idle;
            addr_reg <= (others => '0');
            data_f2s_reg <= (others => '0');
            data_s2f_reg <= (others => '0');
            cas_reg <= '1';
            ras_reg <= '1';
            we_reg <= '1';
            tri_reg <= '1';
        elsif rising_edge(clk) then
            state_reg <= state_next;
            addr_reg <= addr_next;
            data_f2s_reg <= data_f2s_next;
            data_s2f_reg <= data_s2f_next;
            cas_reg <= cas_buf;
            ras_reg <= ras_buf;
            we_reg <= we_buf;
            tri_reg <= tri_buf;
        end if;
    end process;
    -- next-state logic
    process(state_reg, mem, rw, dio_a, addr, data_f2s, data_f2s_reg, data_s2f_reg, addr_reg)
    begin
        addr_next <= addr_reg;
        data_f2s_next <= data_f2s_reg;
        data_s2f_next <= data_s2f_reg;
        ready <= '0';
        case state_reg is
            when idle =>
                if mem = '0' then
                    state_next <= idle;
                else
                    addr_next <= addr;
                    if rw='0' then --write
                        state_next <= wr1;
                        data_f2s_next <= data_f2s;
                    else --read
                        state_next <= rd1;
                    end if;
                end if;
                ready <= '1';
            when wr1 =>
                state_next <= wr2;
            when wr2 =>
                state_next <= idle;
            when rd1 =>
                state_next <= rd2;
            when rd2 =>
                data_s2f_next <= dio_a;
                state_next <= idle;
        end case;
    end process;
    -- "look-ahead" output logic
    process(state_next)
    begin
        cas_buf <= '1'; --default
        ras_buf <= '1';
        we_buf <= '1';
        tri_buf <= '1';
        case state_next is
            when idle =>
            when wr1 =>
                tri_buf <= '0';
                cas_buf <= '0';
                we_buf <= '0';
            when wr2 =>
                tri_buf <= '0';
            when rd1 =>
                cas_buf <= '0';
            when rd2 =>
                cas_buf <= '0';
        end case;
    end process;
    -- to main system
    data_s2f_r <= data_s2f_reg;
    data_s2f_ur <= dio_a;
    -- to SRAM
    cas_b <= cas_reg;
    ras_b <= ras_reg;
    we_b <= we_reg;
    ad <= addr_reg;
    bank <= "001";
    -- i/o for SRAM chip a
    cke <= '0';
    dio_a <= data_f2s_reg when tri_reg = '0' else (others => 'Z');
end arch;
\$\endgroup\$
6
  • \$\begingroup\$ SRAM doesn't generally have CAS, RAS signals and 1GB is rather a lot of SRAM. Do you mean DRAM or SDRAM? \$\endgroup\$ – user_1818839 Oct 6 '15 at 20:26
  • \$\begingroup\$ Oops, it's SDRAM. I've edited my question. \$\endgroup\$ – Eugene Wu Oct 6 '15 at 20:29
  • \$\begingroup\$ I would recommend locating a functional HDL model of the SDRAM chip that you are using so you can test properly in a testbench. \$\endgroup\$ – alex.forencich Oct 6 '15 at 20:37
  • 1
    \$\begingroup\$ You will find an archive here pudn.com/downloads37/sourcecode/embed/detail122819.html of the old Micron SDRAM simulation model, both VHDL and Verilog versions, which Microm deleted from their own website several years ago. \$\endgroup\$ – user_1818839 Oct 6 '15 at 20:37
  • 1
    \$\begingroup\$ Definitely simulate. Two observations on the code itself : (1) single process state machine is much easier to get right than the 3-process form, simpler and less code (Some people prefer the 2 or 3 process form nevertheless). (2) State machine also needs to deal with the rather complex SDRAM initialisation sequence, as well as being able to handle refresh sequences (with auto precharge if necessary) when the refresh timer commands it. \$\endgroup\$ – user_1818839 Oct 6 '15 at 20:52
4
\$\begingroup\$

It looks like you're doing most of your development on the actual hardware. In some cases it makes sense to do this, but this is not really one of those cases as complete functional models should be available for the SDRAM chips. I would highly recommend locating a functional model for either the specific chip you're using or a very similar one and doing all of your debugging in the testbench. From the testbench, it should be obvious what is going on with the interface and you should be able to look at any internal signal you want while the simulation is running, something which is no so easy when your design is running on the FPGA.

\$\endgroup\$
2
  • \$\begingroup\$ Most functional models (e.g. Micron) are available only in Verilog these days. There is a Verilog model available of the Micron equivalent. \$\endgroup\$ – user8352 Oct 6 '15 at 22:12
  • 1
    \$\begingroup\$ That's interesting. I think it may still be workable even if a VHDL model is not available as a number of current toolchains support mixed projects of Verilog and VHDL. \$\endgroup\$ – alex.forencich Oct 6 '15 at 22:14
2
\$\begingroup\$

Another option to a functional model of the RAM would be to use the on-chip logic analyzer from Xilinx. This way you can analyze the real signal values during "execution" of your design in real-time. For example, you can observe the outputs from your design to the RAM and the inputs from the RAM and compare this with the intended behavior.

In the ISE toolchain it is called Chipscope. A step by step intrudction can be found in this Xilinx document: ISE Tutorial: Using Xilinx ChipScope Pro ILA Core with Project Navigator to Debug FPGA Applications

Vivado also includes a on-chip logic analyzer. Below the design step "Open synthesized design" you will find an entry "Set up Debug". Selecting this opens the synthesized design view and starts a wizard to configure the on-chip logic analyzer. The first wizard window includes a link to the Vivado Design Suite User Guide: Programming and Debugging (UG908)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.