写在前面
本文主要介绍了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
。如果你命名你的 bundle io
,SpinalHDL 将检查它的所有元素是否被定义为输入或输出。
输入/输出定义
定义输入和输出的语法如下:
Syntax | Description | Return |
in Bool()/out Bool() | 创建输入 Bool/输出 Bool | Bool |
in/out Bits/UInt/SInt[(x bit)] | 创建相应类型的输入/输出 | Bits/UInt/SInt |
in/out(T) | 对于所有其他数据类型,可能需要在其周围添加一些括号。抱歉,这是 Scala 限制。 | T |
master/slave(T) | 此语法由 | T |
组件互连需要遵循一些规则:
- 组件只能读取子组件的输出和输入信号。
- 组件可以读取自己的输出端口值(与 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官方文档