进阶实例

一、分形计算器(Fractal calculator)

  1. 简介

    该例子将会展示如何利用数据流以及定点计算实现一个未优化的曼德布洛特分形计算器(Mandelbrot fractal calculator)。

  2. 规范

    组件将会接收像素任务的一个Stream(包含曼德布洛特空间的XY坐标)并且产生像素结果的一个Stream(包含对应任务的迭代次数)。

    定义组件的IO为:

IO名称 方向 类型 描述
cmd slave Stream[PixelTask] 提供XY坐标来处理
rsp master Stream[PixelResult] 返回对应cmd交换所需的迭代次数

PixelTaskBundle为:

单元名称 类型 描述
x SFix 曼德布洛特空间的坐标
y SFix 曼德布洛特空间的坐标

PixelResultBundle为:

单元名称 类型 描述
iteration UInt 所需的迭代次数
  1. 细化参数(Elaboration parameters)

    定义可以为用户系统提供构造参数的类:

    case class PixelSolverGenerics(fixAmplitude : Int,
                               fixResolution : Int,
                               iterationLimit : Int){
      val iterationWidth = log2Up(iterationLimit+1)
      def iterationType = UInt(iterationWidth bits)
      def fixType = SFix(
        peak = fixAmplitude exp,
        resolution = fixResolution exp
      )
    }
    

    iterationType和fixType是可以调用来实例化新信号的函数。就像C语言中的typedef。

  2. Bundle定义

    case class PixelTask(g : PixelSolverGenerics) extends Bundle{
      val x,y = g.fixType
    }
    
    case class PixelResult(g : PixelSolverGenerics) extends Bundle{
      val iteration = g.iterationType
    }
    
  3. 组件实现

    下述代码是一个无流水线/多线程的简单实现:

    case class PixelSolver(g : PixelSolverGenerics) extends Component{
      val io = new Bundle{
        val cmd = slave  Stream(PixelTask(g))
        val rsp = master Stream(PixelResult(g))
      }
    
      import g._
    
      //定义状态
      val x,y       = Reg(fixType) init(0)
      val iteration = Reg(iterationType) init(0)
    
      //共享计算
      val xx = x*x
      val yy = y*y
      val xy = x*y
    
      //默认赋值
      io.cmd.ready := False
      io.rsp.valid := False
      io.rsp.iteration := iteration
    
      when(io.cmd.valid) {
    
        when(xx + yy >= 4.0 || iteration === iterationLimit) {
          io.rsp.valid := True
          when(io.rsp.ready){
            io.cmd.ready := True
            x := 0
            y := 0
            iteration := 0
          }
        } otherwise {
          x := (xx - yy + io.cmd.x).truncated
          y := (((xy) << 1) + io.cmd.y).truncated
          iteration := iteration + 1
        }
      }
    }
    

    生成的Verilog如下:

    // Generator : SpinalHDL v1.6.0    git head : 73c8d8e2b86b45646e9d0b2e729291f2b65e6be3
    // Component : PixelSolver
    
    
    
    module PixelSolver (
      input               io_cmd_valid,
      output reg          io_cmd_ready,
      input      [0:0]    io_cmd_payload_x,
      input      [0:0]    io_cmd_payload_y,
      output reg          io_rsp_valid,
      input               io_rsp_ready,
      output     [3:0]    io_rsp_payload_iteration,
      input               clk,
      input               reset
    );
      wire       [1:0]    _zz_when_PixelSolver_l45;
      wire       [1:0]    _zz_when_PixelSolver_l45_1;
      wire       [17:0]   _zz_x;
      wire       [17:0]   _zz_x_1;
      wire       [1:0]    _zz_x_2;
      wire       [17:0]   _zz_x_3;
      wire       [18:0]   _zz_y;
      wire       [18:0]   _zz_y_1;
      wire       [18:0]   _zz_y_2;
      reg        [0:0]    x;
      reg        [0:0]    y;
      reg        [3:0]    iteration;
      wire       [1:0]    xx;
      wire       [1:0]    yy;
      wire       [1:0]    xy;
      wire                when_PixelSolver_l45;
    
      assign _zz_when_PixelSolver_l45 = 2'b00;
      assign _zz_when_PixelSolver_l45_1 = ($signed(xx) + $signed(yy));
      assign _zz_x = ($signed(_zz_x_1) + $signed(_zz_x_3));
      assign _zz_x_1 = ({16'd0,_zz_x_2} <<< 16);
      assign _zz_x_2 = ($signed(xx) - $signed(yy));
      assign _zz_x_3 = {{17{io_cmd_payload_x[0]}}, io_cmd_payload_x};
      assign _zz_y = ($signed(_zz_y_1) + $signed(_zz_y_2));
      assign _zz_y_1 = ({17'd0,xy} <<< 17);
      assign _zz_y_2 = {{18{io_cmd_payload_y[0]}}, io_cmd_payload_y};
      assign xx = ($signed(x) * $signed(x));
      assign yy = ($signed(y) * $signed(y));
      assign xy = ($signed(x) * $signed(y));
      always @(*) begin
        io_cmd_ready = 1'b0;
        if(io_cmd_valid) begin
          if(when_PixelSolver_l45) begin
            if(io_rsp_ready) begin
              io_cmd_ready = 1'b1;
            end
          end
        end
      end
    
      always @(*) begin
        io_rsp_valid = 1'b0;
        if(io_cmd_valid) begin
          if(when_PixelSolver_l45) begin
            io_rsp_valid = 1'b1;
          end
        end
      end
    
      assign io_rsp_payload_iteration = iteration;
      assign when_PixelSolver_l45 = (($signed(_zz_when_PixelSolver_l45) <= $signed(_zz_when_PixelSolver_l45_1)) || (iteration == 4'b1010));
      always @(posedge clk or posedge reset) begin
        if(reset) begin
          x <= 1'b0;
          y <= 1'b0;
          iteration <= 4'b0000;
        end else begin
          if(io_cmd_valid) begin
            if(when_PixelSolver_l45) begin
              if(io_rsp_ready) begin
                x <= 1'b0;
                y <= 1'b0;
                iteration <= 4'b0000;
              end
            end else begin
              x <= _zz_x[0:0];
              y <= _zz_y[0:0];
              iteration <= (iteration + 4'b0001);
            end
          end
        end
      end
    
    
    endmodule
    

二、UART

  1. 规范

    该UART实现参考来自于https://github.com/SpinalHDL/SpinalHDL/tree/master/lib/src/main/scala/spinal/lib/com/uart的指导。

    该实现的特征有:

    • ClockDivider/Parity/StopBit/DataLength的配置被组件的输入所设置

    • RXD输入采用N个采样点和多数投票法的抽样窗口进行滤波。

    该UART控制端口为:

名称 类型 描述
config UartCtrlConfig 将所有配置发给控制器
write Stream[Bits] 系统发送传输顺序给控制器所使用的端口
read Flow[Bits] 控制器发送成功接收帧提示给系统所使用的的端口
uart Uart 带rxd/txd的Uart端口
  1. 数据结构

    在实现控制器之前,需要定义一些数据结构。

    • 控制器构造参数

名称 类型 描述
dataWidthMax Int 使用单个UART帧可以发送的最大数据位数
clockDividerWidth Int 时钟分频器的比特数
preSamplingSize Int 在采样窗口开始时要drop的样本数
samplingSize Int 获得滤波后的RXD值所需的窗口中间使用的样本数量
postSamplingSize Int 在采样窗口结束时要drop的样本数

为了使得实现简单,假设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配置枚举(UART configuration enums)

定义奇偶校验和停止位枚举(stop bit enumerations)

object UartParityType extends SpinalEnum(sequancial) {
  val NONE, EVEN, ODD = newElement()
}

object UartStopType extends SpinalEnum(sequancial) {
  val ONE, TWO = newElement()
  def toBitCount(that : T) : UInt = (that === ONE) ? U"0" | U"1"
}
  • UartCtrl配置类

定义将会被用作IO单元来设置UartCtrlBundle

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) //请见UartCtrlGenerics.clockDividerWidth

  def setClockDivider(baudrate : Double,clkFrequency : Double = ClockDomain.current.frequency.getValue) : Unit = {
    clockDivider := (clkFrequency / baudrate / g.rxSamplePerBit).toInt
  }
}
  1. 实现

    UartCtrl中,三个物体会被实例化:

    • 一个时钟分频器,以UART RX采样率产生采样脉冲

    • 一个UartCtrlTx``Component

    • 一个UartCtrlRx``Component

    • UartCtrlTx

    Component的端口如下:

名称 类型 描述
configFrame UartCtrlFrameConfig 包含数据位宽度计数和停止位配置
samplingTick Bool 每UART波特,脉冲rxSamplePerBit次的时间参考
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
  }

  
  val clockDivider = new Area {
    val counter = Reg(UInt(log2Up(rxSamplePerBit) bits)) init(0)
    val tick = False
    ..
  }

  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
  }

  
  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
    }
  }

  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

    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 === toBitCount(io.configFrame.stop)) {
            state := io.write.valid ? START | IDLE
          }
        }
      }
    }
  }

  io.txd := RegNext(stateMachine.txd, True)
}
  • UartCtrlRx

