本文作为SpinalHDL学习笔记第一篇,记录使用SpinalHDL的BlackBox库函数封装Verilog代码的方法。
SpinalHDL学习笔记总纲链接如下:
现有的Verilog代码是不能直接放入SpinalHDL代码里的,需要使用BlackBox库函数进行封装,打个比方,Verilog是USB接口,Scala是TypeC,就需要一个转换器才能进行数据传输,这个转换器就是BlackBox,本文以SPI总线的封装为例介绍BlackBox。
代码github路径:
GitHub - tklk610/SpinalHDL-Blackbox-for-SPI: 本文以SPI总线的封装为例介绍BlackBox
目录:
1.导入包和库
2.SpiPhyIfBundle
3.spi_v1p0
4.SpiTop
整个代码分为四部分,导入的包和库文件,1个SpiTop component,一个SpiPhyIfBundle Bundle,一个spi_v1p0 BlockBox。SpiTop是顶层component,spi_v1p0是封装了Verilog代码的BlockBox,SpiPhyIfBundle是对外的Spi接口,spi_v1p0和SpiPhyIfBundle在SpiTop里链接。
导入包和库
package parrot.perp.spi
import parrot.common.CKCELL
import spinal.lib._
import spinal.core._
import spinal.lib.bus.amba3.apb.{Apb3, Apb3Config}
import spinal.lib.io.{TriState, TriStateArray}
SpiPhyIfBundle
case class SpiPhyIfBundle() extends Bundle with IMasterSlave {
val csn = Vec(TriState(Bool()), 8)
val sck = TriState(Bool())
// val csn_m =TriState(Bool())
// val sck_m =TriState(Bool())
val miso = TriState(Bool())
val mosi = TriState(Bool())
// val csn_s = in Bool ()
// val sck_s = in Bool ()
// val csn_m = out Bits (8 bits)
// val sck_m = out Bool ()
// val miso = in Bool ()
// val mosi = out Bool ()
override def asMaster(): Unit = {
master(csn(0), csn(1), csn(2), csn(3), csn(4), csn(5), csn(6), csn(7), sck, miso, mosi)
}
}
SpiPhyIfBundle定义了标准Spi的对外4个接口,包括csn、sck、miso和mosi,这里csn是8位,对应8个从机。
override def asMaster()是作为主机情况下的固定写法。
spi_v1p0
case class spi_v1p0(
APB_DWIDTH: Int = 32,
CFG_FRAME_SIZE: Int = 32,
CFG_FIFO_DEPTH: Int = 32,
CFG_CLK: Int = 7,
DEFAULT_MODE: Bits = B"16'h0400"
) extends BlackBox {
addGeneric("APB_DWIDTH", APB_DWIDTH)
addGeneric("CFG_FRAME_SIZE", CFG_FRAME_SIZE)
addGeneric("CFG_FIFO_DEPTH", CFG_FIFO_DEPTH)
addGeneric("CFG_CLK", CFG_CLK)
addGeneric("DEFAULT_MODE", DEFAULT_MODE)
val io = new Bundle() {
// APB
val PCLK = in Bool ()
val PRESETN = in Bool ()
val PSEL = in Bits (1 bit)
val PENABLE = in Bool ()
val PWRITE = in Bool ()
val PADDR = in UInt (7 bits)
val PWDATA = in Bits (32 bits)
val PRDDATA = out Bits (32 bits)
val PREADY = out Bool ()
// SPI PHY
val aresetn = in Bool ()
val SPISSI = in Bool ()
val SPISDI = in Bool ()
val SPICLKI = in Bool ()
val SPIINT = out Bool ()
val SPISS = out Bits (8 bits)
val SPISCLKO = out Bool ()
val SPIRXAVAIL = out Bool ()
val SPITXRFM = out Bool ()
val SPIOEN = out Bool ()
val SPISDO = out Bool ()
val SPIMODE = out Bool ()
}
noIoPrefix()
addRTLPath("./hw/verilog/spi/spi_v1p0.v")
addRTLPath("./hw/verilog/spi/spi_chanctrl.v")
addRTLPath("./hw/verilog/spi/spi_clockmux.v")
addRTLPath("./hw/verilog/spi/spi_control.v")
addRTLPath("./hw/verilog/spi/spi_fifo.v")
addRTLPath("./hw/verilog/spi/spi_rf.v")
}
使用addRTLPath将Verilog代码吃进来,io定义了这些代码里的信号, noIoPrefix()规定IO信号名不加前缀。
SpiTop
case class SpiTop(apb3cfg: Apb3Config = Apb3Config(8, 32)) extends Component {
val io = new Bundle() {
val apb3 = slave(Apb3(apb3cfg))
val dma_txe = out Bool ()
val dma_rxne = out Bool ()
val irq = out Bool ()
val phy = master(SpiPhyIfBundle())
}
require(apb3cfg.addressWidth >= 8, "addressWidth must over 8")
val SpiBlackBoxInst = spi_v1p0()
val spi_scko_buffer = Bool()
val spi_scki_buffer = Bool()
if (GenerationFlags.synthesis) {
val spiClkInBuffer = CKCELL("CKBUF")
val spiClkOutBuffer = CKCELL("CKBUF")
spiClkOutBuffer.io.A := SpiBlackBoxInst.io.SPISCLKO
spi_scko_buffer := spiClkOutBuffer.io.Z
spiClkInBuffer.io.A := io.phy.sck.read
spi_scki_buffer := spiClkInBuffer.io.Z
} else {
spi_scko_buffer := SpiBlackBoxInst.io.SPISCLKO
spi_scki_buffer := io.phy.sck.read
}
SpiBlackBoxInst.io.PCLK := ClockDomain.current.readClockWire
SpiBlackBoxInst.io.PRESETN := ClockDomain.current.readResetWire
SpiBlackBoxInst.io.PSEL := io.apb3.PSEL
SpiBlackBoxInst.io.PENABLE := io.apb3.PENABLE
SpiBlackBoxInst.io.PWRITE := io.apb3.PWRITE
SpiBlackBoxInst.io.PADDR := io.apb3.PADDR(6 downto 0)
SpiBlackBoxInst.io.PWDATA := io.apb3.PWDATA
io.apb3.PRDATA := SpiBlackBoxInst.io.PRDDATA
io.apb3.PREADY := SpiBlackBoxInst.io.PREADY
io.apb3.PSLVERROR.clear()
SpiBlackBoxInst.io.aresetn := ClockDomain.current.readResetWire
SpiBlackBoxInst.io.SPISSI := io.phy.csn(0).read
SpiBlackBoxInst.io.SPICLKI := spi_scki_buffer
// SpiBlackBoxInst.io.SPISDI := io.phy.miso
for (i <- 0 until 8) io.phy.csn(i).write := SpiBlackBoxInst.io.SPISS(i)
io.phy.sck.write := spi_scko_buffer
// 主机模式
when(SpiBlackBoxInst.io.SPIMODE) {
io.phy.sck.writeEnable.set()
io.phy.csn.foreach(_.writeEnable.set())
io.phy.miso.writeEnable.clear()
io.phy.miso.write.set()
SpiBlackBoxInst.io.SPISDI := io.phy.miso.read
io.phy.mosi.writeEnable := SpiBlackBoxInst.io.SPIOEN
io.phy.mosi.write := SpiBlackBoxInst.io.SPISDO
} otherwise {
io.phy.sck.writeEnable.clear()
io.phy.csn.foreach(_.writeEnable.clear())
io.phy.miso.writeEnable := SpiBlackBoxInst.io.SPIOEN
io.phy.miso.write := SpiBlackBoxInst.io.SPISDO
io.phy.mosi.writeEnable.clear()
io.phy.mosi.write.set()
SpiBlackBoxInst.io.SPISDI := io.phy.mosi.read
}
io.dma_txe := SpiBlackBoxInst.io.SPITXRFM
io.dma_rxne := SpiBlackBoxInst.io.SPIRXAVAIL
io.irq := SpiBlackBoxInst.io.SPIINT
// val csn_s = in Bool ()
// val sck_s = in Bool ()
// val csn_m = out Bits (8 bits)
// val sck_m = out Bool ()
// val miso = in Bool ()
// val mosi = out Bool ()
}
SpiTop里定义了接口io,包括APB总线信号,DMA相关信号和外部接口信号phy。
val phy = master(SpiPhyIfBundle()):将SpiPhyIfBundle作为master的端口信号定义phy;
val SpiBlackBoxInst = spi_v1p0():将spi_v1p0作为BlockBox进行声明;
require(apb3cfg.addressWidth >= 8, "addressWidth must over 8"):类似断言
将SpiTop里的io和spi_v1p0里的信号做链接。
基于SpinalHDL BlackBox的CANFD实现,结构大体相同: