diff --git a/litex_boards/platforms/qmtech_ep4ce15.py b/litex_boards/platforms/qmtech_ep4ce15.py
index 33ac6c3d947919a49cb9ad07af14c7523c685265..36d0c38bed25f8d62b639d05066c6a0b797f3c49 100644
--- a/litex_boards/platforms/qmtech_ep4ce15.py
+++ b/litex_boards/platforms/qmtech_ep4ce15.py
@@ -2,6 +2,7 @@
 # This file is part of LiteX-Boards.
 #
 # Copyright (c) 2020 Basel Sayeh <Basel.Sayeh@hotmail.com>
+# Copyright (c) 2021 Hans Baier <hansfbaier@gmail.com>
 # SPDX-License-Identifier: BSD-2-Clause
 
 from litex.build.generic_platform import *
@@ -14,18 +15,25 @@ _io = [
     # Clk
     ("clk50", 0, Pins("T2"), IOStandard("3.3-V LVTTL")),
 
-    # Leds
-    ("user_led", 0, Pins("E4"), IOStandard("3.3-V LVTTL")),
-
     # Button
-    ("key", 0, Pins("Y13"), IOStandard("3.3-V LVTTL")),
+    ("key", 0, Pins("Y13"),  IOStandard("3.3-V LVTTL")),
     ("key", 1, Pins("W13"),  IOStandard("3.3-V LVTTL")),
 
     # Serial
     ("serial", 0,
         # Compatible with cheap FT232 based cables (ex: Gaoominy 6Pin Ftdi Ft232Rl Ft232)
-        Subsignal("tx", Pins("AA13"), IOStandard("3.3-V LVTTL")), # GPIO_07 (JP1 Pin 10)
-        Subsignal("rx", Pins("AA14"), IOStandard("3.3-V LVTTL"))  # GPIO_05 (JP1 Pin 8)
+        Subsignal("tx", Pins("J3:7"), IOStandard("3.3-V LVTTL")), # GPIO_07 (JP1 Pin 10)
+        Subsignal("rx", Pins("J3:8"), IOStandard("3.3-V LVTTL"))  # GPIO_05 (JP1 Pin 8)
+    ),
+
+    # SPIFlash (W25Q64)
+    ("spiflash", 0,
+        # clk
+        Subsignal("cs_n", Pins("E2")),
+        Subsignal("clk",  Pins("K2")),
+        Subsignal("mosi", Pins("D1")),
+        Subsignal("miso", Pins("E2")),
+        IOStandard("3.3-V LVTTL"),
     ),
 
     # SDR SDRAM
@@ -46,30 +54,74 @@ _io = [
         Subsignal("dm", Pins("AA5 W7")),
         IOStandard("3.3-V LVTTL")
     ),
+]
 
