TM-4 DDR Controller

Table of Contents

    Memory organization and operation
    Configuring your project
    Configuring the controller
    Instantiating the controller
    Controller signal assignments


    The DDR controller module provides a multi-ported interface to the TM4's DDR SDRAM. It allows burst reads and burst writes of 8-1024 144-bit words at 133MHz.

Memory organization and operation

    The DDR controller organizes the external DDR memory into 131072 rows of 1024 words, where each word is 144 bits wide. However, the controller is not designed to access single words, and instead only allows reading/writing in multiples of 8 word blocks.

    The interface to the controller is transaction-based. The user requests that the controller perform a read or write, and the controller acknowledges the transaction upon completion. Actual data transfer between the controller and the user circuit occurs in the time after the request and before the acknowledgement.

    A transaction is defined as a DDR read or write of anywhere from a single 8-word block up to an entire DDR row (128 blocks). Transactions can not cross DDR row boundaries.


    Download this .zip file and extract it to your project directory. It should contain the following files:


Configuring your project

    The .qsf file included with the jddr controller contains important timing assignments that are vital for the controller's proper operation. Rename the .qsf file so that it matches your top-level module name.

    Example: If your top-level design is in top1.vhd then rename the .qsf file to top1.qsf

Configuring the controller

    In order to make the DDR controller work with your project, the controller itself must be configured to the clock frequency of your circuit.

    1) Open Quartus II
    2) Go to: Tools-->MegaWizard Plug-In Manager
    3) Select "Edit an existing custom megafunction variation" and click Next
    4) Open jddr_pll.vhd
    5) Under "What is the frequency of the inclock0 input?", enter the frequency of the TM4 global clock your project will be using, and click Finish
    6) Click Finish and exit Quartus II


    To use the DDR controller, the necessary DDR interface pins must be declared in your top-level module:

    tm4_ddr_DQA : inout std_logic_vector(71 downto 0);
    tm4_ddr_DQSA : inout std_logic_vector(8 downto 0);
    tm4_ddr_DMA : out std_logic_vector(8 downto 0);
    tm4_ddr_clkA : out std_logic_vector(2 downto 0);
    tm4_ddr_AA : out std_logic_vector(13 downto 0);
    tm4_ddr_RASnA : out std_logic;
    tm4_ddr_CASnA : out std_logic;
    tm4_ddr_WEnA : out std_logic;
    tm4_ddr_SnA : out std_logic_vector(1 downto 0);
    tm4_ddr_BAA : out std_logic_vector(1 downto 0);
    tm4_ddr_CLKEA : out std_logic_vector(1 downto 0);
    tm4_ddr_RESETnA : out std_logic;
    tm4_dqsclkA_in : in std_logic;
    tm4_dqsclkA_out : out std_logic;

    If you wish to use two DDR interfaces on a single FPGA, there is a second set of pins that must also be declared:

    tm4_ddr_DQB : inout std_logic_vector(71 downto 0);
    tm4_ddr_DQSB : inout std_logic_vector(8 downto 0);
    tm4_ddr_DMB : out std_logic_vector(8 downto 0);
    tm4_ddr_clkB : out std_logic_vector(2 downto 0);
    tm4_ddr_AB : out std_logic_vector(13 downto 0);
    tm4_ddr_RASnB : out std_logic;
    tm4_ddr_CASnB : out std_logic;
    tm4_ddr_WEnB : out std_logic;
    tm4_ddr_SnB : out std_logic_vector(1 downto 0);
    tm4_ddr_BAB : out std_logic_vector(1 downto 0);
    tm4_ddr_CLKEB : out std_logic_vector(1 downto 0);
    tm4_ddr_RESETnB : out std_logic;
    tm4_dqsclkB_in : in std_logic;
    tm4_dqsclkB_out : out std_logic;

Instantiating the controller

    To make it more convenient to use the controller, the VHDL component declaration has been placed in jddr_pkg.vhd. If you are using VHDL rather than Verilog, add these lines to the top of your project's .vhd file:

    library work;
    use work.jddr_pkg.all;

    To instantiate the controller, create one or two instances of the "jddr" component, depending on whether you'll be using one or both DDR interfaces on an FPGA.

    The instance names must be "jddr_inst0" and "jddr_inst1" for the first and second controllers respectively. This is necessary for the timing settings in the .qsf file to be assigned properly. A description of the signals that need to be assigned is in the next section.

Controller signal assignments

    This section details all the signals that go in and out of the jddr module and how to use them in your circuit.

    Here is the jddc component declaration from jddr_pkg.vhd repeated for convenience:

    component jddr
      NUMPORTS : natural := 2;
      EXTRA_WRITE_LATENCY : natural := 0
      -- Connect to a global clock (0 or 1)
      tm4_glbclk : in std_logic;
      tm4_devbus : inout std_logic_vector(40 downto 0);

      -- PortN request signals @ individual user clocks
      clock : in std_logic_vector(NUMPORTS-1 downto 0);
      transrequest : in std_logic_vector(NUMPORTS-1 downto 0);
      transcomplete : out std_logic_vector(NUMPORTS-1 downto 0);
      ddr_address : in std_logic_vector(17*NUMPORTS-1 downto 0);
      ddr_start_block : in std_logic_vector(7*NUMPORTS-1 downto 0);
      ddr_end_block : in std_logic_vector(7*NUMPORTS-1 downto 0);
      readwriten : in std_logic_vector(NUMPORTS-1 downto 0);

      -- PortN data signals @ 133mhz data_clk clock
      data_clk : out std_logic;
      read_data : out std_logic_vector(143 downto 0);
      write_data : in std_logic_vector(144*NUMPORTS-1 downto 0);
      data_available : out std_logic_vector(NUMPORTS-1 downto 0);
      data_wanted : out std_logic_vector(NUMPORTS-1 downto 0);

      -- DDR Module Interface Pin Signals
      tm4_ddr_DQ : inout std_logic_vector(71 downto 0);
      tm4_ddr_DQS : inout std_logic_vector(8 downto 0);
      tm4_ddr_DM : out std_logic_vector(8 downto 0);
      tm4_ddr_clk : out std_logic_vector(2 downto 0);
      tm4_ddr_A : out std_logic_vector(13 downto 0);
      tm4_ddr_RASn : out std_logic;
      tm4_ddr_CASn : out std_logic;
      tm4_ddr_WEn : out std_logic;
      tm4_ddr_Sn : out std_logic_vector(1 downto 0);
      tm4_ddr_BA : out std_logic_vector(1 downto 0);
      tm4_ddr_CLKE : out std_logic_vector(1 downto 0);
      tm4_ddr_RESETn : out std_logic;
      tm4_dqsclk_in : in std_logic;
      tm4_dqsclk_out : out std_logic
    end component;

    Notice that some signals have widths dependent on the number of configured ports. This is how multi-portedness is implemented in the controller. Each port is allocated to a different range of each of these bus signals. For example: clock[1] and ddr_address[33:17] are the clock and ddr_address signals for port #1, while clock[0] and ddr_address[16:0] are the same signals but for port #0.

    Global Signals

      Name Mode Width Description
      NUMPORTSgenericnatural The number of ports provided by the controller. The default value of 2 makes the memory dual-ported.
      EXTRA_WRITE_LATENCYgenericnatural The number of extra cycles to wait when writing to DDR memory. See the descriptions of data_wanted and write_data for more information.
      tm4_glbclkin1 Connect this to one of the TM4's global clocks, tm4_glbclk0 or tm4_glbclk1.
      tm4_devbusinout40 Connect this to the TM4's development bus, which at the top level is also called tm4_devbus.

    Control Signals

      The following signals are synchronized to the rising edge of clock(N), where N is a number from 0 to NUMPORTS - 1. This allows each port to have its own group of control signals synchronized to its own clock.

      Name Mode Width Description
      clockin1 The user-provided clock that the rest of the signals will be synchronized to.
      transrequestin1 Assert this signal high to ask the controller to perform a burst read/write to DDR SDRAM. This signal must be kept high until transcomplete goes high. This signal should then be lowered to complete the handshake.
      transcompleteout1 This signal is asserted high by the controller to indicate that the requested read/write has completed. It stays high until transrequest has been lowered.
      readwritenin1 Set by the user to 0 to write to DDR memory or 1 to perform a read from DDR memory.
      ddr_addressin17 The address of the DDR row to access.
      ddr_start_blockin7 The starting 8-word block within the DDR row.
      ddr_start_blockin7 The ending 8-word block within the DDR row.

      ddr_start_block and ddr_end_block define a range within the DDR row that will be read from or written to. The minimum size of such a range is a single block. ddr_end_block should be greater than or equal to ddr_start_block.

    Data Signals

      The following signals are synchronized to the rising edge of data_clk:

      Name Mode Width Description
      data_clkout1 A 133MHz clock provided by the DDR controller, which the rest of the data signals will be synchronized to.
      read_dataout144 Contains data read from DDR memory.
      data_availableout1 Stays high as long as read_data is valid.
      write_datain144 Data to be written to DDR memory.
      data_wantedout1 This signal is high (1 + EXTRA_WRITE_LATENCY) cycles before the controller needs write_data to be valid.

      data_wanted or data_available are asserted during a transaction and stay asserted until the requested amount of data words have been transferred.

    DDR Interface Signals

      tm4_ddr_* Connect these pins directly to the top-level DDR pins with corresponding names.


    Reading from DDR SDRAM

    1. Set ddr_address, ddr_start_block, ddr_end_block to specify where in memory to read from and how many words to read, set readwriten to '1', and set transrequest to '1'. Keep these signals stable and at these values.
    2. When data_available becomes '1', capture the read data from the read_data signal on the same clock cycle. Continue capturing until data_available becomes '0'.
    3. When transcomplete is '1', set transrequest to '0'.
    4. Once transcomplete is '0', another read or write transaction can be made.

    Writing to DDR SDRAM

    1. Set ddr_address, ddr_start_block, ddr_end_block to specify where in memory to read from and how many words to read, set readwriten to '0', and set transrequest to '1'. Keep these signals stable and at these values.
    2. When data_wanted becomes '1', place the first word of data on the write_data signal during the NEXT clock cycle (plus EXTRA_WRITE_LATENCY clock cycles, if that parameter was configured). Continue providing data for as many cycles as data_wanted is high.
    3. When transcomplete is '1', set transrequest to '0'.
    4. Once transcomplete is '0', another read or write transaction can be made.