1
0

Compare commits

..

18 Commits

Author SHA1 Message Date
b49f5cfa78 Reduce crossing and queue depths to save space and ease timing 2018-05-14 20:07:01 +02:00
700e6b640d Document address extraction for the mig 2018-05-13 19:52:38 +02:00
12cb1c2fa5 Implement XilinxML507MIGToTL TL to MIG converter 2018-05-10 21:40:52 +02:00
7e53be49f9 Fix memory controller signal name 2018-05-10 02:27:31 +02:00
77694a6741 Add clock generation for the mig 2018-05-10 01:04:52 +02:00
589e9960c0 Move XilinxML507MIGToTL and MIG into a separate clock domain 2018-05-10 00:30:23 +02:00
2707fa59a4 Add XilinxML507MIG periphery and connect top level signals 2018-05-10 00:29:22 +02:00
3797385a8c Import ml507 mig TL implementation stub 2018-05-09 23:17:08 +02:00
79b53cf2ae Add dip switches and clean up top interface 2018-05-01 00:07:58 +02:00
5bcc4e82fd Generate separate processor and terminal clocks 2018-04-30 22:52:02 +02:00
9c06418352 Add terminal/dvi io (unsing the same clock for now) 2018-04-30 00:41:05 +02:00
b2b19cc822 Add clock and proper reset feedback to ml507 2018-04-19 01:29:15 +02:00
5db71d11c2 Fix polarity of buttons and dips on the ml507 2018-04-19 01:28:36 +02:00
f4ae1d469f Remove unused signals (pcie, mem) from ml507 shell 2018-04-19 01:27:35 +02:00
0b421d5645 Remove incorrect jtag pin constraints form ml507 2018-04-19 01:26:15 +02:00
8329b232e2 Hold ml507 in reset while clock not locked 2018-04-19 01:25:31 +02:00
2ff28e6af6 Add status indication led for the reset button 2018-04-18 00:26:43 +02:00
41362a1cb5 Remove unused UART signals (rs and cs) from ml507 2018-04-18 00:26:00 +02:00
4 changed files with 380 additions and 126 deletions

View File

