SDRAM Memory Controller

From Hamsterworks Wiki!

Jump to: navigation, search

This is my design for a memory controller for my Terasic DE0-nano FPGA board, which uses the ISSI IS42S16160B-7 32MB SDRAM chip

Update - AlvieBoy has taken this disign, and is using it as part of his ZPUino project - you can find a debugged version at https://github.com/alvieboy/ZPUino-HDL/blob/dcache/zpu/hdl/zpuino/boards/papilio-pro/S6LX9/sdram_hamster.vhd, and a Wishbone wrapper at https://github.com/alvieboy/ZPUino-HDL/blob/dcache/zpu/hdl/zpuino/boards/papilio-pro/S6LX9/sdram_wrap.vhd

I've completed a new, tested, fully working controller - have a look at Simple_SDRAM_Controller

Although aimed at 100MHz all of the designs below can be adapted to other clock speeds. The only changes needed are to increase the number of NOPs in the refresh chain to ensure that it takes at least 70ns.

Adapting to a CAS setting of 2 is only a little bit more difficult, as the data is available one cycle earlier. A CAS of 2 can only be used with a clock speed of 100MHz, and will make the biggest difference with the simple FSM where it saves a cycle on every read, or in the most complex where it saves a cycle flipping between reads and writes.

The priority should be first to perform any pending refresh, but priority of performing reads over writes depends on your target application. For example if you are generating a VGA video signal reads should take priority over writes otherwise "tearing" of the picture could occur.

I might not be completely following accepted conventions, but the idea behind the directed graphs in this context are that as you follow the arrows from node to node you will always generate a valid set of commands for the SDRAM. For each clock tick you must follow an arrow, so at 100MHz it takes 10ns to go from node to node, allowing you to partially verify the design on paper.

Contents

FSM1 - Simple controller

Based on the information above, here is the design for a simple FSM to access the SDRAM with a burst length of 4 at 100MHz, CAS = 3:

Memctl fsm1.png

(The blue circles indicate where data is transferred to/from the SDRAM)

Performance:

  • Read is 11 cycles for four words = 72MB/s @ 100Hz, excluding refresh overhead
  • Write is 11 cycles for four words = 72MB/s @ 100Hz, excluding refresh overhead
  • Refresh is 8 cycles.

Pros:

  • Simple to implement
  • Fast logic - only two nodes has multiple exits, and the choice is simple.
  • Predictable performance.

Cons:

  • Poor performance

This can be slightly improved on with a few little changes. These focus around skipping the idle stage where possible.

FSM2 - Optimised simple controller

Memctl fsm2.png

(The blue circles indicate where data is transferred to/from the SDRAM)

Performance:

  • Read is 10 cycles for four words = 80MB/s @ 143Hz, excluding refresh overhead
  • Write is 10 cycles for four words = 80MB/s @ 143Hz, excluding refresh overhead
  • Refresh is 7 cycles.

Pros:

  • Simple to implement
  • Fast logic - only three nodes has multiple exits, and the choice at these nodes is simple.
  • Predictable performance.

Cons:

  • Poor performance

Further improvements can be made by not activating and precharging the row every time.

FSM3 - With back-to-back reads or back-to-back writes

Memctl fsm3.png

(The blue circles indicate where data is transferred to/from the SDRAM)

Performance:

  • Single read is 10 cycles for four words = 80MB/s @ 100Hz, excluding refresh overhead. For back to back reads this gets close 200MB/s
  • Single write is 10 cycles for four words = 80MB/s @ 100Hz, excluding refresh overhead. For back to back writes this gets close 200MB/s
  • For mixed read/write workloads performance can be as low as 72MB/s
  • Refresh is 7 cycles.

