进阶实例
一、分形计算器(Fractal calculator)
简介
该例子将会展示如何利用数据流以及定点计算实现一个未优化的曼德布洛特分形计算器(Mandelbrot fractal calculator)。
规范
组件将会接收像素任务的一个
Stream
(包含曼德布洛特空间的XY坐标)并且产生像素结果的一个Stream
(包含对应任务的迭代次数)。定义组件的IO为:
IO名称 | 方向 | 类型 | 描述 |
---|---|---|---|
cmd | slave | Stream[PixelTask] | 提供XY坐标来处理 |
rsp | master | Stream[PixelResult] | 返回对应cmd交换所需的迭代次数 |
PixelTaskBundle
为:
单元名称 | 类型 | 描述 |
---|---|---|
x | SFix | 曼德布洛特空间的坐标 |
y | SFix | 曼德布洛特空间的坐标 |
PixelResultBundle
为:
单元名称 | 类型 | 描述 |
---|---|---|
iteration | UInt | 所需的迭代次数 |
细化参数(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。
Bundle定义
case class PixelTask(g : PixelSolverGenerics) extends Bundle{ val x,y = g.fixType } case class PixelResult(g : PixelSolverGenerics) extends Bundle{ val iteration = g.iterationType }
组件实现
下述代码是一个无流水线/多线程的简单实现:
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
规范
该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端口 |
数据结构
在实现控制器之前,需要定义一些数据结构。
控制器构造参数
名称 | 类型 | 描述 |
---|---|---|
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单元来设置UartCtrl
的Bundle
。
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
}
}
实现
在
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
来实例化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
}
简单使用
将
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 )
带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测试文件。
使用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
简介
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实现。
数据结构
在实现控制器前需要定义一些数据结构。
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
}
}
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
,它将RgbConfig
和timingsWidth
作为参数。将位宽设置为默认值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
}
}
}