写在前面
本文主要介绍了spinal HDL的组件、层次结构和区域。
组件和层次结构(Component and hierarchy)
就像在 VHDL 和 Verilog 中一样,可以定义可用于构建设计层次结构的组件。但是,在 SpinalHDL 中,不需要在实例化时绑定它们的端口:
class AdderCell extends Component { // Declaring external ports in a Bundle called `io` is recommended val io = new Bundle { val a, b, cin = in Bool() val sum, cout = out Bool() } // Do some logic io.sum := io.a ^ io.b ^ io.cin io.cout := (io.a & io.b) | (io.a & io.cin) | (io.b & io.cin) } class Adder(width: Int) extends Component { ... // Create 2 AdderCell instances val cell0 = new AdderCell val cell1 = new AdderCell cell1.io.cin := cell0.io.cout // Connect cout of cell0 to cin of cell1 // Another example which creates an array of ArrayCell instances val cellArray = Array.fill(width)(new AdderCell) cellArray(1).io.cin := cellArray(0).io.cout // Connect cout of cell(0) to cin of cell(1) ... }
val io = new Bundle { … }
建议在
Bundle
被叫方声明外部端口io
。如果你命名你的 bundleio
,SpinalHDL 将检查它的所有元素是否被定义为输入或输出。
输入/输出定义
定义输入和输出的语法如下:
组件互连需要遵循一些规则:
- 组件只能读取子组件的输出和输入信号。
- 组件可以读取自己的输出端口值(与 VHDL 不同)。
修剪信号
SpinalHDL 只生成直接或间接驱动顶级实体输出所需的东西。
所有其他信号(无用的)都从 RTL 生成中删除,并插入到修剪过的信号列表中。您可以通过生成的对象上的printPruned
和printPrunedIo
函数获取此列表SpinalReport
:
class TopLevel extends Component { val io = new Bundle { val a,b = in UInt(8 bits) val result = out UInt(8 bits) } io.result := io.a + io.b val unusedSignal = UInt(8 bits) val unusedSignal2 = UInt(8 bits) unusedSignal2 := unusedSignal } object Main { def main(args: Array[String]) { SpinalVhdl(new TopLevel).printPruned() //This will report : // [Warning] Unused wire detected : toplevel/unusedSignal : UInt[8 bits] // [Warning] Unused wire detected : toplevel/unusedSignal2 : UInt[8 bits] } }
如果您出于调试原因想在生成的 RTL 中保留修剪后的信号,您可以使用该keep
信号的函数:
class TopLevel extends Component { val io = new Bundle { val a, b = in UInt(8 bits) val result = out UInt(8 bits) } io.result := io.a + io.b val unusedSignal = UInt(8 bits) val unusedSignal2 = UInt(8 bits).keep() unusedSignal := 0 unusedSignal2 := unusedSignal } object Main { def main(args: Array[String]) { SpinalVhdl(new TopLevel).printPruned() // This will report nothing } }
参数化硬件(VHDL 中的“Generic”,Verilog 中的“Parameter”)
如果你想参数化你的组件,你可以给组件的构造函数提供参数,如下所示:
class MyAdder(width: BitCount) extends Component { val io = new Bundle { val a, b = in UInt(width) val result = out UInt(width) } io.result := io.a + io.b } object Main { def main(args: Array[String]) { SpinalVhdl(new MyAdder(32 bits)) } }
如果有多个参数,那么给出一个具体的配置类是一个很好的做法,如下所示:
case class MySocConfig(axiFrequency : HertzNumber, onChipRamSize : BigInt, cpu : RiscCoreConfig, iCache : InstructionCacheConfig) class MySoc(config: MySocConfig) extends Component { ... }
合成成分名称
在一个模块中,每个组件都有一个名称,称为“部分名称”。“完整”名称是通过用“_”连接每个组件的父名称来构建的,例如:io_clockDomain_reset
。您可以使用setName
自定义名称替换此约定。这在与外部组件接口时特别有用。其他的方法被称为getName
,setPartialName
和getPartialName
分别。
合成时,每个模块都会获得定义它的 Scala 类的名称。您也可以使用setDefinitionName
.
区域(Area)
有时,创建一个Component
来定义一些逻辑是矫枉过正的,因为你:
- 需要定义所有的构造参数和IO(verbosity,duplication)
- 拆分您的代码(超出需要)
对于这种情况,您可以使用 anArea
来定义一组信号/逻辑:
class UartCtrl extends Component { ... val timer = new Area { val counter = Reg(UInt(8 bit)) val tick = counter === 0 counter := counter - 1 when(tick) { counter := 100 } } val tickCounter = new Area { val value = Reg(UInt(3 bit)) val reset = False when(timer.tick) { // Refer to the tick from timer area value := value + 1 } when(reset) { value := 0 } } val stateMachine = new Area { ... } }
Reference
- spinal HDL官方文档