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:
jddr.vhdThe .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
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 IITo 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;
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.
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
generic
(
NUMPORTS : natural := 2;
EXTRA_WRITE_LATENCY : natural := 0
);
port
(
-- 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.
Name | Mode | Width | Description |
NUMPORTS | generic | natural | The number of ports provided by the controller. The default value of 2 makes the memory dual-ported. |
EXTRA_WRITE_LATENCY | generic | natural | 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_glbclk | in | 1 | Connect this to one of the TM4's global clocks, tm4_glbclk0 or tm4_glbclk1. |
tm4_devbus | inout | 40 | Connect this to the TM4's development bus, which at the top level is also called tm4_devbus. |
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 |
clock | in | 1 | The user-provided clock that the rest of the signals will be synchronized to. |
transrequest | in | 1 | 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. |
transcomplete | out | 1 | 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. |
readwriten | in | 1 | Set by the user to 0 to write to DDR memory or 1 to perform a read from DDR memory. | ddr_address | in | 17 | The address of the DDR row to access. |
ddr_start_block | in | 7 | The starting 8-word block within the DDR row. |
ddr_start_block | in | 7 | 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.
The following signals are synchronized to the rising edge of data_clk:
Name | Mode | Width | Description |
data_clk | out | 1 | A 133MHz clock provided by the DDR controller, which the rest of the data signals will be synchronized to. |
read_data | out | 144 | Contains data read from DDR memory. |
data_available | out | 1 | Stays high as long as read_data is valid. |
write_data | in | 144 | Data to be written to DDR memory. |
data_wanted | out | 1 | 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.
tm4_ddr_* | Connect these pins directly to the top-level DDR pins with corresponding names. |