## 进阶实例 ### 一、分形计算器(Fractal calculator) 1. 简介 该例子将会展示如何利用数据流以及定点计算实现一个未优化的曼德布洛特分形计算器(Mandelbrot fractal calculator)。 2. 规范 组件将会接收像素任务的一个`Stream`(包含曼德布洛特空间的XY坐标)并且产生像素结果的一个`Stream`(包含对应任务的迭代次数)。 定义组件的IO为: | IO名称 | 方向 | 类型 | 描述 | | :----: | :----: | :-----------------: | :---------------------------: | | cmd | slave | Stream[PixelTask] | 提供XY坐标来处理 | | rsp | master | Stream[PixelResult] | 返回对应cmd交换所需的迭代次数 | PixelTask`Bundle`为: | 单元名称 | 类型 | 描述 | | :------: | :---: | :------------------: | | x | SFix | 曼德布洛特空间的坐标 | | y | SFix | 曼德布洛特空间的坐标 | PixelResult`Bundle`为: | 单元名称 | 类型 | 描述 | | :-------: | :---: | :------------: | | iteration | UInt | 所需的迭代次数 | 3. 细化参数(Elaboration parameters) 定义可以为用户系统提供构造参数的类: ```Scala 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。** 4. Bundle定义 ```Scala case class PixelTask(g : PixelSolverGenerics) extends Bundle{ val x,y = g.fixType } case class PixelResult(g : PixelSolverGenerics) extends Bundle{ val iteration = g.iterationType } ``` 5. 组件实现 下述代码是一个无流水线/多线程的简单实现: ```Scala 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如下: ```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端口 | 2. 数据结构 在实现控制器之前,需要定义一些数据结构。 - 控制器构造参数 | 名称 | 类型 | 描述 | | :---------------: | :---: | :-------------------------------------------: | | dataWidthMax | Int | 使用单个UART帧可以发送的最大数据位数 | | clockDividerWidth | Int | 时钟分频器的比特数 | | preSamplingSize | Int | 在采样窗口开始时要drop的样本数 | | samplingSize | Int | 获得滤波后的RXD值所需的窗口中间使用的样本数量 | | postSamplingSize | Int | 在采样窗口结束时要drop的样本数 | 为了使得实现简单,假设`preSamplingSize + samplingSize + postSamplingSize`都是2的幂次方。 同时不需要将每个构造参数(泛型)逐个添加到`UartCtrl`中,而是可以将它们分组到一个类中,这个类将用作`UartCtrl`的单个参数。 ```Scala 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总线: ```Scala 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) ```Scala 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单元来设置`UartCtrl`的`Bundle`。 ```Scala 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 } } ``` 3. 实现 在`UartCtrl`中,三个物体会被实例化: - 一个时钟分频器,以UART RX采样率产生采样脉冲 - 一个`UartCtrlTx``Component` - 一个`UartCtrlRx``Component` - UartCtrlTx 该`Component`的端口如下: | 名称 | 类型 | 描述 | | :----------: | :-----------------: | :------------------------------------------: | | configFrame | UartCtrlFrameConfig | 包含数据位宽度计数和停止位配置 | | samplingTick | Bool | 每UART波特,脉冲`rxSamplePerBit`次的时间参考 | | write | Stream[Bits] | 系统向控制器发出传输指令的端口 | | txd | Bool | UART txd 引脚 | 定义用作存储`UartCtrlTX`状态的枚举: ```Scala object UartCtrlTxState extends SpinalEnum { val IDLE, START, DATA, PARITY, STOP = newElement() } ``` 定义`UartCtrlTx`的框架: ```Scala 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) } ``` 完整实现如下: ```Scala 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`状态的枚举: ```Scala object UartCtrlRxState extends SpinalEnum { val IDLE, START, DATA, PARITY, STOP = newElement() } ``` 定义`UartCtrlTx`的框架: ```Scala 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) { ... } } } ``` 完整实现如下: ```Scala 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`来实例化`UartCtrlRx`和`UartCtrlTx`部分,生成时钟分频器逻辑,并将它们彼此连接起来。 ```Scala 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 } ``` 4. 简单使用 将`uartCtrl`综合为`115200-N-8-1`: ```Scala val uartCtrl: UartCtrl = UartCtrl( config = UartCtrlInitConfig( baudrate = 115200, dataLength = 7, // 8 bits parity = UartParityType.NONE, stop = UartStopType.ONE ) ) ``` 如果只使用`txd`引脚: ```Scala uartCtrl.io.uart.rxd := True //UART空闲状态置高 txd := uartCtrl.io.uart.txd ``` 相反,如果只使用`rxd`引脚: ```Scala val uartCtrl: UartCtrl = UartCtrl( config = UartCtrlInitConfig( baudrate = 115200, dataLength = 7, // 8 bits parity = UartParityType.NONE, stop = UartStopType.ONE ), readonly = true ) ``` 5. 带TB的例子 下面是一个顶层的例子,做了以下事情: - 实例化`UartCtrl`并将其配置为921600波特/秒,无奇偶校验,1个停止位。 - 每次从UART接收到一个字节时,它就把它写到led输出上。 - 每2000个周期,它将开关的输入值发送给UART。 ```Scala 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头部,可以用下面的例子来替换写生成器: ```Scala 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测试文件。 6. 使用Stream 想要将UART来的数据入队: ```Scala val uartCtrl = new UartCtrl() val queuedReads = uartCtrl.io.read.toStream.queue(16) ``` 想要在写端口上加一个队列并做流控制: ```Scala 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总线使用。 ```Scala 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 | 携带颜色 | ```Scala 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`的简单例子。 ```Scala 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) } ``` 但这不是一个很好的方式来指定它,因为它对于垂直和水平时间信号来说是多余的。 用更清晰的方式来写: ```Scala 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) } ``` 然后可以添加一些函数来设置特定分辨率和帧率的时间: ```Scala 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 } } ``` 3. 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定义 定义一个新的VgaCtrl`Component`,它将`RgbConfig`和`timingsWidth`作为参数。将位宽设置为默认值12。 ```Scala 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,然后实例化它两次:一次用于水平同步和一次用于垂直同步 ```Scala 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`,否则会变得冗长得多。 - 互联 有了用于水平和垂直同步的定时生成器,需要驱动输出。 ```Scala 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`。 ```Scala 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 } } } ```