-    # GPIOs
-    #ignore for now
-    #("gpio_0", 0, Pins(
-    #    "D3 C3  A2  A3  B3  B4  A4  B5",
-    #    "A5 D5  B6  A6  B7  D6  A7  C6",
-    #    "C8 E6  E7  D8  E8  F8  F9  E9",
-    #    "C9 D9 E11 E10 C11 B11 A12 D11",
-    #    "D12 B12"),
-    #    IOStandard("3.3-V LVTTL")
-    #),
-    #("gpio_1", 0, Pins(
-    #    "F13 T15 T14 T13 R13 T12 R12 T11",
-    #    "T10 R11 P11 R10 N12  P9  N9 N11",
-    #    "L16 K16 R16 L15 P15 P16 R14 N16",
-    #    "N15 P14 L14 N14 M10 L13 J16 K15",
-    #    "J13 J14"),
-    #    IOStandard("3.3-V LVTTL")
-    #),
-    #("gpio_2", 0, Pins(
-    #    "A14 B16 C14 C16 C15 D16 D15 D14",
-    #    "F15 F16 F14 G16 G15"),
-    #    IOStandard("3.3-V LVTTL")
-    #),
+# The connectors are named after the daughterboard, not the core board
+# because on the different core boards the names vary, but on the
+# daughterboard they stay the same, which we need to connect the
+# daughterboard peripherals to the core board.
+# On this board J2 is U7 and J3 is U8
+_connectors = [
+    ("J2", {
+         # odd row     even row
+          7: "R1",   8: "R2",
+          9: "P1",  10: "P2",
+         11: "N1",  12: "N2",
+         13: "M1",  14: "M2",
+         15: "J1",  16: "J2",
+         17: "H1",  18: "H2",
+         19: "F1",  20: "F2",
+         21: "E1",  22: "D2",
+         23: "C1",  24: "C2",
+         25: "B1",  26: "B2",
+         27: "B3",  28: "A3",
+         29: "B4",  30: "A4",
+         31: "C4",  32: "C3",
+         33: "B5",  34: "A5",
+         35: "B6",  36: "A6",
+         37: "B7",  38: "A7",
+         39: "B8",  40: "A8",
+         41: "B9",  42: "A9",
+         43: "B10", 44: "A10",
+         45: "B13", 46: "A13",
+         47: "B14", 48: "A14",
+         49: "B15", 50: "A15",
+         51: "B16", 52: "A16",
+         53: "B17", 54: "A17",
+         55: "B18", 56: "A18",
+         57: "B19", 58: "A19",
+         59: "B20", 60: "A20",
+    }),
+    ("J3", {
+        # odd row     even row
+         7: "AA13",   8: "AB13",
+         9: "AA14",  10: "AB14",
+        11: "AA15",  12: "AB15",
+        13: "AA16",  14: "AB16",
+        15: "AA17",  16: "AB17",
+        17: "AA18",  18: "AB18",
+        19: "AA19",  20: "AB19",
+        21: "AA20",  22: "AB20",
+        23: "Y22",   24: "Y21",
+        25: "W22",   26: "W21",
+        27: "V22",   28: "V21",
+        29: "U22",   30: "U21",
+        31: "R22",   32: "R21",
+        33: "P22",   34: "P21",
+        35: "N22",   36: "N21",
+        37: "M22",   38: "M21",
+        39: "L22",   40: "L21",
+        41: "K22",   42: "K21",
+        43: "J22",   44: "J21",
+        45: "H22",   46: "H21",
+        47: "F22",   48: "F21",
+        49: "E22",   50: "E21",
+        51: "D22",   52: "D21",
+        53: "C22",   54: "C21",
+        55: "B22",   56: "B21",
+        57: "N20",   58: "N19",
+        59: "M20",   60: "M19",
+    })
 ]
 
 # Platform -----------------------------------------------------------------------------------------
