diff --git a/fpga/Makefile b/fpga/Makefile index d037833b..fdb0daaf 100644 --- a/fpga/Makefile +++ b/fpga/Makefile @@ -57,7 +57,6 @@ ifeq ($(SUB_PROJECT),bringup) BOARD ?= vcu118 FPGA_BRAND ?= xilinx endif - ifeq ($(SUB_PROJECT),arty) # TODO: Fix with Arty SBT_PROJECT ?= fpga_platforms @@ -72,6 +71,20 @@ ifeq ($(SUB_PROJECT),arty) BOARD ?= arty FPGA_BRAND ?= xilinx endif +ifeq ($(SUB_PROJECT),arty100t) + # TODO: Fix with Arty + SBT_PROJECT ?= fpga_platforms + MODEL ?= Arty100THarness + VLOG_MODEL ?= Arty100THarness + MODEL_PACKAGE ?= chipyard.fpga.arty100t + CONFIG ?= RocketArtyConfig + CONFIG_PACKAGE ?= chipyard.fpga.arty100t + GENERATOR_PACKAGE ?= chipyard + TB ?= none # unused + TOP ?= ChipTop + BOARD ?= arty_a7_100 + FPGA_BRAND ?= xilinx +endif include $(base_dir)/variables.mk @@ -111,8 +124,7 @@ include $(base_dir)/common.mk # copy from other directory ######################################################################################### all_vsrcs := \ - $(base_dir)/generators/sifive-blocks/vsrc/SRLatch.v \ - $(fpga_dir)/common/vsrc/PowerOnResetFPGAOnly.v + $(base_dir)/generators/sifive-blocks/vsrc/SRLatch.v ######################################################################################### # vivado rules diff --git a/fpga/fpga-shells b/fpga/fpga-shells index 474ad191..34678a81 160000 --- a/fpga/fpga-shells +++ b/fpga/fpga-shells @@ -1 +1 @@ -Subproject commit 474ad19113b89ed5679695b269acdb011b9b871a +Subproject commit 34678a8123602860d897b8b9d731d951e99aa21d diff --git a/fpga/src/main/scala/arty/Configs.scala b/fpga/src/main/scala/arty/Configs.scala index 1c81f481..a88848d0 100644 --- a/fpga/src/main/scala/arty/Configs.scala +++ b/fpga/src/main/scala/arty/Configs.scala @@ -15,30 +15,20 @@ import testchipip.{SerialTLKey} import chipyard.{BuildSystem} -class WithDefaultPeripherals extends Config((site, here, up) => { - case PeripheryUARTKey => List( - UARTParams(address = 0x10013000)) - case DTSTimebase => BigInt(32768) - case JtagDTMKey => new JtagDTMConfig ( - idcodeVersion = 2, - idcodePartNum = 0x000, - idcodeManufId = 0x489, - debugIdleCycles = 5) - case SerialTLKey => None // remove serialized tl port -}) - // DOC include start: AbstractArty and Rocket class WithArtyTweaks extends Config( new WithArtyJTAGHarnessBinder ++ new WithArtyUARTHarnessBinder ++ new WithArtyResetHarnessBinder ++ new WithDebugResetPassthrough ++ - new WithDefaultPeripherals ++ - new freechips.rocketchip.subsystem.WithNBreakpoints(2) + + new chipyard.config.WithDTSTimebase(32768) ++ + new testchipip.WithNoSerialTL ) class TinyRocketArtyConfig extends Config( new WithArtyTweaks ++ + new freechips.rocketchip.subsystem.WithNBreakpoints(2) ++ new chipyard.TinyRocketConfig ) // DOC include end: AbstractArty and Rocket diff --git a/fpga/src/main/scala/arty100t/Configs.scala b/fpga/src/main/scala/arty100t/Configs.scala new file mode 100644 index 00000000..bade069d --- /dev/null +++ b/fpga/src/main/scala/arty100t/Configs.scala @@ -0,0 +1,40 @@ +// See LICENSE for license details. +package chipyard.fpga.arty100t + +import freechips.rocketchip.config._ +import freechips.rocketchip.subsystem._ +import freechips.rocketchip.devices.debug._ +import freechips.rocketchip.devices.tilelink._ +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.system._ +import freechips.rocketchip.tile._ + +import sifive.blocks.devices.uart._ +import sifive.fpgashells.shell.{DesignKey} + +import testchipip.{SerialTLKey} + +import chipyard.{BuildSystem} + +// don't use FPGAShell's DesignKey +class WithNoDesignKey extends Config((site, here, up) => { + case DesignKey => (p: Parameters) => new SimpleLazyModule()(p) +}) + +class WithArty100TTweaks extends Config( + new WithArty100TUARTTSI ++ + new WithArty100TDDRTL ++ + new WithNoDesignKey ++ + new chipyard.config.WithNoDebug ++ // no jtag + new chipyard.config.WithNoUART ++ + new chipyard.config.WithTLBackingMemory ++ + new freechips.rocketchip.subsystem.WithExtMemSize(BigInt(256) << 20) // 256mb on ARTY +) + +class RocketArtyConfig extends Config( + new WithArty100TTweaks ++ + new chipyard.config.WithMemoryBusFrequency(10.0) ++ // 2x the U540 freq (appropriate for a 128b Mbus) + new chipyard.config.WithPeripheryBusFrequency(10.0) ++ // Match the sbus and pbus frequency + new chipyard.config.WithBroadcastManager ++ // no l2 + new chipyard.RocketConfig +) diff --git a/fpga/src/main/scala/arty100t/Harness.scala b/fpga/src/main/scala/arty100t/Harness.scala new file mode 100644 index 00000000..2b2dd209 --- /dev/null +++ b/fpga/src/main/scala/arty100t/Harness.scala @@ -0,0 +1,58 @@ +package chipyard.fpga.arty100t + +import chisel3._ + +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.config.{Parameters} +import freechips.rocketchip.tilelink.{TLClientNode} + +import sifive.fpgashells.shell.xilinx._ +import sifive.fpgashells.shell._ +import sifive.fpgashells.clocks.{ClockGroup, ClockSinkNode, PLLFactoryKey, ResetWrangler} +import sifive.fpgashells.ip.xilinx.{IBUF, PowerOnResetFPGAOnly} + +import sifive.blocks.devices.uart._ + +import chipyard._ +import chipyard.harness.{ApplyHarnessBinders} +import chipyard.iobinders.{HasIOBinders} + +class Arty100THarness(override implicit val p: Parameters) extends Arty100TShell with HasHarnessSignalReferences +{ + def dp = designParameters + + val chiptop = LazyModule(p(BuildTop)(p)) + + val clockOverlay = dp(ClockInputOverlayKey).map(_.place(ClockInputDesignInput())).head + val harnessSysPLL = dp(PLLFactoryKey)() + println(s"Arty100T FPGA Base Clock Freq: ${dp(DefaultClockFrequencyKey)} MHz") + val dutClock = ClockSinkNode(freqMHz = dp(DefaultClockFrequencyKey)) + val dutWrangler = LazyModule(new ResetWrangler) + val dutGroup = ClockGroup() + dutClock := dutWrangler.node := dutGroup := harnessSysPLL + + harnessSysPLL := clockOverlay.overlayOutput.node + + val io_uart_bb = BundleBridgeSource(() => new UARTPortIO(dp(PeripheryUARTKey).headOption.getOrElse(UARTParams(0)))) + val uartOverlay = dp(UARTOverlayKey).head.place(UARTDesignInput(io_uart_bb)) + + val ddrOverlay = dp(DDROverlayKey).head.place(DDRDesignInput(dp(ExtTLMem).get.master.base, dutWrangler.node, harnessSysPLL)) + val ddrInParams = chiptop match { case td: ChipTop => + td.lazySystem match { case lsys: CanHaveMasterTLMemPort => + lsys.memTLNode.edges.in(0) + } + } + val ddrClient = TLClientNode(Seq(ddrInParams.master)) + ddrOverlay.overlayOutput.ddr := ddrClient + + def buildtopClock = dutClock.in.head._1.clock + def buildtopReset = dutClock.in.head._1.reset + def success = { require(false, "Unused"); false.B } + + InModuleBody { + chiptop match { case d: HasIOBinders => + ApplyHarnessBinders(this, d.lazySystem, d.portMap) + } + } + +} diff --git a/fpga/src/main/scala/arty100t/HarnessBinders.scala b/fpga/src/main/scala/arty100t/HarnessBinders.scala new file mode 100644 index 00000000..f8763948 --- /dev/null +++ b/fpga/src/main/scala/arty100t/HarnessBinders.scala @@ -0,0 +1,48 @@ +package chipyard.fpga.arty100t + +import chisel3._ + +import freechips.rocketchip.devices.debug.{HasPeripheryDebug, HasPeripheryDebugModuleImp} +import freechips.rocketchip.jtag.{JTAGIO} +import freechips.rocketchip.subsystem.{PeripheryBusKey} +import freechips.rocketchip.tilelink.{TLBundle} +import freechips.rocketchip.util.{HeterogeneousBag} + +import sifive.blocks.devices.uart.{UARTPortIO, HasPeripheryUARTModuleImp} +import sifive.blocks.devices.jtag.{JTAGPins, JTAGPinsFromPort} +import sifive.blocks.devices.pinctrl.{BasePin} + +import sifive.fpgashells.ip.xilinx.{IBUFG, IOBUF, PULLUP, PowerOnResetFPGAOnly} + +import chipyard._ +import chipyard.harness._ +import chipyard.iobinders.JTAGChipIO + +import testchipip._ + +class WithArty100TUARTTSI extends OverrideHarnessBinder({ + (system: CanHavePeripheryTLSerial, th: HasHarnessSignalReferences, ports: Seq[ClockedIO[SerialIO]]) => { + implicit val p = chipyard.iobinders.GetSystemParameters(system) + ports.map({ port => + val freq = p(PeripheryBusKey).dtsFrequency.get + val bits = SerialAdapter.asyncQueue(port, th.buildtopClock, th.buildtopReset) + withClockAndReset(th.buildtopClock, th.buildtopReset) { + val ram = SerialAdapter.connectHarnessRAM(system.serdesser.get, bits, th.buildtopReset) + val uart_to_tsi = Module(new UARTToTSI(freq)) + ram.module.io.tsi_ser.flipConnect(uart_to_tsi.io.serial) + th.asInstanceOf[Arty100THarness].io_uart_bb.bundle <> uart_to_tsi.io.uart + } + }) + } +}) + +class WithArty100TDDRTL extends OverrideHarnessBinder({ + (system: CanHaveMasterTLMemPort, th: HasHarnessSignalReferences, ports: Seq[HeterogeneousBag[TLBundle]]) => { + require(ports.size == 1) + val artyTh = th.asInstanceOf[Arty100THarness] + val bundles = artyTh.ddrClient.out.map(_._1) + val ddrClientBundle = Wire(new HeterogeneousBag(bundles.map(_.cloneType))) + bundles.zip(ddrClientBundle).foreach { case (bundle, io) => bundle <> io } + ddrClientBundle <> ports.head + } +}) diff --git a/fpga/src/main/scala/arty100t/IOBinders.scala b/fpga/src/main/scala/arty100t/IOBinders.scala new file mode 100644 index 00000000..5a83838d --- /dev/null +++ b/fpga/src/main/scala/arty100t/IOBinders.scala @@ -0,0 +1,23 @@ +package chipyard.fpga.arty100t + +import chisel3._ +import chisel3.experimental.{IO} + +import freechips.rocketchip.devices.debug.{HasPeripheryDebugModuleImp} + +import chipyard.iobinders.{ComposeIOBinder} + +// class WithDebugResetPassthrough extends ComposeIOBinder({ +// (system: HasPeripheryDebugModuleImp) => { +// // Debug module reset +// val io_ndreset: Bool = IO(Output(Bool())).suggestName("ndreset") +// io_ndreset := system.debug.get.ndreset + +// // JTAG reset +// val sjtag = system.debug.get.systemjtag.get +// val io_sjtag_reset: Bool = IO(Input(Bool())).suggestName("sjtag_reset") +// sjtag.reset := io_sjtag_reset + +// (Seq(io_ndreset, io_sjtag_reset), Nil) +// } +// }) diff --git a/fpga/src/main/scala/vcu118/Configs.scala b/fpga/src/main/scala/vcu118/Configs.scala index 6c8cb3a6..85b6ee24 100644 --- a/fpga/src/main/scala/vcu118/Configs.scala +++ b/fpga/src/main/scala/vcu118/Configs.scala @@ -47,7 +47,6 @@ class WithVCU118Tweaks extends Config( // io binders new WithUARTIOPassthrough ++ new WithSPIIOPassthrough ++ - new WithTLIOPassthrough ++ // other configuration new WithDefaultPeripherals ++ new chipyard.config.WithTLBackingMemory ++ // use TL backing memory diff --git a/fpga/src/main/scala/vcu118/IOBinders.scala b/fpga/src/main/scala/vcu118/IOBinders.scala index a1f67bcd..279f9865 100644 --- a/fpga/src/main/scala/vcu118/IOBinders.scala +++ b/fpga/src/main/scala/vcu118/IOBinders.scala @@ -42,11 +42,3 @@ class WithSPIIOPassthrough extends OverrideLazyIOBinder({ } } }) - -class WithTLIOPassthrough extends OverrideIOBinder({ - (system: CanHaveMasterTLMemPort) => { - val io_tl_mem_pins_temp = IO(DataMirror.internal.chiselTypeClone[HeterogeneousBag[TLBundle]](system.mem_tl)).suggestName("tl_slave") - io_tl_mem_pins_temp <> system.mem_tl - (Seq(io_tl_mem_pins_temp), Nil) - } -}) diff --git a/fpga/src/main/scala/vcu118/TestHarness.scala b/fpga/src/main/scala/vcu118/TestHarness.scala index 90ce51e0..9a3cc0d5 100644 --- a/fpga/src/main/scala/vcu118/TestHarness.scala +++ b/fpga/src/main/scala/vcu118/TestHarness.scala @@ -7,15 +7,15 @@ import freechips.rocketchip.diplomacy.{LazyModule, LazyRawModuleImp, BundleBridg import freechips.rocketchip.config.{Parameters} import freechips.rocketchip.tilelink.{TLClientNode} -import sifive.fpgashells.shell.xilinx.{VCU118ShellBasicOverlays, UARTVCU118ShellPlacer, SDIOVCU118ShellPlacer, JTAGDebugBScanVCU118ShellPlacer, JTAGDebugVCU118ShellPlacer, cJTAGDebugVCU118ShellPlacer, PCIeVCU118FMCShellPlacer, PCIeVCU118EdgeShellPlacer, VCU118ShellPMOD, ChipLinkVCU118PlacedOverlay} +import sifive.fpgashells.shell.xilinx._ import sifive.fpgashells.ip.xilinx.{IBUF, PowerOnResetFPGAOnly} -import sifive.fpgashells.shell.{ClockInputOverlayKey, ClockInputDesignInput, ClockInputShellInput, UARTOverlayKey, UARTDesignInput, UARTShellInput, SPIOverlayKey, SPIDesignInput, SPIShellInput, JTAGDebugOverlayKey, JTAGDebugShellInput, JTAGDebugBScanOverlayKey, JTAGDebugBScanShellInput, cJTAGDebugOverlayKey, cJTAGDebugShellInput, PCIeOverlayKey, PCIeDesignInput, PCIeShellInput, DDROverlayKey, DDRDesignInput, DDRShellInput} +import sifive.fpgashells.shell._ import sifive.fpgashells.clocks.{ClockGroup, ClockSinkNode, PLLFactoryKey, ResetWrangler} import sifive.blocks.devices.uart.{PeripheryUARTKey, UARTPortIO} import sifive.blocks.devices.spi.{PeripherySPIKey, SPIPortIO} -import chipyard.{HasHarnessSignalReferences, BuildTop, ChipTop, ExtTLMem, CanHaveMasterTLMemPort, DefaultClockFrequencyKey} +import chipyard._ import chipyard.iobinders.{HasIOBinders} import chipyard.harness.{ApplyHarnessBinders} diff --git a/generators/chipyard/src/main/scala/HarnessBinders.scala b/generators/chipyard/src/main/scala/HarnessBinders.scala index 5ff78a65..c0cc942c 100644 --- a/generators/chipyard/src/main/scala/HarnessBinders.scala +++ b/generators/chipyard/src/main/scala/HarnessBinders.scala @@ -321,6 +321,24 @@ class WithSimSerial extends OverrideHarnessBinder({ } }) +class WithUARTSerial extends OverrideHarnessBinder({ + (system: CanHavePeripheryTLSerial, th: HasHarnessSignalReferences, ports: Seq[ClockedIO[SerialIO]]) => { + implicit val p = chipyard.iobinders.GetSystemParameters(system) + ports.map({ port => + val freq = p(PeripheryBusKey).dtsFrequency.get + val bits = SerialAdapter.asyncQueue(port, th.buildtopClock, th.buildtopReset) + withClockAndReset(th.buildtopClock, th.buildtopReset) { + val ram = SerialAdapter.connectHarnessRAM(system.serdesser.get, bits, th.buildtopReset) + val uart_to_tsi = Module(new UARTToTSI(freq)) + UARTAdapter.connect(Seq(uart_to_tsi.io.uart), uart_to_tsi.div) + ram.module.io.tsi_ser.flipConnect(uart_to_tsi.io.serial) + th.success := false.B + } + }) + } +}) + + class WithTraceGenSuccess extends OverrideHarnessBinder({ (system: TraceGenSystemModuleImp, th: HasHarnessSignalReferences, ports: Seq[Bool]) => { ports.map { p => when (p) { th.success := true.B } } diff --git a/generators/chipyard/src/main/scala/IOBinders.scala b/generators/chipyard/src/main/scala/IOBinders.scala index efbe542d..71eef713 100644 --- a/generators/chipyard/src/main/scala/IOBinders.scala +++ b/generators/chipyard/src/main/scala/IOBinders.scala @@ -13,6 +13,7 @@ import freechips.rocketchip.amba.axi4.{AXI4Bundle, AXI4SlaveNode, AXI4MasterNode import freechips.rocketchip.util._ import freechips.rocketchip.prci._ import freechips.rocketchip.groundtest.{GroundTestSubsystemModuleImp, GroundTestSubsystem} +import freechips.rocketchip.tilelink.{TLBundle} import sifive.blocks.devices.gpio._ import sifive.blocks.devices.uart._ @@ -23,6 +24,7 @@ import barstools.iocell.chisel._ import testchipip._ import icenet.{CanHavePeripheryIceNIC, SimNetwork, NicLoopback, NICKey, NICIOvonly} +import chipyard.{CanHaveMasterTLMemPort} import chipyard.clocking.{HasChipyardPRCI, DividerOnlyClockGenerator} import scala.reflect.{ClassTag} @@ -381,6 +383,15 @@ class WithCustomBootPin extends OverrideIOBinder({ }).getOrElse((Nil, Nil)) }) +class WithTLMemPunchthrough extends OverrideIOBinder({ + (system: CanHaveMasterTLMemPort) => { + val io_tl_mem_pins_temp = IO(DataMirror.internal.chiselTypeClone[HeterogeneousBag[TLBundle]](system.mem_tl)).suggestName("tl_slave") + io_tl_mem_pins_temp <> system.mem_tl + (Seq(io_tl_mem_pins_temp), Nil) + } +}) + + class WithDontTouchPorts extends OverrideIOBinder({ (system: DontTouch) => system.dontTouchPorts(); (Nil, Nil) }) diff --git a/generators/chipyard/src/main/scala/TestHarness.scala b/generators/chipyard/src/main/scala/TestHarness.scala index d8884410..240ae5cc 100644 --- a/generators/chipyard/src/main/scala/TestHarness.scala +++ b/generators/chipyard/src/main/scala/TestHarness.scala @@ -27,7 +27,6 @@ trait HasHarnessSignalReferences { def getRefClockFreq: Double = refClockFreq def buildtopClock: Clock def buildtopReset: Reset - def dutReset: Reset def success: Bool } @@ -91,7 +90,6 @@ class TestHarness(implicit val p: Parameters) extends Module with HasHarnessSign io.success := false.B - val dutReset = buildtopReset.asAsyncReset val success = io.success lazyDut match { case d: HasIOBinders => diff --git a/generators/chipyard/src/main/scala/config/AbstractConfig.scala b/generators/chipyard/src/main/scala/config/AbstractConfig.scala index 1f43dcbf..146d7294 100644 --- a/generators/chipyard/src/main/scala/config/AbstractConfig.scala +++ b/generators/chipyard/src/main/scala/config/AbstractConfig.scala @@ -28,6 +28,7 @@ class AbstractConfig extends Config( // IOCells are generated for "Chip-like" IOs, while simulation-only IOs are directly punched through new chipyard.iobinders.WithAXI4MemPunchthrough ++ new chipyard.iobinders.WithAXI4MMIOPunchthrough ++ + new chipyard.iobinders.WithTLMemPunchthrough ++ new chipyard.iobinders.WithL2FBusAXI4Punchthrough ++ new chipyard.iobinders.WithBlockDeviceIOPunchthrough ++ new chipyard.iobinders.WithNICIOPunchthrough ++ diff --git a/generators/chipyard/src/main/scala/config/RocketConfigs.scala b/generators/chipyard/src/main/scala/config/RocketConfigs.scala index c6f5ca22..b6677cb1 100644 --- a/generators/chipyard/src/main/scala/config/RocketConfigs.scala +++ b/generators/chipyard/src/main/scala/config/RocketConfigs.scala @@ -21,6 +21,14 @@ class TinyRocketConfig extends Config( new freechips.rocketchip.subsystem.With1TinyCore ++ // single tiny rocket-core new chipyard.config.AbstractConfig) +class UARTTSIRocketConfig extends Config( + new chipyard.harness.WithUARTSerial ++ + new chipyard.config.WithNoUART ++ + new chipyard.config.WithMemoryBusFrequency(10) ++ + new chipyard.config.WithPeripheryBusFrequency(10) ++ + new freechips.rocketchip.subsystem.WithNBigCores(1) ++ // single rocket-core + new chipyard.config.AbstractConfig) + class SimAXIRocketConfig extends Config( new chipyard.harness.WithSimAXIMem ++ // drive the master AXI4 memory with a SimAXIMem, a 1-cycle magic memory, instead of default SimDRAM new freechips.rocketchip.subsystem.WithNBigCores(1) ++ diff --git a/generators/chipyard/src/main/scala/config/fragments/PeripheralFragments.scala b/generators/chipyard/src/main/scala/config/fragments/PeripheralFragments.scala index eec0d15d..ec9ff47c 100644 --- a/generators/chipyard/src/main/scala/config/fragments/PeripheralFragments.scala +++ b/generators/chipyard/src/main/scala/config/fragments/PeripheralFragments.scala @@ -37,6 +37,10 @@ class WithUART(baudrate: BigInt = 115200) extends Config((site, here, up) => { UARTParams(address = 0x54000000L, nTxEntries = 256, nRxEntries = 256, initBaudRate = baudrate)) }) +class WithNoUART extends Config((site, here, up) => { + case PeripheryUARTKey => Nil +}) + class WithUARTFIFOEntries(txEntries: Int, rxEntries: Int) extends Config((site, here, up) => { case PeripheryUARTKey => up(PeripheryUARTKey).map(_.copy(nTxEntries = txEntries, nRxEntries = rxEntries)) }) diff --git a/generators/chipyard/src/main/scala/config/fragments/SubsystemFragments.scala b/generators/chipyard/src/main/scala/config/fragments/SubsystemFragments.scala index 534259ab..c41e2716 100644 --- a/generators/chipyard/src/main/scala/config/fragments/SubsystemFragments.scala +++ b/generators/chipyard/src/main/scala/config/fragments/SubsystemFragments.scala @@ -2,6 +2,7 @@ package chipyard.config import freechips.rocketchip.config.{Config} import freechips.rocketchip.subsystem.{SystemBusKey, BankedL2Key, CoherenceManagerWrapper} +import freechips.rocketchip.diplomacy.{DTSTimebase} // Replaces the L2 with a broadcast manager for maintaining coherence class WithBroadcastManager extends Config((site, here, up) => { @@ -11,3 +12,7 @@ class WithBroadcastManager extends Config((site, here, up) => { class WithSystemBusWidth(bitWidth: Int) extends Config((site, here, up) => { case SystemBusKey => up(SystemBusKey, site).copy(beatBytes=bitWidth/8) }) + +class WithDTSTimebase(freqMHz: BigInt) extends Config((site, here, up) => { + case DTSTimebase => freqMHz +}) diff --git a/generators/testchipip b/generators/testchipip index 2906d503..653c86b0 160000 --- a/generators/testchipip +++ b/generators/testchipip @@ -1 +1 @@ -Subproject commit 2906d503cf6df42e5b6da576ff9e67d0c65368bc +Subproject commit 653c86b0e81f3f9ac7e6d0a50f250be5a4bb0e1c