Component的端口如下:

名称 类型 描述
configFrame UartCtrlFrameConfig 包含数据位宽度计数和停止位配置
samplingTick Bool 每UART波特,脉冲rxSamplePerBit次的时间参考
read Flow[Bits] 用来向系统告知数据帧接收成功
txd Bool UART txd 引脚,没有与当前时钟域同步

定义用作存储UartCtrlTX状态的枚举:

object UartCtrlRxState extends SpinalEnum {
  val IDLE, START, DATA, PARITY, STOP = newElement()
}

定义UartCtrlTx的框架:

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
  }

  
  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)
  }

  
  val bitTimer = new Area {
    val counter = Reg(UInt(log2Up(rxSamplePerBit) bits))
    def reset() = counter := preSamplingSize + (samplingSize - 1) / 2 - 1)
    val tick = False
    ...
  }

  
  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
  }

  
  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)
  }

  
  val bitTimer = new Area {
    val counter = Reg(UInt(log2Up(rxSamplePerBit) bits))
    def reset() = counter := preSamplingSize + (samplingSize - 1) / 2 - 1
    val tick = False
    when(sampler.tick) {
      counter := counter - 1
      when(counter === 0) {
        tick := True
      }
    }
  }

  
  val bitCounter = new Area {
    val value = Reg(UInt(Math.max(dataWidthMax, 2) bits))
    def reset() = 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)

    
    when(bitTimer.tick) {
      parity := parity ^ sampler.value
    }

    io.read.valid := False
    switch(state) {
      is(IDLE) {
        when(sampler.value === False) {
          state := START
          bitTimer.reset()
        }
      }
      is(START) {
        when(bitTimer.tick) {
          state := DATA
          bitCounter.reset()
          parity := io.configFrame.parity === UartParityType.ODD
          when(sampler.value === True) {
            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 === toBitCount(io.configFrame.stop)) {
            state := IDLE
            io.read.valid := True
          }
        }
      }
    }
  }
  io.read.payload := stateMachine.shifter
}
  • UartCtrl