@ -0,0 +1,199 @@
// See LICENSE.SiFive for license details.
package sifive.fpgashells.devices.xilinx.xilinxml507mig
import Chisel._
import chisel3.core.{Input, Output}
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.subsystem.{AsynchronousCrossing, HasCrossing}
import freechips.rocketchip.tilelink._
import freechips.rocketchip.util._
case class XilinxML507MIGParams(
address: Seq[AddressSet]
)
class MemoryController extends BlackBox {
val io = IO(new Bundle {
val sys = new MemorySysIO
val ddr2 = new MemoryDDR2IO
val request_addr = Input(UInt(28.W))
val request_type = Input(Bool())
val request_data = Input(UInt(256.W))
val request_mask = Input(UInt(32.W))
val request_valid = Input(Bool())
val request_ready = Output(Bool())
val response_data = Output(UInt(256.W))
val response_valid = Output(Bool())
// no ready, as the mig does not wait
})
override def desiredName: String = "memory_controller"
}
class ResponseQueueIO extends Bundle {
val read = Bool()
val source = UInt()
val size = UInt()
}
class XilinxML507MIGToTL(c: XilinxML507MIGParams)(implicit p: Parameters) extends LazyModule with HasCrossing {
// Corresponds to MIG interface with 64 bit width and a burst length of 4
val width = 256
val beatBytes = width/8 // 32 byte (half a cache-line, fragmented)
val address_range = AddressRange.fromSets(c.address).head
require(log2Ceil(address_range.size) == 28, "Max 256MiB DIMMs supported")
val crossing = AsynchronousCrossing(1)
val device = new MemoryDevice
val node = TLManagerNode(
Seq(TLManagerPortParameters(
Seq(TLManagerParameters(
address = c.address,
resources = device.reg,
regionType = RegionType.UNCACHED,
executable = true,
supportsGet = TransferSizes(1, beatBytes),
supportsPutFull = TransferSizes(1, beatBytes),
fifoId = Some(0) // in-order
)),
beatBytes = beatBytes
))
)
// We could possibly also support supportsPutPartial, as we need support
// for masks anyway because of the possibility of transfers smaller that
// the data width (size signal, see below).
// Seems we can: TL$7.3
lazy val module = new LazyModuleImp(this) {
val io = IO(new Bundle {
val port_sys = new MemorySysIO
val port_ddr2 = new MemoryDDR2IO
})
val controller = Module(new MemoryController)
io.port_sys <> controller.io.sys
io.port_ddr2 <> controller.io.ddr2
// in: TLBundle, edge: TLEdgeIn
val (in, edge) = node.in(0)
// Due to the TLFragmenter defined below, all messages are 32 bytes or
// smaller. The data signal of the TL channels is also 32 bytes, so
// all messages will be transfered in a single beat.
// Also, TL guarantees (see TL$4.6) that the payload of a data message
// is always aligned to the width of the beat, e.g. in case of a 32
// byte data signal, data[7:0] will always have address 0x***00000 and
// data[255:247] address 0x***11111. It is also guaranteed that the
// mask bits always correctly reflect the active bytes inside the beat
// with respect to the size and address. So we can directly forward
// the mask, (relative) address and data to the MIG interface.
// An AddressSet is always aligned, so we don't need to subtract the
// base address, we can just take the lower bits. The lowest 5 bits
// are used for indexing the 32 byte word of the MIG.
val address = in.a.bits.address(27, 0) & "hFFFFFE0".U
// Save the source, size and type of the requests in a queue so we
// can synthesize the right responses in fifo order. The length also
// determines the maximum number of in-flight requests.
val ack_queue = Module(new Queue(new ResponseQueueIO, 2))
// Pass data directly to the controller
controller.io.request_addr := address
controller.io.request_type := !edge.hasData(in.a.bits)
controller.io.request_data := in.a.bits.data
// TL uses high to indicate valid data while mig uses low
controller.io.request_mask := ~ in.a.bits.mask
ack_queue.io.enq.bits.read := !edge.hasData(in.a.bits)
ack_queue.io.enq.bits.source := in.a.bits.source
ack_queue.io.enq.bits.size := in.a.bits.size
// We are ready when the controller and the queue input are ready
in.a.ready := controller.io.request_ready && ack_queue.io.enq.ready
// Both queues only latch data if the other is ready, so that data
// is latched into both queues or not at all
controller.io.request_valid := in.a.valid && ack_queue.io.enq.ready
ack_queue.io.enq.valid := in.a.valid && controller.io.request_ready
// We have to buffer the responses from the MIG as it has no internal
// buffer and will output its read responses only for one cycle. To
// avoid losing any responses, this queue *must* be at least as wide
// as the ack queue, so that we can catch all responses, even if the
// ack queue is completely filled with read requests.
val response_queue = Module(new Queue(controller.io.response_data, 2))
response_queue.io.enq.bits := controller.io.response_data
response_queue.io.enq.valid := controller.io.response_valid
// MIG does not support delaying a response, so we ignore enq.ready.
// This will result in lost reads and returning wrong data in further
// AccessAckData messages, so this must be avoided (see above).
// Acks may or may not contain data depending on the request, but we
// can always pass the data, even if it is invalid in the write case,
// because it is ignored for AccessAck responses
val response_read = ack_queue.io.deq.bits.read
in.d.bits.opcode := Mux(response_read, TLMessages.AccessAckData, TLMessages.AccessAck)
in.d.bits.param := UInt(0) // reserved, must be 0
in.d.bits.size := ack_queue.io.deq.bits.size
in.d.bits.source := ack_queue.io.deq.bits.source
in.d.bits.sink := UInt(0) // ignored
in.d.bits.data := response_queue.io.deq.bits
in.d.bits.error := Bool(false)
// The data is valid when the ack queue data is valid (write case) or
// when the ack *and* response queues are valid (read case)
in.d.valid := ack_queue.io.deq.valid && (!response_read ||
response_queue.io.deq.valid)
// Let the ack queue dequeue when the master is ready (write case) or
// when the master is ready *and* there is a valid response (read case)
ack_queue.io.deq.ready := in.d.ready && (!response_read ||
response_queue.io.deq.valid)
// Let the response queue dequeue when the master is ready and there
// is a valid read ack waiting
response_queue.io.deq.ready := in.d.ready && response_read &&
ack_queue.io.deq.valid
// Tie off unused channels
in.b.valid := Bool(false)
in.c.ready := Bool(true)
in.e.ready := Bool(true)
}
}
class XilinxML507MIG(c : XilinxML507MIGParams)(implicit p: Parameters) extends LazyModule {
// The Fragmenter will not fragment messages <= 32 bytes, so all
// slaves have to support this size. 64 byte specifies the maximum
// supported transfer size that the slave side of the fragmenter supports
// against the master (here the main memory bus). Specifying alwaysMin as
// true results in all messages being fragmented to the minimal size
// (32 byte). In TL1 terms, slaves correspond roughly to managers and
// masters to clients (confusingly…).
val fragmenter = LazyModule(new TLFragmenter(32, 64, alwaysMin=true))
val island = LazyModule(new XilinxML507MIGToTL(c))
val node: TLInwardNode =
island.node := island.crossTLIn := fragmenter.node
lazy val module = new LazyModuleImp(this) {
val io = IO(new Bundle {
val port_sys = new MemorySysIO
val port_ddr2 = new MemoryDDR2IO
})
io.port_sys <> island.module.io.port_sys
io.port_ddr2 <> island.module.io.port_ddr2
// The MIGToTL module lives in a separate clock domain together with
// the MIG, which is why it is called "island".
island.module.clock := io.port_sys.clk0
island.module.reset := io.port_sys.reset
}
}

