Minicon: small SDRAM controller

This commit is contained in:
Yann Sionneau 2014-10-31 23:36:06 +01:00 committed by Sebastien Bourdeauducq
parent 5202f89db1
commit f33b285af1
3 changed files with 433 additions and 15 deletions

View File

@ -9,6 +9,7 @@ from migen.bus import wishbone, csr, lasmibus, dfi
from migen.bus import wishbone2lasmi, wishbone2csr from migen.bus import wishbone2lasmi, wishbone2csr
from misoclib import lm32, mor1kx, uart, dfii, lasmicon, identifier, timer, memtest from misoclib import lm32, mor1kx, uart, dfii, lasmicon, identifier, timer, memtest
from misoclib.lasmicon.minicon import Minicon
class GenSoC(Module): class GenSoC(Module):
csr_base = 0xe0000000 csr_base = 0xe0000000
@ -141,9 +142,10 @@ class SDRAMSoC(GenSoC):
} }
csr_map.update(GenSoC.csr_map) csr_map.update(GenSoC.csr_map)
def __init__(self, platform, clk_freq, cpu_reset_address, with_memtest=False, sram_size=4096, l2_size=8192, with_uart=True, **kwargs): def __init__(self, platform, clk_freq, cpu_reset_address, with_memtest=False, sram_size=4096, l2_size=8192, with_uart=True, ramcon_type="lasmicon", **kwargs):
GenSoC.__init__(self, platform, clk_freq, cpu_reset_address, sram_size, l2_size, with_uart, **kwargs) GenSoC.__init__(self, platform, clk_freq, cpu_reset_address, sram_size, l2_size, with_uart, **kwargs)
self.with_memtest = with_memtest self.with_memtest = with_memtest
self.ramcon_type = ramcon_type
self._sdram_phy_registered = False self._sdram_phy_registered = False
def register_sdram_phy(self, phy_dfi, phy_settings, sdram_geom, sdram_timing): def register_sdram_phy(self, phy_dfi, phy_settings, sdram_geom, sdram_timing):
@ -156,6 +158,7 @@ class SDRAMSoC(GenSoC):
phy_settings.dfi_d, phy_settings.nphases) phy_settings.dfi_d, phy_settings.nphases)
self.submodules.dficon0 = dfi.Interconnect(self.dfii.master, phy_dfi) self.submodules.dficon0 = dfi.Interconnect(self.dfii.master, phy_dfi)
if self.ramcon_type == "lasmicon":
# LASMI # LASMI
self.submodules.lasmicon = lasmicon.LASMIcon(phy_settings, sdram_geom, sdram_timing) self.submodules.lasmicon = lasmicon.LASMIcon(phy_settings, sdram_geom, sdram_timing)
self.submodules.dficon1 = dfi.Interconnect(self.lasmicon.dfi, self.dfii.slave) self.submodules.dficon1 = dfi.Interconnect(self.lasmicon.dfi, self.dfii.slave)
@ -171,6 +174,26 @@ class SDRAMSoC(GenSoC):
self.add_wb_slave(lambda a: a[27:29] == 2, self.wishbone2lasmi.wishbone) self.add_wb_slave(lambda a: a[27:29] == 2, self.wishbone2lasmi.wishbone)
self.add_cpu_memory_region("sdram", 0x40000000, self.add_cpu_memory_region("sdram", 0x40000000,
2**self.lasmicon.lasmic.aw*self.lasmicon.lasmic.dw*self.lasmicon.lasmic.nbanks//8) 2**self.lasmicon.lasmic.aw*self.lasmicon.lasmic.dw*self.lasmicon.lasmic.nbanks//8)
elif self.ramcon_type == "minicon":
rdphase = phy_settings.rdphase
self.submodules.minicon = sdramcon = Minicon(phy_settings, sdram_geom, sdram_timing)
self.submodules.dficon1 = dfi.Interconnect(sdramcon.dfi, self.dfii.slave)
sdram_width = flen(sdramcon.bus.dat_r)
if (sdram_width == 32):
self.add_wb_slave(lambda a: a[27:29] == 2, sdramcon.bus)
elif (sdram_width < 32):
self.submodules.dc = dc = wishbone.DownConverter(32, sdram_width)
self.submodules.intercon = wishbone.InterconnectPointToPoint(dc.wishbone_o, sdramcon.bus)
self.add_wb_slave(lambda a: a[27:29] == 2, dc.wishbone_i)
else:
raise NotImplementedError("Unsupported SDRAM width of {} > 32".format(sdram_width))
# map SDRAM at 0x40000000 (shadow @0xc0000000)
self.add_cpu_memory_region("sdram", 0x40000000,
2**(sdram_geom.bank_a+sdram_geom.row_a+sdram_geom.col_a)*sdram_width//8)
else:
raise ValueError("Unsupported SDRAM controller type: {}".format(self.ramcon_type))
def do_finalize(self): def do_finalize(self):
if not self._sdram_phy_registered: if not self._sdram_phy_registered:

203
misoclib/lasmicon/minicon.py Executable file
View File

@ -0,0 +1,203 @@
from migen.fhdl.std import *
from migen.bus import wishbone
from migen.bus import dfi as dfibus
from migen.genlib.fsm import FSM, NextState
class _AddressSlicer:
def __init__(self, col_a, bank_a, row_a, address_align):
self.col_a = col_a
self.bank_a = bank_a
self.row_a = row_a
self.max_a = col_a + row_a + bank_a
self.address_align = address_align
def row(self, address):
split = self.bank_a + self.col_a
if isinstance(address, int):
return address >> split
else:
return address[split:self.max_a]
def bank(self, address):
mask = 2**(self.bank_a + self.col_a) - 1
shift = self.col_a
if isinstance(address, int):
return (address & mask) >> shift
else:
return address[self.col_a:self.col_a+self.bank_a]
def col(self, address):
split = self.col_a
if isinstance(address, int):
return (address & (2**split - 1)) << self.address_align
else:
return Cat(Replicate(0, self.address_align), address[:split])
class Minicon(Module):
def __init__(self, phy_settings, geom_settings, timing_settings):
if phy_settings.memtype in ["SDR"]:
burst_length = phy_settings.nphases*1 # command multiplication*SDR
elif phy_settings.memtype in ["DDR", "LPDDR", "DDR2", "DDR3"]:
burst_length = phy_settings.nphases*2 # command multiplication*DDR
address_align = log2_int(burst_length)
nbanks = range(2**geom_settings.bank_a)
A10_ENABLED = 0
COLUMN = 1
ROW = 2
rdphase = phy_settings.rdphase
wrphase = phy_settings.wrphase
rdcmdphase = phy_settings.rdcmdphase
wrcmdphase = phy_settings.wrcmdphase
self.dfi = dfi = dfibus.Interface(geom_settings.mux_a,
geom_settings.bank_a,
phy_settings.dfi_d,
phy_settings.nphases)
self.bus = bus = wishbone.Interface(data_width=phy_settings.nphases*flen(dfi.phases[rdphase].rddata))
slicer = _AddressSlicer(geom_settings.col_a, geom_settings.bank_a, geom_settings.row_a, address_align)
req_addr = Signal(geom_settings.col_a + geom_settings.bank_a + geom_settings.row_a)
refresh_req = Signal()
refresh_ack = Signal()
wb_access = Signal()
refresh_counter = Signal(max=timing_settings.tREFI+1)
hit = Signal()
row_open = Signal()
row_closeall = Signal()
addr_sel = Signal(max=3, reset=A10_ENABLED)
has_curbank_openrow = Signal()
cl_counter = Signal(max=phy_settings.cl+1)
# Extra bit means row is active when asserted
self.openrow = openrow = Array(Signal(geom_settings.row_a + 1) for b in nbanks)
self.comb += [
hit.eq(openrow[slicer.bank(bus.adr)] == Cat(slicer.row(bus.adr), 1)),
has_curbank_openrow.eq(openrow[slicer.bank(bus.adr)][-1]),
wb_access.eq(bus.stb & bus.cyc),
bus.dat_r.eq(Cat([phase.rddata for phase in dfi.phases])),
Cat([phase.wrdata for phase in dfi.phases]).eq(bus.dat_w),
Cat([phase.wrdata_mask for phase in dfi.phases]).eq(~bus.sel),
]
for phase in dfi.phases:
self.comb += [
phase.cke.eq(1),
phase.address.eq(Array([2**10, slicer.col(bus.adr), slicer.row(bus.adr)])[addr_sel]),
If(wb_access,
phase.bank.eq(slicer.bank(bus.adr))
)
]
phase.cs_n.reset = 0
phase.ras_n.reset = 1
phase.cas_n.reset = 1
phase.we_n.reset = 1
for b in nbanks:
self.sync += [
If(row_open & (b == slicer.bank(bus.adr)),
openrow[b].eq(Cat(slicer.row(bus.adr), 1)),
),
If(row_closeall,
openrow[b][-1].eq(0)
)
]
self.sync += [
If(refresh_ack,
refresh_req.eq(0)
),
If(refresh_counter == 0,
refresh_counter.eq(timing_settings.tREFI),
refresh_req.eq(1)
).Else(
refresh_counter.eq(refresh_counter - 1)
)
]
fsm = FSM()
self.submodules += fsm
fsm.act("IDLE",
If(refresh_req,
NextState("PRECHARGEALL")
).Elif(wb_access,
If(hit & bus.we,
NextState("WRITE"),
),
If(hit & ~bus.we,
NextState("READ"),
),
If(has_curbank_openrow & ~hit,
NextState("PRECHARGE")
),
If(~has_curbank_openrow,
NextState("ACTIVATE")
),
)
)
fsm.act("READ",
# We output Column bits at address pins so that A10 is 0
# to disable row Auto-Precharge
dfi.phases[rdcmdphase].ras_n.eq(1),
dfi.phases[rdcmdphase].cas_n.eq(0),
dfi.phases[rdcmdphase].we_n.eq(1),
dfi.phases[rdphase].rddata_en.eq(1),
addr_sel.eq(COLUMN),
NextState("READ-WAIT-ACK"),
)
fsm.act("READ-WAIT-ACK",
If(dfi.phases[rdphase].rddata_valid,
NextState("IDLE"),
bus.ack.eq(1)
).Else(
NextState("READ-WAIT-ACK")
)
)
fsm.act("WRITE",
dfi.phases[wrcmdphase].ras_n.eq(1),
dfi.phases[wrcmdphase].cas_n.eq(0),
dfi.phases[wrcmdphase].we_n.eq(0),
dfi.phases[wrphase].wrdata_en.eq(1),
addr_sel.eq(COLUMN),
bus.ack.eq(1),
NextState("IDLE")
)
fsm.act("PRECHARGEALL",
row_closeall.eq(1),
dfi.phases[rdphase].ras_n.eq(0),
dfi.phases[rdphase].cas_n.eq(1),
dfi.phases[rdphase].we_n.eq(0),
addr_sel.eq(A10_ENABLED),
NextState("PRE-REFRESH")
)
fsm.act("PRECHARGE",
# Notes:
# 1. we are presenting the column address so that A10 is low
# 2. since we always go to the ACTIVATE state, we do not need
# to assert row_close because it will be reopen right after.
NextState("TRP"),
addr_sel.eq(COLUMN),
dfi.phases[rdphase].ras_n.eq(0),
dfi.phases[rdphase].cas_n.eq(1),
dfi.phases[rdphase].we_n.eq(0)
)
fsm.act("ACTIVATE",
row_open.eq(1),
NextState("TRCD"),
dfi.phases[rdphase].ras_n.eq(0),
dfi.phases[rdphase].cas_n.eq(1),
dfi.phases[rdphase].we_n.eq(1),
addr_sel.eq(ROW)
)
fsm.act("REFRESH",
refresh_ack.eq(1),
dfi.phases[rdphase].ras_n.eq(0),
dfi.phases[rdphase].cas_n.eq(0),
dfi.phases[rdphase].we_n.eq(1),
NextState("POST-REFRESH")
)
fsm.delayed_enter("TRP", "ACTIVATE", timing_settings.tRP-1)
fsm.delayed_enter("PRE-REFRESH", "REFRESH", timing_settings.tRP-1)
fsm.delayed_enter("TRCD", "IDLE", timing_settings.tRCD-1)
fsm.delayed_enter("POST-REFRESH", "IDLE", timing_settings.tRFC-1)

192
misoclib/lasmicon/minicontb.py Executable file
View File

@ -0,0 +1,192 @@
from migen.fhdl.std import *
from migen.bus.transactions import TRead, TWrite
from migen.bus import wishbone
from migen.sim.generic import Simulator
from migen.sim import icarus
from mibuild.platforms import papilio_pro as board
from misoclib import lasmicon
from misoclib.lasmicon.minicon import Minicon
from misoclib.sdramphy import gensdrphy
from itertools import chain
from os.path import isfile
import sys
clk_freq = 80000000
from math import ceil
def ns(t, margin=True):
clk_period_ns = 1000000000/clk_freq
if margin:
t += clk_period_ns/2
return ceil(t/clk_period_ns)
class MiniconTB(Module):
def __init__(self, sdrphy, dfi, sdram_geom, sdram_timing, pads, sdram_clk):
self.clk_freq = 80000000
phy_settings = sdrphy.phy_settings
rdphase = phy_settings.rdphase
self.submodules.slave = Minicon(phy_settings, sdram_geom, sdram_timing)
self.submodules.tap = wishbone.Tap(self.slave.bus)
self.submodules.dc = dc = wishbone.DownConverter(32, phy_settings.nphases*flen(dfi.phases[rdphase].rddata))
self.submodules.master = wishbone.Initiator(self.genxfers(), bus=dc.wishbone_i)
self.submodules.intercon = wishbone.InterconnectPointToPoint(dc.wishbone_o, self.slave.bus)
self.submodules.sdrphy = self.sdrphy = sdrphy
self.dfi = dfi
self.pads = pads
self.specials += Instance("mt48lc4m16a2",
io_Dq=pads.dq,
i_Addr=pads.a,
i_Ba=pads.ba,
i_Clk=ClockSignal(),
i_Cke=pads.cke,
i_Cs_n=pads.cs_n,
i_Ras_n=pads.ras_n,
i_Cas_n=pads.cas_n,
i_We_n=pads.we_n,
i_Dqm=pads.dm
)
def genxfers(self):
cycle = 0
for a in chain(range(4),range(256,260),range(1024,1028)):
t = TRead(a)
yield t
print("read {} in {} cycles".format(t.data, t.latency))
for a in chain(range(4),range(256,260),range(1024,1028),range(4096,4100)):
t = TWrite(a, 0xaa55aa55+cycle)
cycle += 1
yield t
print("read {} in {} cycles".format(t.data, t.latency))
for a in chain(range(4),range(256,260),range(1024,1028),range(4096,4100)):
t = TRead(a)
yield t
print("read {} in {} cycles".format(t.data, t.latency))
def gen_simulation(self, selfp):
dfi = selfp.dfi
phy = self.sdrphy
rdphase = phy.phy_settings.rdphase
cycle = 0
while True:
yield
class MyTopLevel:
def __init__(self, vcd_name=None, vcd_level=1,
top_name="top", dut_type="dut", dut_name="dut",
cd_name="sys", clk_period=10):
self.vcd_name = vcd_name
self.vcd_level = vcd_level
self.top_name = top_name
self.dut_type = dut_type
self.dut_name = dut_name
self._cd_name = cd_name
self._clk_period = clk_period
cd = ClockDomain(self._cd_name)
cd_ps = ClockDomain("sys_ps")
self.clock_domains = [cd, cd_ps]
self.ios = {cd.clk, cd.rst, cd_ps.clk}
def get(self, sockaddr):
template1 = """`timescale 1ns / 1ps
module {top_name}();
reg {clk_name};
reg {rst_name};
reg sys_ps_clk;
initial begin
{rst_name} <= 1'b1;
@(posedge {clk_name});
{rst_name} <= 1'b0;
end
always begin
{clk_name} <= 1'b0;
#{hclk_period};
{clk_name} <= 1'b1;
#{hclk_period};
end
always @(posedge {clk_name} or negedge {clk_name})
sys_ps_clk <= #({hclk_period}*2-3) {clk_name};
{dut_type} {dut_name}(
.{rst_name}({rst_name}),
.{clk_name}({clk_name}),
.sys_ps_clk(sys_ps_clk)
);
initial $migensim_connect("{sockaddr}");
always @(posedge {clk_name}) $migensim_tick;
"""
template2 = """
initial begin
$dumpfile("{vcd_name}");
$dumpvars({vcd_level}, {dut_name});
end
"""
r = template1.format(top_name=self.top_name,
dut_type=self.dut_type,
dut_name=self.dut_name,
clk_name=self._cd_name + "_clk",
rst_name=self._cd_name + "_rst",
hclk_period=str(self._clk_period/2),
sockaddr=sockaddr)
if self.vcd_name is not None:
r += template2.format(vcd_name=self.vcd_name,
vcd_level=str(self.vcd_level),
dut_name=self.dut_name)
r += "\nendmodule"
return r
if __name__ == "__main__":
plat = board.Platform()
sdram_geom = lasmicon.GeomSettings(
bank_a=2,
row_a=12,
col_a=8
)
sdram_timing = lasmicon.TimingSettings(
tRP=ns(15),
tRCD=ns(15),
tWR=ns(14),
tWTR=2,
tREFI=ns(64*1000*1000/4096, False),
tRFC=ns(66),
req_queue_size=8,
read_time=32,
write_time=16
)
sdram_pads = plat.request("sdram")
sdram_clk = plat.request("sdram_clock")
sdrphy = gensdrphy.GENSDRPHY(sdram_pads)
# This sets CL to 2 during LMR done on 1st cycle
sdram_pads.a.reset = 1<<5
s = MiniconTB(sdrphy, sdrphy.dfi, sdram_geom, sdram_timing, pads=sdram_pads, sdram_clk=sdram_clk)
extra_files = [ "sdram_model/mt48lc4m16a2.v" ]
if not isfile(extra_files[0]):
print("ERROR: You need to download Micron Verilog simulation model for MT48LC4M16A2 and put it in sdram_model/mt48lc4m16a2.v")
print("File can be downloaded from this URL: http://www.micron.com/-/media/documents/products/sim%20model/dram/dram/4054mt48lc4m16a2.zip")
sys.exit(1)
with Simulator(s, MyTopLevel("top.vcd", clk_period=int(1/0.08)), icarus.Runner(extra_files=extra_files, keep_files=True)) as sim:
sim.run(5000)