Add DDRSoC simulation
authorJean THOMAS <git0@pub.jeanthomas.me>
Fri, 26 Jun 2020 13:20:34 +0000 (15:20 +0200)
committerJean THOMAS <git0@pub.jeanthomas.me>
Fri, 26 Jun 2020 13:20:34 +0000 (15:20 +0200)
gram/simulation/icarusecpix5platform.py [new file with mode: 0644]
gram/simulation/runsimsoc.sh [new file with mode: 0755]
gram/simulation/simsoc.py [new file with mode: 0644]
gram/simulation/uartbridge.py [new file with mode: 0644]

diff --git a/gram/simulation/icarusecpix5platform.py b/gram/simulation/icarusecpix5platform.py
new file mode 100644 (file)
index 0000000..5c9e866
--- /dev/null
@@ -0,0 +1,180 @@
+import os
+import subprocess
+
+from nmigen.build import *
+from nmigen.vendor.lattice_ecp5 import *
+from nmigen_boards.resources import *
+
+
+__all__ = ["IcarusECPIX5Platform"]
+
+
+class IcarusECPIX5Platform(LatticeECP5Platform):
+    device = "LFE5UM5G-85F"
+    package = "BG554"
+    speed = "8"
+    default_clk = "clk100"
+    default_rst = "rst"
+
+    resources = [
+        Resource("rst", 0, PinsN("AB1", dir="i"), Attrs(IO_TYPE="LVCMOS33")),
+        Resource("clk100", 0, Pins("K23", dir="i"),
+                 Clock(100e6), Attrs(IO_TYPE="LVCMOS33")),
+
+        RGBLEDResource(0, r="U21", g="W21", b="T24",
+                       attrs=Attrs(IO_TYPE="LVCMOS33")),
+        RGBLEDResource(1, r="T23", g="R21", b="T22",
+                       attrs=Attrs(IO_TYPE="LVCMOS33")),
+        RGBLEDResource(2, r="P21", g="R23", b="P22",
+                       attrs=Attrs(IO_TYPE="LVCMOS33")),
+        RGBLEDResource(3, r="K21", g="K24", b="M21",
+                       attrs=Attrs(IO_TYPE="LVCMOS33")),
+
+        UARTResource(0,
+                     rx="R26", tx="R24",
+                     attrs=Attrs(IO_TYPE="LVCMOS33", PULLMODE="UP")
+                     ),
+
+        *SPIFlashResources(0,
+                           cs="AA2", clk="AE3", miso="AE2", mosi="AD2", wp="AF2", hold="AE1",
+                           attrs=Attrs(IO_TYPE="LVCMOS33")
+                           ),
+
+        Resource("eth_rgmii", 0,
+                 Subsignal("rst",     PinsN("C13", dir="o")),
+                 Subsignal("mdio",    Pins("A13", dir="io")),
+                 Subsignal("mdc",     Pins("C11", dir="o")),
+                 Subsignal("tx_clk",  Pins("A12", dir="o")),
+                 Subsignal("tx_ctrl", Pins("C9", dir="o")),
+                 Subsignal("tx_data", Pins("D8 C8 B8 A8", dir="o")),
+                 Subsignal("rx_clk",  Pins("E11", dir="i")),
+                 Subsignal("rx_ctrl", Pins("A11", dir="i")),
+                 Subsignal("rx_data", Pins("B11 A10 B10 A9", dir="i")),
+                 Attrs(IO_TYPE="LVCMOS33")
+                 ),
+        Resource("eth_int", 0, PinsN("B13", dir="i"),
+                 Attrs(IO_TYPE="LVCMOS33")),
+
+        *SDCardResources(0,
+                         clk="P24", cmd="M24", dat0="N26", dat1="N25", dat2="N23", dat3="N21", cd="L22",
+                         # TODO
+                         # clk_fb="P25", cmd_dir="M23", dat0_dir="N24", dat123_dir="P26",
+                         attrs=Attrs(IO_TYPE="LVCMOS33"),
+                         ),
+
+        # ERROR: cannot place differential IO at location PIOB
+        # if we choose to use DiffPairs
+        Resource("ddr3", 0,
+                 Subsignal("clk", Pins("H3", dir="o")),
+                 #Subsignal("clk",    DiffPairs("H3", "J3", dir="o"), Attrs(IO_TYPE="SSTL135D_I")),
+                 Subsignal("cke", Pins("P1", dir="o")),
+                 Subsignal("we_n", Pins("R3", dir="o")),
+                 Subsignal("ras_n", Pins("T3", dir="o")),
+                 Subsignal("cas_n", Pins("P2", dir="o")),
+                 Subsignal("a", Pins("T5 M3 L3 V6 K2 W6 K3 L1 H2 L2 N1 J1 M1 K1", dir="o")),
+                 Subsignal("ba", Pins("U6 N3 N4", dir="o")),
+                 #Subsignal("dqs",    DiffPairs("V4 V1", "U5 U2", dir="io"), Attrs(IO_TYPE="SSTL135D_I")),
+                 Subsignal("dqs", Pins("V4 V1", dir="io"), Attrs(IO_TYPE="SSTL135D_I", TERMINATION="OFF", DIFFRESISTOR="100")),
+                 Subsignal("dq", Pins("T4 W4 R4 W5 R6 P6 P5 P4 R1 W3 T2 V3 U3 W1 T1 W2", dir="io")),
+                 Subsignal("dm", Pins("U4 U1", dir="o")),
+                 Subsignal("odt", Pins("P3", dir="o")),
+                 Attrs(IO_TYPE="SSTL135_I")
+                 ),
+
+        Resource("hdmi", 0,
+                 Subsignal("rst",   PinsN("N6", dir="o")),
+                 Subsignal("scl",   Pins("C17", dir="io")),
+                 Subsignal("sda",   Pins("E17", dir="io")),
+                 Subsignal("pclk",  Pins("C1", dir="o")),
+                 Subsignal("vsync", Pins("A4", dir="o")),
+                 Subsignal("hsync", Pins("B4", dir="o")),
+                 Subsignal("de",    Pins("A3", dir="o")),
+                 Subsignal("d",
+                           Subsignal(
+                               "b", Pins("AD25 AC26 AB24 AB25  B3  C3  D3  B1  C2  D2 D1 E3", dir="o")),
+                           Subsignal(
+                               "g", Pins("AA23 AA22 AA24 AA25  E1  F2  F1 D17 D16 E16 J6 H6", dir="o")),
+                           Subsignal(
+                               "r", Pins("AD26 AE25 AF25 AE26 E10 D11 D10 C10  D9  E8 H5 J4", dir="o")),
+                           ),
+                 Subsignal("mclk",  Pins("E19", dir="o")),
+                 Subsignal("sck",   Pins("D6", dir="o")),
+                 Subsignal("ws",    Pins("C6", dir="o")),
+                 Subsignal("i2s",   Pins("A6 B6 A5 C5", dir="o")),
+                 Subsignal("int",   PinsN("C4", dir="i")),
+                 Attrs(IO_TYPE="LVTTL33")
+                 ),
+
+        Resource("sata", 0,
+                 Subsignal("tx", DiffPairs("AD16", "AD17", dir="o")),
+                 Subsignal("rx", DiffPairs("AF15", "AF16", dir="i")),
+                 Attrs(IO_TYPE="LVDS")
+                 ),
+
+        Resource("ulpi", 0,
+                 Subsignal("rst",  Pins("E23", dir="o")),
+                 Subsignal("clk",  Pins("H24", dir="i")),
+                 Subsignal("dir",  Pins("F22", dir="i")),
+                 Subsignal("nxt",  Pins("F23", dir="i")),
+                 Subsignal("stp",  Pins("H23", dir="o")),
+                 Subsignal("data", Pins(
+                     "M26 L25 L26 K25 K26 J23 J26 H25", dir="io")),
+                 Attrs(IO_TYPE="LVCMOS33")
+                 ),
+
+        Resource("usbc_cfg", 0,
+                 Subsignal("scl", Pins("D24", dir="io")),
+                 Subsignal("sda", Pins("C24", dir="io")),
+                 Subsignal("dir", Pins("B23", dir="i")),
+                 Subsignal("id",  Pins("D23", dir="i")),
+                 Subsignal("int", PinsN("B24", dir="i")),
+                 Attrs(IO_TYPE="LVCMOS33")
+                 ),
+        Resource("usbc_mux", 0,
+                 Subsignal("en",    Pins("C23", dir="oe")),
+                 Subsignal("amsel", Pins("B26", dir="oe")),
+                 Subsignal("pol",   Pins("D26", dir="o")),
+                 Subsignal("lna",   DiffPairs("AF9", "AF10", dir="i"),
+                           Attrs(IO_TYPE="LVCMOS18D")),
+                 Subsignal("lnb",   DiffPairs("AD10", "AD11",
+                                              dir="o"), Attrs(IO_TYPE="LVCMOS18D")),
+                 Subsignal("lnc",   DiffPairs("AD7",  "AD8", dir="o"),
+                           Attrs(IO_TYPE="LVCMOS18D")),
+                 Subsignal("lnd",   DiffPairs("AF6",  "AF7", dir="i"),
+                           Attrs(IO_TYPE="LVCMOS18D")),
+                 Attrs(IO_TYPE="LVCMOS33")
+                 ),
+    ]
+
+    connectors = [
+        Connector("pmod", 0, "T25 U25 U24 V24 - - T26 U26 V26 W26 - -"),
+        Connector("pmod", 1, "U23 V23 U22 V21 - - W25 W24 W23 W22 - -"),
+        Connector("pmod", 2, "J24 H22 E21 D18 - - K22 J21 H21 D22 - -"),
+        Connector("pmod", 3, " E4  F4  E6  H4 - -  F3  D4  D5  F5 - -"),
+        Connector("pmod", 4, "E26 D25 F26 F25 - - A25 A24 C26 C25 - -"),
+        Connector("pmod", 5, "D19 C21 B21 C22 - - D21 A21 A22 A23 - -"),
+        Connector("pmod", 6, "C16 B17 C18 B19 - - A17 A18 A19 C19 - -"),
+        Connector("pmod", 7, "D14 B14 E14 B16 - - C14 A14 A15 A16 - -"),
+    ]
+
+    @property
+    def required_tools(self):
+        return ["yosys"]
+
+    @property
+    def file_templates(self):
+        return {
+            **TemplatedPlatform.build_script_templates,
+            "{{name}}.v": r"""
+            /* {{autogenerated}} */
+            {{emit_verilog()}}
+            """,
+            "{{name}}.debug.v": r"""
+            /* {{autogenerated}} */
+            {{emit_debug_verilog()}}
+            """,
+        }
+
+    @property
+    def command_templates(self):
+        return []
diff --git a/gram/simulation/runsimsoc.sh b/gram/simulation/runsimsoc.sh
new file mode 100755 (executable)
index 0000000..cb4fb0b
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -e
+
+LIB_DIR=/usr/local/diamond/3.11_x64/ispfpga/verilog/data/ecp5u
+
+python simsoc.py
+iverilog -g2012 -s simsoctb -o simsoc simsoctb.v build/top.debug.v dram_model/ddr3.v ${LIB_DIR}/ECLKSYNCB.v ${LIB_DIR}/EHXPLLL.v ${LIB_DIR}/PUR.v ${LIB_DIR}/GSR.v \
+       ${LIB_DIR}/FD1S3AX.v ${LIB_DIR}/SGSR.v ${LIB_DIR}/ODDRX2F.v ${LIB_DIR}/ODDRX2DQA.v ${LIB_DIR}/DELAYF.v ${LIB_DIR}/BB.v ${LIB_DIR}/OB.v ${LIB_DIR}/IB.v \
+       ${LIB_DIR}/DQSBUFM.v ${LIB_DIR}/UDFDL5_UDP_X.v ${LIB_DIR}/TSHX2DQSA.v ${LIB_DIR}/TSHX2DQA.v ${LIB_DIR}/ODDRX2DQSB.v ${LIB_DIR}/IDDRX2DQA.v DDRDLLA.patched.v \
+       ${LIB_DIR}/CLKDIVF.v
+vvp simsoc -vcd
diff --git a/gram/simulation/simsoc.py b/gram/simulation/simsoc.py
new file mode 100644 (file)
index 0000000..cb886b3
--- /dev/null
@@ -0,0 +1,292 @@
+# This file is Copyright (c) 2020 LambdaConcept <contact@lambdaconcept.com>
+
+from nmigen import *
+from nmigen.lib.cdc import ResetSynchronizer
+from nmigen_soc import wishbone, memory
+
+from lambdasoc.cpu.minerva import MinervaCPU
+from lambdasoc.periph.intc import GenericInterruptController
+from lambdasoc.periph.serial import AsyncSerialPeripheral
+from lambdasoc.periph.sram import SRAMPeripheral
+from lambdasoc.periph.timer import TimerPeripheral
+from lambdasoc.periph import Peripheral
+from lambdasoc.soc.base import SoC
+
+from gram.core import gramCore
+from gram.phy.ecp5ddrphy import ECP5DDRPHY
+from gram.modules import MT41K256M16
+from gram.frontend.wishbone import gramWishbone
+
+from icarusecpix5platform import IcarusECPIX5Platform
+from uartbridge import UARTBridge
+
+class PLL(Elaboratable):
+    def __init__(self, clkin, clksel=Signal(shape=2, reset=2), clkout1=Signal(), clkout2=Signal(), clkout3=Signal(), clkout4=Signal(), lock=Signal(), CLKI_DIV=1, CLKFB_DIV=1, CLK1_DIV=3, CLK2_DIV=4, CLK3_DIV=5, CLK4_DIV=6):
+        self.clkin = clkin
+        self.clkout1 = clkout1
+        self.clkout2 = clkout2
+        self.clkout3 = clkout3
+        self.clkout4 = clkout4
+        self.clksel = clksel
+        self.lock = lock
+        self.CLKI_DIV = CLKI_DIV
+        self.CLKFB_DIV = CLKFB_DIV
+        self.CLKOP_DIV = CLK1_DIV
+        self.CLKOS_DIV = CLK2_DIV
+        self.CLKOS2_DIV = CLK3_DIV
+        self.CLKOS3_DIV = CLK4_DIV
+        self.ports = [
+            self.clkin,
+            self.clkout1,
+            self.clkout2,
+            self.clkout3,
+            self.clkout4,
+            self.clksel,
+            self.lock,
+        ]
+
+    def elaborate(self, platform):
+        clkfb = Signal()
+        pll = Instance("EHXPLLL",
+                       p_PLLRST_ENA='DISABLED',
+                       p_INTFB_WAKE='DISABLED',
+                       p_STDBY_ENABLE='DISABLED',
+                       p_CLKOP_FPHASE=0,
+                       p_CLKOP_CPHASE=1,
+                       p_OUTDIVIDER_MUXA='DIVA',
+                       p_CLKOP_ENABLE='ENABLED',
+                       p_CLKOP_DIV=self.CLKOP_DIV,
+                       p_CLKOS_DIV=self.CLKOS_DIV,
+                       p_CLKOS2_DIV=self.CLKOS2_DIV,
+                       p_CLKOS3_DIV=self.CLKOS3_DIV,
+                       p_CLKFB_DIV=self.CLKFB_DIV,
+                       p_CLKI_DIV=self.CLKI_DIV,
+                       p_FEEDBK_PATH='CLKOP',
+                       #p_FREQUENCY_PIN_CLKOP='200',
+                       i_CLKI=self.clkin,
+                       i_CLKFB=clkfb,
+                       i_RST=0,
+                       i_STDBY=0,
+                       i_PHASESEL0=0,
+                       i_PHASESEL1=0,
+                       i_PHASEDIR=0,
+                       i_PHASESTEP=0,
+                       i_PLLWAKESYNC=0,
+                       i_ENCLKOP=0,
+                       i_ENCLKOS=0,
+                       i_ENCLKOS2=0,
+                       i_ENCLKOS3=0,
+                       o_CLKOP=self.clkout1,
+                       o_CLKOS=self.clkout2,
+                       o_CLKOS2=self.clkout3,
+                       o_CLKOS3=self.clkout4,
+                       o_LOCK=self.lock,
+                       )
+        m = Module()
+        m.submodules += pll
+        with m.If(self.clksel == 0):
+            m.d.comb += clkfb.eq(self.clkout1)
+        with m.Elif(self.clksel == 1):
+            m.d.comb += clkfb.eq(self.clkout2)
+        with m.Elif(self.clksel == 2):
+            m.d.comb += clkfb.eq(self.clkout3)
+        with m.Else():
+            m.d.comb += clkfb.eq(self.clkout4)
+        return m
+
+
+class ECPIX5CRG(Elaboratable):
+    def __init__(self):
+        ...
+
+    def elaborate(self, platform):
+        m = Module()
+
+        # Get 100Mhz from oscillator
+        clk100 = platform.request("clk100")
+        cd_rawclk = ClockDomain("rawclk", local=True, reset_less=True)
+        m.d.comb += cd_rawclk.clk.eq(clk100)
+        m.domains += cd_rawclk
+
+        # Reset
+        reset = platform.request(platform.default_rst).i
+        gsr0 = Signal()
+        gsr1 = Signal()
+
+        m.submodules += [
+            Instance("FD1S3AX", p_GSR="DISABLED", i_CK=ClockSignal("rawclk"), i_D=~reset, o_Q=gsr0),
+            Instance("FD1S3AX", p_GSR="DISABLED", i_CK=ClockSignal("rawclk"), i_D=gsr0,   o_Q=gsr1),
+            Instance("SGSR", i_CLK=ClockSignal("rawclk"), i_GSR=gsr1),
+        ]
+
+        # Power-on delay (655us)
+        podcnt = Signal(16, reset=2**16-1)
+        pod_done = Signal()
+        with m.If(podcnt != 0):
+            m.d.rawclk += podcnt.eq(podcnt-1)
+        m.d.comb += pod_done.eq(podcnt == 0)
+
+        # Generating sync2x (200Mhz) and init (25Mhz) from clk100
+        cd_sync2x = ClockDomain("sync2x", local=False)
+        cd_sync2x_unbuf = ClockDomain("sync2x_unbuf", local=True, reset_less=True)
+        cd_init = ClockDomain("init", local=False)
+        cd_sync = ClockDomain("sync", local=False, reset_less=True)
+        cd_dramsync = ClockDomain("dramsync", local=False)
+        m.submodules.pll = pll = PLL(ClockSignal("rawclk"), CLKI_DIV=1, CLKFB_DIV=2, CLK1_DIV=2, CLK2_DIV=16, CLK3_DIV=4,
+            clkout1=ClockSignal("sync2x_unbuf"), clkout2=ClockSignal("init"))
+        m.submodules += Instance("ECLKSYNCB",
+                i_ECLKI = ClockSignal("sync2x_unbuf"),
+                i_STOP  = 0,
+                o_ECLKO = ClockSignal("sync2x"))
+        m.domains += cd_sync2x_unbuf
+        m.domains += cd_sync2x
+        m.domains += cd_init
+        m.domains += cd_sync
+        m.domains += cd_dramsync
+        m.d.comb += ResetSignal("init").eq(~pll.lock|~pod_done)
+        m.d.comb += ResetSignal("dramsync").eq(~pll.lock|~pod_done)
+
+        rgb_led = platform.request("rgb_led", 2)
+        cnt = Signal(25)
+        m.d.sync += cnt.eq(cnt+1)
+        m.d.comb += rgb_led.r.eq(cnt[24])
+        m.d.comb += rgb_led.g.eq(~pod_done)
+        m.d.comb += rgb_led.b.eq(~pll.lock)
+
+        # Generating sync (100Mhz) from sync2x
+        
+        m.submodules += Instance("CLKDIVF",
+            p_DIV="2.0",
+            i_ALIGNWD=0,
+            i_CLKI=ClockSignal("sync2x"),
+            i_RST=0,
+            o_CDIVX=ClockSignal("sync"))
+        m.d.comb += ClockSignal("dramsync").eq(ClockSignal("sync"))
+
+        return m
+
+class OldCRG(Elaboratable):
+    def elaborate(self, platform):
+        m = Module()
+
+        m.submodules.pll = pll = PLL(ClockSignal(
+            "sync"), CLKI_DIV=1, CLKFB_DIV=2, CLK1_DIV=2, CLK2_DIV=16)
+        cd_sync2x = ClockDomain("sync2x", local=False)
+        m.d.comb += cd_sync2x.clk.eq(pll.clkout1)
+        m.domains += cd_sync2x
+
+        cd_init = ClockDomain("init", local=False)
+        m.d.comb += cd_init.clk.eq(pll.clkout2)
+        m.domains += cd_init
+
+        return m
+
+class ThinCRG(Elaboratable):
+    """
+    Sync (clk100, resetless) => PLL => sync2x_unbuf (200Mhz) => ECLKSYNC => sync2x => CLKDIVF => dramsync
+    """
+
+    def __init__(self):
+        ...
+
+    def elaborate(self, platform):
+        m = Module()
+
+        # Power-on delay (655us)
+        podcnt = Signal(16, reset=2**16-1)
+        pod_done = Signal()
+        with m.If(podcnt != 0):
+            m.d.sync += podcnt.eq(podcnt-1)
+        m.d.comb += pod_done.eq(podcnt == 0)
+
+        # Generating sync2x (200Mhz) and init (25Mhz) from clk100
+        cd_sync2x = ClockDomain("sync2x", local=False)
+        cd_sync2x_unbuf = ClockDomain("sync2x_unbuf", local=True, reset_less=True)
+        cd_init = ClockDomain("init", local=False)
+        cd_dramsync = ClockDomain("dramsync", local=False)
+        m.submodules.pll = pll = PLL(ClockSignal("sync"), CLKI_DIV=1, CLKFB_DIV=2, CLK1_DIV=2, CLK2_DIV=16, CLK3_DIV=4,
+            clkout1=ClockSignal("sync2x_unbuf"), clkout2=ClockSignal("init"))
+        m.submodules += Instance("ECLKSYNCB",
+                i_ECLKI = ClockSignal("sync2x_unbuf"),
+                i_STOP  = 0,
+                o_ECLKO = ClockSignal("sync2x"))
+        m.domains += cd_sync2x_unbuf
+        m.domains += cd_sync2x
+        m.domains += cd_init
+        m.domains += cd_dramsync
+        m.d.comb += ResetSignal("init").eq(~pll.lock|~pod_done)
+        m.d.comb += ResetSignal("dramsync").eq(~pll.lock|~pod_done)
+
+        # Generating sync (100Mhz) from sync2x
+        m.submodules += Instance("CLKDIVF",
+            p_DIV="2.0",
+            i_ALIGNWD=0,
+            i_CLKI=ClockSignal("sync2x"),
+            i_RST=0,
+            o_CDIVX=ClockSignal("dramsync"))
+
+        return m
+
+
+class DDR3SoC(SoC, Elaboratable):
+    def __init__(self, *, clk_freq,
+                 ddrphy_addr, dramcore_addr,
+                 ddr_addr):
+        self._arbiter = wishbone.Arbiter(addr_width=30, data_width=32, granularity=8,
+                                         features={"cti", "bte"})
+        self._decoder = wishbone.Decoder(addr_width=30, data_width=32, granularity=8,
+                                         features={"cti", "bte"})
+
+        self.crg = ECPIX5CRG()
+
+        self.ub = UARTBridge(divisor=868, pins=platform.request("uart", 0))
+        self._arbiter.add(self.ub.bus)
+
+        self.ddrphy = ECP5DDRPHY(platform.request("ddr3", 0, dir={"dq":"-", "dqs":"-"}))
+        self._decoder.add(self.ddrphy.bus, addr=ddrphy_addr)
+
+        ddrmodule = MT41K256M16(clk_freq, "1:4")
+
+        self.dramcore = gramCore(
+            phy=self.ddrphy,
+            geom_settings=ddrmodule.geom_settings,
+            timing_settings=ddrmodule.timing_settings,
+            clk_freq=clk_freq)
+        self._decoder.add(self.dramcore.bus, addr=dramcore_addr)
+
+        self.drambone = gramWishbone(self.dramcore)
+        self._decoder.add(self.drambone.bus, addr=ddr_addr)
+
+        self.memory_map = self._decoder.bus.memory_map
+
+        self.clk_freq = clk_freq
+
+    def elaborate(self, platform):
+        m = Module()
+
+        m.submodules.sysclk = self.crg
+
+        m.submodules.arbiter = self._arbiter
+        m.submodules.ub = self.ub
+
+        m.submodules.decoder = self._decoder
+        m.submodules.ddrphy = self.ddrphy
+        m.submodules.dramcore = self.dramcore
+        m.submodules.drambone = self.drambone
+
+        m.d.comb += [
+            self._arbiter.bus.connect(self._decoder.bus),
+        ]
+
+        return m
+
+
+if __name__ == "__main__":
+    platform = IcarusECPIX5Platform()
+
+    soc = DDR3SoC(clk_freq=int(platform.default_clk_frequency),
+        ddrphy_addr=0x00008000, dramcore_addr=0x00009000,
+        ddr_addr=0x10000000)
+
+    soc.build(do_build=True)
+    platform.build(soc)
diff --git a/gram/simulation/uartbridge.py b/gram/simulation/uartbridge.py
new file mode 100644 (file)
index 0000000..747f63f
--- /dev/null
@@ -0,0 +1,304 @@
+from nmigen import *
+from nmigen.lib.io import pin_layout
+from nmigen_soc import wishbone
+from nmigen_stdio.serial import AsyncSerial, AsyncSerialTX
+from nmigen.back.pysim import *
+
+import unittest
+
+__ALL__ = ["UARTBridge"]
+
+class UARTBridge(Elaboratable):
+    def __init__(self, divisor, pins):
+        self.bus = wishbone.Interface(addr_width=30,
+                                      data_width=32, granularity=32)
+        self._pins = pins
+        self._divisor = divisor
+
+    def elaborate(self, platform):
+        m = Module()
+
+        m.submodules.serial = serial = AsyncSerial(divisor=self._divisor, pins=self._pins)
+
+        address_width = 32
+        data_width = 32
+
+        cmd = Signal(8)
+        length = Signal(8)
+        address = Signal(address_width)
+        data = Signal(data_width)
+        bytes_count = Signal(range(data_width//8))
+        words_count = Signal(8)
+
+        m.d.comb += [
+            self.bus.dat_w.eq(data),
+            self.bus.adr.eq(address),
+        ]
+
+        with m.FSM():
+            with m.State("Receive-Cmd"):
+                m.d.comb += serial.rx.ack.eq(1)
+
+                # Reset registers
+                m.d.sync += [
+                    bytes_count.eq(data_width//8-1),
+                    words_count.eq(0),
+                ]
+
+                with m.If(serial.rx.rdy):
+                    m.d.sync += cmd.eq(serial.rx.data)
+                    m.next = "Receive-Length"
+
+            with m.State("Receive-Length"):
+                m.d.comb += serial.rx.ack.eq(1)
+
+                with m.If(serial.rx.rdy):
+                    m.d.sync += length.eq(serial.rx.data)
+                    m.next = "Receive-Address"
+
+            with m.State("Receive-Address"):
+                m.d.comb += serial.rx.ack.eq(1)
+
+                with m.If(serial.rx.rdy):
+                    m.d.sync += [
+                        address.eq(Cat(serial.rx.data, address)),
+                        bytes_count.eq(bytes_count-1),
+                    ]
+
+                    with m.If(bytes_count == 0):
+                        with m.Switch(cmd):
+                            with m.Case(0x01):
+                                m.next = "Handle-Write"
+                            with m.Case(0x02):
+                                m.next = "Handle-Read"
+                            with m.Case():
+                                m.next = "Receive-Cmd"
+
+            with m.State("Handle-Write"):
+                m.d.comb += serial.rx.ack.eq(1)
+
+                with m.If(serial.rx.rdy):
+                    m.d.sync += [
+                        data.eq(Cat(serial.rx.data, data)),
+                        bytes_count.eq(bytes_count-1),
+                    ]
+                    with m.If(bytes_count == 0):
+                        m.next = "Write-Data"
+
+            with m.State("Write-Data"):
+                m.d.comb += [
+                    self.bus.stb.eq(1),
+                    self.bus.we.eq(1),
+                    self.bus.cyc.eq(1),
+                    self.bus.sel.eq(0xF),
+                ]
+
+                with m.If(self.bus.ack):
+                    m.next = "Receive-Cmd"
+
+
+            with m.State("Handle-Read"):
+                m.d.comb += [
+                    self.bus.stb.eq(1),
+                    self.bus.we.eq(0),
+                    self.bus.cyc.eq(1),
+                ]
+
+                with m.If(self.bus.ack):
+                    m.d.sync += [
+                        bytes_count.eq(data_width//8-1),
+                        data.eq(self.bus.dat_r),
+                    ]
+                    m.next = "Send-Data"
+
+            with m.State("Send-Data"):
+                m.d.comb += serial.tx.ack.eq(1)
+
+                with m.Switch(bytes_count):
+                    for i in range(data_width//8):
+                        with m.Case(i):
+                            m.d.comb += serial.tx.data.eq(data[i*8:(i+1)*8])
+
+                with m.If(serial.tx.rdy):
+                    m.next = "Send-Data-Wait"
+
+            with m.State("Send-Data-Wait"):
+                with m.If(serial.tx.rdy):
+                    m.d.sync += [
+                        bytes_count.eq(bytes_count-1),
+                    ]
+
+                    with m.If(bytes_count == 0):
+                        m.next = "Receive-Cmd"
+                    with m.Else():
+                        m.next = "Send-Data"
+
+        return m
+
+def serial_write(serial, val):
+    while not (yield serial.tx.rdy):
+        yield
+
+    yield serial.tx.data.eq(val)
+    yield serial.tx.ack.eq(1)
+    yield
+
+    while (yield serial.tx.rdy):
+        yield
+
+    yield serial.tx.ack.eq(0)
+
+    while not (yield serial.tx.rdy):
+        yield
+
+    yield
+
+def serial_read(serial):
+    yield serial.rx.ack.eq(1)
+
+    while not (yield serial.rx.rdy):
+        yield
+
+    data = (yield serial.rx.data)
+    yield serial.rx.ack.eq(0)
+
+    while (yield serial.rx.rdy):
+        yield
+
+    return data
+
+class UARTBridgeTestCase(unittest.TestCase):
+    # Minimum 5, lowest makes the simulation faster
+    divisor = 5
+    timeout = 10000
+
+    def test_read(self):
+        pins = Record([("rx", pin_layout(1, dir="i")),
+                       ("tx", pin_layout(1, dir="o"))])
+        dut = UARTBridge(divisor=self.divisor, pins=pins)
+        serial = AsyncSerial(divisor=self.divisor)
+        m = Module()
+        m.submodules.bridge = dut
+        m.submodules.serial = serial
+        m.d.comb += [
+            pins.rx.i.eq(serial.tx.o),
+            serial.rx.i.eq(pins.tx.o),
+        ]
+
+        def process():
+            # Send read command
+            yield from serial_write(serial, 0x02)
+            yield
+
+            # Length = 1
+            yield from serial_write(serial, 0x01)
+            yield
+
+            # Send 0x4000 as address
+            yield from serial_write(serial, 0x00)
+            yield
+            yield from serial_write(serial, 0x00)
+            yield
+            yield from serial_write(serial, 0x40)
+            yield
+            yield from serial_write(serial, 0x00)
+            yield
+            
+            # Handle wishbone request
+            timeout = 0
+            while not (yield dut.bus.cyc):
+                yield
+                timeout += 1
+                if timeout > self.timeout:
+                    raise RuntimeError("Simulation timed out")
+
+            # Ensure Wishbone address is the one we asked for
+            self.assertEqual((yield dut.bus.adr), 0x00004000)
+            self.assertFalse((yield dut.bus.we))
+            
+            # Answer
+            yield dut.bus.dat_r.eq(0x0DEFACED)
+            yield dut.bus.ack.eq(1)
+            yield
+
+            # Check response on UART
+            rx = yield from serial_read(serial)
+            self.assertEqual(rx, 0x0D)
+            rx = yield from serial_read(serial)
+            self.assertEqual(rx, 0xEF)
+            rx = yield from serial_read(serial)
+            self.assertEqual(rx, 0xAC)
+            rx = yield from serial_read(serial)
+            self.assertEqual(rx, 0xED)            
+
+            yield
+
+        sim = Simulator(m)
+        with sim.write_vcd("test_uartbridge.vcd"):
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+            sim.run()
+
+    def test_write(self):
+        pins = Record([("rx", pin_layout(1, dir="i")),
+                       ("tx", pin_layout(1, dir="o"))])
+        dut = UARTBridge(divisor=self.divisor, pins=pins)
+        serial = AsyncSerial(divisor=self.divisor)
+        m = Module()
+        m.submodules.bridge = dut
+        m.submodules.serial = serial
+        m.d.comb += [
+            pins.rx.i.eq(serial.tx.o),
+            serial.rx.i.eq(pins.tx.o),
+        ]
+
+        def process():
+            # Send write command
+            yield from serial_write(serial, 0x01)
+            yield
+
+            # Length = 1
+            yield from serial_write(serial, 0x01)
+            yield
+
+            # Send 0x4000 as address
+            yield from serial_write(serial, 0x00)
+            yield
+            yield from serial_write(serial, 0x00)
+            yield
+            yield from serial_write(serial, 0x40)
+            yield
+            yield from serial_write(serial, 0x00)
+            yield
+
+            # Send 0xFEEDFACE as value
+            yield from serial_write(serial, 0xFE)
+            yield
+            yield from serial_write(serial, 0xED)
+            yield
+            yield from serial_write(serial, 0xFA)
+            yield
+            yield from serial_write(serial, 0xCE)
+            
+            # Handle wishbone request
+            timeout = 0
+            while not (yield dut.bus.cyc):
+                yield
+                timeout += 1
+                if timeout > self.timeout:
+                    raise RuntimeError("Simulation timed out")
+
+            # Ensure Wishbone address is the one we asked for
+            self.assertEqual((yield dut.bus.adr), 0x00004000)
+            self.assertEqual((yield dut.bus.dat_w), 0xFEEDFACE)
+            self.assertTrue((yield dut.bus.we))
+            
+            # Answer
+            yield dut.bus.ack.eq(1)
+            yield
+
+        sim = Simulator(m)
+        with sim.write_vcd("test_uartbridge.vcd"):
+            sim.add_clock(1e-6)
+            sim.add_sync_process(process)
+            sim.run()