串口
规范
本UART控制器教程基于 此文件 实现。
该实现的特征有:
- ClockDivider/Parity/StopBit/DataLength的配置由组件输入设置。 
- RXD输入通过使用N个样本的采样窗口和多数投票法进行滤波。 
该UartCtrl控制端口为:
| 名称 | 类型 | 描述 | 
|---|---|---|
| config | UartCtrlConfig | 将所有配置发给控制器 | 
| write | Stream[Bits] | 系统向控制器发送传输顺序所用的端口 | 
| read | Flow[Bits] | 控制器发送已成功接收帧给系统所用的端口 | 
| uart | Uart | 带rxd/txd的Uart接口 | 
数据结构
在实现控制器本体之前,我们需要定义一些数据结构。
控制器构造参数
| 名称 | 类型 | 描述 | 
|---|---|---|
| dataWidthMax | Int | 使用单个UART帧可以发送的最大数据位数 | 
| clockDividerWidth | Int | 时钟分频器的位数 | 
| preSamplingSize | Int | 采样窗口开始时要丢弃的样本数 | 
| samplingSize | Int | 使用多个窗口中部的样本来获得过滤后的 RXD 值 | 
| postSamplingSize | Int | 采样窗口结束时丢弃的样本数 | 
为了使实现更容易,我们假设 preSamplingSize + samplingSize + postSamplingSize 始终是 2 的幂次方。这样做可以在一些地方跳过计数器清零操作。
同时,不需要将每个构造参数(泛型)一一添加到 UartCtrl 中,我们可以将它们分组到一个类中,该类将用作 UartCtrl 的单个参数。
case class UartCtrlGenerics(dataWidthMax: Int = 8,
                            clockDividerWidth: Int = 20, // baudrate = Fclk / rxSamplePerBit / clockDividerWidth
                            preSamplingSize: Int = 1,
                            samplingSize: Int = 5,
                            postSamplingSize: Int = 2) {
  val rxSamplePerBit = preSamplingSize + samplingSize + postSamplingSize
  assert(isPow2(rxSamplePerBit))
  if((samplingSize % 2) == 0)
    SpinalWarning(s"It's not nice to have a odd samplingSize value (because of the majority vote)")
}
UART接口
让我们定义一个没有流量控制的UART接口线束。
case class Uart() extends Bundle with IMasterSlave {
  val txd = Bool()
  val rxd = Bool()
  override def asMaster(): Unit = {
    out(txd)
    in(rxd)
  }
}
UART配置枚举
让我们定义奇偶校验和停止位枚举。
object UartParityType extends SpinalEnum(binarySequential) {
  val NONE, EVEN, ODD = newElement()
}
object UartStopType extends SpinalEnum(binarySequential) {
  val ONE, TWO = newElement()
  def toBitCount(that: C): UInt = (that === ONE) ? U"0" | U"1"
}
UartCtrl配置线束
让我们定义一些线束,它们将被用于设置 UartCtrl 的IO单元。
case class UartCtrlFrameConfig(g: UartCtrlGenerics) extends Bundle {
  val dataLength = UInt(log2Up(g.dataWidthMax) bits) // Bit count = dataLength + 1
  val stop       = UartStopType()
  val parity     = UartParityType()
}
case class UartCtrlConfig(g: UartCtrlGenerics) extends Bundle {
  val frame        = UartCtrlFrameConfig(g)
  val clockDivider = UInt(g.clockDividerWidth bits) // see UartCtrlGenerics.clockDividerWidth for calculation
  def setClockDivider(baudrate: Double, clkFrequency: HertzNumber = ClockDomain.current.frequency.getValue): Unit = {
    clockDivider := (clkFrequency.toDouble / baudrate / g.rxSamplePerBit).toInt
  }
}
实现
在 UartCtrl 中会实例化3个东西 :
- 一个时钟分频器,以UART RX采样率产生采样脉冲。 
- 一个 - UartCtrlTx组件
- 一个 - UartCtrlRx组件
UARTCtrlTx
该 Component 的接口如下:
| 名称 | 类型 | 描述 | 
|---|---|---|
| configFrame | UartCtrlFrameConfig | 包含数据位宽计数和奇偶校验位/停止位配置 | 
| samplingTick | Bool | 以每UART波特  | 
| write | Stream[Bits] | 系统向控制器发出传输命令的端口 | 
| txd | Bool | UART txd引脚 | 
让我们定义用于存储 UartCtrlTx 状态的枚举:
object UartCtrlTxState extends SpinalEnum {
  val IDLE, START, DATA, PARITY, STOP = newElement()
}
让我们定义 UartCtrlTx 的框架:
class UartCtrlTx(g : UartCtrlGenerics) extends Component {
  import g._
  val io = new Bundle {
    val configFrame  = in(UartCtrlFrameConfig(g))
    val samplingTick = in Bool()
    val write        = slave Stream (Bits(dataWidthMax bits))
    val txd          = out Bool()
  }
  // Provide one clockDivider.tick each rxSamplePerBit pulses of io.samplingTick
  // Used by the stateMachine as a baud rate time reference
  val clockDivider = new Area {
    val counter = Reg(UInt(log2Up(rxSamplePerBit) bits)) init(0)
    val tick = False
    ..
  }
  // Count up each clockDivider.tick, used by the state machine to count up data bits and stop bits
  val tickCounter = new Area {
    val value = Reg(UInt(Math.max(dataWidthMax, 2) bits))
    def reset() = value := 0
    ..
  }
  val stateMachine = new Area {
    import UartCtrlTxState._
    val state = RegInit(IDLE)
    val parity = Reg(Bool())
    val txd = True
    ..
    switch(state) {
      ..
    }
  }
  io.txd := RegNext(stateMachine.txd) init(True)
}
完整实现如下:
class UartCtrlTx(g : UartCtrlGenerics) extends Component {
  import g._
  val io = new Bundle {
    val configFrame  = in(UartCtrlFrameConfig(g))
    val samplingTick = in Bool()
    val write        = slave Stream Bits(dataWidthMax bits)
    val txd          = out Bool()
  }
  // Provide one clockDivider.tick each rxSamplePerBit pulse of io.samplingTick
  // Used by the stateMachine as a baudrate time reference
  val clockDivider = new Area {
    val counter = Reg(UInt(log2Up(rxSamplePerBit) bits)) init 0
    val tick = False
    when(io.samplingTick) {
      counter := counter - 1
      tick := counter === 0
    }
  }
  // Count up each clockDivider.tick, used by the state machine to count up data bits and stop bits
  val tickCounter = new Area {
    val value = Reg(UInt(log2Up(Math.max(dataWidthMax, 2)) bits))
    def reset(): Unit = value := 0
    when(clockDivider.tick) {
      value := value + 1
    }
  }
  val stateMachine = new Area {
    import UartCtrlTxState._
    val state = RegInit(IDLE)
    val parity = Reg(Bool())
    val txd = True
    when(clockDivider.tick) {
      parity := parity ^ txd
    }
    io.write.ready := False
    switch(state) {
      is(IDLE) {
        when(io.write.valid && clockDivider.tick) {
          state := START
        }
      }
      is(START) {
        txd := False
        when(clockDivider.tick) {
          state := DATA
          parity := io.configFrame.parity === UartParityType.ODD
          tickCounter.reset()
        }
      }
      is(DATA) {
        txd := io.write.payload(tickCounter.value)
        when(clockDivider.tick) {
          when(tickCounter.value === io.configFrame.dataLength) {
            io.write.ready := True
            tickCounter.reset()
            when(io.configFrame.parity === UartParityType.NONE) {
              state := STOP
            } otherwise {
              state := PARITY
            }
          }
        }
      }
      is(PARITY) {
        txd := parity
        when(clockDivider.tick) {
          state := STOP
          tickCounter.reset()
        }
      }
      is(STOP) {
        when(clockDivider.tick) {
          when(tickCounter.value === UartStopType.toBitCount(io.configFrame.stop)) {
            state := io.write.valid ? START | IDLE
          }
        }
      }
    }
  }
  io.txd := RegNext(stateMachine.txd, True)
}
UartCtrlRx
该 Component 的接口如下:
| 名称 | 类型 | 描述 | 
|---|---|---|
| configFrame | UartCtrlFrameConfig | 包含数据位宽和奇偶校验/停止位配置 | 
| samplingTick | Bool | 以每UART波特  | 
| read | Flow[Bits] | 控制器发送已成功接收帧给系统所用的端口 | 
| rxd | Bool | UART rxd 引脚,与当前时钟域不同步 | 
让我们定义用于存储 UartCtrlTx 状态的枚举:
object UartCtrlRxState extends SpinalEnum {
  val IDLE, START, DATA, PARITY, STOP = newElement()
}
让我们定义 UartCtrlRx 的框架:
class UartCtrlRx(g : UartCtrlGenerics) extends Component {
  import g._
  val io = new Bundle {
    val configFrame  = in(UartCtrlFrameConfig(g))
    val samplingTick = in Bool()
    val read         = master Flow (Bits(dataWidthMax bits))
    val rxd          = in Bool()
  }
  // Implement the rxd sampling with a majority vote over samplingSize bits
  // Provide a new sampler.value each time sampler.tick is high
  val sampler = new Area {
    val syncroniser = BufferCC(io.rxd)
    val samples     = History(that=syncroniser,when=io.samplingTick,length=samplingSize)
    val value       = RegNext(MajorityVote(samples))
    val tick        = RegNext(io.samplingTick)
  }
  // Provide a bitTimer.tick each rxSamplePerBit
  // reset() can be called to recenter the counter over a start bit.
  val bitTimer = new Area {
    val counter = Reg(UInt(log2Up(rxSamplePerBit) bits))
    def reset() = counter := preSamplingSize + (samplingSize - 1) / 2 - 1)
    val tick = False
    ...
  }
  // Provide bitCounter.value that count up each bitTimer.tick, Used by the state machine to count data bits and stop bits
  // reset() can be called to reset it to zero
  val bitCounter = new Area {
    val value = Reg(UInt(Math.max(dataWidthMax, 2) bits))
    def reset() = value := 0
    ...
  }
  val stateMachine = new Area {
    import UartCtrlRxState._
    val state   = RegInit(IDLE)
    val parity  = Reg(Bool())
    val shifter = Reg(io.read.payload)
    ...
    switch(state) {
      ...
    }
  }
}
完整实现如下:
class UartCtrlRx(g : UartCtrlGenerics) extends Component {
  import g._
  val io = new Bundle {
    val configFrame  = in(UartCtrlFrameConfig(g))
    val samplingTick = in Bool()
    val read         = master Flow Bits(dataWidthMax bits)
    val rxd          = in Bool()
  }
  // Implement the rxd sampling with a majority vote over samplingSize bits
  // Provide a new sampler.value each time sampler.tick is high
  val sampler = new Area {
    val synchronizer = BufferCC(io.rxd)
    val samples     = spinal.lib.History(that=synchronizer, when=io.samplingTick, length=samplingSize)
    val value       = RegNext(MajorityVote(samples))
    val tick        = RegNext(io.samplingTick)
  }
  // Provide a bitTimer.tick each rxSamplePerBit
  // reset() can be called to recenter the counter over a start bit.
  val bitTimer = new Area {
    val counter = Reg(UInt(log2Up(rxSamplePerBit) bits))
    def reset(): Unit = counter := preSamplingSize + (samplingSize - 1) / 2 - 1
    val tick = False
    when(sampler.tick) {
      counter := counter - 1
      tick := counter === 0
    }
  }
  // Provide bitCounter.value that count up each bitTimer.tick, Used by the state machine to count data bits and stop bits
  // reset() can be called to reset it to zero
  val bitCounter = new Area {
    val value = Reg(UInt(log2Up(Math.max(dataWidthMax, 2)) bits))
    def reset(): Unit = value := 0
    when(bitTimer.tick) {
      value := value + 1
    }
  }
  val stateMachine = new Area {
    import UartCtrlRxState._
    val state   = RegInit(IDLE)
    val parity  = Reg(Bool())
    val shifter = Reg(io.read.payload)
    // Parity calculation
    when(bitTimer.tick) {
      parity := parity ^ sampler.value
    }
    io.read.valid := False
    switch(state) {
      is(IDLE) {
        when(!sampler.value) {
          state := START
          bitTimer.reset()
          bitCounter.reset()
        }
      }
      is(START) {
        when(bitTimer.tick) {
          state := DATA
          bitCounter.reset()
          parity := io.configFrame.parity === UartParityType.ODD
          when(sampler.value) {
            state := IDLE
          }
        }
      }
      is(DATA) {
        when(bitTimer.tick) {
          shifter(bitCounter.value) := sampler.value
          when(bitCounter.value === io.configFrame.dataLength) {
            bitCounter.reset()
            when(io.configFrame.parity === UartParityType.NONE) {
              state := STOP
            } otherwise {
              state := PARITY
            }
          }
        }
      }
      is(PARITY) {
        when(bitTimer.tick) {
          state := STOP
          bitCounter.reset()
          when(parity =/= sampler.value) {
            state := IDLE
          }
        }
      }
      is(STOP) {
        when(bitTimer.tick) {
          when(!sampler.value) {
            state := IDLE
          }.elsewhen(bitCounter.value === UartStopType.toBitCount(io.configFrame.stop)) {
            state := IDLE
            io.read.valid := True
          }
        }
      }
    }
  }
  io.read.payload := stateMachine.shifter
}
UartCtrl
让我们编写 UartCtrl 来实例化 UartCtrlRx 和 UartCtrlTx 部分,生成时钟分频器逻辑,并将它们相互连接。
class UartCtrl(g: UartCtrlGenerics=UartCtrlGenerics()) extends Component {
  val io = new Bundle {
    val config = in(UartCtrlConfig(g))
    val write  = slave(Stream(Bits(g.dataWidthMax bits)))
    val read   = master(Flow(Bits(g.dataWidthMax bits)))
    val uart   = master(Uart())
  }
  val tx = new UartCtrlTx(g)
  val rx = new UartCtrlRx(g)
  // Clock divider used by RX and TX
  val clockDivider = new Area {
    val counter = Reg(UInt(g.clockDividerWidth bits)) init 0
    val tick = counter === 0
    counter := counter - 1
    when(tick) {
      counter := io.config.clockDivider
    }
  }
  tx.io.samplingTick := clockDivider.tick
  rx.io.samplingTick := clockDivider.tick
  tx.io.configFrame := io.config.frame
  rx.io.configFrame := io.config.frame
  tx.io.write << io.write
  rx.io.read >> io.read
  io.uart.txd <> tx.io.txd
  io.uart.rxd <> rx.io.rxd
}
为了更简单地使用具有固定设置的 UART,我们引入了 UartCtrl 的伴生对象。这使我们能够以不同的参数集实例化UartCtrl组件,提供了额外的实例化方式。这里我们定义了 UartCtrlInitConfig ,用它保存那些无法在运行时配置的组件的设置。请注意,如果需要一个能在运行时进行配置的UART,您仍然可以像所有其他组件一样(通过 val uart = new UartCtrl() )手动实例化UartCtrl。
case class UartCtrlInitConfig(baudrate: Int = 0,
                              dataLength: Int = 1,
                              parity: UartParityType.E = null,
                              stop: UartStopType.E = null
                             ) {
  require(dataLength >= 1)
  def initReg(reg : UartCtrlConfig): Unit = {
    require(reg.isReg)
    if(baudrate != 0) reg.clockDivider init((ClockDomain.current.frequency.getValue / baudrate / reg.g.rxSamplePerBit).toInt-1)
    if(dataLength != 1) reg.frame.dataLength init (dataLength - 1)
    if(parity != null) reg.frame.parity init parity
    if(stop != null) reg.frame.stop init stop
  }
}
object UartCtrl {
  def apply(config: UartCtrlInitConfig, readonly: Boolean = false): UartCtrl = {
    val uartCtrl = new UartCtrl()
    uartCtrl.io.config.setClockDivider(config.baudrate)
    uartCtrl.io.config.frame.dataLength := config.dataLength - 1
    uartCtrl.io.config.frame.parity := config.parity
    uartCtrl.io.config.frame.stop := config.stop
    if (readonly) {
      uartCtrl.io.write.valid := False
      uartCtrl.io.write.payload := B(0)
    }
    uartCtrl
  }
}
简单应用
用 115200-N-8-1 的参数综合 UartCtrl :
  val uartCtrl = UartCtrl(
    config=UartCtrlInitConfig(
      baudrate = 115200,
      dataLength = 7,
      parity = UartParityType.NONE,
      stop = UartStopType.ONE
    )
  )
