------------------------------------------------------------------------------
--  This file is a part of the GRLIB VHDL IP LIBRARY
--  Copyright (C) 2003 - 2008, Gaisler Research
--  Copyright (C) 2008 - 2014, Aeroflex Gaisler
--  Copyright (C) 2015 - 2019, Cobham Gaisler
--
--  This program is free software; you can redistribute it and/or modify
--  it under the terms of the GNU General Public License as published by
--  the Free Software Foundation; either version 2 of the License, or
--  (at your option) any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU General Public License for more details.
--
--  You should have received a copy of the GNU General Public License
--  along with this program; if not, write to the Free Software
--  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
-----------------------------------------------------------------------------
-- Entity:      ddr1spax_ddr
-- File:        ddr1spax_ddr.vhd
-- Author:      Magnus Hjorth - Aeroflex Gaisler
-- Description: Merged 16/32/64-bit DDR/mobile-DDR backend
--              Based on ddrsp*a and ddr2spax_ddr
--------------------------------------------------------------------------------

-- Added features from the original ddrspa:
-- * Separated AHB,DDR parts of controller like for DDR2SPA
-- * 64/32/16 bit interfaces in the same entity
-- * Checkbit support for use with ft_ddr2spax_ahb front-end.
-- * Extended timing fields plus tRAS setting to meet DDR400 timing.
-- * Configurable burst length
-- * Support for PHY:s with read data valid signaling and extra latency
-- Incompatibility/differences to the original ddrspa:
-- * The mobile DDR had an undocumented feature that tRFC was extended with 8
--   cycles if the TRP bit was set. This is replaced by the extended
--   timing fields.
-- * ddrsp16a used a separate read-clock supplied only from the Spartan PHY.
-- * Reads/writes are made as multiple length-2 burst commands.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library grlib;
use grlib.stdlib.all;
use grlib.amba.all;
use grlib.devices.all;
library gaisler;
use gaisler.ddrpkg.all;
use gaisler.ddrintpkg.all;

entity ddr1spax_ddr is
   generic (
      ddrbits    : integer := 32;
      burstlen   : integer := 8;
      MHz        : integer := 100;
      col        : integer := 9;
      Mbyte      : integer := 8;
      pwron      : integer := 0;
      oepol      : integer := 0;
      mobile     : integer := 0;
      confapi    : integer := 0;
      conf0      : integer := 0;
      conf1      : integer := 0;
      nosync     : integer := 0;
      ddr_syncrst: integer range 0 to 1 := 0;
      chkbits    : integer := 0;
      hasdqvalid : integer := 0;
      readdly    : integer := 0;
      regoutput  : integer := 1;
      ddr400     : integer := 1;
      rstdel     : integer := 200;
      phyptctrl  : integer := 0;
      scantest   : integer := 0
   );
   port (
      ddr_rst  : in  std_ulogic;
      clk_ddr  : in  std_ulogic;
      request  : in  ddr_request_type;
      start_tog: in  std_logic;
      response : out ddr_response_type;
      sdi      : in  ddrctrl_in_type;
      sdo      : out ddrctrl_out_type;
      wbraddr  : out std_logic_vector(log2((16*burstlen)/ddrbits) downto 0);
      wbrdata  : in  std_logic_vector(2*(ddrbits+chkbits)-1 downto 0);
      rbwaddr  : out std_logic_vector(log2((16*burstlen)/ddrbits)-1 downto 0);
      rbwdata  : out std_logic_vector(2*(ddrbits+chkbits)-1 downto 0);
      rbwrite  : out std_logic;
      reqsel   : in std_ulogic;
      frequest : in  ddr_request_type;
      response2: out ddr_response_type;
      testen   : in std_ulogic;
      testrst  : in std_ulogic;
      testoen  : in std_ulogic
   );  
end ddr1spax_ddr;