Pros:

  • Much improved performance for back-to-back operations (as long as you don't mix reads and writes.
  • You can choose to allow back-to-back operations in only the write or read sections, optimising for the applicaiton

Cons:

  • Logic is starting to get complex (and slow).
  • Unpredictable latency.

I can see this being a good choice for something like my Mandelbrot project, where writes are a few MB/s and are greatly outnumbered by the reads required to service the VGA controller (which can be 75MB/s or more!).

One major issue with this design is that it is possible to get stuck in a loop in either the 'read' or 'write' operations. In the unlikely case that this occurs there is the chance that refresh operations will not performed as needed. The easy solution would be to not perform back-to-back writes if a refresh operation is pending.

To improve on this we have to start mixing the read and write operations, as long as they are on the same row. This is where things get complex!

FSM4 - With mixed back-to-back reads and writes

Memctl fsm4.png

(The blue circles indicate where data is transferred to/from the SDRAM)

Performance:

  • Single read is 10 cycles for four words = 80MB/s @ 100Hz, excluding refresh overhead. For back to back reads this gets close 200MB/s
  • Single write is 10 cycles for four words = 80MB/s @ 100Hz, excluding refresh overhead. For back to back writes this gets close 200MB/s
  • For mixed read/write workloads performance can be upto 145MB/s
  • Refresh is 7 cycles.

Pros:

  • Much improved performance for back-to-back operations, including mixed reads and writes.

Cons:

  • Logic is getting complex (and slow).
  • Unpredictable latency
  • Only back-to-back operations are improved, if the requests to the same rwo are separated by a few clock cycles the row gets precharged and opened again.

One other issue with this design is that it is possible to get stuck in a loop in either the 'read' or 'write' operations. In the unlikely case that this occurs there is the chance that refresh operations will not performed as needed. The easy solution is to not perform back-to-back operations if a refresh operation is pending.

Further improvements - FSM5

The FSM4 design can also be improved on. It involves having an "idle row activated" state, which would reduce latency for operations that are interspersed with a few idle cycles - down from 10 cycles to 7 for reads, and down from 7 cycles to 4 four for writes. These are pretty big improvements.

As it involves a lot more complexity than the above designs, so the diagram looks completely different:

Memctl fsm5.png

(Blue circles are data transfers from the SDRAM, red circles are data transfers to the SDRAM)

Pros:

  • Nearly a full featured design, everything but the ability to abort burst transfers is catered for

Cons:

  • Very complex to code and test.
  • Complexity may reduce speed.
  • Large number of states to understand and manage.
  • Design in some cases is slower simpler design. for example from and idle activerow to a completed read in a different row is takes three cycles longer..

As long as priority is given to getting back to the idle state when a refresh is pending this seems to be close to optimal design.

Source code

This is the source code for the FSM.

------------------------------------------------------
-- FSM for a SDRAM controller
--
-- Version 0.1 - Ready to simulate
--
-- Author: Mike Field (hamster@snap.net.nz)
--
-- Feel free to use it however you would like, but
-- just drop me an email to say thanks.
-------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity sdram_controller is
   PORT (
   CLOCK_50      : IN STD_LOGIC;
   
   -- Signals to/from the SDRAM chip
   DRAM_ADDR   : OUT   STD_LOGIC_VECTOR (12 downto 0);
   DRAM_BA      : OUT   STD_LOGIC_VECTOR (1 downto 0);
   DRAM_CAS_N   : OUT   STD_LOGIC;
   DRAM_CKE      : OUT   STD_LOGIC;
   DRAM_CLK      : OUT   STD_LOGIC;
   DRAM_CS_N   : OUT   STD_LOGIC;
   DRAM_DQ      : INOUT STD_LOGIC_VECTOR(15 downto 0);
   DRAM_DQM      : OUT   STD_LOGIC_VECTOR(1 downto 0);
   DRAM_RAS_N   : OUT   STD_LOGIC;
   DRAM_WE_N    : OUT   STD_LOGIC;
   
   --- Inputs from rest of the system
   address      : IN     STD_LOGIC_VECTOR (23 downto 0);
   req_read      : IN     STD_LOGIC;
   req_write   : IN     STD_LOGIC;
   data_out      : OUT     STD_LOGIC_VECTOR (31 downto 0);
   data_out_valid : OUT     STD_LOGIC;
   data_in      : IN     STD_LOGIC_VECTOR (31 downto 0)
   );
end entity;
   
   
architecture rtl of sdram_controller is

   
   type reg is record
      state       : std_logic_vector(8 downto 0);

      address     : std_logic_vector(12 downto 0);
      bank         : std_logic_vector( 1 downto 0);

      init_counter: std_logic_vector(14 downto 0);
      rf_counter   : std_logic_vector( 9 downto 0);
      rf_pending    : std_logic;

      rd_pending    : std_logic;
      wr_pending    : std_logic;
      act_row       : std_logic_vector(12 downto 0);

      data_out_low: std_logic_vector(15 downto 0);
      data_out_valid : std_logic;

      dq_masks      : std_logic_vector(1 downto 0);
   end record;
   component sdram_clk_gen
   PORT
   (
      inclk0: IN  STD_LOGIC;
      c0      : OUT STD_LOGIC;
      c1      : OUT STD_LOGIC
   );
   end component;

   -- note to self - this constant should be "(others => '0')" when not simulating!!!
   signal r : reg := ((others => '0'), (others => '0'), 
                      (others => '0'), "000000000001000", (others => '0'), 
                      '0', '0', '0', (others => '0'), (others => '0'), '0', (others => '0'));
   signal n : reg;
   
   -- Vectors for each SDRAM 'command'
   --- CS_N, RAS_N, CAS_N, WE_N 
   constant cmd_nop   : std_logic_vector(3 downto 0) := "0111";
   constant cmd_read  : std_logic_vector(3 downto 0) := "0101";   -- Must be sure A10 is low.
   constant cmd_write : std_logic_vector(3 downto 0) := "0100";
   constant cmd_act   : std_logic_vector(3 downto 0) := "0011";
   constant cmd_pre   : std_logic_vector(3 downto 0) := "0010";  -- Must set A10 to '1'.
   constant cmd_ref   : std_logic_vector(3 downto 0) := "0001";
   constant cmd_mrs     : std_logic_vector(3 downto 0) := "0000"; -- Mode register set
   -- State assignments
   constant s_init_nop  : std_logic_vector(8 downto 0) := "00000" & cmd_nop;
   constant s_init_pre  : std_logic_vector(8 downto 0) := "00000" & cmd_pre;
   constant s_init_ref  : std_logic_vector(8 downto 0) := "00000" & cmd_ref;
   constant s_init_mrs  : std_logic_vector(8 downto 0) := "00000" & cmd_mrs;
   
   constant s_idle  : std_logic_vector(8 downto 0) := "00001" & cmd_nop;
   
   constant s_rf0   : std_logic_vector(8 downto 0) := "00010" & cmd_ref;
   constant s_rf1   : std_logic_vector(8 downto 0) := "00011" & cmd_nop;
   constant s_rf2   : std_logic_vector(8 downto 0) := "00100" & cmd_nop;
   constant s_rf3   : std_logic_vector(8 downto 0) := "00101" & cmd_nop;
   constant s_rf4   : std_logic_vector(8 downto 0) := "00110" & cmd_nop;
   constant s_rf5   : std_logic_vector(8 downto 0) := "00111" & cmd_nop;

   constant s_ra0   : std_logic_vector(8 downto 0) := "01000" & cmd_act;
   constant s_ra1   : std_logic_vector(8 downto 0) := "01001" & cmd_nop;
   constant s_ra2   : std_logic_vector(8 downto 0) := "01010" & cmd_nop;

   constant s_dr0   : std_logic_vector(8 downto 0) := "01011" & cmd_pre;
   constant s_dr1   : std_logic_vector(8 downto 0) := "01100" & cmd_nop;

   constant s_wr0   : std_logic_vector(8 downto 0) := "01101" & cmd_write;
   constant s_wr1   : std_logic_vector(8 downto 0) := "01110" & cmd_nop;
   constant s_wr2   : std_logic_vector(8 downto 0) := "01111" & cmd_nop;
   constant s_wr3   : std_logic_vector(8 downto 0) := "10000" & cmd_nop;

   constant s_rd0   : std_logic_vector(8 downto 0) := "10001" & cmd_read;
   constant s_rd1   : std_logic_vector(8 downto 0) := "10010" & cmd_nop;
   constant s_rd2   : std_logic_vector(8 downto 0) := "10011" & cmd_nop;
   constant s_rd3   : std_logic_vector(8 downto 0) := "10100" & cmd_nop;
   constant s_rd4   : std_logic_vector(8 downto 0) := "10101" & cmd_read;
   constant s_rd5   : std_logic_vector(8 downto 0) := "10110" & cmd_nop;
   constant s_rd6   : std_logic_vector(8 downto 0) := "10111" & cmd_nop;
   constant s_rd7   : std_logic_vector(8 downto 0) := "11000" & cmd_nop;
   constant s_rd8   : std_logic_vector(8 downto 0) := "11001" & cmd_nop;
   constant s_rd9   : std_logic_vector(8 downto 0) := "11011" & cmd_nop;

   constant s_drdr0 : std_logic_vector(8 downto 0) := "11101" & cmd_pre;
   constant s_drdr1 : std_logic_vector(8 downto 0) := "11110" & cmd_nop;
   constant s_drdr2 : std_logic_vector(8 downto 0) := "11111" & cmd_nop;
   
   signal addr_row : std_logic_vector(12 downto 0);
   signal addr_bank: std_logic_vector(1 downto 0);
   signal addr_col : std_logic_vector(9 downto 0);

   signal captured : std_logic_vector(15 downto 0);
   
   signal clock_100          : std_logic;
   signal clock_100_delayed_3ns : std_logic;
begin
   -- Addressing is in 32 bit words - twice that of the DRAM width,
   -- so each burst of four access two system words.
   addr_row  <= address(23 downto 11);
   addr_bank <= address(10 downto 9);
   addr_col  <= address(8 downto  1) & "00";
   
sdram_clk_pll: sdram_clk_gen

   -- Generate the 100MHz clock and the same phase shifted by 3ns
   PORT MAP
   (
      inclk0    => CLOCK_50,
      c0         => clock_100,
      c1         => clock_100_delayed_3ns
   );

   DRAM_CLK         <= clock_100_delayed_3ns;
   DRAM_CKE       <= '1';
   DRAM_CS_N       <= r.state(3);
   DRAM_RAS_N       <= r.state(2);
   DRAM_CAS_N       <= r.state(1);
   DRAM_WE_N       <= r.state(0);
   DRAM_ADDR      <= r.address;
   DRAM_BA          <= r.bank;
   DATA_OUT         <= captured & r.data_out_low;
   DRAM_DQM       <= r.dq_masks;
   data_out_valid <= r.data_out_valid;

   process (r, address, req_read, req_write, addr_row, addr_bank, addr_col, data_in, captured)
   begin
      -- copy the existing values
      n <= r;
      if req_read = '1' then
         n.rd_pending <= '1';
      end if;
      
      if req_write = '1' then
         n.wr_pending <= '1';
      end if;
      
      n.dq_masks     <= "11";
      
      -- first off, do we need to perform a refresh cycle ASAP?
      if r.rf_counter = 770 then -- 781 = 64,000,000ns / 8192 / 10ns
         n.rf_counter <= (others => '0');
         n.rf_pending <= '1';
      else
         -- only start looking for refreshes outside of the initialisation state.
         if not(r.state(8 downto 4) = s_init_nop(8 downto 4)) then
            n.rf_counter <= r.rf_counter + 1;
         end if;
      end if;
      
      -- Set the data bus into HIZ, high and low bytes masked
      DRAM_DQ    <= (others => 'Z');

      n.init_counter <= r.init_counter-1;
      
      -- Process the FSM
      case r.state(8 downto 4) is
         when s_init_nop(8 downto 4) =>
            n.state     <= s_init_nop;
            n.address <= (others => '0');
            n.bank    <= (others => '0');
            n.rf_counter   <= (others => '0');
            n.data_out_valid <= '1';
            
            -- T-130, precharge all banks.
            if r.init_counter = "000000010000010" then
               n.state     <= s_init_pre;
               n.address(10)   <= '1';
            end if;

            -- T-127, T-111, T-95, T-79, T-63, T-47, T-31, T-15, the 8 refreshes
            
            if r.init_counter(14 downto 7) = 0 and r.init_counter(3 downto 0) = 15 then
               n.state     <= s_init_ref;
            end if;
            
            -- T-3, the load mode register 
            if r.init_counter = 3 then
               n.state     <= s_init_mrs;
                           -- Mode register is as follows:
                           -- resvd   wr_b   OpMd   CAS=3   Seq   bust=4
                n.address   <= "000" & "0" & "00" & "011" & "0" & "010";
                           -- resvd
               n.bank      <= "00";
            end if;

            
            -- T-1 The switch to the FSM (first command will be a NOP
            if r.init_counter = 1 then
               n.state          <= s_idle;
            end if;

         ------------------------------
         -- The Idle section
         ------------------------------
         when s_idle(8 downto 4) =>
            n.state <= s_idle;

            -- do we have to activate a row?
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state        <= s_ra0;
               n.address     <= addr_row;
               n.act_row    <= addr_row;
            end if;

            -- refreshes take priority over everything
            if r.rf_pending = '1' then
               n.state        <= s_rf0;
               n.rf_pending <= '0';
            end if;
         ------------------------------
         -- Row activation
         -- s_ra2 is also the "idle with active row" state and provides
         -- a resting point between operations on the same row
         ------------------------------
         when s_ra0(8 downto 4) =>
            n.state        <= s_ra1;
         when s_ra1(8 downto 4) =>
            n.state        <= s_ra2;
         when s_ra2(8 downto 4) =>
            -- we can stay in this state until we have something to do
            n.state       <= s_ra2;

            -- If there is a read pending, deactivate the row
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state     <= s_dr0;
               n.address(10) <= '1';
            end if;
            
            -- unless we have a read to perform on the same row? do that instead
            if r.rd_pending = '1' and r.act_row = addr_row then
               n.state     <= s_rd0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks <= "00";
               n.rd_pending <= '0';
            end if;
            
            -- unless we have a write on the same row? writes take priroty over reads
            if r.wr_pending = '1' and r.act_row = addr_row then
               n.state     <= s_wr0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks<= "00";
               n.wr_pending <= '0';
            end if;
            
            -- But refreshes take piority over everything!
            if r.rf_pending = '1' then
               n.state     <= s_dr0;
               n.address(10) <= '1';
            end if;
            
         ------------------------------------------------------
         -- Deactivate the current row and return to idle state
         ------------------------------------------------------
         when s_dr0(8 downto 4) =>
            n.state <= s_dr1;
         when s_dr1(8 downto 4) =>
            n.state <= s_idle;

         ------------------------------
         -- The Refresh section
         ------------------------------
         when s_rf0(8 downto 4) =>
            n.state <= s_rf1;
         when s_rf1(8 downto 4) =>
            n.state <= s_rf2;
         when s_rf2(8 downto 4) =>
            n.state <= s_rf3;
         when s_rf3(8 downto 4) =>
            n.state <= s_rf4;
         when s_rf4(8 downto 4) =>
            n.state <= s_rf5;
         when s_rf5(8 downto 4) =>
            n.state <= s_idle;
         ------------------------------
         -- The Write section
         ------------------------------
         when s_wr0(8 downto 4) =>
            n.state    <= s_wr1;
            n.address <= "000" & addr_col;
            n.bank    <= addr_bank;
            DRAM_DQ     <= data_in(15 downto 0);
            n.dq_masks<= "00";
         when s_wr1(8 downto 4) =>
            n.state    <= s_wr2;
            DRAM_DQ     <= data_in(31 downto 16);
            n.dq_masks<= "00";
         when s_wr2(8 downto 4) =>
            DRAM_DQ     <= data_in(15 downto 0);
            n.state     <= s_wr3;
            n.dq_masks<= "00";
         when s_wr3(8 downto 4) =>
            -- Default to the idle+row active state
            n.state     <= s_ra2;
            DRAM_DQ     <= data_in(31 downto 16);
            n.dq_masks<= "11";
            
            -- If there is a read or write then deactivate the row
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state         <= s_dr0;
               n.address(10) <= '1';
            end if;

            -- But if there is a read pending in the same row, do that
            if r.rd_pending = '1' and r.act_row = addr_row then
               n.state     <= s_rd0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks <= "00";
               n.rd_pending <= '0';
            end if;

            -- unless there is a write pending in the same row, do that
            if r.wr_pending = '1' and r.act_row = addr_row then
               n.state     <= s_wr0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks<= "00";
               n.wr_pending <= '0';
            end if;

            -- But always try and refresh if one is pending!
            if r.rf_pending = '1' then
               n.state       <= s_dr0;
               n.address(10) <= '1';
            end if;
         
         ------------------------------
         -- The Read section
         ------------------------------
         when s_rd0(8 downto 4) =>
            n.state <= s_rd1;
            n.dq_masks <= "00";
         when s_rd1(8 downto 4) =>
            n.state <= s_rd2;
            n.dq_masks <= "00";
         when s_rd2(8 downto 4) =>
            n.state <= s_rd3;
            n.dq_masks <= "00";
         when s_rd3(8 downto 4) =>
            -- default is to end the read with the row open
            n.state <= s_rd7;

            -- otherwise if there is a read or write prepare to deactivate the row.
            -- (This is overridden if the read/write is to the same page)
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state       <= s_drdr0;
               n.address(10) <= '1';
            end if;

            -- override if the write is from the same row
            if r.wr_pending = '1' and r.act_row = addr_row then
               n.state <= s_rd7;
            end if;
         
            -- override if the read is from the same row
            if r.rd_pending = '1' and r.act_row = addr_row then
               n.state     <= s_rd4;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks<= "00";
            end if;

               -- If a refresh is pending then always deactivate the row
            if r.rf_pending = '1' then 
               n.state <= s_drdr0;
               n.address(10) <= '1';
            end if;
            n.data_out_low <= captured;
            n.data_out_valid <= '1';   
         when s_rd4(8 downto 4) =>
            n.state <= s_rd5;
            n.dq_masks<= "00";
         when s_rd5(8 downto 4) =>
            n.state <= s_rd6;
            n.data_out_low <= captured;
            n.data_out_valid <= '1';   
            n.dq_masks<= "00";
         when s_rd6(8 downto 4) =>
            n.state <= s_rd3;
            n.dq_masks<= "00";
         when s_rd7(8 downto 4) =>
            n.state <= s_rd8;
            n.data_out_low <= captured;
            n.data_out_valid <= '1';   
         when s_rd8(8 downto 4) =>
            n.state <= s_rd9;
         when s_rd9(8 downto 4) =>
            -- by default go to the idle-with-row-active state
            n.state <= s_ra2;
            n.data_out_low <= captured;
            n.data_out_valid <= '1';   
            
            -- otherwise if there is a read or write prepare to deactivate the row.
            -- (This is overridden if the read/write is to the same row)
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state <= s_dr0;
               n.address(10) <= '1';
            end if;
            
            -- this is to catch if a read has turned up since the choices at state s_dr3
            if r.rd_pending = '1' and r.act_row = addr_row then
               n.state <= s_rd0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks <= "00";
               n.rd_pending <= '0';
            end if;

            -- this is to catch if a read has turned up since the choices at state s_dr3
            if r.wr_pending = '1' and r.act_row = addr_row then
               n.state <= s_wr0;
               n.address <= "000" & addr_col;
               n.bank    <= addr_bank;
               n.dq_masks<= "00";
               n.wr_pending <= '0';
            end if;
            
            if r.rf_pending = '1' then
               n.state <= s_dr0;
               n.address(10) <= '1';
            end if;
            
         ------------------------------
         -- The Deactivate row during read section
         ------------------------------
         when s_drdr0(8 downto 4) =>
            n.state <= s_drdr1;
         when s_drdr1(8 downto 4) =>
            n.state <= s_drdr2;
            n.data_out_low <= captured;
            n.data_out_valid <= '1';   
         when s_drdr2(8 downto 4) =>
            n.state <= s_idle;

            if r.rf_pending = '1' then
               n.state <= s_rf0;
            end if;
            
            if r.rd_pending = '1' or r.wr_pending = '1' then
               n.state       <= s_ra0;
               n.address    <= addr_row;
               n.act_row    <= addr_row;
               n.bank       <= addr_bank;
            end if;

         when others =>
            n.state <= s_init_nop;
      end case;
   end process;
   
   --- The clock driven logic
   process (clock_100, n)
   begin
      if clock_100'event and clock_100 = '1' then
         r <= n;
      end if;
   end process;

   process (clock_100_delayed_3ns, dram_dq)
   begin
      if clock_100_delayed_3ns'event and clock_100_delayed_3ns = '1' then
         captured <= dram_dq;
      end if;
   end process;

end rtl;


Personal tools