简单介绍在SpinalHDL编程中使用到的结构语句,主要包括赋值语句、选择语句,由于SpinalHDL中没有循环语句,这里在不介绍。
1. 赋值语句
SpinalHDL中有多个赋值运算:
:= | 标准赋值, 等价于Verilog中的<= ;对变量的最后一次赋值有效;直到下一个仿真周期数据才更新 |
\= | 等价于Verilog中的= , 数据会立即更新 |
<> | 自动地链接两个信号或相同类型的两个Bundle, 信号的方向通过in/out推断(行为上更像:= ) |
//因为硬件的并发性, `a`的值一直是1
val a, b, c = UInt(4 bits)
a := 0
b := a
a := 1 //a := 1 "wins"
c := a
var x UInt(4 bits)
val y, z = UInt(4 bits)
x := 0
y := x //y读到0
x \= x + 1
z := x //z读到1
//自动连接两个UART接口
uartCtrl.io.uart <> io.uart
第一部分代码会出现重赋值错误
always @(*) begin
x_1 = x;
x_1 = (x + 4'b0001);
end
assign b = a;
assign a = 8'h01;
assign c = a;
assign x = 4'b0000;
assign y = x;
assign z = x_1;
1.1 并行性(Concurrency)
你给每个组合逻辑或寄存器赋值的顺序对其硬件行为没有影响。例如以下两段代码完全等价:
//code0
val a, b, c = UInt(8 bits) // Define 3 combinational signals
c := a + b // c will be set to 7
b := 2 // b will be set to 2
a := b + 3 // a will be set to 5
//code1
val a, b, c = UInt(8 bits) // Define 3 combinational signals
b := 2 // b will be set to 2
a := b + 3 // a will be set to 5
c := a + b // c will be set to 7
更加通俗来说, 当你用:=
赋值操作符, 就好比给左侧的信号/寄存器一个新的数据产生规则。
//code0
assign c = (a + b);
assign b = 8'h02;
assign a = (b + 8'h03);
//code1
assign b = 8'h02;
assign a = (b + 8'h03);
assign c = (a + b);
1.2 以最后赋值为准
如果组合逻辑信号或寄存器多次被赋值, 最后一次赋值有效。例如:
val x, y = Bool() //定义两个组合逻辑信号
val result = UInt(8 bits) //定义一个组合逻辑信号
result := 1
when(x) {
result := 2
when(y) {
result := 3
}
}
这会产生如下真值表:
x | y | => | 结果 |
---|---|---|---|
False | False | 1 | |
Fasle | True | 1 | |
True | False | 2 | |
True | True | 3 |
1.3 位宽检查
SpinalHDL会检查赋值的左右两侧bit宽度是否一致。有多种方式能够去适应给定的Bit向量(Bits, UInt, SInt):
改变尺寸的方法 | 描述 |
---|---|
x:=y.resized | 用y的改变大小后的复制赋值给x, 改变的值会根据匹配的x自适应 |
x:=y.resize(newWidth) | 用y的改变大小后的复制赋值给x, 尺寸是手工计算的 |
2. 选择语句when/switch/mux
2.1 when语句
when 语句是一种条件语句,可以根据条件执行不同的操作。基本语法如下:
when(cond1) {
//当cond1真时执行操作
}.elsewhen(cond2) {
//当cond1假但cond2真时执行操作
}.otherwise {
//cond1和cond2同假时执行操作
}
在 when 语句中,你可以根据条件执行相应的代码块,并且还可以使用 .otherwise 块指定当条件不满足时的默认操作。
注意:如果otherwise关键字和花括号的后半部分}在同一行, .可以省略;但如果.otherwise在另一行, 需要.
//省略.标识
when(cond1) {
//当cond1真时执行操作
} otherwise {
//cond1和cond2同假时执行操作
}
//不能省略.标识
when(cond1) {
//当cond1真时执行操作
}
.otherwise {
//cond1和cond2同假时执行操作
}
2.2 switch语句
switch 语句允许根据不同的选择值执行不同的操作。它的基本语法如下:
switch(x) {
is(value1) {
//当x===value1执行
}
is(value2) {
//当x===value2执行
}
default {
//当之前的条件都没有符合执行
}
}
在 switch 语句中,你可以使用多个 is(value) 块来指定不同选择值时执行的代码块,并且可以使用 default 块指定当选择值不匹配任何已定义值时的默认操作。同时is语句块可以用is(value1, value2)这种逗号分割的方式书写,代表多种条件下执行相同语句块。
switch(aluop) {
is(ALUOp.add) {
immediate := instruction.immI.signExtend
}
is(ALUOp.slt) {
immediate := instruction.immI.signExtend
}
is(ALUOp.sltu) {
immediate := instruction.immI.signExtend
}
is(ALUOp.sll) {
immediate := instruction.shamt
}
is(ALUOp.sra) {
immediate := instruction.shamt
}
}
等价于
switch(aluop) {
is(ALU0p.add, ALU0p.slt, ALU0p.sltu) {
immediate := instruction.immI.signExtend
}
is(ALU0p.sll, ALU0p.sra) {
immediate := instruction.shamt
}
}
2.3 Mux语句
在 SpinalHDL中,可以使用 Mux(多路复用)语句来实现多路选择逻辑。Mux 语句可以根据一个选择信号选择其中一个输入,并输出所选输入的值。Mux 语句的基本语法如下:
val result = Mux(select, inputTrue, inputFalse)
其中:
- select 是一个布尔型选择信号,用于选择要输出的输入之一。
- inputTrue 是当 select 为真时要输出的输入值。
- inputFalse 是当 select 为假时要输出的输入值。
以下是一个示例,演示如何使用 Mux 语句来实现多路选择:
val select = Bool() // 假设有一个布尔型选择信号 select
val inputTrue = UInt(8 bits) // 假设有一个8位无符号整数输入 inputTrue
val inputFalse = UInt(8 bits) // 假设有一个8位无符号整数输入 inputFalse
val result = Mux(select, inputTrue, inputFalse)
在上面的示例中,根据 select 的值,Mux 语句将选择输出 inputTrue 或 inputFalse 中的一个值作为 result。
通过使用 Mux 语句,你可以在 SpinalHDL 中实现多路选择逻辑,根据选择信号选择不同的输入。这对于构建选择器、多路开关和其他需要选择输入的硬件电路非常有用。
3.循环语句when/for
在 SpinalHDL 中,虽然没有直接的循环语句(例如 Verilog 中的 for 循环),但你可以使用递归和生成器来实现类似的功能。
3.1 when递归
你可以使用递归来实现一系列相似的操作。通过定义一个递归函数,每次调用函数时传递不同的参数,从而模拟循环的效果。以下是一个简单的示例,演示如何使用递归在 SpinalHDL 中实现类似于 for 循环的功能:
def generateLogic(index: UInt): Unit = {
when(index < N) {
// 在每次迭代中执行的逻辑
generateLogic(index + 1) // 递归调用
}
}
val index = Reg(UInt(log2Up(N) bits)) // 假设有一个用于迭代的计数器 index
generateLogic(index)
在上面的示例中,generateLogic 是一个递归函数,它根据 index 的值执行一系列逻辑,并在每次迭代中递增 index 的值。通过递归调用 generateLogic,可以实现类似于 for 循环的迭代效果。
3.2 for生成器
SpinalHDL 还提供了一种强大的机制,即使用生成器来生成硬件电路。生成器允许你在编译时生成多个相似的硬件模块,从而实现类似于循环的效果。你可以使用 for 循环语句或迭代器来生成多个硬件实例。以下是一个简单的示例,演示如何使用生成器在 SpinalHDL 中生成多个硬件模块:
import spinal.core._
class MyModule extends Component {
val inputs = Vec(5, UInt(8 bits)) // 假设有一个包含 5 个 8 位无符号整数的输入向量
// 生成多个硬件模块
for (i <- 0 until 5) {
val submodule = new MySubmodule(inputs(i))
// 连接其他信号和逻辑
// ...
}
}
在上面的示例中,通过使用 for 循环来迭代 5 次,并在每次迭代中生成一个名为 MySubmodule 的硬件子模块。每个子模块都接收不同的输入信号。
使用递归和生成器的这些技术,你可以在 SpinalHDL 中实现类似于 Verilog 中的 for 循环的功能。通过利用递归和生成器的强大特性,可以有效地描述复杂的硬件电路结构和行为。