@@ -77,9 +129,26 @@ _io = [
 class Platform(AlteraPlatform):
     default_clk_name   = "clk50"
     default_clk_period = 1e9/50e6
+    core_resources = [ ("user_led", 0, Pins("E4"), IOStandard("3.3-V LVTTL")) ]
+
+    def __init__(self, with_daughterboard=False):
+        device = "EP4CE15F23C8"
+        io = _io
+        connectors = _connectors
+
+        if with_daughterboard:
+            from litex_boards.platforms.qmtech_daughterboard import QMTechDaughterboard
+            daughterboard = QMTechDaughterboard(IOStandard("3.3-V LVTTL"))
+            io += daughterboard.io
+            connectors += daughterboard.connectors
+        else:
+            io += self.core_resources
+
+        AlteraPlatform.__init__(self, device, io, connectors)
 
-    def __init__(self):
-        AlteraPlatform.__init__(self, "EP4CE15F23C8", _io)
+        if with_daughterboard:
+            # an ethernet pin takes K22, so make it available
+            self.add_platform_command("set_global_assignment -name CYCLONEII_RESERVE_NCEO_AFTER_CONFIGURATION \"USE AS REGULAR IO\"")
 
     def create_programmer(self):
         return USBBlaster()
diff --git a/litex_boards/targets/qmtech_ep4ce15.py b/litex_boards/targets/qmtech_ep4ce15.py
index 15c23081886a12c1df41eae75bfc2d33e13ad7cc..994461b5c622c9bcd3fa85e59343eeecb5e526e5 100755
--- a/litex_boards/targets/qmtech_ep4ce15.py
+++ b/litex_boards/targets/qmtech_ep4ce15.py
@@ -24,10 +24,13 @@ from litex.soc.cores.led import LedChaser
 from litedram.modules import IS42S16160
 from litedram.phy import GENSDRPHY, HalfRateGENSDRPHY
 
+from litex.soc.cores.video import VideoVGAPHY
+from liteeth.phy.mii import LiteEthPHYMII
+
 # CRG ----------------------------------------------------------------------------------------------
 
 class _CRG(Module):
-    def __init__(self, platform, sys_clk_freq, sdram_rate="1:1"):
+    def __init__(self, platform, sys_clk_freq, with_ethernet, with_vga, sdram_rate="1:1"):
         self.rst = Signal()
         self.clock_domains.cd_sys    = ClockDomain()
         if sdram_rate == "1:2":
@@ -36,6 +39,11 @@ class _CRG(Module):
         else:
             self.clock_domains.cd_sys_ps = ClockDomain(reset_less=True)
 
+        if with_ethernet:
+            self.clock_domains.cd_eth   = ClockDomain()
+        if with_vga:
+            self.clock_domains.cd_vga   = ClockDomain(reset_less=True)
+
         # # #
 
         # Clk / Rst
@@ -48,10 +56,16 @@ class _CRG(Module):
         pll.create_clkout(self.cd_sys,    sys_clk_freq)
         if sdram_rate == "1:2":
             pll.create_clkout(self.cd_sys2x,    2*sys_clk_freq)
-            pll.create_clkout(self.cd_sys2x_ps, 2*sys_clk_freq, phase=180)  # Idealy 90° but needs to be increased.
+            # theoretically 90 degrees, but increase to relax timing
+            pll.create_clkout(self.cd_sys2x_ps, 2*sys_clk_freq, phase=180)
         else:
             pll.create_clkout(self.cd_sys_ps, sys_clk_freq, phase=90)
 
+        if with_ethernet:
+            pll.create_clkout(self.cd_eth,   25e6)
+        if with_vga:
+            pll.create_clkout(self.cd_vga,   40e6)
+
         # SDRAM clock
         sdram_clk = ClockSignal("sys2x_ps" if sdram_rate == "1:2" else "sys_ps")
         self.specials += DDROutput(1, 0, platform.request("sdram_clock"), sdram_clk)
@@ -59,17 +73,23 @@ class _CRG(Module):
 # BaseSoC ------------------------------------------------------------------------------------------
 
 class BaseSoC(SoCCore):
-    def __init__(self, sys_clk_freq=int(50e6), sdram_rate="1:1", **kwargs):
-        platform = qmtech_ep4ce15.Platform()
+    def __init__(self, sys_clk_freq=int(50e6), with_daughterboard=False,
+                 with_ethernet=False, with_etherbone=False, eth_ip="192.168.1.50", eth_dynamic_ip=False,
+                 with_video_terminal=False, with_video_framebuffer=False,
+                 ident_version=True, sdram_rate="1:1", **kwargs):
+        platform = qmtech_ep4ce15.Platform(with_daughterboard=with_daughterboard)
 
         # SoCCore ----------------------------------------------------------------------------------
         SoCCore.__init__(self, platform, sys_clk_freq,
-            ident          = "LiteX SoC on QMTECH EP4CE15",
-            ident_version  = True,
+            ident          = "LiteX SoC on QMTECH EP4CE15" + (" + Daughterboard" if with_daughterboard else ""),
+            ident_version  = ident_version,
             **kwargs)
 
         # CRG --------------------------------------------------------------------------------------
-        self.submodules.crg = _CRG(platform, sys_clk_freq, sdram_rate=sdram_rate)
+        self.submodules.crg = _CRG(platform,
+                                   sys_clk_freq, with_ethernet or with_etherbone,
+                                   with_video_terminal or with_video_framebuffer,
+                                   sdram_rate=sdram_rate)
 
         # SDR SDRAM --------------------------------------------------------------------------------
         if not self.integrated_main_ram_size:
@@ -81,6 +101,24 @@ class BaseSoC(SoCCore):
                 l2_cache_size = kwargs.get("l2_size", 8192)
             )
 