如果您仅使用 txd 引脚,请添加:
  uartCtrl.io.uart.rxd := True
  io.tx := uartCtrl.io.uart.txd
相反,如果您仅使用 rxd 引脚:
  val uartCtrl = UartCtrl(
    config = UartCtrlInitConfig(
      baudrate = 115200,
      dataLength = 7,
      parity = UartParityType.NONE,
      stop = UartStopType.ONE
    ),
    readonly = true
  )
带TestBench的例子
下面是一个顶层的示例,它执行以下操作:
- 实例化 - UartCtrl并将其配置设置为 921600 baud/s,无奇偶校验,1 个停止位。
- 每次从 UART 接收到一个字节时,它都会将其写到 LED 输出上。 
- 把switches输入值以每2000个周期发送到 UART。 
case class UartCtrlUsageExample() extends Component {
  val io = new Bundle {
    val uart = master(Uart())
    val switches = in Bits(8 bits)
    val leds = out Bits(8 bits)
  }
  val uartCtrl = new UartCtrl()
  // set config manually to show that this is still OK
  uartCtrl.io.config.setClockDivider(921600)
  uartCtrl.io.config.frame.dataLength := 7  // 8 bits
  uartCtrl.io.config.frame.parity := UartParityType.NONE
  uartCtrl.io.config.frame.stop := UartStopType.ONE
  uartCtrl.io.uart <> io.uart
  // Assign io.led with a register loaded each time a byte is received
  io.leds := uartCtrl.io.read.toReg()
  // Write the value of switch on the uart each 2000 cycles
  val write = Stream(Bits(8 bits))
  write.valid := CounterFreeRun(2000).willOverflow
  write.payload := io.switches
  write >-> uartCtrl.io.write
}
object UartCtrlUsageExample extends App {
  SpinalConfig(
    defaultClockDomainFrequency = FixedFrequency(100 MHz)
  ).generateVhdl(UartCtrlUsageExample())
}
您可以在 这里 为这个小 UartCtrlUsageExample 获取一个简单的 VHDL 测试文件。
额外奖励:享受 Stream 带来的乐趣
如果您想将从 UART 接收到的数据入队:
  val queuedReads = uartCtrl.io.read.toStream.queue(16)
如果要在写接口上添加一个队列并做一些流控制:
  val writeCmd = Stream(Bits(8 bits))
  val stopIt = Bool()
  writeCmd.queue(16).haltWhen(stopIt) >> uartCtrl.io.write
如果您想在发送switches值之前发送 0x55 标头,可以将上例中的写生成器替换为:
  val write = Stream(Fragment(Bits(8 bits)))
  write.valid := CounterFreeRun(4000).willOverflow
  write.fragment := io.switches
  write.last := True
  write.stage().insertHeader(0x55).toStreamOfFragment >> uartCtrl.io.write