architecture rtl of ddr1spax_ddr is
  
  constant l2blen: integer := log2(burstlen)+log2(32);
  constant l2ddrw: integer := log2(ddrbits*2);
  constant l2ddr_burstlen: integer := l2blen-l2ddrw;
  
  -- constant oepols: std_logic := tosl(oepol);

  -- Write buffer dimensions
  -- Write buffer is addressable down to 32-bit level on write (AHB) side.
  constant wbuf_rabits: integer := 1+l2blen-l2ddrw; -- log2((burstlen*32)/(2*ddrbits));
  constant wbuf_rdbits: integer := 2*ddrbits;
  -- Read buffer dimensions
  constant rbuf_wabits: integer := l2blen-l2ddrw; -- log2((burstlen*32)/(2*ddrbits));
  constant rbuf_wdbits: integer := 2*(ddrbits+chkbits);
  
  type ddrstate is (dsidle,dsact1,dsact2,dsact3,dswr1,dswr2,dswr3,dswr4,dswr5,dswr6,
                    dsrd1,dsrd2,dsrd3,dsrd4,dsreg1,dsreg2,dscmd1,dscmd2,dspdown1,dspdown2,dsref1,
                    dssrr1,dssrr2);
  type ddrinitstate is (disrstdel,disidle,disrun,disfinished);

  type sdram_cfg_type is record
    command  : std_logic_vector(2 downto 0);
    csize    : std_logic_vector(1 downto 0);
    bsize    : std_logic_vector(2 downto 0);
    trcd     : std_ulogic;  -- tCD : 2/3 clock cycles
    trfc     : std_logic_vector(4 downto 0);
    trp      : std_logic_vector(1 downto 0);  -- precharge to activate: 2/3 clock cycles
    refresh  : std_logic_vector(11 downto 0);
    renable  : std_ulogic;
    dllrst   : std_ulogic;
    refon    : std_ulogic;
    cke      : std_ulogic;
    pasr     : std_logic_vector(5 downto 0); -- pasr(2:0) (pasr(5:3) used to detect update)
    tcsr     : std_logic_vector(3 downto 0); -- tcrs(1:0) (tcrs(3:2) used to detect update)
    ds       : std_logic_vector(5 downto 0); -- ds(1:0) (ds(3:2) used to detect update)
    pmode    : std_logic_vector(2 downto 0); -- Power-Saving mode
    mobileen : std_logic; -- Mobile SD support, Mobile SD enabled
    txsr     : std_logic_vector(5 downto 0); -- Exit Self Refresh timing
    txp      : std_logic_vector(1 downto 0); -- Exit Power-Down timing
    tcke     : std_logic; -- Clock enable timing
    cl       : std_logic; -- CAS latency 2/3 (0/1)
    conf     : std_logic_vector(63 downto 0); -- PHY control
    tras     : std_logic_vector(1 downto 0); -- tRAS minimum (6-9 cycles)
    twr      : std_logic; -- tWR write recovery, 2/3 cycles
  end record;
  
  type ddr_reg_type is record
    s             : ddrstate;
    initstate     : ddrinitstate;
    cfg           : sdram_cfg_type;
    resp,resp2    : ddr_response_type;
    req1,req2     : ddr_request_type;
    start1,start2 : std_logic;
    start3        : std_logic;
    ramaddr       : std_logic_vector(rbuf_wabits-1 downto 0);
    readpipe      : std_logic_vector(4+readdly downto 0);
    initpos       : std_logic_vector(2 downto 0);
    cmdctr        : std_logic_vector(7 downto 0);
    readdone      : std_logic;
    refctr        : std_logic_vector(17 downto 0);
    refpend       : std_logic;
    idlectr       : std_logic_vector(3 downto 0);
    pdowns        : std_logic_vector(1 downto 0);
    sdo_casn      : std_logic;
    sdo_rasn      : std_logic;
    sdo_wen       : std_logic;
    sdo_csn       : std_logic_vector(1 downto 0);
    sdo_ba        : std_logic_vector(1 downto 0);
    sdo_address   : std_logic_vector(14 downto 0);
    sdo_data      : std_logic_vector(2*ddrbits-1 downto 0);
    sdo_dqm       : std_logic_vector(ddrbits/4-1 downto 0);
    sdo_cb        : std_logic_vector(2*chkbits downto 0);
    sdo_ck        : std_logic_vector(2 downto 0);
    sdo_bdrive    : std_logic;
    sdo_qdrive    : std_logic;
  end record;

  signal dr,ndr: ddr_reg_type;

  constant onev: std_logic_vector(15 downto 0) := x"FFFF";
  constant zerov: std_logic_vector(15 downto 0) := x"0000";

  signal arst : std_ulogic;
