Chisel及Scala部分学习参考
:onstructing ardware n a cala mbedded anguage (原文主要参考:https://github.com/freechipsproject/chisel-bootcamp)
一、Scala介绍
变量和常量
变量使用var
定义,可以赋值
常量使用val
定义不能修改
条件语句
能正常使用if…else…语句
val likelyCharactersSet = if (alphabet.length == 26) "english" else "not english" println(likelyCharactersSet) /*输出 english
方法(函数)
方法使用def
进行定义
逗号分隔列表中指定函数参数,列表指定。为明确起见,应指定
而对于没有参数的Scala按照惯例,函数不需要空括号: 无副作用的无参数函数(即调用它们不会改变任何东西,它们只是返回一个值)不使用括号 副作用函数(也许它们会改变类变量或打印出来) ) 括号应该需要
简单声明
// Simple scaling function with an input argument, e.g., times2(3) returns 6 // Curly braces can be omitted for short one-line functions. def times2(x: Int): Int = 2 * x // More complicated function def distance(x: Int, y: Int, returnPositive: Boolean): Int = {
val xy = x * y if (returnPositive) xy.abs else -xy.abs }
重载函数
// Overloaded function def times2(x: Int): Int = 2 * x def times2(x: String): Int = 2 * x.toInt times2(5) times2("7")
虽然可以使用这种重载声明,但是
提交嵌套函数
花括号定义代码范围。在一个函数的作用域内可能存在更多的函数或递归函数调用。在特定范围内定义的函数只能在该范围内访问
/** Prints a triangle made of "X"s * This is another style of comment */
def asciiTriangle(rows: Int) {
// This is cute: multiplying "X" makes a string with many copies of "X"
def printRow(columns: Int): Unit = println("X" * columns) //Unit为一个返回类型
if(rows > 0) {
printRow(rows)
asciiTriangle(rows - 1) // Here is the recursive call
}
}
// printRow(1) // This would not work, since we're calling printRow outside its scope
asciiTriangle(6)
/*输出 XXXXXX XXXXX XXXX XXX XX X
列表
列表和数组类似,支持追加和提取操作
val x = 7
val y = 14
val list1 = List(1, 2, 3)
val list2 = x :: y :: y :: Nil // An alternate notation for assembling a list
val list3 = list1 ++ list2 // Appends the second list to the first list
val m = list2.length
val s = list2.size
val headOfList = list1.head // Gets the first element of the list
val restOfList = list1.tail // Get a new list with first element removed
val third = list1(2) // Gets the third element of a list (0-indexed)
/*result x: Int = 7 y: Int = 14 list1: List[Int] = List(1, 2, 3) list2: List[Int] = List(7, 14, 14) list3: List[Int] = List(1, 2, 3, 7, 14, 14) m: Int = 3 s: Int = 3 headOfList: Int = 1 restOfList: List[Int] = List(2, 3) third: Int = 3
For语句
for语句和其他语言类似,其中 to
表示从0~n(n包含),until
表示0~n-1(不包括n),by
可以实现迭代变量增加固定数量
for (i <- 0 to 7) {
print(i + " ") }
println()
for (i <- 0 until 7) {
print(i + " ") }
println()
for(i <- 0 to 10 by 2) {
print(i + " ") }
println()
//此为列表求和
val randomList = List(scala.util.Random.nextInt(), scala.util.Random.nextInt(), scala.util.Random.nextInt(), scala.util.Random.nextInt())
var listSum = 0
for (value <- randomList) {
listSum += value
}
println("sum is " + listSum)
/*输出 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 0 2 4 6 8 10
包和导入
外部包的导入与java、python类似
package mytools
class Tool1 {
... }
import mytools.Tool1
import chisel3._
import chisel3.iotesters.{
ChiselFlatSpec, Driver, PeekPokeTester}
包名称应当与目录层次结构相匹配;按照惯例,包名称是,并且
包的导入中_
表示导入其中所有类和方法,下划线用作通配符
面向对象思想
- 变量是对象。
- Scala
val
声明式意义上的常量也是对象。 - 甚至文字值也是对象。
- 甚至函数本身也是对象。
- 对象是类的实例。
- 事实上,在 Scala 中几乎所有重要的方面,面向对象的对象都将被称为。
- 在定义类时,由程序员指定
- 与类关联的数据 (
val
,var
)。 - 类的实例可以执行的操作,称为方法或函数。
- 类可以扩展其他类。
- 被扩展的类是超类;扩展对象是子类。
- 在这种情况下,子类从超类继承数据和方法。
- 有许多有用但可控的方式可以让类扩展或覆盖继承的属性。
- 类可以从特征继承。将特征视为允许从多个超类继承的特定的、有限的方法的轻量级类。
- (单例)对象是一种特殊的 Scala 类。
- 它们不是上面的对象。请记住,我们正在调用这些。
类实例
举例说明
// WrapCounter counts up to a max value based on a bit size
class WrapCounter(counterBits: Int) {
val max: Long = (1 << counterBits) - 1
var counter = 0L
def inc(): Long = {
counter = counter + 1
if (counter > max) {
counter = 0
}
counter
}
println(s"counter created with max value $max")
}
-
class WrapCounter
– 这是的定义。 -
(counterBits: Int)
– 创建一个 WrapCounter 需要一个整数参数,这个参数很好地表明它是计数器的位宽。 -
大括号 ({}) 分隔代码块。大多数类使用代码块来定义变量、常量和方法(函数)。
-
val max: Long =
– 该类包含一个成员变量,声明为 typeLong
并在创建类时进行初始化。 -
(1 << counterBits) - 1
计算可包含在位中的。由于是用val
它创建的,因此无法更改。 -
创建一个变量并将其初始化为,大写说明0是类型; 因此,被推断为 Long。
-
和通常称为类的成员变量。
-
定义了一个类方法,它不接受任何参数并返回一个值。
-
方法的主体是一个代码块,它具有:
counter = counter + 1
递增。if (counter > max) { counter = 0 }
测试计数器是否大于,如果是,则将其设置回零。counter
– 代码块的最后一行很重要。- 任何表示为代码块最后一行的值都被视为该代码块的返回值。调用语句可以使用或忽略返回值。
- 这很普遍;例如,由于
if
thenelse
语句使用代码块定义其 true 和 false 子句,因此它可以返回一个值,即,val result = if (10 * 10 > 90) "greater" else "lesser"
将创建一个val
值“greater”的 。
- 所以在这种情况下,函数返回的值。
-
println(s"counter created with max value $max")
将字符串打印到标准输出。因为直接在定义代码块中,所以它是类初始化代码的一部分并运行,即每次创建该类的实例时打印出字符串。 -
在这种情况下打印的字符串是一个
内插
字符串。
- 第一个双引号前面的前导将其标识为内插字符串。
- 在运行时处理内插字符串。
- 在**$max**替换为常量
max
。 - 如果**$**后跟一个代码块,则任意 Scala 语句都可以在该代码块中。
- 例如,。
- 此代码块的返回值将被插入来代替
${...}
。 - 如果返回值不是字符串,则将其转换为 1;实际上,scala 中的每个类或类型都隐式转换为已定义的字串。
- 通常应该避免在每次创建类的实例时打印某些内容以避免泛滥标准输出,除非您正在调试。
实例的创建
使用关键字new
进行实例的创建
val x = new WrapCounter(2)
val y = WrapCounter(6)
有时也有不使用new
进行创建,此时一般需要使用伴生对象
代码块
与其他高级语言类似,可用在if、for之中。但Scala 代码的最后一行成为代码块的返回值(可以忽略)。没有行的代码块将返回一个名为Unit
的对象
val intList = List(1, 2, 3)
val stringList = intList.map {
i =>
i.toString
}
代码块被传递给map
类 List的方法。该map
方法要求其代码块具有单个参数。为列表的每个成员调用代码块,代码块返回转换为字符串的成员。Scala 几乎过度接受了这种语法的变体。这种类型的代码块称为
命名参数和参数默认值
def myMethod(count: Int, wrap: Boolean, wrapValue: Int = 24): Unit = {
... }
myMethod(count = 10, wrap = false, wrapValue = 23)//1
myMethod(wrapValue = 23, wrap = false, count = 10)//2
myMethod(wrap = false, count = 10) //3
可以使用不同的顺序进行传参(见2),当已有默认值且无需覆盖时,可不传(见3)
二、第一个Chisel模块
模块样例
// Chisel Code: Declare a new module definition
class Passthrough extends Module {
val io = IO(new Bundle {
val in = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
io.out := io.in
}
逐行解释:
class Passthrough extends Module {
我们声明了一个名为Passthrough
. Module
是所有硬件模块都必须扩展的内置 Chisel 类。
val io = IO(...)
我们声明一个特殊的io
val
. 它必须被定义为io
并且是一个IO
对象或实例,它需要某种形式的东西IO(_instantiated_bundle_)
。
new Bundle {
val in = Input(...)
val out = Output(...)
}
我们声明了一个新的硬件结构类型(Bundle
),它包含一些命名信号in
和out
方向分别为 Input 和 Output。
UInt(4.W)
我们声明一个信号的硬件类型。在这种情况下,它是一个宽度为 4 的无符号整数。(.W不要忘了)
io.out := io.in
我们将输入端口连接到输出端口,从而io.in
驱动 io.out
. 请注意,:=
运算符是***Chisel***运算符,表示右手信号驱动左手信号。它是一个定向操作符。
细化
// Scala Code: Elaborate our Chisel design by translating it to Verilog
// Don't worry about understanding this code; it is very complicated Scala
println(getVerilog(new Passthrough))
/*输出 module Passthrough( input clock, input reset, input [3:0] io_in, output [3:0] io_out ); assign io_out = io_in; // @[cmd2.sc 6:10] endmodule
此即可将Chisel翻译为Verilog,此过程称为
模块生成器
// Chisel Code, but pass in a parameter to set widths of ports
class PassthroughGenerator(width: Int) extends Module {
val io = IO(new Bundle {
val in = Input(UInt(width.W))
val out = Output(UInt(width.W))
})
io.out := io.in
}
// Let's now generate modules with different widths
println(getVerilog(new PassthroughGenerator(10)))
println(getVerilog(new PassthroughGenerator(20)))
/*输出 module PassthroughGenerator( input clock, input reset, input [9:0] io_in, output [9:0] io_out ); assign io_out = io_in; // @[cmd4.sc 6:10] endmodule module PassthroughGenerator( input clock, input reset, input [19:0] io_in, output [19:0] io_out ); assign io_out = io_in; // @[cmd4.sc 6:10] endmodule
PassthroughGenerator
不再描述单个模块,而是描述由参数化的一系列模块width
,我们将其Passthrough
称为。
测试
// Scala Code: `test` runs the unit test.
// test takes a user Module and has a code block that applies pokes and expects to the
// circuit under test (c)
test(new Passthrough()) {
c =>
c.io.in.poke(0.U) // Set our input to value 0
c.io.out.expect(0.U) // Assert that the output correctly has 0
c.io.in.poke(1.U) // Set our input to value 1
c.io.out.expect(1.U) // Assert that the output correctly has 1
c.io.in.poke(2.U) // Set our input to value 2
c.io.out.expect(2.U) // Assert that the output correctly has 2
}
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
该测试接受一个Passthrough
模块,为该模块的输入分配值,并检查其输出。要设置输入,我们调用poke
. 要检查输出,我们调用expect
. 如果我们不想将输出与预期值(无断言)进行比较,我们可以peek
改为输出。
如果所有expect
语句都为真,那么我们的样板代码将返回 pass。
poke
和expect
使用Chisel硬件文字表示法。这两个操作都需要正确类型的文字。如果poke
需要一个UInt()
你必须提供一个UInt
文字(例如:c.io.in.poke(10.U)
)同样,如果输入的是Bool()
在poke
所期望的任何true.B
或false.B
。
运行代码???
将产生NotImplementedError
printf调试注意事项
三种常见的print输出场景:
- 电路生成期间Chisel生成器print
- 电路仿真期间的电路print
- 测试期间测试仪print
println
是打印到控制台的内置 Scala 函数。它在电路仿真期间用于打印,因为生成的电路是FIRRTL或 Verilog- 不是 Scala。
class PrintingModule extends Module {
val io = IO(new Bundle {
val in = Input(UInt(4.W))
val out = Output(UInt(4.W))
})
io.out := io.in
printf("Print during simulation: Input is %d\n", io.in)
// chisel printf has its own string interpolator too
printf(p"Print during simulation: IO is $io\n")
println(s"Print during generation: Input is ${io.in}")
}
test(new PrintingModule ) {
c =>
c.io.in.poke(3.U)
c.clock.step(5) // circuit will print
println(s"Print during testing: Input is ${c.io.in.peek()}")
}
/*运行过程 Elaborating design... Print during generation: Input is UInt<4>(IO in unelaborated PrintingModule) Done elaborating. Print during simulation: Input is 3 Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3) Print during simulation: Input is 3 Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3) Print during simulation: Input is 3 Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3) Print during simulation: Input is 3 Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3) Print during simulation: Input is 3 Print during simulation: IO is AnonymousBundle(in -> 3, out -> 3) Print during testing: Input is UInt<4>(3) Print during simulation: Input is 0 Print during simulation: IO is AnonymousBundle(in -> 0, out -> 0)
三、组合逻辑
UInt
- 无符号整数;SInt
- 有符号整数;Bool
- 可以连接和操作真或假。
注意所有 Chisel 变量是如何声明为 Scalaval
的。永远不要将 Scalavar
用于硬件构造
常见运算符
将两个Chisel UInt
相加,因此println
将其视为硬件节点并打印出类型名称和指针
1.U
是从 Scala Int
(1) 到 ChiselUInt
文字的类型转换。
Scala 是一种强类型语言,因此任何类型转换都必须是显式的。所以1.U+1
将会出现报错
class MyOperators extends Module {
val io = IO(new Bundle {
val in = Input(UInt(4.W))
val out_add = Output(UInt(4.W))
val out_sub = Output(UInt(4.W))
val out_mul = Output(UInt(4.W))
})
io.out_add := 1.U + 4.U
io.out_sub := 2.U - 1.U
io.out_mul := 4.U * 2.U
}
println(getVerilog(new MyOperators))
/*运行过程 Elaborating design... Done elaborating. module MyOperators( input clock, input reset, input [3:0] io_in, output [3:0] io_out_add, output [3:0] io_out_sub, output [3:0] io_out_mul ); wire [1:0] _T_3 = 2'h2 - 2'h1; // @[cmd4.sc 10:21] wire [4:0] _T_4 = 3'h4 * 2'h2; // @[cmd4.sc 11:21] assign io_out_add = 4'h5; // @[cmd4.sc 9:21] assign io_out_sub = {
{2'd0}, _T_3}; // @[cmd4.sc 10:21] assign io_out_mul = _T_4[3:0]; // @[cmd4.sc 11:14] endmodule
转化为Verilog的转化过程不必深究,最终转化结果所表达的意思对即可。
除了加、减、乘,还有其他如mux(select, value if true, value if false)
、Cat(MSB,LSB)
,是选择器,而是将两个数拼接在一起。
val s = true.B
Mux(s, 3.U, 0.U) //3.U
Cat(2.U, 1.U) //5.U(101)
val sum = io.in_a +& io.in_b //+&可以将超过位数放入新增一位,即可解决溢出问题
四、控制流
:=
连接符连接组件时,多个语句同时连接时,以最后一个语句为准。(相当于非阻塞赋值)
when,elsewhen,otherwise
when(someBooleanCondition) {
// things to do when true
}.elsewhen(someOtherBooleanCondition) {
// things to do on this condition
}.otherwise {
// things to do if none of th boolean conditions are true
}
此结构在嵌套使用时,不会像if
一样返回值,以下赋值就
val result = when(squareIt) {
x * x }.otherwise {
x }
wire构建
wire
可以定义一些连线的中间变量进行运算。
/** Sort4 sorts its 4 inputs to its 4 outputs */
class Sort4 extends Module {
val io = IO(new Bundle {
val in0 = Input(UInt(16.W))
val in1 = Input(UInt(16.W))
val in2 = Input(UInt(16.W))
val in3 = Input(UInt(16.W))
val out0 = Output(UInt(16.W))
val out1 = Output(UInt(16.W))
val out2 = Output(UInt(16.W))
val out3 = Output(UInt(16.W))
})
val row10 = Wire(UInt(16.W))
val row11 = Wire(UInt(16.W))
val row12 = Wire(UInt(16.W))
val row13 = Wire(UInt(16.W))
when(io.in0 < io.in1) {
row10 := io.in0 // preserve first two elements
row11 := io.in1
}.otherwise {
row10 := io.in1 // swap first two elements
row11 := io.in0
}
when(io.in2 < io.in3) {
row12 := io.in2 // preserve last two elements
row13 := io.in3
}.otherwise {
row12 := io.in3 // swap last two elements
row13 := io.in2
}
val row21 = Wire(UInt(16.W))
val row22 = Wire(UInt(16.W))
when(row11 < row12) {
row21 := row11 // preserve middle 2 elements
row22 := row12
}.otherwise {
row21 := row12 // swap middle two elements
row22 := row11
}
val row20 = Wire(UInt(16.W))
val row23 = Wire(UInt(16.W))
when(row10 < row13) {
row20 := row10 // preserve middle 2 elements
row23 := row13
}.otherwise {
row20 := row13 // swap middle two elements
row23 := row10
}
when(row20 < row21) {
io.out0 := row20 // preserve first two elements
io.out1 := row21
}.otherwise {
io.out0 := row21 // swap first two elements
io.out1 := row20
}
when(row22 < row23) {
io.out2 := row22 // preserve first two elements
io.out3 := row23
}.otherwise {
io.out2 := row23 // swap first two elements
io.out3 := row22
}
}
// Here's the tester
test(new Sort4) {
c =>
// verify the inputs are sorted
c.io.in0.poke(3.U)
c.io.in1.poke(6.U)
c.io.in2.poke(9.U)
c.io.in3.poke(12.U)
c.io.out0.expect(3.U)
c.io.out1.expect(6.U)
c.io.out2.expect(9.U)
c.io.out3.expect(12.U)
c.io.in0.poke(13.U)
c.io.in1.poke(4.U)
c.io.in2.poke(6.U)
c.io.in3.poke(1.U)
c.io.out0.expect(1.U)
c.io.out1.expect(4.U)
c.io.out2.expect(6.U)
c.io.out3.expect(13.U)
c.io.in0.poke(13.U)
c.io.in1.poke(6.U)
c.io.in2.poke(4.U)
c.io.in3.poke(1.U)
c.io.out0.expect(1.U)
c.io.out1.expect(4.U)
c.io.out2.expect(6.U)
c.io.out3.expect(13.U)
}
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
有限状态机
// state map
def states = Map("idle" -> 0, "coding" -> 1, "writing" -> 2, "grad" -> 3)
// life is full of question marks
def gradLife (state: Int, coffee: Boolean, idea: Boolean, pressure: Boolean): Int = {
var nextState = states("idle")
if (state == states("idle")) {
if (coffee) {
nextState = states("coding") }
else if (idea) {
nextState = states("idle") }
else if (pressure) {
nextState = states("writing") }
} else if (state == states("coding")) {
if (coffee) {
nextState = states("coding") }
else if (idea || pressure) {
nextState = states("writing") }
} else if (state == states("writing")) {
if (coffee || idea) {
nextState = states("writing") }
else if (pressure) {
nextState = states("grad") }
}
nextState
}
// some sanity checks
(0 until states.size).foreach{
state => assert(gradLife(state, false, false, false) == states("idle")) }
assert(gradLife(states("writing"), true, false, true) == states("writing"))
assert(gradLife(states("idle"), true, true, true) == states("coding"))
assert(gradLife(states("idle"), false, true, true) == states("idle"))
assert(gradLife(states("grad"), false, false, false) == states("idle"))
上述转化为Module
即为:
// life gets hard-er
class GradLife extends Module {
val io = IO(new Bundle {
val state = Input(UInt(2.W))
val coffee = Input(Bool())
val idea = Input(Bool())
val pressure = Input(Bool())
val nextState = Output(UInt(2.W))
})
val idle :: coding :: writing :: grad :: Nil = Enum(4)
io.nextState := idle
when (io.state === idle) {
when (io.coffee) {
io.nextState := coding }
.elsewhen (io.idea) {
io.nextState := idle }
.elsewhen (io.pressure) {
io.nextState := writing }
} .elsewhen (io.state === coding) {
when (io.coffee) {
io.nextState := coding }
.elsewhen (io.idea || io.pressure) {
io.nextState := writing }
} .elsewhen (io.state === writing) {
when (io.coffee || io.idea) {
io.nextState := writing }
.elsewhen (io.pressure) {
io.nextState := grad }
}
}
// Test
test(new GradLife) {
c =>
// verify that the hardware matches the golden model
for (state <- 0 to 3) {
for (coffee <- List(true, false)) {
for (idea <- List(true, false)) {
for (pressure <- List(true, false)) {
c.io.state.poke(state.U)
c.io.coffee.poke(coffee.B)
c.io.idea.poke(idea.B)
c.io.pressure.poke(pressure.B)
c.io.nextState.expect(gradLife(state, coffee, idea, pressure).U)
}
}
}
}
}
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!