From f33b285af10d4f415f230a64faaa6e1dc162ab99 Mon Sep 17 00:00:00 2001 From: Yann Sionneau Date: Fri, 31 Oct 2014 23:36:06 +0100 Subject: [PATCH] Minicon: small SDRAM controller --- misoclib/gensoc/__init__.py | 53 ++++++--- misoclib/lasmicon/minicon.py | 203 +++++++++++++++++++++++++++++++++ misoclib/lasmicon/minicontb.py | 192 +++++++++++++++++++++++++++++++ 3 files changed, 433 insertions(+), 15 deletions(-) create mode 100755 misoclib/lasmicon/minicon.py create mode 100755 misoclib/lasmicon/minicontb.py diff --git a/misoclib/gensoc/__init__.py b/misoclib/gensoc/__init__.py index c821639a8..661b1d109 100644 --- a/misoclib/gensoc/__init__.py +++ b/misoclib/gensoc/__init__.py @@ -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) + 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) + 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()) + 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) + # 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 index 000000000..6cd36744f --- /dev/null +++ b/misoclib/lasmicon/minicon.py @@ -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 index 000000000..8fb982b43 --- /dev/null +++ b/misoclib/lasmicon/minicontb.py @@ -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)