begin
  
  arst <= testrst when (scantest/=0 and ddr_syncrst=0) and testen='1' else ddr_rst;
  
  ddrcomb: process(ddr_rst,sdi,request,frequest,start_tog,dr,wbrdata,testen,testoen,reqsel)

    variable dv: ddr_reg_type;
    variable o: ddrctrl_out_type;
    variable rbw: std_logic;
    variable rbwd: std_logic_vector(2*(ddrbits+chkbits)-1 downto 0);
    
    variable vstart, vstartd, vdone, incdone: std_logic;
    variable vrctr: std_logic_vector(3 downto 0);
    variable vreq,vreqf: ddr_request_type;
    variable regsd1   : std_logic_vector(31 downto 0);
    variable regsd2   : std_logic_vector(31 downto 0);
    variable regsd3   : std_logic_vector(31 downto 0);
    variable lastreadcmd: std_logic;
    variable lastwrite : std_logic;
    variable vmaskfirst, vmasklast: std_logic_vector(ddrbits/4-1 downto 0);
    variable ea: std_logic_vector(3 downto 2);
    variable inc_sdoaddr, inc_ramaddr: std_logic;
    variable datavalid: std_logic;
    variable vcsf: std_logic_vector(1 downto 0);
    variable vrowf: std_logic_vector(14 downto 0);
    variable vbankf: std_logic_vector(1 downto 0);
    variable vcol,vcoladdr: std_logic_vector(14 downto 1);
    variable seqin,seqout: std_logic_vector(3 downto 0);
    variable regrdata: std_logic_vector(2*ddrbits-1 downto 0);
    variable regad: std_logic_vector(2 downto 0);
    variable wrdreg1,wrdreg2,wrdreg3: std_logic_vector(31 downto 0);
    variable reqselv: std_logic_vector(3 downto 0);
  begin
    ---------------------------------------------------------------------------
    -- Init vars
    ---------------------------------------------------------------------------
    dv := dr;
    o := ddrctrl_out_none;
    o.bdrive := '1'; o.qdrive := '1';
    vdone := dr.resp.done_tog or dr.resp2.done_tog;
    vrctr := dr.resp.rctr_gray or dr.resp2.rctr_gray;
    
    incdone := '0';
    lastreadcmd := '0';
    lastwrite := '0';
    reqselv := reqsel & reqsel & reqsel & reqsel;
    
    -- Config registers    
    regsd1 := (others => '0');
    regsd1(31 downto 15) := dr.cfg.refon & dr.cfg.trp(0) & dr.cfg.trfc(2 downto 0) &
                            dr.cfg.trcd & dr.cfg.bsize & dr.cfg.csize & dr.cfg.command &
                            dr.cfg.dllrst & dr.cfg.renable & dr.cfg.cke; 
    regsd1(11 downto 0) := dr.cfg.refresh;
    regsd2 := (others => '0');
    regsd2(8 downto 0) := conv_std_logic_vector(MHz, 9);
    regsd2(14 downto 12) := conv_std_logic_vector(log2(ddrbits/8),3);
    if mobile/=0 then regsd2(15):='1'; end if;-- Mobile DDR support
    regsd2(19 downto 16) := conv_std_logic_vector(confapi, 4);
    regsd3 := (others => '0');
    regsd3(31) := dr.cfg.mobileen; -- Mobile DDR enable
    regsd3(30) := dr.cfg.cl;
    regsd3(24 downto 19) := dr.cfg.tcke & dr.cfg.txsr(3 downto 0) & dr.cfg.txp(0);
    regsd3(18 downto 16) := dr.cfg.pmode;
    regsd3( 7 downto  0) := dr.cfg.ds(2 downto 0) & dr.cfg.tcsr(1 downto 0) 
                            & dr.cfg.pasr(2 downto 0);
    -- Extended timing fields for DDR400
    if ddr400 /= 0 then
      regsd2(20) := '1';   -- Ext. fields available
      regsd3(29 downto 28) := dr.cfg.tras;
      regsd3(27 downto 26) := dr.cfg.txsr(5 downto 4);
      regsd3(25) := dr.cfg.txp(1);
      regsd3(11) := dr.cfg.twr;
      regsd3(10) := dr.cfg.trp(1);
      regsd3(9 downto 8) := dr.cfg.trfc(4 downto 3);
    end if;
    
    -- Data path
    rbw := '0';
    rbwd := (others => '0');
    rbwd(ddrbits-1 downto 0) := sdi.data(ddrbits-1 downto 0);
    rbwd(2*ddrbits+chkbits-1 downto ddrbits+chkbits) :=
      sdi.data(2*ddrbits-1 downto ddrbits);
    if chkbits > 0 then
      rbwd(ddrbits+chkbits-1 downto ddrbits) := sdi.cb(chkbits-1 downto 0);
      rbwd(2*(ddrbits+chkbits)-1 downto 2*ddrbits+chkbits) :=
        sdi.cb(2*chkbits-1 downto chkbits);
    end if;
    dv.sdo_data(ddrbits-1 downto 0) := wbrdata(ddrbits-1 downto 0);
    dv.sdo_data(2*ddrbits-1 downto ddrbits) :=
      wbrdata(2*ddrbits+chkbits-1 downto ddrbits+chkbits);
    dv.sdo_cb(chkbits) := '0'; -- dummy bit just to ensure length>0
    if chkbits > 0 then
      dv.sdo_cb(chkbits-1 downto 0) := wbrdata(ddrbits+chkbits-1 downto ddrbits);
      dv.sdo_cb(2*chkbits-1 downto chkbits) :=
        wbrdata(2*(ddrbits+chkbits)-1 downto 2*ddrbits+chkbits);
    end if;
    
    ---------------------------------------------------------------------------
    -- Request handling logic
    ---------------------------------------------------------------------------

    -- Sync request inputs
    dv.req1 := request;
    dv.req2 := dr.req1;
    dv.start1 := start_tog;
    dv.start2 := dr.start1;
    dv.start3 := dr.start2;
    vstart := dr.start2;
    vstartd := dr.start3;
    vreq := dr.req2;
    vreqf := dr.req1;
    if nosync/=0 then
      vstart:=start_tog;
      vstartd:=start_tog;
      vreq:=request;
      vreqf:=request;
    end if;
    if nosync > 1 then
      vreqf := frequest;
    end if;

    -- Address muxing
    vcsf(0) := genmux(dr.cfg.bsize, vreqf.startaddr(30 downto 23));
    vcsf(1) := not vcsf(0);
    vbankf := genmux(dr.cfg.bsize, vreqf.startaddr(29 downto 22)) &
              genmux(dr.cfg.bsize, vreqf.startaddr(28 downto 21));
    case dr.cfg.csize is
      when "00"   => vrowf := vreqf.startaddr(19+l2ddrw downto 5+l2ddrw);
      when "01"   => vrowf := vreqf.startaddr(20+l2ddrw downto 6+l2ddrw);
      when "10"   => vrowf := vreqf.startaddr(21+l2ddrw downto 7+l2ddrw);
      when others => vrowf := vreqf.startaddr(22+l2ddrw downto 8+l2ddrw);
    end case;
    vcol := vreq.startaddr(l2ddrw+10 downto l2ddrw-3);
    -- vcoladdr==vcol when dr.ramaddr==lsb of vcol
    vcoladdr := vcol(14 downto rbuf_wabits+1) & dr.ramaddr;  
    
    -- Generate data mask
    -- Mask for 32-bit and larger bursts and single access
    vmaskfirst := (others => '0');
    vmasklast := (others => '0');
    ea := vreq.endaddr(3 downto 2);
    if vreq.hsize(1 downto 0)="11" then ea(2):='1'; end if;
    if vreq.hsize(2)='1' then ea(3 downto 2):="11"; end if;
    case ddrbits is
      when 64 =>
        -- 64-bit DDR width
        case vreq.startaddr(3 downto 2) is
          when "11"   => vmaskfirst := "1111111111110000";
          when "10"   => vmaskfirst := "1111111100000000";
          when "01"   => vmaskfirst := "1111000000000000";
          when others => vmaskfirst := "0000000000000000";
        end case;
        case ea(3 downto 2) is
          when "11"   => vmasklast := "0000000000000000";
          when "10"   => vmasklast := "0000000000001111";
          when "01"   => vmasklast := "0000000011111111";
          when others => vmasklast := "0000111111111111";
        end case;
        if vreq.hsize(2 downto 1)="00" then 
          if vreq.startaddr(1)='1' then
            vmaskfirst := vmaskfirst or "1100110011001100";
          else
            vmaskfirst := vmaskfirst or "0011001100110011";
          end if;
        end if;
        if vreq.hsize="000" then
          if vreq.startaddr(0)='1' then
            vmaskfirst := vmaskfirst or "1010101010101010";
          else
            vmaskfirst := vmaskfirst or "0101010101010101";
          end if;
        end if;
      when 32 =>
        -- 32-bit DDR width
        case vreq.startaddr(2) is
          when '1'    => vmaskfirst := "11110000";
          when others => vmaskfirst := "00000000";
        end case;
        case ea(2) is
          when '1'    => vmasklast := "00000000";
          when others => vmasklast := "00001111";
        end case;
        if vreq.hsize(2 downto 1)="00" then 
          if vreq.startaddr(1)='1' then
            vmaskfirst := vmaskfirst or "11001100";
          else
            vmaskfirst := vmaskfirst or "00110011";
          end if;          
        end if;
        if vreq.hsize="000" then
          if vreq.startaddr(0)='1' then
            vmaskfirst := vmaskfirst or "10101010";
          else
            vmaskfirst := vmaskfirst or "01010101";
          end if;
        end if;
        
      when others =>
        -- 16-bit DDR width
        if vreq.hsize(2 downto 1)="00" then 
          if vreq.startaddr(1)='1' then
            vmaskfirst := vmaskfirst or "1100";
          else
            vmaskfirst := vmaskfirst or "0011";
          end if;          
        end if;
        if vreq.hsize="000" then
          if vreq.startaddr(0)='1' then
            vmaskfirst := vmaskfirst or "1010";
          else
            vmaskfirst := vmaskfirst or "0101";
          end if;
        end if;
    end case;

    -- Register read/write data muxing
    regrdata := (others => '0');    
    case ddrbits is
      when 64 =>
        regad := vreq.startaddr(4 downto 2);
        regrdata := regsd1 & regsd2 & regsd3 & x"00000000";
        if confapi /= 0 and regad(2)='1' then
          regrdata(95 downto 32) := dr.cfg.conf(31 downto 0) & dr.cfg.conf(63 downto 32);
        end if;          
        wrdreg1 := wbrdata(128+chkbits-1 downto 96+chkbits);
        wrdreg2 := wbrdata(96+chkbits-1 downto 64+chkbits);
        wrdreg3 := wbrdata(63 downto 32);
      when 32 =>
        regad := dr.ramaddr(1 downto 0) & vreq.startaddr(2);        
        if regad(1)='0' then
          regrdata := regsd1 & regsd2;
          if confapi /= 0 and regad(2)='1' then
            regrdata := regsd1 & dr.cfg.conf(31 downto 0);
          end if;
        else
          regrdata := regsd3 & regsd2;
          if confapi /= 0 and regad(2)='1' then
            regrdata := dr.cfg.conf(63 downto 0);
          end if;
        end if;        
        wrdreg1 := wbrdata(64+chkbits-1 downto 32+chkbits);
        wrdreg2 := wbrdata(31 downto 0);
        wrdreg3 := wbrdata(64+chkbits-1 downto 32+chkbits);
      when others =>
        regad := dr.ramaddr(2 downto 0);
        case regad is
          when "000"|"100" => regrdata := regsd1;
          when "001" => regrdata := regsd2;
          when "010" => regrdata := regsd3;
          when "101" =>
            if confapi /= 0 then
              regrdata := dr.cfg.conf(31 downto 0);
            else
              regrdata := regsd2;
            end if;
          when "110" =>
            if confapi /= 0 then
              regrdata := dr.cfg.conf(63 downto 32);
            else
              regrdata := regsd3;
            end if;
          when others => regrdata := regsd3;
        end case;
        wrdreg1 := wbrdata(31+chkbits downto 16+chkbits) & wbrdata(15 downto 0);
        wrdreg2 := wbrdata(31+chkbits downto 16+chkbits) & wbrdata(15 downto 0);
        wrdreg3 := wbrdata(31+chkbits downto 16+chkbits) & wbrdata(15 downto 0);        
    end case;
           
    ---------------------------------------------------------------------------
    -- Main DDR-SDRAM access FSM
    ---------------------------------------------------------------------------

    dv.sdo_ck := "111";
    dv.sdo_rasn := '1';
    dv.sdo_casn := '1';
    dv.sdo_wen := '1';
    dv.sdo_dqm := (others => '1');
    dv.sdo_bdrive := '1';
    dv.sdo_qdrive := '1';
    
    inc_sdoaddr := '0';
    inc_ramaddr := '0';

    dv.readpipe := dr.readpipe(3+readdly downto 0) & '0';

    datavalid := '0';
    if hasdqvalid/=0 then
      datavalid := sdi.datavalid;
      if dr.s/=dsrd1 and dr.s/=dsrd2 and dr.s/=dsrd3 and dr.s/=dsrd4 and dr.s/=dssrr2 then
        datavalid := '0';
      end if;
    end if;
    if hasdqvalid=0 then
      if dr.cfg.cl='0' then
        datavalid := dr.readpipe(3+readdly);
      else
        datavalid := dr.readpipe(4+readdly);
      end if;
    end if;

    if datavalid='1' and dr.s/=dsidle then
      inc_ramaddr := '1';
      rbw := '1';
      vrctr(l2ddr_burstlen-1 downto 0) :=
        nextgray(vrctr(l2ddr_burstlen-1 downto 0));
      if dr.ramaddr=onev(dr.ramaddr'length-1 downto 0) then
        dv.readdone := '1';
        incdone:='1';
        vrctr := "0000";
      end if;
    end if;

    if dr.sdo_address((l2blen-l2ddrw) downto 1)=onev((l2blen-l2ddrw) downto 1) then
      lastreadcmd := '1';
    end if;

    if dr.ramaddr=vreq.endaddr((l2blen-3)-1 downto (l2ddrw-3)) then      
      lastwrite := '1';
    end if;

    -- Update EMR when ds, tcsr or pasr change
    if dr.cfg.command="000" and
      ( dr.cfg.ds(2 downto 0) /= dr.cfg.ds(5 downto 3) or
        dr.cfg.tcsr(1 downto 0) /= dr.cfg.tcsr(3 downto 2) or
        dr.cfg.pasr(2 downto 0) /= dr.cfg.pasr(5 downto 3) ) then
      dv.cfg.command := "111";
    end if;

    -- Auto-refresh counter
    dv.refctr := std_logic_vector(unsigned(dr.refctr)+1);
    if (dr.refctr(11 downto 0)=dr.cfg.refresh and dr.cfg.refon='1') then
      dv.refpend := '1';
      dv.refctr := (others => '0');
    end if;
    if dr.initstate/=disrstdel and (dr.cfg.refon='0' or dr.cfg.pmode(1)='1') then
      dv.refpend := '0';
      dv.refctr := (others => '0');
    end if;

    dv.idlectr := "0000";
    dv.pdowns(0) := '0';
    
    if not (dr.cmdctr=(dr.cmdctr'range => '0')) and dr.pdowns(0)='0' then
      dv.cmdctr := std_logic_vector(unsigned(dr.cmdctr)-1);
    end if;
    
    case dr.s is
      when dsidle =>
        vrctr := "0000";
        dv.sdo_ck := "111";
        if dr.cfg.pmode /= "000" then
          dv.idlectr := std_logic_vector(unsigned(dr.idlectr)+1);
        end if;
        dv.sdo_csn := "11";
        if dr.refpend='1' then
          dv.sdo_csn := "00";
          dv.sdo_rasn := '0';
          dv.sdo_casn := '0';
          dv.s := dsref1;
          dv.refpend := '0';
        elsif vstart /= vdone and dr.cfg.renable='0' then
          -- Transfer
          dv.sdo_csn := vcsf;
          dv.sdo_address := vrowf;
          dv.sdo_ba := vbankf;
          dv.sdo_rasn := '0' or vreqf.hio;
          dv.s := dsact1;
        elsif dr.cfg.command /= "000" then
          dv.s := dscmd1;
        elsif dr.idlectr="1111" then
          dv.s := dspdown1;
        end if;

      when dsact1 =>
        dv.ramaddr := vcol(rbuf_wabits downto 1);
        if ddr400 /= 0 then
          dv.cmdctr(2 downto 0) := "1" & dr.cfg.tras; -- t(RAS)-2t(CK) = TRAS+6-2 = TRAS+4
        else
          dv.cmdctr(2 downto 0) := "10" & dr.cfg.trcd;
        end if;
        dv.readdone := '0';
        if dr.cfg.trcd='1' then
          dv.s := dsact2;
        else
          dv.s := dsact3;
        end if;
        if vreq.hio='1' then
          dv.s := dsreg1;
        end if;

      when dsact2 =>
        dv.s := dsact3;

      when dsact3 =>
        dv.sdo_casn := '0';
        dv.sdo_wen := not vreq.hwrite;
        dv.sdo_qdrive := not vreq.hwrite;
        -- dv.sdo_address := vcol(12 downto 10) & '0' & vcol(9 downto 1) & '0';
        -- Since part of column is stored in ramaddr in dsact1, use that to
        -- reduce fanout on vreq.startaddr
        dv.sdo_address := vcoladdr(13 downto 10) & '0' & vcoladdr(9 downto 1) & '0';
        if vreq.hwrite='1' then
          dv.s := dswr1;
        else
          dv.s := dsrd1;
          dv.readpipe(0) := '1';
        end if;

      when dswr1 =>
        -- NOP,NOP,[WR]: issue either WR+D or NOP+D
        dv.sdo_bdrive := '0';
        dv.sdo_qdrive := '0';
        inc_sdoaddr := '1';
        inc_ramaddr := '1';
        if lastwrite='1' then
          dv.sdo_dqm := vmaskfirst or vmasklast;
          dv.s := dswr3;
        else
          dv.sdo_casn := '0';
          dv.sdo_wen := '0';
          dv.sdo_dqm := vmaskfirst;
          dv.s := dswr2;
        end if;

      when dswr2 =>
        dv.sdo_dqm := (others => '0');
        dv.sdo_bdrive := '0';
        dv.sdo_qdrive := '0';
        inc_sdoaddr := '1';
        inc_ramaddr := '1';
        if lastwrite='0' then
          dv.sdo_casn := '0';
          dv.sdo_wen := '0';
        else
          dv.s := dswr3;
          dv.sdo_dqm := vmasklast;
        end if;
        
      when dswr3 =>
        -- ...,WR+D,WR+D,[NOP+D]: issue NOP
        dv.sdo_qdrive := '0';
        dv.sdo_dqm := (others => '1');
        dv.s := dswr4;
        incdone := '1';
        
      when dswr4 =>
        -- Issue more NOP:s to meet tWR
        dv.idlectr := std_logic_vector(unsigned(dr.idlectr)+1);
        if dr.idlectr(0)=dr.cfg.twr then
          dv.s := dswr5;
        end if;

      when dswr5 => 
        -- Issue NOP:s until tRAS met.
        if dr.cmdctr(2 downto 0)="000" then
          dv.sdo_rasn := '0';
          dv.sdo_wen := '0';
          dv.s := dswr6;
        end if;

      when dswr6 =>
        -- PRE: issue one or two NOP:s depending on trp setting
        if dr.idlectr(1 downto 0)=dr.cfg.trp then
          dv.s := dsidle;
        else
          dv.idlectr := std_logic_vector(unsigned(dr.idlectr)+1);          
        end if;

      when dsrd1 =>
        inc_sdoaddr := '1';
        if lastreadcmd='0' then
          dv.sdo_casn := '0';
          dv.readpipe(0):='1';
        elsif dr.cmdctr(2 downto 0)="000" then
          dv.sdo_rasn := '0';
          dv.sdo_wen := '0';
          dv.s := dsrd3;
        else
          dv.s := dsrd2;
        end if;

      when dsrd2 =>
        if dr.cmdctr(2 downto 0)="000" then
          dv.sdo_rasn := '0';
          dv.sdo_wen := '0';
          dv.s := dsrd3;
        end if;

      when dsrd3 =>
        if dr.idlectr(1 downto 0)=dr.cfg.trp then
          if dv.readdone='1' then
            dv.s := dsidle;
          else
            dv.s := dsrd4;
          end if;
        else
          dv.idlectr := std_logic_vector(unsigned(dr.idlectr)+1);          
        end if;

      when dsrd4 =>
        if dv.readdone='1' then
          dv.s := dsidle;
        end if;
        
      when dsreg1 =>
        rbw := '1';
        rbwd(2*ddrbits+chkbits-1 downto ddrbits+chkbits) := regrdata(2*ddrbits-1 downto ddrbits);
        rbwd(ddrbits-1 downto 0) := regrdata(ddrbits-1 downto 0);
        if vreq.hwrite='1' then
          dv.s := dsreg2;
        elsif regad="100" and dr.cfg.mobileen='1' then
          dv.sdo_address := (others => '0');
          dv.sdo_ba := "01";
          dv.sdo_csn := "10";
          dv.sdo_rasn := '0';
          dv.sdo_casn := '0';
          dv.sdo_wen := '0';
          dv.s := dssrr1;
          dv.cmdctr(0) := '1';
          null;
        else
          incdone := '1';
          dv.s := dsidle;
        end if;

      when dsreg2 =>        
        case regad is
          when "000" =>
            dv.cfg.refon   := wrdreg1(31);
            dv.cfg.trp(0)  := wrdreg1(30);
            dv.cfg.trfc(2 downto 0) := wrdreg1(29 downto 27);
            dv.cfg.trcd    := wrdreg1(26);
            dv.cfg.bsize   := wrdreg1(25 downto 23);
            dv.cfg.csize   := wrdreg1(22 downto 21);
            dv.cfg.command := wrdreg1(20 downto 18);
            dv.cfg.dllrst  := wrdreg1(17);
            dv.cfg.renable := wrdreg1(16);
            dv.cfg.cke     := wrdreg1(15);
            dv.cfg.refresh := wrdreg1(11 downto 0);
          when "010" =>
            dv.cfg.mobileen         := wrdreg3(31);
            dv.cfg.cl               := wrdreg3(30);
            dv.cfg.tcke             := wrdreg3(24);
            dv.cfg.txsr(3 downto 0) := wrdreg3(23 downto 20);
            dv.cfg.txp(0)           := wrdreg3(19);
            dv.cfg.pmode            := wrdreg3(18 downto 16);
            dv.cfg.ds  (5 downto 3) := wrdreg3(7 downto 5);
            dv.cfg.tcsr(3 downto 2) := wrdreg3(4 downto 3);
            dv.cfg.pasr(5 downto 3) := wrdreg3(2 downto 0);
            -- Extended DDR400 fields
            dv.cfg.tras             := wrdreg3(29 downto 28);
            dv.cfg.txsr(5 downto 4) := wrdreg3(27 downto 26);
            dv.cfg.txp(1)           := wrdreg3(25);
            dv.cfg.twr              := wrdreg3(11);
            dv.cfg.trp(1)           := wrdreg3(10);
            dv.cfg.trfc(4 downto 3) := wrdreg3(9 downto 8);
          when "101" =>
            if confapi /= 0 then
              dv.cfg.conf(31 downto 0) := wrdreg2;
            end if;
          when "110" =>
            if confapi /= 0 then
              dv.cfg.conf(63 downto 32) := wrdreg3;
            end if;
          when others =>
            null;
        end case;
        incdone := '1';
        dv.s := dsidle;
        
        
      when dscmd1 =>
        dv.sdo_csn := (others => '0');
        dv.sdo_address(10) := '1';
        dv.cfg.command := "000";
        dv.s := dscmd2;
        case dr.cfg.command is
          when "010" => -- PRECHARGE ALL
            dv.sdo_rasn := '0';
            dv.sdo_wen := '0';
            dv.cmdctr(1 downto 0) := "11";
          when "100" => -- AUTO-REFRESH
            dv.sdo_rasn := '0';
            dv.sdo_casn := '0';
            dv.cmdctr(4 downto 0) := dr.cfg.trfc;
          when "110" => -- MODE REGISTER
            dv.sdo_rasn := '0';
            dv.sdo_casn := '0';
            dv.sdo_wen := '0';
            dv.sdo_ba := "00";
            dv.sdo_address := "00000000" & "01" & dr.cfg.cl & "0001";
            if dr.cfg.mobileen='0' then
              dv.sdo_address(8) := dr.cfg.dllrst;
            end if;
            if dr.cfg.dllrst='1' then
              dv.cmdctr := std_logic_vector(to_unsigned(200,dr.cmdctr'length));
            end if;
          when "111" => -- EXT. MODE REGISTER
            dv.sdo_rasn := '0';
            dv.sdo_casn := '0';
            dv.sdo_wen := '0';
            if dr.cfg.mobileen='1' then
              dv.sdo_ba := "10";
              dv.sdo_address := "0000000" & dr.cfg.ds(5 downto 3) & dr.cfg.tcsr(3 downto 2) 
                                & dr.cfg.pasr(5 downto 3);
            else
              dv.sdo_ba := "01";
              dv.sdo_address := "000000000000000";  -- bit0=0 -> DLL enable
            end if;
            dv.cfg.pasr(2 downto 0) := dr.cfg.pasr(5 downto 3);
            dv.cfg.ds(2 downto 0) := dr.cfg.ds(5 downto 3);
            dv.cfg.tcsr(1 downto 0) := dr.cfg.tcsr(3 downto 2);

          when others => null;
            
        end case;

      when dscmd2 =>
        if dr.cmdctr=(dr.cmdctr'range => '0') then
          dv.s := dsidle;
        end if;

      when dspdown1 =>        
        dv.sdo_csn := "00";
        if dr.cfg.pmode(0)='1' or dr.cfg.pmode(1)='1' then
          dv.cfg.cke := '0';
        end if;
        if dr.cfg.pmode(1)='1' then
          dv.sdo_rasn := '0';
          dv.sdo_casn := '0';
        end if;
        if dr.cfg.pmode(2)='1' and dr.cfg.pmode(0)='1' then
          dv.sdo_wen := '0';
        end if;
        if dr.cfg.pmode(0)='1' then
          dv.cmdctr(1 downto 0) := dr.cfg.txp;
        end if;
        if dr.cfg.pmode(1)='1' then
          if dr.cfg.mobileen='1' then
            dv.cmdctr(5 downto 0) := dr.cfg.txsr;
          else
            dv.cmdctr(7 downto 0) := std_logic_vector(to_unsigned(200,8));
          end if;
        end if;
        dv.pdowns(1) := '0';
        dv.s := dspdown2;

      when dspdown2 =>
        dv.pdowns(0) := '1';
        if dr.pdowns(0)='0' and dr.cmdctr=(dr.cmdctr'range => '0') then
          dv.pdowns(1):='1';
        end if;
        if dr.cfg.pmode(2)='1' and dr.cfg.pmode(0)='0' then
          dv.sdo_ck := "000";
        end if;
        if dr.cfg.pmode(1)='1' then
          dv.refpend := '1';
        end if;
        if (dr.refpend='1' and dr.cfg.pmode(1)='0') or vstart /= vdone then
          if (dr.pdowns(0) or not dr.cfg.tcke)='1' then
            dv.cfg.cke := '1';
            if dr.pdowns(1)='1' then
              dv.s := dsidle;
            else
              dv.s := dscmd2;
              dv.pdowns(0) := '0';
            end if;
          end if;
        end if;

      when dsref1 =>
        dv.s := dscmd2;
        dv.cmdctr(4 downto 0) := dr.cfg.trfc;

      when dssrr1 =>
        if dr.cmdctr(0)='0' then
          dv.sdo_casn := '0';
          dv.readpipe(0):='1';
          dv.s := dssrr2;
        end if;

      when dssrr2 =>
        if datavalid='1' then
          incdone := '1';
          dv.s := dsidle;
        end if;
        
    end case;
    
    if inc_sdoaddr='1' then
      dv.sdo_address(l2blen-l2ddrw downto 1) :=
        std_logic_vector(unsigned(dr.sdo_address(l2blen-l2ddrw downto 1))+1);
    end if;
    if inc_ramaddr='1' then
      dv.ramaddr := std_logic_vector(unsigned(dr.ramaddr)+1);
    end if;

    -- Update the done flags
    dv.resp.done_tog  := (dr.resp.done_tog xor incdone) and (not reqsel);
    dv.resp.rctr_gray := vrctr and (not reqselv);
    dv.resp2.done_tog := (dr.resp2.done_tog xor incdone) and reqsel;
    dv.resp2.rctr_gray := vrctr and reqselv;
    
    ---------------------------------------------------------------------------
    -- DDR Init Sequence FSM
    ---------------------------------------------------------------------------

    -- Command sequence lookup table
    seqin := dr.cfg.mobileen & dr.initpos;
    case seqin is
      -- Mobile DDR
      when "1100" => seqout := "0010";   -- PRECHARGE ALL
      when "1011" => seqout := "0100";   -- AUTO REFRESH #1
      when "1010" => seqout := "0100";   -- AUTO REFRESH #2
      when "1001" => seqout := "0110";   -- MODE REG
      when "1000" => seqout := "0111";   -- EXT MODE REG
      -- Normal DDR
      when "0110" => seqout := "0010";   -- PRECHARGE ALL
      when "0101" => seqout := "0111";   -- EXT MODE REG En DLL
      when "0100" => seqout := "1110";   -- MODE REG Rst DLL
      when "0011" => seqout := "0010";   -- PRECHARGE ALL
      when "0010" => seqout := "0100";   -- AUTO REFRESH #1
      when "0001" => seqout := "0100";   -- AUTO REFRESH #2
      when "0000" => seqout := "0110";   -- MODE REG NoRst DLL
      when others => seqout := "0000";
    end case;
    
    case dr.initstate is

      when disrstdel =>
        if dr.refctr=std_logic_vector(to_unsigned(MHz*rstdel,dr.refctr'length)) then
          dv.initstate := disidle;
          if pwron=0 then dv.cfg.renable:='0'; end if;
        end if;
        -- Bypass reset delay by writing anything to regsd2        
        if vstartd='1' and (vreq.hio='1' and vreq.hwrite='1' and vreq.endaddr(4 downto 2)="001") then
          dv.initstate := disidle;
          if pwron=0 then dv.cfg.renable:='0'; end if;
        end if;
        
      when disidle =>
        if dr.cfg.renable='1' then
          dv.cfg.cke := '1';          
          if dr.cfg.cke='1' then
            dv.initpos := "111";
            dv.initstate := disrun;
          end if;
        end if;

      when disrun =>
        if dr.cfg.command="000" then          
          dv.cfg.dllrst := seqout(3);
          dv.cfg.command := seqout(2 downto 0);
          dv.initpos := std_logic_vector(unsigned(dr.initpos)-1);
          if dr.initpos="000" then
            dv.initstate := disfinished;
          end if;
        end if;        

      when disfinished =>
        if dr.cfg.command="000" then
          dv.cfg.renable := '0';
          dv.cfg.refon := '1';
          dv.initstate := disidle;
        end if;
    end case;
    
    ---------------------------------------------------------------------------
    -- Reset
    ---------------------------------------------------------------------------
    if ddr_rst='0' then
      dv.s := dsidle;
      dv.cmdctr := (others => '0');
      dv.refctr := (others => '0');
      dv.resp := ddr_response_none;
      dv.resp2 := ddr_response_none;
      dv.initstate := disrstdel;
      dv.refpend := '0';
      -- Reset cfg record
      dv.cfg.command       := "000";
      dv.cfg.csize         := conv_std_logic_vector(col-9, 2);
      dv.cfg.bsize         := conv_std_logic_vector(log2(Mbyte/8), 3);
      dv.cfg.refon         :=  '0';
      dv.cfg.refresh       := conv_std_logic_vector(7800*MHz/1000, 12);
      dv.cfg.dllrst        := '0';
      dv.cfg.pasr          := (others => '0');
      dv.cfg.tcsr          := (others => '0');
      dv.cfg.ds            := (others => '0');
      dv.cfg.pmode         := (others => '0');
      dv.cfg.txsr          := conv_std_logic_vector(120*MHz/1000, 6);
      dv.cfg.txp           := "01";
      dv.cfg.cl            := '0'; -- CL = 3/2 -- ****
      dv.cfg.tcke          := '1';
      if MHz > 100 then
           dv.cfg.trcd     := '1';
      else dv.cfg.trcd     := '0';
      end if;
      if MHz > 100 then
           dv.cfg.trp      := "01";
      else dv.cfg.trp      := "00";
      end if;
      dv.cfg.renable       := '1'; -- Updated in disrstdel state
      if mobile >= 2 then
           dv.cfg.mobileen := '1';   -- Default: Mobile DDR
      else dv.cfg.mobileen := '0';
      end if;
      if mobile >= 2 then
           dv.cfg.trfc   := conv_std_logic_vector(98*MHz/1000-2, 5);
      else dv.cfg.trfc   := conv_std_logic_vector(75*MHz/1000-2, 5);
      end if;
      if ddr_syncrst /= 0 then
        dv.sdo_ck := "000";
        if mobile >= 2 then
             dv.cfg.cke      := '1';
        else dv.cfg.cke      := '0';
        end if;
      end if;
      if confapi /= 0 then
        dv.cfg.conf(31 downto  0) := conv_std_logic_vector(conf0, 32); --x"0000A0A0";
        dv.cfg.conf(63 downto 32) := conv_std_logic_vector(conf1, 32); --x"00060606";
      else
        dv.cfg.conf := (others => '0');
      end if;
      if MHz > 175 then
        dv.cfg.tras := "10";
      elsif MHz > 150 then
        dv.cfg.tras := "01";
      else
        dv.cfg.tras := "00";
      end if;
      if MHz > 133 then
        dv.cfg.twr := '1';
      else
        dv.cfg.twr := '0';
      end if;
      
      dv.sdo_csn := "11";
      dv.sdo_dqm         := (others => '1');
      dv.sdo_wen         := '1';
      dv.sdo_rasn          := '1';
      dv.sdo_casn          := '1';

      -- Extra reset for X-sensitive techs
      dv.ramaddr := (others => '0');
    end if;

    ---------------------------------------------------------------------------
    -- Static logic/forced regs, etc
    ---------------------------------------------------------------------------
    
    -- Force mobile disable/enabled
    if mobile=0 then dv.cfg.mobileen := '0'; end if;
    if mobile=3 then dv.cfg.mobileen := '1'; end if;
    if mobile=0 then
      dv.cfg.pasr := (others => '0');
      dv.cfg.tcsr := (others => '0');
      dv.cfg.ds := (others => '0');
      dv.cfg.pmode := (others => '0');
      dv.cfg.txp := "00";
      dv.cfg.txsr := (others => '0');
      dv.cfg.tcke := '0';
    end if;

    if ddr400=0 then
      dv.cfg.tras := "00";
      dv.cfg.txsr(5 downto 4) := "00";
      dv.cfg.txp(1) := '0';
      dv.cfg.trp(1) := '0';
      dv.cfg.trfc(4 downto 3) := "00";
      dv.cfg.twr := '0';
    end if;
    
    -- Assign sdo
    o.bdrive := '1'; o.qdrive := '1';   --Temp.
    o.sdck := dr.sdo_ck;
    if ddr_syncrst/=0 and phyptctrl/=0 then
      o.sdck := o.sdck and (o.sdck'range => ddr_rst);
    end if;
    
    if regoutput /= 0 then
      o.casn    := dr.sdo_casn;
      o.rasn    := dr.sdo_rasn;
      o.sdwen   := dr.sdo_wen;
      o.sdcsn   := dr.sdo_csn;      
      o.ba      := '0' & dr.sdo_ba;
      o.address := dr.sdo_address;
      o.sdcke   := (others => dr.cfg.cke);
      if ddr_syncrst /= 0 and phyptctrl /= 0 then
        if ddr_rst='0' then
          if mobile >= 2 then o.sdcke := (others => '1');
          else                o.sdcke := (others => '0');
          end if;
        end if;
      end if;
      o.data(2*ddrbits-1 downto 0) := dr.sdo_data;
      o.dqm(ddrbits/4-1 downto 0)  := dr.sdo_dqm;
      if chkbits > 0 then
        o.cb(2*chkbits-1 downto 0)   := dr.sdo_cb(2*chkbits-1 downto 0);
      end if;
      o.bdrive := dr.sdo_bdrive;
      o.qdrive := dr.sdo_qdrive;
    else
      o.casn    := dv.sdo_casn;
      o.rasn    := dv.sdo_rasn;
      o.sdwen   := dv.sdo_wen;
      o.sdcsn   := dv.sdo_csn;
      o.ba      := '0' & dv.sdo_ba;
      o.address := dv.sdo_address;
      o.sdcke   := (others => dv.cfg.cke);
      o.data(2*ddrbits-1 downto 0) := dv.sdo_data;
      o.dqm(ddrbits/4-1 downto 0)  := dv.sdo_dqm;
      if chkbits > 0 then
        o.cb(2*chkbits-1 downto 0)   := dv.sdo_cb(2*chkbits-1 downto 0);
      end if;
      o.bdrive := dv.sdo_bdrive;
      o.qdrive := dv.sdo_qdrive;
     end if;
    for x in 7 downto 0 loop
      o.cbdqm(x) := o.dqm(2*x);
    end loop;

    -- Diag access
    if vreq.maskcb='1' then
      o.cbdqm := (others => '1');
    end if;
    if vreq.maskdata='1' then
      o.dqm := (others => '1');
    end if;

    if scantest/=0 and phyptctrl/=0 then
      if testen='1' then
        o.bdrive := testoen;
        o.qdrive := testoen;
      end if;
    end if;
    
    ---------------------------------------------------------------------------
    -- Drive outputs
    ---------------------------------------------------------------------------
    ndr <= dv;
    sdo <= o;
    response <= dr.resp;
    response2 <= dr.resp2;
    rbwrite <= rbw;
    rbwaddr <= dr.ramaddr;
    rbwdata <= rbwd;
    wbraddr <= vdone & dv.ramaddr;
  end process;

  ddrregs: process(clk_ddr,arst)
  begin
    if rising_edge(clk_ddr) then
      dr <= ndr;
    end if;
    if ddr_syncrst=0 and arst='0' then
      dr.sdo_ck <= "000";
      if mobile >= 2 then
           dr.cfg.cke      <= '1';
      else dr.cfg.cke      <= '0';
      end if;
    end if;
  end process;
  
end;

