Minicon: small SDRAM controller
authorYann Sionneau <ys@m-labs.hk>
Fri, 31 Oct 2014 22:36:06 +0000 (23:36 +0100)
committerSebastien Bourdeauducq <sb@m-labs.hk>
Thu, 27 Nov 2014 14:09:03 +0000 (22:09 +0800)
misoclib/gensoc/__init__.py
misoclib/lasmicon/minicon.py [new file with mode: 0755]
misoclib/lasmicon/minicontb.py [new file with mode: 0755]

index c821639a8570090038788cd2b67a057c55bf4b88..661b1d109a619f65c6b3137b7228480ae9c361ed 100644 (file)
@@ -9,6 +9,7 @@ from migen.bus import wishbone, csr, lasmibus, dfi
 from migen.bus import wishbone2lasmi, wishbone2csr
 
 from misoclib import lm32, mor1kx, uart, dfii, lasmicon, identifier, timer, memtest
+from misoclib.lasmicon.minicon import Minicon
 
 class GenSoC(Module):
        csr_base = 0xe0000000
@@ -136,14 +137,15 @@ class SDRAMSoC(GenSoC):
        csr_map = {
                "dfii":                                 6,
                "lasmicon":                             7,
-               "memtest_w":                    8,
-               "memtest_r":                    9
+               "memtest_w":                            8,
+               "memtest_r":                            9
        }
        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)
                self.with_memtest = with_memtest
+               self.ramcon_type = ramcon_type
                self._sdram_phy_registered = False
 
        def register_sdram_phy(self, phy_dfi, phy_settings, sdram_geom, sdram_timing):
@@ -156,21 +158,42 @@ class SDRAMSoC(GenSoC):
                        phy_settings.dfi_d, phy_settings.nphases)
                self.submodules.dficon0 = dfi.Interconnect(self.dfii.master, phy_dfi)
 
-               # LASMI
-               self.submodules.lasmicon = lasmicon.LASMIcon(phy_settings, sdram_geom, sdram_timing)
-               self.submodules.dficon1 = dfi.Interconnect(self.lasmicon.dfi, self.dfii.slave)
-
-               self.submodules.lasmixbar = lasmibus.Crossbar([self.lasmicon.lasmic], self.lasmicon.nrowbits)
-
-               if self.with_memtest:
-                       self.submodules.memtest_w = memtest.MemtestWriter(self.lasmixbar.get_master())
-                       self.submodules.memtest_r = memtest.MemtestReader(self.lasmixbar.get_master())
-
-               # Wishbone bridge: map SDRAM at 0x40000000 (shadow @0xc0000000)
-               self.submodules.wishbone2lasmi = wishbone2lasmi.WB2LASMI(self.l2_size//4, self.lasmixbar.get_master())
-               self.add_wb_slave(lambda a: a[27:29] == 2, self.wishbone2lasmi.wishbone)
-               self.add_cpu_memory_region("sdram", 0x40000000,
-                       2**self.lasmicon.lasmic.aw*self.lasmicon.lasmic.dw*self.lasmicon.lasmic.nbanks//8)
+               if self.ramcon_type == "lasmicon":
+                       # LASMI
+                       self.submodules.lasmicon = lasmicon.LASMIcon(phy_settings, sdram_geom, sdram_timing)
+                       self.submodules.dficon1 = dfi.Interconnect(self.lasmicon.dfi, self.dfii.slave)
+
+                       self.submodules.lasmixbar = lasmibus.Crossbar([self.lasmicon.lasmic], self.lasmicon.nrowbits)
+
+                       if self.with_memtest:
+                               self.submodules.memtest_w = memtest.MemtestWriter(self.lasmixbar.get_master())
+                               self.submodules.memtest_r = memtest.MemtestReader(self.lasmixbar.get_master())
+
+                       # Wishbone bridge: map SDRAM at 0x40000000 (shadow @0xc0000000)
+                       self.submodules.wishbone2lasmi = wishbone2lasmi.WB2LASMI(self.l2_size//4, self.lasmixbar.get_master())
+                       self.add_wb_slave(lambda a: a[27:29] == 2, self.wishbone2lasmi.wishbone)
+                       self.add_cpu_memory_region("sdram", 0x40000000,
+                               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):
                if not self._sdram_phy_registered:
diff --git a/misoclib/lasmicon/minicon.py b/misoclib/lasmicon/minicon.py
new file mode 100755 (executable)
index 0000000..6cd3674
--- /dev/null
@@ -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)
diff --git a/misoclib/lasmicon/minicontb.py b/misoclib/lasmicon/minicontb.py
new file mode 100755 (executable)
index 0000000..8fb982b
--- /dev/null
@@ -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)