类型
简介
该语言提供了 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 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
}