+        # Ethernet / Etherbone ---------------------------------------------------------------------
+        if with_ethernet or with_etherbone:
+            self.submodules.ethphy = LiteEthPHYMII(
+                clock_pads = self.platform.request("eth_clocks"),
+                pads       = self.platform.request("eth"))
+            if with_ethernet:
+                self.add_ethernet(phy=self.ethphy, dynamic_ip=eth_dynamic_ip)
+            if with_etherbone:
+                self.add_etherbone(phy=self.ethphy, ip_address=eth_ip)
+
+        # Video ------------------------------------------------------------------------------------
+        if with_video_terminal or with_video_framebuffer:
+            self.submodules.videophy = VideoVGAPHY(platform.request("vga"), clock_domain="vga")
+            if with_video_terminal:
+                self.add_video_terminal(phy=self.videophy, timings="800x600@60Hz", clock_domain="vga")
+            if with_video_framebuffer:
+                self.add_video_framebuffer(phy=self.videophy, timings="800x600@60Hz", clock_domain="vga")
+
         # Leds -------------------------------------------------------------------------------------
         self.submodules.leds = LedChaser(
             pads         = platform.request_all("user_led"),
@@ -94,15 +132,43 @@ def main():
     parser.add_argument("--load",         action="store_true", help="Load bitstream")
     parser.add_argument("--sys-clk-freq", default=50e6,        help="System clock frequency (default: 50MHz)")
     parser.add_argument("--sdram-rate",   default="1:1",       help="SDRAM Rate: 1:1 Full Rate (default) or 1:2 Half Rate")
+    parser.add_argument("--with-daughterboard",  action="store_true",              help="Whether the core board is plugged into the QMTech daughterboard")
+    ethopts = parser.add_mutually_exclusive_group()
+    ethopts.add_argument("--with-ethernet",      action="store_true",              help="Enable Ethernet support")
+    ethopts.add_argument("--with-etherbone",     action="store_true",              help="Enable Etherbone support")
+    parser.add_argument("--eth-ip",              default="192.168.1.50", type=str, help="Ethernet/Etherbone IP address")
+    parser.add_argument("--eth-dynamic-ip",      action="store_true",              help="Enable dynamic Ethernet IP addresses setting")
+    sdopts = parser.add_mutually_exclusive_group()
+    sdopts.add_argument("--with-spi-sdcard",     action="store_true",              help="Enable SPI-mode SDCard support")
+    sdopts.add_argument("--with-sdcard",         action="store_true",              help="Enable SDCard support")
+    parser.add_argument("--no-ident-version",    action="store_false",             help="Disable build time output")
+    viopts = parser.add_mutually_exclusive_group()
+    viopts.add_argument("--with-video-terminal",    action="store_true", help="Enable Video Terminal (VGA)")
+    viopts.add_argument("--with-video-framebuffer", action="store_true", help="Enable Video Framebuffer (VGA)")
+
     builder_args(parser)
     soc_core_args(parser)
     args = parser.parse_args()
 
     soc = BaseSoC(
         sys_clk_freq = int(float(args.sys_clk_freq)),
-        sdram_rate   = args.sdram_rate,
+        with_daughterboard     = args.with_daughterboard,
+        with_ethernet          = args.with_ethernet,
+        with_etherbone         = args.with_etherbone,
+        eth_ip                 = args.eth_ip,
+        eth_dynamic_ip         = args.eth_dynamic_ip,
+        ident_version          = args.no_ident_version,
+        with_video_terminal    = args.with_video_terminal,
+        with_video_framebuffer = args.with_video_framebuffer,
+        sdram_rate             = args.sdram_rate,
         **soc_core_argdict(args)
     )
+
+    if args.with_spi_sdcard:
+        soc.add_spi_sdcard()
+    if args.with_sdcard:
+        soc.add_sdcard()
+
     builder = Builder(soc, **builder_argdict(args))
     builder.build(run=args.build)