View File

@ -0,0 +1,58 @@
// See LICENSE.SiFive for license details.
package sifive.fpgashells.devices.xilinx.xilinxml507mig
import Chisel._
import chisel3.core.{Input, Output}
import chisel3.experimental.Analog
import freechips.rocketchip.config.Field
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.subsystem.BaseSubsystem
case object MemoryML507Key extends Field[XilinxML507MIGParams]
trait HasMemoryML507 { this: BaseSubsystem =>
val memory = LazyModule(new XilinxML507MIG(p(MemoryML507Key)))
memory.node := memBuses.head.toDRAMController(Some("xilinxml507mig"))()
}
class MemorySysIO extends Bundle {
val clk0 = Input(Clock())
val clk90 = Input(Clock())
val clkdiv0 = Input(Clock())
val clk_locked = Input(Bool())
val clk_idelay = Input(Clock())
val reset = Input(Bool())
}
class MemoryDDR2IO extends Bundle {
val dq = Analog(64.W)
val a = Output(Bits(13.W))
val ba = Output(Bits(2.W))
val ras_n = Output(Bits(1.W))
val cas_n = Output(Bits(1.W))
val we_n = Output(Bits(1.W))
val cs_n = Output(Bits(1.W))
val odt = Output(Bits(1.W))
val cke = Output(Bits(1.W))
val dm = Output(Bits(8.W))
val dqs = Analog(8.W)
val dqs_n = Analog(8.W)
val ck = Output(Bits(2.W))
val ck_n = Output(Bits(2.W))
}
trait HasMemoryML507Bundle {
val ddr_sys: MemorySysIO
val ddr2: MemoryDDR2IO
}
trait HasMemoryML507ModuleImp extends LazyModuleImp with HasMemoryML507Bundle {
val outer: HasMemoryML507
val ddr_sys = IO(new MemorySysIO)
val ddr2 = IO(new MemoryDDR2IO)
ddr_sys <> outer.memory.module.io.port_sys
ddr2 <> outer.memory.module.io.port_ddr2
}

View File

@ -94,11 +94,34 @@ object PowerOnResetFPGAOnly {
}
}
// ML507 clock generation
//-------------------------------------------------------------------------
// ml507 clocks (DCM_ADV)
//-------------------------------------------------------------------------
class ml507_sys_clock extends BlackBox {
val io = new Bundle {
val CLKIN_IN = Bool(INPUT)
val CLKIN_IN = Clock(INPUT)
val CLKFX_OUT = Clock(OUTPUT)
val LOCKED_OUT = Bool(OUTPUT)
}
}
class ml507_dvi_clock extends BlackBox {
val io = new Bundle {
val CLKIN_IN = Clock(INPUT)
val CLKFX_OUT = Clock(OUTPUT)
val LOCKED_OUT = Bool(OUTPUT)
}
}
class ml507_ddr2_clock extends BlackBox {
val io = new Bundle {
val CLKIN_P_IN = Clock(INPUT)
val CLKIN_N_IN = Clock(INPUT)
val CLK0_OUT = Clock(OUTPUT)
val CLK90_OUT = Clock(OUTPUT)
val CLKDV_OUT = Clock(OUTPUT)
val LOCKED_OUT = Bool(OUTPUT)
}
}