让编写UartCtrl来实例化UartCtrlRxUartCtrlTx部分,生成时钟分频器逻辑,并将它们彼此连接起来。

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
}
  1. 简单使用

    uartCtrl综合为115200-N-8-1

    val uartCtrl: UartCtrl = UartCtrl(
      config = UartCtrlInitConfig(
        baudrate = 115200,
        dataLength = 7,  // 8 bits
        parity = UartParityType.NONE,
        stop = UartStopType.ONE
      )
    )
    

    如果只使用txd引脚:

    uartCtrl.io.uart.rxd := True  //UART空闲状态置高
    txd := uartCtrl.io.uart.txd
    

    相反,如果只使用rxd引脚:

    val uartCtrl: UartCtrl = UartCtrl(
      config = UartCtrlInitConfig(
        baudrate = 115200,
        dataLength = 7,  // 8 bits
        parity = UartParityType.NONE,
        stop = UartStopType.ONE
      ),
      readonly = true
    )
    
  2. 带TB的例子

    下面是一个顶层的例子,做了以下事情:

    • 实例化UartCtrl并将其配置为921600波特/秒,无奇偶校验,1个停止位。

    • 每次从UART接收到一个字节时,它就把它写到led输出上。

    • 每2000个周期,它将开关的输入值发送给UART。

    class UartCtrlUsageExample extends Component{
      val io = new Bundle{
        val uart = master(Uart())
        val switchs = in Bits(8 bits)
        val leds = out Bits(8 bits)
      }
    
      val uartCtrl = new UartCtrl()
      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.switchs
      write >-> uartCtrl.io.write
    }
    
    
    object UartCtrlUsageExample{
      def main(args: Array[String]) {
        SpinalVhdl(new UartCtrlUsageExample,defaultClockDomainFrequency=FixedFrequency(50e6))
      }
    }
    

    如果想在发送交换机的值之前发送一个0x55头部,可以用下面的例子来替换写生成器:

    val write = Stream(Fragment(Bits(8 bits)))
    write.valid := CounterFreeRun(4000).willOverflow
    write.fragment := io.switchs
    write.last := True
    write.stage().insertHeader(0x55).toStreamOfFragment >> uartCtrl.io.write
    

    可以从https://github.com/SpinalHDL/SpinalHDL/blob/master/tester/src/test/resources/UartCtrlUsageExample_tb.vhd获取简单的VHDL测试文件。

  3. 使用Stream

    想要将UART来的数据入队:

    val uartCtrl = new UartCtrl()
    val queuedReads = uartCtrl.io.read.toStream.queue(16)
    

    想要在写端口上加一个队列并做流控制:

    val uartCtrl = new UartCtrl()
    val writeCmd = Stream(Bits(8 bits))
    val stopIt = Bool
    writeCmd.queue(16).haltWhen(stopIt) >> uartCtrl.io.write
    

