------------------------------------------------------------------------------ -- 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 - 2017, 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: ddrram -- File: ddrram.vhd -- Author: Magnus Hjorth, Aeroflex Gaisler -- Description: Generic simulation model of DDR SDRAM (JESD79E) ------------------------------------------------------------------------------ --pragma translate_off use std.textio.all; library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; library grlib; use grlib.stdio.hread; use grlib.stdlib.all; entity ddrram is generic ( width: integer := 32; abits: integer range 12 to 14 := 12; colbits: integer range 8 to 13 := 8; rowbits: integer range 1 to 14 := 12; implbanks: integer range 1 to 4 := 1; fname: string; lddelay: time := (0 ns); speedbin: integer range 0 to 5 := 0; -- 0:DDR200,1:266,2:333,3:400C,4:400B,5:400A density: integer range 0 to 3 := 0; -- 0:128Mbit 1:256Mbit 2:512Mbit 3:1Gbit / chip igndqs: integer range 0 to 1 := 0 ); port ( ck: in std_ulogic; cke: in std_ulogic; csn: in std_ulogic; rasn: in std_ulogic; casn: in std_ulogic; wen: in std_ulogic; dm: in std_logic_vector(width/8-1 downto 0); ba: in std_logic_vector(1 downto 0); a: in std_logic_vector(abits-1 downto 0); dq: inout std_logic_vector(width-1 downto 0); dqs: inout std_logic_vector(width/8-1 downto 0) ); end; architecture sim of ddrram is type moderegs is record -- Mode register (0) opmode: std_logic_vector(6 downto 0); caslat: std_logic_vector(2 downto 0); bt: std_ulogic; blen: std_logic_vector(2 downto 0); -- Extended mode register (1) opmode1: std_logic_vector(10 downto 0); res1: std_ulogic; ds: std_ulogic; dlldis: std_ulogic; end record; -- Mode registers as signal, useful for debugging signal mr: moderegs; -- Handshaking between command and DQ/DQS processes signal read_en, write_en: boolean := false; signal hcmode: boolean := false; -- Shift DQS/read data one cycle for CL=1.5/2.5 signal hcread_en: boolean := false; -- One cycle earlier for half-cycle mode read preamble gen signal read_data, write_data: std_logic_vector(2*width-1 downto 0); signal write_mask: std_logic_vector(width/4-1 downto 0); signal initdone: boolean := false; -- Small delta-t to adjust calculations for jitter tol. constant deltat: time := 50 ps; -- Timing parameters constant tWR: time := 15 ns; constant tMRD_ck: integer := 2; type timetab is array (0 to 5) of time; constant tRAS : timetab := (50 ns, 45 ns, 42 ns, 40 ns, 40 ns, 40 ns); constant tRP : timetab := (20 ns, 20 ns, 18 ns, 18 ns, 15 ns, 15 ns); constant tRCD: timetab := (20 ns, 20 ns, 18 ns, 18 ns, 15 ns, 15 ns); constant tRRD: timetab := (15 ns, 15 ns, 12 ns, 10 ns, 10 ns, 10 ns); constant tRFC_lt1G: timetab := (80 ns, 75 ns, 72 ns, 70 ns, 70 ns, 70 ns); --Assuming<1Gb constant tRFC_mt1G: time := 120 ns; function tRFC return time is begin if density < 3 then return tRFC_lt1G(speedbin); else return tRFC_mt1G; end if; end tRFC; begin ----------------------------------------------------------------------------- -- Init sequence checker ----------------------------------------------------------------------------- initp: process variable cyctr : integer := 0; procedure checkcmd(crasn,ccasn,cwen: std_ulogic; cba: std_logic_vector(1 downto 0); a10,a8,a0: std_ulogic) is begin wait until rising_edge(ck); cyctr := cyctr+1; while cke='1' and (csn='1' or (rasn='1' and casn='1' and wen='1')) loop wait until rising_edge(ck); cyctr := cyctr+1; end loop; assert cke='1' and csn='0' and rasn=crasn and casn=ccasn and wen=cwen and (cba="--" or cba=ba) and (a10='-' or a10=a(10)) and (a8='-' or a8=a(8)) and (a0='-' or a0=a(0)) report "Wrong command during init sequence" severity warning; end checkcmd; begin initdone <= false; -- Allow cke to be X or U for a while during sim start if is_x(cke) then wait until not is_x(cke); end if; assert cke='0' report "CKE not deasserted on power-up" severity warning; wait until cke/='0' for 200 us; assert cke='0' report "CKE raised with less than 200 us init delay" severity warning; wait until cke/='0' and rising_edge(ck); assert cke='1' and (csn='1' or (rasn='1' and casn='1' and wen='1')); -- Precharge all checkcmd('0','1','0',"--",'1','-','-'); -- EMRS enable DLL checkcmd('0','0','0',"01",'-','-','0'); -- MRS reset DLL checkcmd('0','0','0',"00",'-','1','-'); cyctr := 0; -- 200 cycle NOP -- Precharge all checkcmd('0','1','0',"--",'1','-','-'); assert cyctr >= 200 report "Command issued too quickly after DLL reset" severity warning; -- 2 x auto refresh checkcmd('0','0','1',"--",'-','-','-'); checkcmd('0','0','1',"--",'-','-','-'); -- MRS !reset DLL checkcmd('0','0','0',"00",'-','0','-'); initdone <= true; wait; end process; ----------------------------------------------------------------------------- -- Command state machine ----------------------------------------------------------------------------- cmdp: process(ck) subtype coldata is std_logic_vector(width-1 downto 0); type coldata_arr is array(0 to implbanks*(2**(colbits+rowbits))-1) of coldata; variable memdata: coldata_arr; procedure load_srec is file TCF : text open read_mode is fname; variable L1: line; variable CH : character; variable rectype : std_logic_vector(3 downto 0); variable recaddr : std_logic_vector(31 downto 0); variable reclen : std_logic_vector(7 downto 0); variable recdata : std_logic_vector(0 to 16*8-1); variable col, coloffs, len: integer; begin L1:= new string'(""); while not endfile(TCF) loop readline(TCF,L1); if (L1'length /= 0) then while (not (L1'length=0)) and (L1(L1'left) = ' ') loop std.textio.read(L1,CH); end loop; if L1'length > 0 then read(L1, ch); if (ch = 'S') or (ch = 's') then hread(L1, rectype); hread(L1, reclen); len := to_integer(unsigned(reclen))-1; recaddr := (others => '0'); case rectype is when "0001" => hread(L1, recaddr(15 downto 0)); len := len - 2; when "0010" => hread(L1, recaddr(23 downto 0)); len := len - 3; when "0011" => hread(L1, recaddr); len := len - 4; when others => next; end case; hread(L1, recdata(0 to len*8-1)); col := to_integer(unsigned(recaddr(log2(width/8)+rowbits+colbits+1 downto log2(width/8)))); coloffs := 8*to_integer(unsigned(recaddr(log2(width/8)-1 downto 0))); while len > width/8 loop assert coloffs=0; memdata(col) := recdata(0 to width-1); col := col+1; len := len-width/8; recdata(0 to recdata'length-width-1) := recdata(width to recdata'length-1); end loop; memdata(col)(width-1-coloffs downto width-coloffs-len*8) := recdata(0 to len*8-1); end if; end if; end if; end loop; end load_srec; variable vmr: moderegs := ((others => '0'), "UUU", 'U', "UUU", (others => '0'), '0', '0', '0'); type bankstate is record openrow: integer; opentime: time; closetime: time; writetime: time; autopch: integer; end record; type bankstate_arr is array(natural range <>) of bankstate; variable banks: bankstate_arr(3 downto 0) := (others => (-1, 0 ns, 0 ns, 0 ns, -1)); type int_arr is array(natural range <>) of integer; type dataacc is record r,w: boolean; col: int_arr(0 to 1); bank: integer; end record; type dataacc_arr is array(natural range <>) of dataacc; variable accpipe: dataacc_arr(0 to 9); variable cmd: std_logic_vector(2 downto 0); variable bank: integer; variable colv: unsigned(a'high-1 downto 0); variable alow: unsigned(2 downto 0); variable col: integer; variable prev_re, re: time; variable blen: integer; variable lastref: time := 0 ns; variable i: integer; variable b: boolean; variable mrscount: integer := 0; variable loaded: boolean := false; procedure checktime(got, exp: time; gt: boolean; req: string) is begin assert (got + deltat > exp and gt) or (got-deltat < exp and not gt) report (req & " violation, got: " & tost(got/(1 ps)) & " ps, exp: " & tost(exp/(1 ps)) & "ps") severity warning; end checktime; begin if rising_edge(ck) then -- Update pipe regs prev_re := re; re := now; accpipe(1 to accpipe'high) := accpipe(0 to accpipe'high-1); accpipe(0).r:=false; accpipe(0).w:=false; -- Main command handler cmd := rasn & casn & wen; if mrscount > 0 then mrscount := mrscount-1; assert cke='1' and (csn='1' or cmd="111") report "tMRS violation!" severity warning; end if; if cke='1' and csn='0' and cmd/="111" then checktime(now-lastref, tRFC, true, "tRFC"); end if; if cke='1' and csn='0' then case cmd is when "111" => -- NOP when "011" => -- RAS assert initdone report "Opening row before init sequence done!" severity warning; bank := to_integer(unsigned(ba)); assert banks(bank).openrow < 0 report "Row already open" severity warning; checktime(now-banks(bank).closetime, tRP(speedbin), true, "tRP"); for x in 0 to 3 loop checktime(now-banks(x).opentime, tRRD(speedbin), true, "tRRD"); end loop; banks(bank).openrow := to_integer(unsigned(a(rowbits-1 downto 0))); banks(bank).opentime := now; when "101" | "100" => -- Read/Write bank := to_integer(unsigned(ba)); assert banks(bank).openrow >= 0 report "Row not open" severity error; checktime(now-banks(bank).opentime, tRCD(speedbin), true, "tRCD"); for x in 0 to 3 loop -- Xilinx V4 MIG controller issues multiple overlapping load commands -- during calibration, therefore this assertion is bypassed before -- load-delay has passed. assert (not accpipe(x).r and not accpipe(x).w) or (now < lddelay); end loop; if cmd(0)='1' then accpipe(3).r:=true; else accpipe(3).w:=true; end if; colv := unsigned(std_logic_vector'(a(a'high downto 11) & a(9 downto 0))); case vmr.blen is when "001" => blen := 2; when "010" => blen := 4; when "011" => blen := 8; when others => assert false report "Invalid burst length setting in MR!" severity error; end case; alow := unsigned(a(2 downto 0)); for x in 0 to blen-1 loop accpipe(3-x/2).bank := bank; if cmd(0)='1' then accpipe(3-x/2).r:=true; else accpipe(3-x/2).w:=true; end if; if vmr.bt='0' then -- Sequential colv(log2(blen)-1 downto 0) := alow(log2(blen)-1 downto 0) + x; else -- Interleaved colv(log2(blen)-1 downto 0) := alow(log2(blen)-1 downto 0) xor to_unsigned(x,log2(blen)); end if; col := to_integer(unsigned(ba))*(2**(colbits+rowbits)) + banks(bank).openrow * (2**colbits) + to_integer(colv(colbits-1 downto 0)); accpipe(3-x/2).col(x mod 2) := col; end loop; -- Auto precharge if a(10)='1' then if cmd(0)='1' then banks(bank).autopch := blen/2; else banks(bank).autopch := 1+blen/2 + (tWR-deltat+(re-prev_re))/(re-prev_re); end if; end if; when "110" => -- Burst terminate assert not accpipe(3).w report "Burst terminate on write burst!" severity warning; assert banks(accpipe(3).bank).autopch<0 report "Burst terminate on read with auto-precharge!" severity warning; assert accpipe(3).r report "Burst terminate with no effect!" severity warning; for x in 3 downto 0 loop accpipe(x).r := false; accpipe(x).w := false; end loop; when "010" => -- Precharge for x in 3 downto 0 loop accpipe(x).r := false; accpipe(x).w := false; end loop; for x in 0 to 3 loop if a(10)='1' or ba=std_logic_vector(to_unsigned(x,2)) then assert banks(x).autopch<0 report "Precharging bank that is auto-precharged" severity note; assert a(10)='1' or banks(x).openrow>=0 report "Precharging single bank that is in idle state" severity note; banks(x).autopch := 0; -- Handled below end if; end loop; when "001" => -- Auto refresh for x in 0 to 3 loop assert banks(x).openrow < 0 report "Bank in wrong state for auto refresh!" severity warning; checktime(now-banks(x).closetime, tRP(speedbin), true, "tRP"); end loop; lastref := now; when "000" => -- MRS for x in 0 to 3 loop checktime(now-banks(x).closetime, tRP(speedbin), true, "tRP"); end loop; case ba is when "00" => vmr.opmode(a'high-7 downto 0) := a(a'high downto 7); vmr.caslat := a(6 downto 4); vmr.bt := a(3); vmr.blen := a(2 downto 0); when "01" => vmr.opmode1(a'high-3 downto 0) := a(a'high downto 3); vmr.res1 := a(2); vmr.ds := a(1); vmr.dlldis := a(0); when others => assert false report ("MRS to invalid bank addr: " & std_logic'image(ba(1)) & std_logic'image(ba(0))) severity warning; end case; mrscount := tMRD_ck-1; when others => assert false report ("Invalid command: " & std_logic'image(rasn) & std_logic'image(casn) & std_logic'image(wen)) severity warning; end case; end if; -- Manual or auto precharge for x in 0 to 3 loop if banks(x).autopch=0 then checktime(now-banks(x).writetime, tWR, true, "tWR"); checktime(now-banks(x).opentime, tRAS(speedbin), true, "tRAS"); banks(x).openrow := -1; banks(x).closetime := now; end if; if banks(x).autopch >= 0 then banks(x).autopch := banks(x).autopch - 1; end if; end loop; -- Read/write management if not loaded and lddelay < now then load_srec; loaded := true; end if; case vmr.caslat is when "010" => i := 2; b:=false; -- CL2 when "011" => i := 3; b:=false; -- CL3 when "101" => i := 2; b:=true; -- CL1.5 when "110" => i := 3; b:=true; -- CL2.5 when others => i := 1; end case; hcmode <= b; if b then hcread_en <= accpipe(1+i).r; else hcread_en <= false; end if; if accpipe(2+i).r then assert i>1 report "Incorrect CL setting!" severity warning; read_en <= true; -- print("Reading from col " & tost(accpipe(2+i).col(0)) & " and " & tost(accpipe(2+i).col(1))); -- col0 <= accpipe(2+i).col(0); col1 <= accpipe(2+i).col(1); read_data <= memdata(accpipe(2+i).col(0)) & memdata(accpipe(2+i).col(1)); else read_en <= false; end if; write_en <= accpipe(3).w or accpipe(4).w; if accpipe(5).w and write_mask/=(write_mask'range => '1') then assert not is_x(write_mask) report "Write error"; for x in 0 to 1 loop for b in width/8-1 downto 0 loop if write_mask((1-x)*width/8+b)='0' then memdata(accpipe(5).col(x))(8*b+7 downto 8*b) := write_data( (1-x)*width+b*8+7 downto (1-x)*width+b*8); end if; end loop; end loop; banks(accpipe(5).bank).writetime := now; end if; end if; mr <= vmr; end process; ----------------------------------------------------------------------------- -- DQS/DQ handling and data sampling process ----------------------------------------------------------------------------- dqproc: process variable rdata: std_logic_vector(2*width-1 downto 0); variable hdata: std_logic_vector(width-1 downto 0); variable hmask: std_logic_vector(width/8-1 downto 0); variable prevdqs: std_logic_vector(width/8-1 downto 0); begin dq <= (others => 'Z'); dqs <= (others => 'Z'); wait until (hcmode and hcread_en) or read_en or write_en; assert not ((read_en or hcread_en) and write_en); if (read_en or hcread_en) then if hcmode then wait until falling_edge(ck); end if; dqs <= (others => '0'); wait until falling_edge(ck); while read_en loop rdata := read_data; if not hcmode then wait until rising_edge(ck); end if; dqs <= (others => '1'); dq <= rdata(2*width-1 downto width); if hcmode then wait until rising_edge(ck); else wait until falling_edge(ck); end if; dqs <= (others => '0'); dq <= rdata(width-1 downto 0); if hcmode then wait until falling_edge(ck); end if; end loop; if not hcmode then wait until rising_edge(ck); end if; else wait until falling_edge(ck); assert to_X01(dqs)=(dqs'range => '0') or igndqs/=0; while write_en loop prevdqs := to_X01(dqs); if igndqs /= 0 then wait on ck,write_en; else wait until to_X01(dqs) /= prevdqs or not write_en or rising_edge(ck); end if; if rising_edge(ck) then -- Just to make sure missing DQS is not undetected write_data <= (others => 'X'); write_mask <= (others => 'X'); end if; for x in dqs'range loop if (igndqs=0 and prevdqs(x)='0' and to_X01(dqs(x))='1') or (igndqs/=0 and rising_edge(ck)) then hdata(8*x+7 downto 8*x) := dq(8*x+7 downto 8*x); hmask(x) := dm(x); elsif (igndqs=0 and prevdqs(x)='1' and to_X01(dqs(x))='0') or (igndqs/=0 and falling_edge(ck)) then write_data(width+8*x+7 downto width+8*x) <= hdata(8*x+7 downto 8*x); write_data(8*x+7 downto 8*x) <= dq(8*x+7 downto 8*x); write_mask(width/8+x) <= hmask(x); write_mask(x) <= dm(x); end if; end loop; end loop; end if; end process; end; -- pragma translate_on