View File

@ -13,8 +13,10 @@ import sifive.blocks.devices.gpio._
import sifive.blocks.devices.spi._
import sifive.blocks.devices.uart._
import sifive.blocks.devices.chiplink._
import sifive.blocks.devices.terminal._
import sifive.fpgashells.ip.xilinx.{IBUFDS, PowerOnResetFPGAOnly, sdio_spi_bridge, ml507_sys_clock , vc707reset}
import sifive.fpgashells.devices.xilinx.xilinxml507mig._
import sifive.fpgashells.ip.xilinx.{PowerOnResetFPGAOnly, sdio_spi_bridge, ml507_ddr2_clock, ml507_dvi_clock, ml507_sys_clock, vc707reset}
//-------------------------------------------------------------------------
// ML507Shell
@ -30,47 +32,6 @@ trait HasDebugJTAG { this: ML507Shell =>
val jtag_TDO = IO(Output(Bool()))
def connectDebugJTAG(dut: HasPeripheryDebugModuleImp, fmcxm105: Boolean = true): SystemJTAGIO = {
ElaborationArtefacts.add(
"""debugjtag.vivado.tcl""",
"""set vc707debugjtag_vivado_tcl_dir [file dirname [file normalize [info script]]]
add_files -fileset [current_fileset -constrset] [glob -directory $vc707debugjtag_vivado_tcl_dir {*.vc707debugjtag.xdc}]"""
)
if(fmcxm105) {
//VC707 constraints for Xilinx FMC XM105 Debug Card
ElaborationArtefacts.add(
"""vc707debugjtag.xdc""",
"""set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets jtag_TCK_IBUF]
set_property -dict { PACKAGE_PIN R32 IOSTANDARD LVCMOS18 PULLUP TRUE } [get_ports {jtag_TCK}]
set_property -dict { PACKAGE_PIN W36 IOSTANDARD LVCMOS18 PULLUP TRUE } [get_ports {jtag_TMS}]
set_property -dict { PACKAGE_PIN W37 IOSTANDARD LVCMOS18 PULLUP TRUE } [get_ports {jtag_TDI}]
set_property -dict { PACKAGE_PIN V40 IOSTANDARD LVCMOS18 PULLUP TRUE } [get_ports {jtag_TDO}] """
)
} else {
//VC707 constraints for Olimex connect to LCD panel header
ElaborationArtefacts.add(
"""vc707debugjtag.xdc""",
"""
#Olimex Pin Olimex Function LCD Pin LCD Function FPGA Pin
#1 VREF 14 5V
#3 TTRST_N 1 LCD_DB7 AN40
#5 TTDI 2 LCD_DB6 AR39
#7 TTMS 3 LCD_DB5 AR38
#9 TTCK 4 LCD_DB4 AT42
#11 TRTCK NC NC NC
#13 TTDO 9 LCD_E AT40
#15 TSRST_N 10 LCD_RW AR42
#2 VREF 14 5V
#18 GND 13 GND
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets jtag_TCK_IBUF]
set_property -dict { PACKAGE_PIN AT42 IOSTANDARD LVCMOS18 PULLUP TRUE } [get_ports {jtag_TCK}]
set_property -dict { PACKAGE_PIN AR38 IOSTANDARD LVCMOS18 PULLUP TRUE } [get_ports {jtag_TMS}]
set_property -dict { PACKAGE_PIN AR39 IOSTANDARD LVCMOS18 PULLUP TRUE } [get_ports {jtag_TDI}]
set_property -dict { PACKAGE_PIN AT40 IOSTANDARD LVCMOS18 PULLUP TRUE } [get_ports {jtag_TDO}] """
)
}
val djtag = dut.debug.systemjtag.get
djtag.jtag.TCK := jtag_TCK
@ -94,139 +55,152 @@ abstract class ML507Shell(implicit val p: Parameters) extends RawModule {
//-----------------------------------------------------------------------
// 100Mhz sysclk
val sys_clock_100 = IO(Input(Bool()))
val sys_clock = IO(Input(Clock()))
// active high reset
// 200MHz ddrclk
val ddr_clock_p = IO(Input(Clock()))
val ddr_clock_n = IO(Input(Clock()))
// active high async reset
val reset = IO(Input(Bool()))
// LED
val led = IO(Vec(8, Output(Bool())))
val led = IO(Output(Vec(8, Bool())))
// DIP switches
val dip = IO(Input(Vec(8, Bool())))
// UART
val uart_tx = IO(Output(Bool()))
val uart_rx = IO(Input(Bool()))
val uart_rtsn = IO(Output(Bool()))
val uart_ctsn = IO(Input(Bool()))
// SDIO
val sdio_clk = IO(Output(Bool()))
val sdio_cmd = IO(Analog(1.W))
val sdio_dat = IO(Analog(4.W))
//Buttons
val btn_0 = IO(Analog(1.W))
val btn_1 = IO(Analog(1.W))
val btn_2 = IO(Analog(1.W))
val btn_3 = IO(Analog(1.W))
//Sliding switches
val sw_0 = IO(Analog(1.W))
val sw_1 = IO(Analog(1.W))
val sw_2 = IO(Analog(1.W))
val sw_3 = IO(Analog(1.W))
val sw_4 = IO(Analog(1.W))
val sw_5 = IO(Analog(1.W))
val sw_6 = IO(Analog(1.W))
val sw_7 = IO(Analog(1.W))
// Feedback
val clock_led = IO(Output(Clock()))
val reset_led = IO(Output(Bool()))
val dvi = IO(new TerminalDVIIO)
val ddr2 = IO(new MemoryDDR2IO)
//-----------------------------------------------------------------------
// Wire declrations
//-----------------------------------------------------------------------
val sys_clock = Wire(Clock())
// async resets
val sys_reset = Wire(Bool())
val do_reset = Wire(Bool())
val dut_ndreset = Wire(Bool())
val dut_clock = Wire(Clock())
val dut_reset = Wire(Bool())
val dut_resetn = Wire(Bool())
val dut_ndreset = Wire(Bool())
val dvi_clock = Wire(Clock())
val dvi_reset = Wire(Bool())
val ddr_clk0 = Wire(Clock())
val ddr_clk90 = Wire(Clock())
val ddr_clkdiv0 = Wire(Clock())
val ddr_clk_locked = Wire(Bool())
val ddr_reset = Wire(Bool())
val sd_spi_sck = Wire(Bool())
val sd_spi_cs = Wire(Bool())
val sd_spi_dq_i = Wire(Vec(4, Bool()))
val sd_spi_dq_o = Wire(Vec(4, Bool()))
val do_reset = Wire(Bool())
val clk_locked = Wire(Bool())
val mig_mmcm_locked = Wire(Bool())
val mig_sys_reset = Wire(Bool())
val mig_clock = Wire(Clock())
val mig_reset = Wire(Bool())
val mig_resetn = Wire(Bool())
val pcie_dat_reset = Wire(Bool())
val pcie_dat_resetn = Wire(Bool())
val pcie_cfg_reset = Wire(Bool())
val pcie_cfg_resetn = Wire(Bool())
val pcie_dat_clock = Wire(Clock())
val pcie_cfg_clock = Wire(Clock())
val mmcm_lock_pcie = Wire(Bool())
//-----------------------------------------------------------------------
// System clock and reset
//-----------------------------------------------------------------------
// Clock that drives the clock generator and the MIG
sys_clock := sys_clock_100.asClock()
// Allow the debug module to reset everything. Resets the MIG
sys_reset := reset | dut_ndreset
//-----------------------------------------------------------------------
// Clock Generator
//-----------------------------------------------------------------------
//50MHz (37.5MHz)
val ml507_sys_clock = Module(new ml507_sys_clock)
ml507_sys_clock.io.CLKIN_IN := sys_clock.asUInt
val clk50 = ml507_sys_clock.io.CLKFX_OUT
// DUT clock
dut_clock := clk50
//-----------------------------------------------------------------------
// System reset
//-----------------------------------------------------------------------
do_reset := !mig_mmcm_locked || !mmcm_lock_pcie || mig_sys_reset
mig_resetn := !mig_reset
dut_resetn := !dut_reset
pcie_dat_resetn := !pcie_dat_reset
pcie_cfg_resetn := !pcie_cfg_reset
// Allow the debug module to reset everything. Resets the MIG
sys_reset := reset | dut_ndreset
// TODO: adapt for ml507?
//-----------------------------------------------------------------------
// Clock generators
//-----------------------------------------------------------------------
// 80 MHz (processor clock)
val ml507_sys_clock = Module(new ml507_sys_clock)
ml507_sys_clock.io.CLKIN_IN := sys_clock
dut_clock := ml507_sys_clock.io.CLKFX_OUT
// 48 MHz (DVI pixel clock for SDR 640x480x60)
val ml507_dvi_clock = Module(new ml507_dvi_clock)
ml507_dvi_clock.io.CLKIN_IN := sys_clock
dvi_clock := ml507_dvi_clock.io.CLKFX_OUT
// 200 MHz (DDR2 and IDELAY clock)
val ml507_ddr2_clock = Module(new ml507_ddr2_clock)
ml507_ddr2_clock.io.CLKIN_P_IN := ddr_clock_p
ml507_ddr2_clock.io.CLKIN_N_IN := ddr_clock_n
ddr_clk0 := ml507_ddr2_clock.io.CLK0_OUT
ddr_clk90 := ml507_ddr2_clock.io.CLK90_OUT
ddr_clkdiv0 := ml507_ddr2_clock.io.CLKDV_OUT
ddr_clk_locked := ml507_ddr2_clock.io.LOCKED_OUT
// Clocks locked?
clk_locked := ml507_sys_clock.io.LOCKED_OUT &
ml507_dvi_clock.io.LOCKED_OUT &
ddr_clk_locked
//-----------------------------------------------------------------------
// System reset
//-----------------------------------------------------------------------
do_reset := !clk_locked || sys_reset
// synchronize async resets
val safe_reset = Module(new vc707reset)
safe_reset.io.areset := do_reset
safe_reset.io.clock1 := mig_clock
mig_reset := safe_reset.io.reset1
safe_reset.io.clock2 := pcie_dat_clock
pcie_dat_reset := safe_reset.io.reset2
safe_reset.io.clock3 := pcie_cfg_clock
pcie_cfg_reset := safe_reset.io.reset3
safe_reset.io.clock1 := ddr_clk0
ddr_reset := safe_reset.io.reset1
safe_reset.io.clock2 := dut_clock
safe_reset.io.clock3 := dvi_clock
dvi_reset := safe_reset.io.reset3
safe_reset.io.clock4 := dut_clock
dut_reset := safe_reset.io.reset4
//overrided in connectMIG and connect PCIe
//provide defaults to allow above reset sequencing logic to work without both
mig_clock := dut_clock
pcie_dat_clock := dut_clock
pcie_cfg_clock := dut_clock
mig_mmcm_locked := UInt("b1")
mmcm_lock_pcie := UInt("b1")
// Setup feedback
clock_led := dut_clock
reset_led := dut_reset
//-----------------------------------------------------------------------
// Terminal
//-----------------------------------------------------------------------
def connectTerminal(dut: HasPeripheryTerminalModuleImp): Unit = {
dvi <> dut.dvi
dut.terminal.clk := dvi_clock
dut.terminal.reset := dvi_reset
}
//-----------------------------------------------------------------------
// Memory controller
//-----------------------------------------------------------------------
def connectDDRMemory(dut: HasMemoryML507ModuleImp): Unit = {
ddr2 <> dut.ddr2
dut.ddr_sys.clk0 := ddr_clk0
dut.ddr_sys.clk90 := ddr_clk90
dut.ddr_sys.clkdiv0 := ddr_clkdiv0
dut.ddr_sys.clk_locked := ddr_clk_locked
dut.ddr_sys.clk_idelay := ddr_clk0
dut.ddr_sys.reset := ddr_reset
}
//-----------------------------------------------------------------------
// UART
//-----------------------------------------------------------------------
uart_rtsn := false.B
def connectUART(dut: HasPeripheryUARTModuleImp): Unit = {
val uartParams = p(PeripheryUARTKey)
if (!uartParams.isEmpty) {