三、VGA

  1. 简介

    VGA接口正在变得稀少,但实现VGA控制器仍然是一个很好的练习。

    关于VGA协议的解释可以在https://xess.com/blog/vga-the-rest-of-the-story/找到。

    本VGA控制器教程基于https://github.com/SpinalHDL/SpinalHDL/blob/master/lib/src/main/scala/spinal/lib/graphic/vga/VgaCtrl.scala实现。

  2. 数据结构

    在实现控制器前需要定义一些数据结构。

    • RGB颜色

    首先,需要一个三通道的颜色结构(红、绿、蓝)。这个数据结构将被用来给控制器提供像素,也将被VGA总线使用。

    case class RgbConfig(rWidth : Int,gWidth : Int,bWidth : Int){
      def getWidth = rWidth + gWidth + bWidth
    }
    
    case class Rgb(c: RgbConfig) extends Bundle{
      val r = UInt(c.rWidth bits)
      val g = UInt(c.gWidth bits)
      val b = UInt(c.bWidth bits)
    }
    
    • VGA总线

IO名称 驱动 描述
vSync master 垂直同步,表示新帧的开始
hSync master 水平同步,表示新行的开始
colorEn master 当接口处于可见部分时置高
color master 携带颜色
case class Vga (rgbConfig: RgbConfig) extends Bundle with IMasterSlave{
  val vSync = Bool()
  val hSync = Bool()

  val colorEn = Bool()
  val color   = Rgb(rgbConfig)

  override def asMaster() : Unit = this.asOutput()
}

这个Vga Bundle使用了IMasterSlave特性,它允许用户使用以下方式创建主/从Vga接口:

master(Vga(...))
slave(Vga(...))
  • VGA时序

VGA接口通过使用8种不同的时序来驱动。下面是一个能够携带它们的Bundle的简单例子。

case class VgaTimings(timingsWidth: Int) extends Bundle {
  val hSyncStart  = UInt(timingsWidth bits)
  val hSyncEnd    = UInt(timingsWidth bits)
  val hColorStart = UInt(timingsWidth bits)
  val hColorEnd   = UInt(timingsWidth bits)
  val vSyncStart  = UInt(timingsWidth bits)
  val vSyncEnd    = UInt(timingsWidth bits)
  val vColorStart = UInt(timingsWidth bits)
  val vColorEnd   = UInt(timingsWidth bits)
}

但这不是一个很好的方式来指定它,因为它对于垂直和水平时间信号来说是多余的。

用更清晰的方式来写:

case class VgaTimingsHV(timingsWidth: Int) extends Bundle {
  val colorStart = UInt(timingsWidth bits)
  val colorEnd   = UInt(timingsWidth bits)
  val syncStart  = UInt(timingsWidth bits)
  val syncEnd    = UInt(timingsWidth bits)
}

case class VgaTimings(timingsWidth: Int) extends Bundle {
  val h = VgaTimingsHV(timingsWidth)
  val v = VgaTimingsHV(timingsWidth)
}

然后可以添加一些函数来设置特定分辨率和帧率的时间:

case class VgaTimingsHV(timingsWidth: Int) extends Bundle {
  val colorStart = UInt(timingsWidth bits)
  val colorEnd   = UInt(timingsWidth bits)
  val syncStart  = UInt(timingsWidth bits)
  val syncEnd    = UInt(timingsWidth bits)
}

case class VgaTimings(timingsWidth: Int) extends Bundle {
  val h = VgaTimingsHV(timingsWidth)
  val v = VgaTimingsHV(timingsWidth)

  def setAs_h640_v480_r60: Unit = {
    h.syncStart := 96 - 1
    h.syncEnd := 800 - 1
    h.colorStart := 96 + 16 - 1
    h.colorEnd := 800 - 48 - 1
    v.syncStart := 2 - 1
    v.syncEnd := 525 - 1
    v.colorStart := 2 + 10 - 1
    v.colorEnd := 525 - 33 - 1
  }

  def setAs_h64_v64_r60: Unit = {
    h.syncStart := 96 - 1
    h.syncEnd := 800 - 1
    h.colorStart := 96 + 16 - 1 + 288
    h.colorEnd := 800 - 48 - 1 - 288
    v.syncStart := 2 - 1
    v.syncEnd := 525 - 1
    v.colorStart := 2 + 10 - 1 + 208
    v.colorEnd := 525 - 33 - 1 - 208
  }
}
  1. VGA控制器

    • 规范

