技术标签: fpga开发 Chisel速成班教程 scala risc-v Chisel 计算机体系结构
ChiselTest
Chisel团队给测试框架做了很多工作,ChiselTest
提供了以下改进:
还有以下计划:
ChiselTest
和iotesters
一样都是从基本操作开始讲起,下面是一个简要的总结,关于旧的iotesters
和新的ChiselTest
中的基本功能特性的对应关系:
iotesters |
ChiselTest |
|
---|---|---|
poke |
poke(c.io.in1, 6) |
c.io.in1.poke(6.U) |
peek |
peek(c.io.out1) |
c.io.out1.peek() |
expect |
expect(c.io.out1, 6) |
c.io.out1.expect(6.U) |
step |
step(1) |
c.io.clock.step(1) |
initiate |
Driver.execute(...) { c => |
test(...) { c => |
下面还是先看之前写的一个简单的pass:
// 通过传入参数指定端口宽度的Chisel代码
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
}
如果使用旧风格的测试,那么应该是这样的:
val testResult = Driver(() => new Passthrough()) {
c => new PeekPokeTester(c) {
poke(c.io.in, 0) // Set our input to value 0
expect(c.io.out, 0) // Assert that the output correctly has 0
poke(c.io.in, 1) // Set our input to value 1
expect(c.io.out, 1) // Assert that the output correctly has 1
poke(c.io.in, 2) // Set our input to value 2
expect(c.io.out, 2) // Assert that the output correctly has 2
}
}
assert(testResult) // Scala Code: if testResult == false, will throw an error
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
现在用新风格写(方便起见,这里把测试放在主程序里):
import chisel3._
import chisel3.util._
import chisel3.experimental._
import chisel3.experimental.BundleLiterals._
import chisel3.tester._
import chisel3.tester.RawTester.test
class MyModule(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
}
object MyModule extends App {
test(new MyModule(16)) {
c =>
c.io.in.poke(0.U) // 输入设为0
c.clock.step(1) // 时钟步进
c.io.out.expect(0.U) // 输出应当为0
c.io.in.poke(1.U) // 输入设为1
c.clock.step(1) // 时钟步进
c.io.out.expect(1.U) // 输出应当为1
c.io.in.poke(2.U) // 输入设为2
c.clock.step(1) // 时钟步进
c.io.out.expect(2.U) // 输出应当为2
}
}
这里测试通过,但是会提示一个警告:
那就在sbt run
之前运行一句set scalacOptions += "-deprecation"
,想看看详情,结果直接就不显示警告了。
以上的例子中,需要注意以下几点。
ChiselTest
的测试方法需要的样板更少,以前的PeekPokeTester
已经内置到进程中了。
poke
和expect
方法现在是每个单独的io
元素的方法,这样可以给测试人员提供重要的提示,来更好地检查类型。peek
和step
操作现在已是io
元素上的方法。
还有个区别是poke
和expect
的值时Chisel字面值。虽然这里的例子很简单,但后面更多高级有趣的例子会展示更强大的检查功能。未来的改进中通过指定Bundle
字面值的能力可以进一步增强这一点。
这一部分会了解tester2
中的一些Decoupled
接口的工具。Decoupled
接收一个Chisel数据类型并给他提供ready
和valid
信号。ChiselTest
提供一些很棒的工具来自动化并可靠地测试这些接口。
QueueModule
传递由ioType
确定类型的数据。在QueueModule
内部有entries
状态元素,这意味着可以在推出数据之前容纳这么多元素。
case class QueueModule[T <: Data](ioType: T, entries: Int) extends MultiIOModule {
val in = IO(Flipped(Decoupled(ioType)))
val out = IO(Decoupled(ioType))
out <> Queue(in, entries)
}
注意这里的case
类修饰符一般是不需要的,例子中的是为了在Jupyter
环境的多个单元格中复用。
enqueueNow
和expectDequeueNow
ChiselTest
有一些内置的方法来处理在IO中有解耦合接口的电路。这里展示怎么向queue
中插入或从queue
中提取值。
方法 | 描述 |
---|---|
enqueueNow |
添加(排队)一个元素到一个Decoupled 输入接口 |
expectDequeueNow |
移出(出列)一个元素自一个Decoupled 输出接口 |
注意,这里需要一些样板如initSource
,setSourceClock
等,以此来确保ready
和valid
字段都在测试开始时正确初始化了。
import chisel3._
import chisel3.util._
import chisel3.experimental._
import chisel3.experimental.BundleLiterals._
import chisel3.tester._
import chisel3.tester.RawTester.test
case class QueueModule[T <: Data](ioType: T, entries: Int) extends MultiIOModule {
val in = IO(Flipped(Decoupled(ioType)))
val out = IO(Decoupled(ioType))
out <> Queue(in, entries)
}
object MyModule extends App {
test(QueueModule(UInt(9.W), entries = 200)) {
c =>
c.in.initSource()
c.in.setSourceClock(c.clock)
c.out.initSink()
c.out.setSinkClock(c.clock)
val testVector = Seq.tabulate(200){
i => i.U }
testVector.zip(testVector).foreach {
case (in, out) =>
c.in.enqueueNow(in)
c.out.expectDequeueNow(out)
}
}
}
教程这里还是有很多东西没讲清楚的,这里补充一下:
Decoupled(gen: Data)
:给gen
包装一个ready-valid
协议,测试中就是给UInt(9.W)
类型包装了ready
和valid
信号;
Flipped[T <: Data](source:T)
是把参数列表全都翻转过来,即输出变成输入,输入变成输出,比如:
class MyModule[T <: Data](ioType: T) extends MultiIOModule {
val in = IO(Flipped(Decoupled(ioType)))
val out = IO(Decoupled(ioType))
out <> in
}
object MyModule extends App {
println(getVerilogString(new MyModule(UInt(9.W))))
}
输出的Verilog代码如下:
module MyModule(
input clock,
input reset,
output in_ready,
input in_valid,
input [8:0] in_bits,
input out_ready,
output out_valid,
output [8:0] out_bits
);
assign in_ready = out_ready; // @[MyModule.scala 11:7]
assign out_valid = in_valid; // @[MyModule.scala 11:7]
assign out_bits = in_bits; // @[MyModule.scala 11:7]
endmodule
可以看到in
的三个接口分别为output
、input
和input
,与out
是反的。从这里也可以看到,通过Decoupled
包装的ready
信号是输入,valid
是输出。
<>
表示整体连接,这里就是把in
和out
的三个端口分别连接起来,很方便;
Queue(enq:DecoupleIO, entries:Int)
是一个Chisel硬件模块,创建一个entries
个元素的enq
的队列;
enqueueSeq
和expectDequeueSeq
现在介绍两个新方法来以单个操作完成排队和出列操作:
方法 | 描述 |
---|---|
enqueueSeq |
持续从一个Seq 添加元素到Decoupled 输入接口,一次一个,知道序列的元素用完了 |
expectDequeueSeq |
从Decoupled 输出接口移出元素,一次一个,并且和Seq 的下一个元素进行比较 |
下面这个例子还行,但是就像写的那样,enqueueSeq
必须在expectDequeueSeq
开始之前完成,如果testVector
大于队列的深度,那么这个运行就会出问题,因为队列会被填满没法插入新的元素,可以试试失败是啥样的。
import chisel3._
import chisel3.util._
import chisel3.experimental._
import chisel3.experimental.BundleLiterals._
import chisel3.tester._
import chisel3.tester.RawTester.test
case class QueueModule[T <: Data](ioType: T, entries: Int) extends MultiIOModule {
val in = IO(Flipped(Decoupled(ioType)))
val out = IO(Decoupled(ioType))
out <> Queue(in, entries)
}
object MyModule extends App {
test(QueueModule(UInt(9.W), entries = 200)) {
c =>
c.in.initSource()
c.in.setSourceClock(c.clock)
c.out.initSink()
c.out.setSinkClock(c.clock)
val testVector = Seq.tabulate(100){
i => i.U }
c.in.enqueueSeq(testVector)
c.out.expectDequeueSeq(testVector)
}
}
现在把entries
改成100,队列改成200试试:
排队排不上,报了个超时错误,下一节会将怎么解决这个问题。
还有需要注意的是,enqueueNow
、enqueueSeq
、expectDequeueNow
和expectDequeueSeq
这些刚看到的函数并不是ChiselTest
中复杂的特殊情况逻辑,相反,他们是ChiselTest
鼓励大家从ChiselTest
原语中构建的,具体怎么使用这些方法可以看这里的定义:chiseltest/TestAdapters.scala at d199c5908828d0be5245f55fce8a872b2afb314e · ucb-bar/chiseltest · GitHub。
ChiselTest
中的fork
和join
这一部分将会介绍怎么同时运行一个单元测试的各个部分,因此首先要介绍两个testers2
的新特性:
方法 | 描述 |
---|---|
fork |
发射一个并发的代码块,额外的forks (分支)可以通过.fork 附加到前一个代码块的结尾来同时执行 |
join |
将多个相关的分支变成中 |
下面的例子有两个分支连在一起,然后join
到一起。在第一个fork
块中enqueueSeq
会继续添加元素直到耗尽,第二个fork
会在数据可用时,在每个时钟周期expectDequeueSeq
。
由fork
创建的线程以确定的顺序运行,主要根据代码指定的顺序执行,并且某些依赖于其他线程的容易出bug的操作会在运行时检查中禁止。
import chisel3._
import chisel3.util._
import chisel3.experimental._
import chisel3.experimental.BundleLiterals._
import chisel3.tester._
import chisel3.tester.RawTester.test
case class QueueModule[T <: Data](ioType: T, entries: Int) extends MultiIOModule {
val in = IO(Flipped(Decoupled(ioType)))
val out = IO(Decoupled(ioType))
out <> Queue(in, entries)
}
object MyModule extends App {
test(QueueModule(UInt(9.W), entries = 200)) {
c =>
c.in.initSource()
c.in.setSourceClock(c.clock)
c.out.initSink()
c.out.setSinkClock(c.clock)
val testVector = Seq.tabulate(200){
i => i.U }
fork {
c.in.enqueueSeq(testVector)
}.fork {
c.out.expectDequeueSeq(testVector)
}.join()
}
}
fork
和join
实现GCD这一部分用fork
和join
方法实现GCD(Greatest Common Denominator,最大公约数)的测试。首先定义IO bundle,这里准备添加一点样板来允许使用Bundle
字面值,希望可以支撑支持字面值的代码的自动生成。
// 输入bundle
class GcdInputBundle(val w: Int) extends Bundle {
val value1 = UInt(w.W)
val value2 = UInt(w.W)
}
// 输出bundle
class GcdOutputBundle(val w: Int) extends Bundle {
val value1 = UInt(w.W)
val value2 = UInt(w.W)
val gcd = UInt(w.W)
}
现在来看GCD的Decoupled
版本,这里也可以使用Decoupled
包装器来给两个bundle添加ready
和valid
信号。Flipped
包装器接收一个会被默认创建为输出的Decoupled GcdInputBundle
并将每个字段都转换为相反的方向(递归的),这点在前面的补充中提到过的。Decoupled
的捆绑参数的数据元素放置在顶级字段bits
中。
/**
* 用辗转相减法计算GCD
* 两个寄存器xy中,用大的数减去小的数,小的数和差再存入寄存器,重复此过程直到两个数的差为0
* 此时寄存器x的值即为最大公约数
* 返回一个包,包含两个输入值和他们的GCD
*/
class DecoupledGcd(width: Int) extends MultiIOModule {
val input = IO(Flipped(Decoupled(new GcdInputBundle(width))))
val output = IO(Decoupled(new GcdOutputBundle(width)))
val xInitial = Reg(UInt())
val yInitial = Reg(UInt())
val x = Reg(UInt())
val y = Reg(UInt())
val busy = RegInit(false.B)
val resultValid = RegInit(false.B)
input.ready := ! busy
output.valid := resultValid
output.bits := DontCare // DontCare是一个单例对象,用于赋值给未驱动的端口或线网,防止编译器报错
when(busy) {
// 保证在计算的时候始终是大数减去小数
when(x > y) {
x := x - y
}.otherwise {
y := y - x
}
when(y === 0.U) {
// 当y值为0的时候结束计算
// 如果output已经准备好了,那就把有效的数据发送到output
output.bits.value1 := xInitial
output.bits.value2 := yInitial
output.bits.gcd := x
resultValid := true.B
busy := ! output.ready
}
}.otherwise {
when(input.valid) {
// 有效数据可用且没有正在进行的计算,获取新值并开始
val bundle = input.deq()
x := bundle.value1
y := bundle.value2
xInitial := bundle.value1
yInitial := bundle.value2
busy := true.B
resultValid := false.B
}
}
}
现在这个测试看起来和前面的Queue差不多了,但是还有一些事情要做。因为计算需要多个周期,因此在计算每个GCD是,输入的排队过程会阻塞。不过好消息是这方面的测试和之前的Decoupled
是一样简单且一致的。
这里还需要引入的是Chisel3中的Bundle
字面值符号,看下面这一行:
new GcdInputBundle(16).Lit(_.value1 -> x.U, _.value2 -> y.U)
上面定义的GcdInputBundle
有两个字段value1
和value2
,我们通过先创建一个Bundle
再调用它的.Lit()
方法来创建Bundle
字面值。这个方法接收一个键值对的变量参数列表,这里的键(key,这里是_.value1
)是字段名,值(value,这里是x.U
)是一个Chisel硬件字面值,Scala中的Int
x被转换到Chisel中的UInt
字面值,字段名前的_.
是必要的,不然不知道是这个Bundle
里面的。
这可能不是完美的符号,但是在广泛的开发讨论中,他被视为最小化样板代码和Scala中可用的符号限制之间的最贱平衡。
完整的代码如下:
import chisel3._
import chisel3.util._
import chisel3.experimental._
import chisel3.experimental.BundleLiterals._
import chisel3.tester._
import chisel3.tester.RawTester.test
class GcdInputBundle(val w: Int) extends Bundle {
val value1 = UInt(w.W)
val value2 = UInt(w.W)
}
class GcdOutputBundle(val w: Int) extends Bundle {
val value1 = UInt(w.W)
val value2 = UInt(w.W)
val gcd = UInt(w.W)
}
class DecoupledGcd(width: Int) extends MultiIOModule {
val input = IO(Flipped(Decoupled(new GcdInputBundle(width))))
val output = IO(Decoupled(new GcdOutputBundle(width)))
val xInitial = Reg(UInt())
val yInitial = Reg(UInt())
val x = Reg(UInt())
val y = Reg(UInt())
val busy = RegInit(false.B)
val resultValid = RegInit(false.B)
input.ready := ! busy
output.valid := resultValid
output.bits := DontCare
when(busy) {
when(x > y) {
x := x - y
}.otherwise {
y := y - x
}
when(y === 0.U) {
output.bits.value1 := xInitial
output.bits.value2 := yInitial
output.bits.gcd := x
resultValid := true.B
busy := ! output.ready
}
}.otherwise {
when(input.valid) {
val bundle = input.deq()
x := bundle.value1
y := bundle.value2
xInitial := bundle.value1
yInitial := bundle.value2
busy := true.B
resultValid := false.B
}
}
}
object MyModule extends App {
test(new DecoupledGcd(16)) {
dut =>
dut.input.initSource().setSourceClock(dut.clock)
dut.output.initSink().setSinkClock(dut.clock)
val testValues = for {
x <- 1 to 10; y <- 1 to 10} yield (x, y)
val inputSeq = testValues.map {
case (x, y) =>
(new GcdInputBundle(16)).Lit(_.value1 -> x.U, _.value2 -> y.U)
}
val resultSeq = testValues.map {
case (x, y) =>
new GcdOutputBundle(16).Lit(_.value1 -> x.U, _.value2 -> y.U, _.gcd -> BigInt(x).gcd(BigInt(y)).U)
}
fork {
dut.input.enqueueSeq(inputSeq)
}.fork {
for (expected <- resultSeq) {
dut.output.expectDequeue(expected)
dut.clock.step(5) // 在接收到下一输出前等待几个周期来创建backpressure
}
}.join()
}
}
注意以下几点:
这个test
里面的dut
和前面的c
是一样的,代表被测试的对象,即DUT(Device Under Test,被测器件),起什么名字都行;
这里的初始化有两种写法:
dut.input.initSource()
dut.input.setSourceClock(dut.clock)
dut.input.initSource().setSourceClock(dut.clock)
本质上是一样的,都是先初始化然后再设置时钟;
简单来说,上面的过程就是先创建Scala的值,然后转换为Bundle字面值的序列,再作为输入或用于比对的输出。
文章浏览阅读165次。加入水印,采用lsb%Name: Chris Shoemaker%Course: EER-280 - Digital Watermarking%Project: Least Significant Bit Substitution% Watermark Embedingclear all;% save start timestart_time=cputime;% read in the co..._[cover_object,map]=imread(file_name);
文章浏览阅读1.6w次。原文地址:http://www.xitongtiandi.net/wenzhang/win10/12654.htmlWin10专业版下ipv6无网络访问权限解决方案(只在win10专业版下做了测试,win7和win8.1待测试)1、首先打开 https://support.microsoft.com/en-us/kb/929852 选择Re-enable IPv6 相关的_ipv6无internet访问权限怎么办
文章浏览阅读3.1k次,点赞32次,收藏37次。而且每次都要直接使用上一步骤的具体运算结果(如2,6,24等),也不方便,应当能找到一种通用的表示方法。由于数值运算往往有现成的模型,可以运用数值分析方法,因此对数值运算的算法的研究比较深入,算法比较成熟。从图中可以看出:“其他”这一部分,包括不能被4整除的年份,以及能被4整除,又能被100整除,但不能被400整除的那些年份(如1900年),它们都是非闰年。:若year能被4整除,不能被100整除,则输出year的值和“是闰年”。因此,上述算法不仅是正确的,而且是计算机能方便实现的较好的算法。_程序等于数据结构+算法
文章浏览阅读2.6w次,点赞18次,收藏88次。记录一下碰到的问题解决方法第一步:首先最先要确定的是芯片和设置是否对应!!!!!!!!!第二步:确定芯片和设置对应无误后,再考虑下面的方法Keil : Error-Flash Download failed Cortex-M4错误解决方案整理在开发 nRF51822/nRF52832/nRF52840时候出现如下如下问题:问题: Keil电子下载时候出现 Error: Flash Download failed - "Cortex-M4"的错误,如下图根据官方教程解释如下,还是发现不容易解决,另_error: flash download failed - "cortex-m4
文章浏览阅读313次。在数据库编程中,很多朋友会碰到分割字符串的需求,一般都是分割成一列多行模式,但也有时会需要分割成多列一行的模式,下面我们来看下如何实现这种需求。首先创建一个辅助函数,来得到生成多列的SQL语句:create function toArray(@str nvarchar(1000),@sym nvarchar(10))returns nvarchar(3000)asbe..._sqlserver 字符串分割成多列 小提琴
文章浏览阅读1.3k次。配置MM32微控制器引脚复用功能文章目录配置MM32微控制器引脚复用功能IntroductionAlgorithmGPIOx_CR寄存器GPIOx_AFR寄存器GPIOx_CR & GPIOx_AFR寄存器TIMUARTSPI_MASTERSPI_SLAVEI2CCANADCFSMCQSPIDACCOMPSDIOUSBPraticeConclusionIntroduction使用过NXP(FSL)微控制器的开发者在配置引脚复用功能时,直接在PORT模块中,对应引脚的的PCR寄存器的MUX字段_mm32f单片机的bootloader串行口引脚
文章浏览阅读88次。 &n..._简明易懂 机器学习
文章浏览阅读134次。[color=darkred][size=x-large]存储过程学习总结[/size][/color][size=medium]1、存储过程基本语法:[/size]create procedure sp_name()begin ......end;[size=medium]2、如何调用:[/size]call sp_name();[size=medium]..._存储过程repeatuse near 'end repeat; close curgra;
文章浏览阅读97次。Neil Fraser文章看得糊里糊涂,e文不是很好...一般我们加载数据会生成一个script标签,在onload事件里remove掉,或者在jsonp回调函数中remove掉script标签,取得数据这样其实script占用的内存并没有释放,必须:for (var prop in jsFile) { delete jsFile[prop];}ie下不能d..._script加载的数据在哪里
文章浏览阅读77次。JavaScript 程序应该尽量放在 .js 的文件中,需要调用的时候在 HTML 中以 <script src="filename.js"> 的形式包含进来。JavaScript 代码若不是该 HTML 文件所专用的,则应尽量避免在 HTML 文件中直接编写 JavaScript 代码。因为这样会大大增加 HTML 文件的大小,无益于代码的压缩和缓存的使用。<scr..._js里的下载路径怎么做约束
文章浏览阅读604次。作者:池建强网址:macshuo.com微信:sagacity-mac问:池老师,我是个不爱互动的人,但是您所有的文章我都看了,非常感谢您的引导,我入手了人生第一台 MBP。现在问题来了,但是找不到更合适的人解答,只能求助于您了,如果您有时间的话。问题是这样的:我有个32bit unix file(开启一个服务进程),在 Mac 上执行时错误提示是:exec f_程序员价值
文章浏览阅读143次。测试工作总体流程图_测试的工作流程