类型
简介
该语言提供了 5 种基本类型和 2 种复合类型。
- 基本类型: - Bool、- Bits、- UInt(无符号整数)、- SInt``(有符号整数)、 ``Enum。
- 复合类型: - Bundle、- Vec。
这些类型及其用法(包括示例)将在后面解释。
定点小数支持已归档 Fixed-Point
Bool
这是对应于单个位的标准 boolean 类型。
声明
声明值/对象的语法如下:
| 语法 | 描述 | 返回类型 | 
|---|---|---|
| Bool() | 创建Bool值 | Bool | 
| True | 创建一个分配有  | Bool | 
| False | 创建一个Bool值并赋值为  | Bool | 
| Bool(value : Boolean) | 创建一个赋值有 Scala 布尔值的 Bool信号 | Bool | 
在 SpinalHDL 中使用这种类型会生成:
val myBool = Bool()
myBool := False         // := is the assignment operator
myBool := Bool(false)   // Use a Scala Boolean to create a literal
运算符
以下运算符可用于 Bool 类型
| 运算符 | 描述 | 返回类型 | 
|---|---|---|
| !x | 逻辑非 | Bool | 
| x && y x & y | 逻辑与 | Bool | 
| x || y x | y | 逻辑或 | Bool | 
| x ^ y | 逻辑异或 | Bool | 
| x.set[()] | 将 x 设置为 True | |
| x.clear[()] | 将 x 设置为 False | |
| x.rise[()] | 当 x 在上一个周期为低电平且现在为高电平时返回 True | Bool | 
| x.rise(initAt : Bool) | 与 x.rise 相同但具有重置后的初始值 | Bool | 
| x.fall[()] | 当 x 在上一个周期为高且现在为低时返回 True | Bool | 
| x.fall(initAt : Bool) | 与 x.fall 相同但具有重置后的初始值 | Bool | 
| x.setWhen(cond) | 当 cond 为 True 时设置 x 为 True | Bool | 
| x.clearWhen(cond) | 当 cond 为 True 时设置 x 为 False | Bool | 
BitVector 系列 - (Bits, UInt, SInt)
BitVector 是一个系列的类型,用于在单个值中存储多位信息。该类型具有三个子类型,可用于描述不同的行为:Bits 不传达任何符号信息,而 UInt (无符号整数)和 SInt (有符号整数)提供了计算正确结果所需的操作(如果使用有符号/无符号算术)。声明语法
| 语法 | 描述 | 返回类型 | 
|---|---|---|
| Bits/UInt/SInt [()] | 创建一个 BitVector,自动推断其位数 | Bits/UInt/SInt | 
| Bits/UInt/SInt(x bits) | 创建一个具有 x 位的 BitVector信号 | Bits/UInt/SInt | 
| B/U/S(value : Int[,width : BitCount]) | 创建一个赋值为“value”的 BitVector信号 | Bits/UInt/SInt | 
| B/U/S”[[size’]base]value” | 创建一个赋值为“value”的 BitVector信号 | Bits/UInt/SInt | 
| B/U/S([x bits], element, …) | 创建一个 BitVector信号,并为各元素赋值(见下表) | Bits/UInt/SInt | 
可以这样定义元素:
| 元素语法 | 描述 | 
|---|---|
| x : Int -> y : Boolean/Bool | 用 y 设置位 x | 
| x : Range -> y : Boolean/Bool | 设置 x 范围内的每个位为 y | 
| x : Range -> y : T | 设置 x 范围内的位为y | 
| x : Range -> y : String | 设置 x 范围内的位为y 字符串格式遵循与B/U/S”xyz”相同的规则 | 
| x : Range -> y : T | 设置 x 范围内的位为y | 
| default -> y : Boolean/Bool | 使用 y 值设置所有未连接的位。 此功能只能用于对没有 U/B/S 前缀信号的赋值 | 
您可以定义一个范围值
| 范围语法 | 描述 | 位宽 | 
|---|---|---|
| (x downto y) | [x:y] x >= y | x-y+1 | 
| (x to y) | [x:y] x <= y | y-x+1 | 
| (x until y) | [x:y[ x < y | y-x | 
val myUInt = UInt(8 bits)
myUInt := U(2,8 bits)
myUInt := U(2)
myUInt := U"0000_0101"  // Base per default is binary => 5
myUInt := U"h1A"        // Base could be x (base 16)
                        //               h (base 16)
                        //               d (base 10)
                        //               o (base 8)
                        //               b (base 2)
myUInt := U"8'h1A"
myUInt := 2             // You can use scala Int as literal value
val myBool := myUInt === U(7 -> true,(6 downto 0) -> false)
val myBool := myUInt === U(myUInt.range -> true)
// For assignment purposes, you can omit the B/U/S, which also alow the use of the [default -> ???] feature
myUInt := (default -> true)                       // Assign myUInt with "11111111"
myUInt := (myUInt.range -> true)                  // Assign myUInt with "11111111"
myUInt := (7 -> true,default -> false)            // Assign myUInt with "10000000"
myUInt := ((4 downto 1) -> true,default -> false) // Assign myUInt with "00011110"
运算符
| 运算符 | 描述 | 返回类型 | 
|---|---|---|
| ~x | 按位非 | T(w(x) bits) | 
| x & y | 按位与 | T(max(w(x), w(y) bits) | 
| x | y | 按位或 | T(max(w(x), w(y) bits) | 
| x ^ y | 按位异或 | T(max(w(x), w(y) bits) | 
| x(y) | 读取位域,y : Int/UInt | Bool | 
| x(hi,lo) | 读取位域,hi : Int,lo : Int | T(hi-lo+1 bits) | 
| x(offset,width) | 读取位域,offset: UInt,width: Int | T(width bits) | 
| x(y) := z | 赋值位,y : Int/UInt | Bool | 
| x(hi,lo) := z | 赋值位域,hi : Int,lo : Int | T(hi-lo+1 bits) | 
| x(offset,width) := z | 赋值位域,offset: UInt,width: Int | T(width bits) | 
| x.msb | 返回最高有效位 | Bool | 
| x.lsb | 返回最低有效位 | Bool | 
| x.range | 返回范围(x.high downto 0) | 范围 | 
| x.high | 返回类型 x 的上限 | Int | 
| x.xorR | 对 x 的所有位进行异或 | Bool | 
| x.orR | 对x 的所有位做或运算 | Bool | 
| x.andR | 对x 的所有位做与运算 | Bool | 
| x.clearAll[()] | 清零所有位 | T | 
| x.setAll[()] | 将所有的位设置为1 | T | 
| x.setAllTo(value : Boolean) | 将所有位设置为给定的布尔值(Scala Boolean) | |
| x.setAllTo(value : Bool) | 将所有位设置为给定的布尔值(Spinal Bool) | |
| x.asBools | 转换为 Bool 数组 | Vec(Bool(),width(x)) | 
掩码过滤结果比较
有时候,你需要检查一个 BitVector 和一个包含空位(不需要进行相等性表达式比较的位)的位掩码之间的相等性。
此操作的示例(注意使用“M”前缀):
val myBits = Bits(8 bits)
val itMatch = myBits === M"00--10--"
位
| 运算符 | 描述 | 返回类型 | 
|---|---|---|
| x >> y | 逻辑右移,y : Int | T(w(x) - y bits) | 
| x >> y | 逻辑右移,y : UInt | T(w(x) bits) | 
| x << y | 逻辑左移,y : Int | T(w(x) + y bits) | 
| x << y | 逻辑左移, y : UInt | T(w(x) + max(y) bits) | 
| x.rotateLeft(y) | 逻辑循环左移,y : UInt | T(w(x)) | 
| x.resize(y) | 返回调整位宽后的 x,根据需要在 MSB 处填充零位以加大位宽,也可以截断保留在 LSB 侧的宽度,y : Int | T(y bits) | 
| x.resizeLeft(y) | 返回调整位宽的 x,根据需要在 LSB 处填充零位以加大位宽,也可以在 MSB 端截断宽度,y : Int | T(y bits) | 
UInt、SInt
| 运算符 | 描述 | 返回类型 | 
|---|---|---|
| x + y | 加法 | T(max(w(x), w(y) bits) | 
| x - y | 减法 | T(max(w(x), w(y) bits) | 
| x * y | 乘法 | T(w(x) + w(y) bits) | 
| x > y | 大于 | Bool | 
| x >= y | 大于或等于 | Bool | 
| x < y | 小于 | Bool | 
| x <= y | 小于或等于 | Bool | 
| x >> y | 算术右移,y : Int | T(w(x) - y bits) | 
| x >> y | 算术右移,y : UInt | T(w(x) bits) | 
| x << y | 算术左移,y : Int | T(w(x) + y bits) | 
| x << y | 算术左移,y : UInt | T(w(x) + max(y) bits) | 
| x.resize(y) | 返回算术位宽调整后的x,y。x, y : Int | T(y bits) | 
Bool, Bits, UInt, SInt
| 运算符 | 描述 | 返回类型 | 
|---|---|---|
| x.asBits | 二进制转换为Bits | Bits(w(x) bits) | 
| x.asUInt | 二进制转换为UInt | UInt(w(x) bits) | 
| x.asSInt | 二进制转换为SInt | SInt(w(x) bits) | 
| x.asBool | 二进制转换为Bool | Bool(x.lsb) | 
Vec
| 声明 | 描述 | 
|---|---|
| Vec(type : Data, size : Int) | 创建一个指定类型和位宽的向量 | 
| Vec(x,y,..) | 创建一个向量,其中索引指向给定元素。 这会构造支持混合宽度的元素 | 
| 运算符 | 描述 | 返回类型 | 
|---|---|---|
| x(y) | 读取元素 y, y : Int/UInt | T | 
| x(y) := z | 将元素 y 赋值给 z, y : Int/UInt | 
val myVecOfSInt = Vec(SInt(8 bits),2)
myVecOfSInt(0) := 2
myVecOfSInt(1) := myVecOfSInt(0) + 3
val myVecOfMixedUInt = Vec(UInt(3 bits), UInt(5 bits), UInt(8 bits))
val x,y,z = UInt(8 bits)
val myVecOf_xyz_ref = Vec(x,y,z)
for(element <- myVecOf_xyz_ref) {
  element := 0   // Assign x,y,z with the value 0
}
myVecOf_xyz_ref(1) := 3    // Assign y with the value 3
Bundle
简单示例(RGB/VGA)
以下示例显示了具有某些内部函数的 RGB 线束的定义。
case class RGB(channelWidth : Int) extends Bundle {
  val red   = UInt(channelWidth bits)
  val green = UInt(channelWidth bits)
  val blue  = UInt(channelWidth bits)
  def isBlack : Bool = red === 0 && green === 0 && blue === 0
  def isWhite : Bool = {
    val max = U((channelWidth-1 downto 0) -> true)
    return red === max && green === max && blue === max
  }
}
然后,您还可以根据需要将一个线束实例放置在另一个线束中(没有深度限制):
case class VGA(channelWidth : Int) extends Bundle {
  val hsync = Bool()
  val vsync = Bool()
  val color = RGB(channelWidth)
}
And finally instantiate your Bundles inside the hardware :
val vgaIn  = VGA(8)        // Create a RGB instance
val vgaOut = VGA(8)
vgaOut := vgaIn            // Assign the whole bundle
vgaOut.color.green := 0    // Fix the green to zero
val vgaInRgbIsBlack = vgaIn.rgb.isBlack   // Get if the vgaIn rgb is black
如果想将你的线束指定为组件的输入或输出,你必须通过以下方式来完成:
class MyComponent extends Component {
  val io = Bundle {
    val cmd = in(RGB(8))    // Don't forget the bracket around the bundle.
    val rsp = out(RGB(8))
  }
}
接口示例(APB)
如果你想定义一个接口,比如一个APB接口,就可以使用线束:
class APB(addressWidth: Int,
          dataWidth: Int,
          selWidth : Int,
          useSlaveError : Boolean) extends Bundle {
  val PADDR      = UInt(addressWidth bits)
  val PSEL       = Bits(selWidth bits)
  val PENABLE    = Bool()
  val PREADY     = Bool()
  val PWRITE     = Bool()
  val PWDATA     = Bits(dataWidth bits)
  val PRDATA     = Bits(dataWidth bits)
  val PSLVERROR  = if(useSlaveError) Bool() else null   // This signal is created only when useSlaveError is true
}
// Example of usage :
val bus = APB(addressWidth = 8,
              dataWidth = 32,
              selWidth = 4,
              useSlaveError = false)
一种好的做法是将所有构造参数分组到一个配置类中。这可以使组件中的参数化变得更加容易,特别是当您必须在多个位置重用相同的配置时。另外,如果您需要添加另一个构造参数,您只需将其添加到配置类中,并且在任何地方都可以实例化该参数:
case class APBConfig(addressWidth: Int,
                     dataWidth: Int,
                     selWidth : Int,
                     useSlaveError : Boolean)
class APB(val config: APBConfig) extends Bundle {   // [val] config, make the configuration public
  val PADDR      = UInt(config.addressWidth bits)
  val PSEL       = Bits(config.selWidth bits)
  val PENABLE    = Bool()
  val PREADY     = Bool()
  val PWRITE     = Bool()
  val PWDATA     = Bits(config.dataWidth bits)
  val PRDATA     = Bits(config.dataWidth bits)
  val PSLVERROR  = if(config.useSlaveError) Bool() else null
}
// Example of usage
val apbConfig = APBConfig(addressWidth = 8,dataWidth = 32,selWidth = 4,useSlaveError = false)
val busA = APB(apbConfig)
val busB = APB(apbConfig)
然后在某些时候,您可能需要使用 APB 总线作为某些组件的主端接口或从端接口。为此,您可以定义一些函数:
import spinal.core._
case class APBConfig(addressWidth: Int,
                     dataWidth: Int,
                     selWidth : Int,
                     useSlaveError : Boolean)
class APB(val config: APBConfig) extends Bundle {
  val PADDR      = UInt(config.addressWidth bits)
  val PSEL       = Bits(config.selWidth bits)
  val PENABLE    = Bool()
  val PREADY     = Bool()
  val PWRITE     = Bool()
  val PWDATA     = Bits(config.dataWidth bits)
  val PRDATA     = Bits(config.dataWidth bits)
  val PSLVERROR  = if(config.useSlaveError) Bool() else null
  def asMaster(): this.type = {
    out(PADDR,PSEL,PENABLE,PWRITE,PWDATA)
    in(PREADY,PRDATA)
    if(config.useSlaveError) in(PSLVERROR)
    this
  }
  def asSlave(): this.type = this.asMaster().flip() // Flip reverse all in out configuration.
}
// Example of usage
val apbConfig = APBConfig(addressWidth = 8,dataWidth = 32,selWidth = 4,useSlaveError = false)
val io = new Bundle {
  val masterBus = APB(apbConfig).asMaster()
  val slaveBus = APB(apbConfig).asSlave()
}
更进一步优化,spine.lib 中集成了一个名为 IMasterSlave 的小型主、从端口定义工具。当线束扩展IMasterSlave时,它应该实现/覆盖asMaster函数,以便设置主端接口或从端接口中的信号方向:
val apbConfig = APBConfig(addressWidth = 8,dataWidth = 32,selWidth = 4,useSlaveError = false)
val io = new Bundle {
  val masterBus = master(apbConfig)
  val slaveBus  = slave(apbConfig)
}
实现此 IMasterSlave 的 APB 总线示例:
// You need to import spinal.lib._ to use IMasterSlave
import spinal.core._
import spinal.lib._
case class APBConfig(addressWidth: Int,
                     dataWidth: Int,
                     selWidth : Int,
                     useSlaveError : Boolean)
class APB(val config: APBConfig) extends Bundle with IMasterSlave {
  val PADDR      = UInt(addressWidth bits)
  val PSEL       = Bits(selWidth bits)
  val PENABLE    = Bool()
  val PREADY     = Bool()
  val PWRITE     = Bool()
  val PWDATA     = Bits(dataWidth bits)
  val PRDATA     = Bits(dataWidth bits)
  val PSLVERROR  = if(useSlaveError) Bool() else null   // This signal is created only when useSlaveError is true
  override def asMaster() : Unit = {
    out(PADDR,PSEL,PENABLE,PWRITE,PWDATA)
    in(PREADY,PRDATA)
    if(useSlaveError) in(PSLVERROR)
  }
  // The asSlave is by default the flipped version of asMaster.
}
Enum
SpinalHDL 支持一些编码的枚举:
| 编码 | 位宽 | 描述 | 
|---|---|---|
| native | 使用VHDL枚举系统,这是默认编码 | |
| 二进制顺序 | log2Up(stateCount) | 使用 Bits 按声明顺序存储状态(值从 0 到 n-1) | 
| binaryOneHot | stateCount | 使用位来存储状态。每个位对应一种状态,编码时一次只有一位有效。 | 
定义一个枚举类型:
object UartCtrlTxState extends SpinalEnum { // Or SpinalEnum(defaultEncoding=encodingOfYourChoice)
  val sIdle, sStart, sData, sParity, sStop = newElement()
}
实例化一个信号来存储枚举编码值并为其赋值:
val stateNext = UartCtrlTxState() // Or UartCtrlTxState(encoding=encodingOfYouChoice)
stateNext := UartCtrlTxState.sIdle
// You can also import the enumeration to have the visibility on its elements
import UartCtrlTxState._
stateNext := sIdle
Data (Bool, Bits, UInt, SInt, Enum, Bundle, Vec)
所有硬件类型都扩展了 Data 类,这意味着它们都提供以下运算符:
| 运算符 | 描述 | 返回类型 | 
|---|---|---|
| x === y | 等价性判断 | Bool | 
| x =/= y | 不等价判断运算 | Bool | 
| x.getWidth | 返回位数 | Int | 
| x ## y | 连接Bits,x->高位,y->低位 | Bits(width(x) + width(y) bits) | 
| Cat(x) | 连接列表,第一个元素放置在lsb 上,x : Array[Data] | Bits(sumOfWidth bits) | 
| Mux(cond,x,y) | if cond ? x : y | T(max(w(x), w(y) bits) | 
| x.asBits | 类型转换到Bits | Bits(width(x) bits) | 
| x.assignFromBits(bits) | 从Bits获取信号来赋值 | |
| x.assignFromBits(bits,hi,lo) | 赋值位域,hi : Int,lo : Int | T(hi-lo+1 bits) | 
| x.assignFromBits(bits,offset,width) | 赋值位域,offset: UInt,width: Int | T(width bits) | 
| x.getZero | 获取等效类型且赋值0 | T | 
使用字面量声明信号
字面量通常用作常量值。但您也可以使用它们一次完成两件事:
- Define a signal which is assigned with a constant value 
- 设置推断类型:UInt(4 bits) 
- 当条件 cond =/= True 满足的时钟周期,将信号重新设置为常量 
- 由于最后一条语句获胜规则,满足 cond === True 条件的时钟周期将导致信号具有“red”值。 
实例:
val cond = in Bool()
val red = in UInt(4 bits)
...
val valid = False          // Bool signal which is by default assigned with False
val value = U"0100"        // UInt signal of 4 bits which is by default assigned with 4
when(cond) {
  valid := True
  value := red
}
用连续赋值字面量作来声明信号
您还可以在表达式中使用它们来同时达成三件事:
- 定义一个信号(一条线) 
- 在硬件逻辑实现中,保持等价性比较运算结果的常数值和另一信号的结果 
- 设置推断类型:Bool,因为使用 === 相等运算符,其结果为 Bool 类型 
这里是一个例子:
val done = Bool(False)
val blue = in UInt(4 bits)
...
val value = blue === U"0001"  // inferred type is Bool due to use of === operator
when(value) {
  done := True
}