IO名称 方向 描述
softReset in 重置内部计数器,保持VGA接口不激活
timings in 指定VGA水平和垂直时序
pixels slave 提供给VGA控制器的RGB颜色流
error out 像素流太慢时置高
frameStart out 新的帧开始时置高
vga master VGA接口

控制器不集成任何像素缓冲。它直接把它们从pixels``Stream获取并在适当的时候把它们放在vga.color上。如果pixels无效,那么在一个周期内error会拉高。

  • 组件和io定义

定义一个新的VgaCtrlComponent,它将RgbConfigtimingsWidth作为参数。将位宽设置为默认值12。

class VgaCtrl(rgbConfig: RgbConfig, timingsWidth: Int = 12) extends Component {
  val io = new Bundle {
    val softReset = in Bool
    val timings = in(VgaTimings(timingsWidth))
    val pixels = slave Stream (Rgb(rgbConfig))

    val error = out Bool
    val frameStart = out Bool
    val vga = master(Vga(rgbConfig))
  }
  ...
}
  • 水平和垂直逻辑

产生水平和垂直同步信号的逻辑是完全相同的。有点像~PWM~。水平同步器每循环加一次,而垂直同步器使用水平同步信号作为增量。

定义HVArea,它表示1个 ~PWM,然后实例化它两次:一次用于水平同步和一次用于垂直同步

class VgaCtrl(rgbConfig: RgbConfig, timingsWidth: Int = 12) extends Component {
  val io = new Bundle {...}

  case class HVArea(timingsHV: VgaTimingsHV, enable: Bool) extends Area {
    val counter = Reg(UInt(timingsWidth bits)) init(0)

    val syncStart  = counter === timingsHV.syncStart
    val syncEnd    = counter === timingsHV.syncEnd
    val colorStart = counter === timingsHV.colorStart
    val colorEnd   = counter === timingsHV.colorEnd

    when(enable) {
      counter := counter + 1
      when(syncEnd) {
        counter := 0
      }
    }

    val sync    = RegInit(False) setWhen(syncStart) clearWhen(syncEnd)
    val colorEn = RegInit(False) setWhen(colorStart) clearWhen(colorEnd)

    when(io.softReset) {
      counter := 0
      sync    := False
      colorEn := False
    }
  }
  val h = HVArea(io.timings.h, True)
  val v = HVArea(io.timings.v, h.syncEnd)
}

可见,这是通过使用Area完成的。这是为了避免创建一个新的Component,否则会变得冗长得多。

  • 互联

有了用于水平和垂直同步的定时生成器,需要驱动输出。

class VgaCtrl(rgbConfig: RgbConfig, timingsWidth: Int = 12) extends Component {
  val io = new Bundle {...}

  case class HVArea(timingsHV: VgaTimingsHV, enable: Bool) extends Area {...}
  val h = HVArea(io.timings.h, True)
  val v = HVArea(io.timings.v, h.syncEnd)

  val colorEn = h.colorEn && v.colorEn
  io.pixels.ready := colorEn
  io.error := colorEn && ! io.pixels.valid

  io.frameStart := v.syncEnd

  io.vga.hSync := h.sync
  io.vga.vSync := v.sync
  io.vga.colorEn := colorEn
  io.vga.color := io.pixels.payload
}
  • 奖励

上面定义的VgaCtrl是通用的(不是特定于应用程序的)。可以想象这样一个情况,系统提供了RGB的片段流(Stream of Fragment),这意味着系统在图像开始/结束指示之间传输像素。

在这种情况下,可以通过在error发生时激活它来自动管理softReset输入,然后等待当前pixel图片的结束来无效error

VgaCtrl添加一个函数,可以通过使用RGB片段流从父组件调用该函数来提供VgaCtrl

class VgaCtrl(rgbConfig: RgbConfig, timingsWidth: Int = 12) extends Component {
  ...
  def feedWith(that : Stream[Fragment[Rgb]]): Unit ={
    io.pixels << that.toStreamOfFragment

    val error = RegInit(False)
    when(io.error){
      error := True
    }
    when(that.isLast){
      error := False
    }

    io.softReset := error
    when(error){
      that.ready := True
    }
  }
}