mirror of
https://github.com/lcbcFoo/ReonV.git
synced 2025-04-22 20:47:15 -04:00
520 lines
20 KiB
VHDL
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
|
|
|