类型

简介

该语言提供了 5 种基本类型和 2 种复合类型。

  • 基本类型: BoolBitsUInt (无符号整数)、SInt``(有符号整数)、 ``Enum

  • 复合类型:BundleVec

../../_images/types.svg

这些类型及其用法(包括示例)将在后面解释。

定点小数支持已归档 Fixed-Point

Bool

这是对应于单个位的标准 boolean 类型。

声明

声明值/对象的语法如下:

语法

描述

返回类型

Bool()

创建Bool值

Bool

True

创建一个分配有 true 值的 Bool对象

Bool

False

创建一个Bool值并赋值为 false

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

线束可用于对数据结构信号总线和接口进行建模。
线束内定义的所有数据属性(Bool、Bits、UInt…)都被视为线束的一部分。

简单示例(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 wire 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 wire 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

使用字面量声明信号

字面量通常用作常量值。但您也可以使用它们一次完成两件事:

  • 定义一条分配有常量值的连线

  • 设置推断类型:UInt(4 bits)

  • 当条件 cond =/= True 满足的时钟周期,将信号重新设置为常量

  • 由于最后一条语句获胜规则,满足 cond === True 条件的时钟周期将导致信号具有“red”值。

实例:

val cond = in Bool()
val red = in UInt(4 bits)
...
val valid = False          // Bool wire which is by default assigned with False
val value = U"0100"        // UInt wire 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
}