ReonV/lib/gaisler/sim/ddrram.vhd

520 lines
20 KiB
VHDL

------------------------------------------------------------------------------
-- 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