做读书笔记的网站,东莞手机网站建设怎么选,在网站文章锚文本怎么做,汕头网站制作设计上一篇见此#xff1a;
chisel快速入门#xff08;一#xff09;_沧海一升的博客-CSDN博客简单介绍了chisel#xff0c;使硬件开发者能快速上手chisel。https://blog.csdn.net/qq_21842097/article/details/121415341
十、运行和测试 现在我们已经定义了模块#xff0c;… 上一篇见此
chisel快速入门一_沧海一升的博客-CSDN博客简单介绍了chisel使硬件开发者能快速上手chisel。https://blog.csdn.net/qq_21842097/article/details/121415341
十、运行和测试 现在我们已经定义了模块我们将讨论如何实际运行并测试电路。Chisel代码可以转换为C或Verilog。 为了编译电路我们需要调用chiselMain
object tutorial {def main(args: Array[String]) {chiselMain(args, () Module(new Mux2())) }
} 测试是电路设计的关键部分因此在 Chisel 中我们通过使用 Tester 类的子类在 Scala 中提供测试向量来提供一种测试电路的机制
class Tester[T : Module] (val c: T, val isTrace: Boolean true) {var t: Intval rnd: Randomdef int(x: Boolean): BigInt def int(x: Int): BigIntdef int(x: Bits): BigInt def reset(n: Int 1)def step(n: Int): Intdef pokeAt(data: Mem[T], index: Int, x: BigInt) def poke(data: Bits, x: BigInt)def poke(data: Aggregate, x: Array[BigInt])def peekAt(data: Mem[T], index: Int)def peek(data: Bits): BigIntdef peek(data: Aggregate): Array[BigInt]def expect (good: Boolean, msg: String): Boolean def expect (data: Bits, target: BigInt): Boolean
} 它将tester绑定到模块并允许用户使用给定的调试协议编写测试。用户会用到一下这些
poke: 设置输入端口以及状态值step: 以一个时间单元执行电路peek: 读取端口和状态值expect: 比较peek获得的值和期望的值用户使用如下的方式连接tester和模块
object chiselMainTest { def apply[T : Module](args: Array[String], comp: () T)( tester: T Tester[T]): T
} 当- -test作为参数传递给chiselMainTest时tester实例在独立的进程中运行被测器件DUT并连接stdin和stdout这样调试命令可以发送到DUT响应也可以从DUT接收如图所示。 举例说明
class Mux2Tests(c: Mux2) extends Tester(c) { val n pow(2, 3).toIntfor (s - 0 until 2) {for (i0 - 0 until 2) { for (i1 - 0 until 2) {poke(c.io.sel, s)poke(c.io.in1, i1)poke(c.io.in0, i0)step(1)expect(c.io.out, (if (s 1) i1 else i0))}}}
} 使用poke将Mux2的每个输入的分别设置为合适的值。对于这个例子我们通过硬编码输入到一些已知的值并检查输出是否对应于已知的值来测试Mux2。为此在每次迭代中我们生成模块输入让模拟将这些值分配给我们正在测试的器件c的输入单步运行电路并对比期望值。最后简单说明一下如何调用测试器
chiselMainTest(args --test, () Module(new Mux2())){ c new Mux2Tests(c)
}
还有其他的一些命令参数
–targetDir 目标路径名前缀–genHarness 生成C文件–backend v 生成verilog–backend c 生成C默认–vcd 开启vcd打印–debug 把所有的wire放入class文件
十一、状态元素 Chisel支持的状态元素的最简单形式是上升沿触发寄存器可以实例化为
val reg Reg(next in) 该电路具有输出该输出是前一个时钟周期的输入信号产生的值。注意我们不必指定Reg的类型因为它会在实例化时从输入开始自动推断。在当前版本的Chisel中时钟和复位是全局信号在需要时可以隐式包含。 使用寄存器我们可以快速定义一些有用的电路结构。 例如当当前值为true且之前的值为false时上升沿检测器能够获取到布尔信号并输出true如下所示
def risingedge(x: Bool) x !Reg(next x) 计数器是一个重要的时序电路。 如果想构建一个向上计数器计数到最大值max后回到零
def counter(max: UInt) {val x Reg(init UInt(0, max.getWidth))x : Mux(x max, UInt(0), x UInt(1))x
} 计数器复位值为0宽度大到足以容纳max当电路的全局复位置位时寄存器将初始化为该值。 计数器可用于构建很多有用的时序电路。例如我们可以通过在计数器达到零时输出true来构建脉冲发生器
def pulse(n: UInt) counter(n - UInt(1)) UInt(0) 然后可以通过切换方波发生器脉冲序列在每个脉冲上的true和false之间切换
// Flip internal state when input true.
def toggle(p: Bool) {val x Reg(init Bool(false)) x : Mux(p, !x, x)x
}
// Square wave of a given period.
def squareWave(period: UInt) toggle(pulse(period/2))
1、转发声明 纯组合电路在节点之间不存在周期如果检测到这样的周期则Chisel将报告错误。因为它们不具有周期所以可以总是以前馈方式构建组合电路通过添加一些输入从已经定义的节点导出的新节点。 时序电路在节点之间具有反馈因此有时需要在生成节点被定义之前输出。因为Scala顺序执行程序语句所以我们允许数据节点作为wire来提供节点声明这样可以立即被使用但其输入将稍后设置。 如下例所示在简单的CPU中我们需要定义pcPlus4和brTarget的线以便在定义之前引用它们
val pcPlus4 UInt()
val brTarget UInt()
val pcNext Mux(io.ctrl.pcSel, brTarget, pcPlus4)
val pcReg Reg(next pcNext, init UInt(0, 32))
pcPlus4 : pcReg UInt(4)
...
brTarget : addOut 接线操作符:用于在pcReg和addOut定义后连接。
2、条件更新 在前面使用到寄存器的示例中我们简单地将组合逻辑块连接到寄存器的输入。当描述状态元素的操作时指定何时将发生寄存器更新并且用几个单独的语句指明这些更新。 Chisel以when的形式提供条件更新规则以支持这种顺序逻辑描述的风格。例如
val r Reg(init UInt(0, 16))
when (cond) {r : r UInt(1)
} 其中只有在cond为真时才在当前时钟周期的结尾更新寄存器r。when的参数是返回Bool值。后面的更新块只能包含使用赋值运算符:简单表达式和用val定义的命名引线的更新语句。 在条件更新序列中条件为真的最近条件更新优先。 例如
when (c1) { r : UInt(1) }
when (c2) { r : UInt(2) } 上述表达式会根据以下真值表更新r 条件更新结构可以嵌套任何给定块在所有外嵌套条件的联合下才能执行。 条件可以使用when.elsewhen.otherwise来链式表达对应于Scala中的if, else if, else。例如
when (c1) { u1 }
.elsewhen (c2) { u2 }
.otherwise { ud }
// the same as
when (c1) { u1 }
when (!c1 c2) { u2 }
when (!(c1 || c2)) { ud } Chisel还允许Wire即一些组合逻辑的输出成为条件性更新语句的目标以允许逐步构建复杂的组合逻辑表达式。Chisel不允许不指定组合输出并且如果组合输出未遇到无条件更新则报告错误。
3、有限状态机 在数字设计中有限状态机FSM是时序电路常用的类型。简单FSM的例子就是奇偶校验生成器
class Parity extends Module { val io new Bundle {val in Bool(dir INPUT)val out Bool(dir OUTPUT) }val s_even :: s_odd :: Nil Enum(UInt(), 2) val state Reg(init s_even)when (io.in) {when (state s_even) { state : s_odd }when (state s_odd) { state : s_even } }io.out : (state s_odd)
} 其中Enum(Uint(), 2)生成两个UInt数。当io.in为true时更新状态。需要注意的是FSM的所有机制都建立在寄存器线和条件更新的基础上。 下面是一个复杂的FSM例子这是一个自动售货机接收货币的电路
class VendingMachine extends Module {val io new Bundle {val nickel Bool(dir INPUT)val dime Bool(dir INPUT)val valid Bool(dir OUTPUT)}val s_idle :: s_5 :: s_10 :: s_15 :: s_ok :: Nil Enum(UInt(), 5)val state Reg(init s_idle) when (state s_idle) {when (io.nickel) { state : s_5 }when (io.dime) { state : s_10 } }when (state s_5) {when (io.nickel) { state : s_10 } when (io.dime) { state : s_15 }}when (state s_10) {when (io.nickel) { state : s_15 }when (io.dime) { state : s_ok } }when (state s_15) {when (io.nickel) { state : s_ok } when (io.dime) { state : s_ok }}when (state s_ok) {state : s_idle}io.valid : (state s_ok)
} 采用switch风格代码如下
class VendingMachine extends Module {val io new Bundle {val nickle Bool(dir INPUT)val dime Bool(dir INPUT)val valid Bool(dir OUTPUT)}val s_idle :: s_5 :: s_10 :: s_15 :: s_ok :: Nil Enum(UInt(), 5)val state Reg(init s_idle) switch (state) { is (s_idle) {when (io.nickel) { state : s_5 }when (io.dime) { state : s_10 } } is (s_5) {when (io.nickel) { state : s_10 } when (io.dime) { state : s_15 }}is (s_10) {when (io.nickel) { state : s_15 }when (io.dime) { state : s_ok } }is (s_ok) {state : s_idle}}io.valid : (state s_ok)
}
十二、内存 Chisel提供了创建只读和读/写存储器的功能。
1、ROM 用户可以使用Vec定义ROM
Vec(inits: Seq[T])
Vec(elt0: T, elts: T*) 其中inits是初始化ROM的初始Data序列。例如用户可以创建一个初始化为1,2,4,8的小型ROM并使用计数器作为地址生成器循环访问所有值如下所示
val m Vec(Array(UInt(1), UInt(2), UInt(4), UInt(8)))
val r m(counter(UInt(m.length))) 我们可以使用如下初始化的ROM创建n值正弦查找表
def sinTable (amp: Double, n: Int) { val times Range(0, n, 1).map(i (i*2*Pi)/(n.toDouble-1) - Pi) val inits times.map(t SInt(round(amp * sin(t)), width 32)) Vec(inits)
}
def sinWave (amp: Double, n: Int) sinTable(amp, n)(counter(UInt(n)) 其中amp用于缩放存储在ROM中的固定点值。
2、Mem 存储器在Chisel中被给予特殊处理因为存储器的硬件实现具有许多变化例如FPGA存储器与ASIC存储实例化的结果完全不同。Chisel定义了一个内存抽象可以映射到简单的Verilog行为描述也可以映射到从代工厂或IP厂商提供的外部内存生成器获得的内存模块实例。 Chisel通过Mem结构可以支持随机存取存储器。写入Mems是正边沿触发读取是组合或正边沿触发。
object Mem {def apply[T : Data](type: T, depth: Int,seqRead: Boolean false): Mem
}
class Mem[T : Data](type: T, depth: Int, seqRead: Boolean false)extends Updateable { def apply(idx: UInt): T} 通过使用UInt索引创建到Mems的端口。具有一个写入端口和两个组合读取端口的32-entry的寄存器堆可以如下表示
val rf Mem(UInt(width 64), 32)
when (wen) { rf(waddr) : wdata }
val dout1 rf(waddr1)
val dout2 rf(waddr2) 如果设置了可选参数seqRead当读地址为Reg时Chisel将尝试推断顺序读端口。 单读端口单写端口SRAM可以描述如下
val ram1r1w Mem(UInt(width 32), 1024, seqRead true)
val reg_raddr Reg(UInt())
when (wen) { ram1r1w(waddr) : wdata }
when (ren) { reg_raddr : raddr }
val rdata ram1r1w(reg_raddr) 单端口SRAM可以在读和写条件在链中相同时相互排斥时推断
val ram1p Mem(UInt(width 32), 1024, seqRead true)
val reg_raddr Reg(UInt())
when (wen) { ram1p(waddr) : wdata }
.elsewhen (ren) { reg_raddr : raddr }
val rdata ram1p(reg_raddr) 如果相同的Mem地址在相同的时钟沿上被写入和顺序读取或者如果顺序读取使能被清除则读取数据为未定义。
十三、接口和批量连接 对于更复杂的模块在定义模块的 IO 时定义和实例化接口类通常很有用。 首先接口类促进重用允许用户以有用的形式一次性捕获所有通用接口。 其次接口允许用户通过模块之间的批量连接来显着减少布线。 最后用户可以在一个地方对大型接口进行更改从而减少添加或删除接口部分时所需的更新次数。
1、端口类、子类和嵌套 正如我们之前看到的用户可以通过定义一个继承 Bundle 的类来定义他们自己的接口。 例如可以为握手数据定义一个简单的链接如下所示
class SimpleLink extends Bundle {
val data UInt(16, OUTPUT)
val valid Bool(OUTPUT)
}然后我们可以通过使用包继承添加奇偶校验位来扩展 SimpleLink
class PLink extends SimpleLink {
val parity UInt(5, OUTPUT)
} 通常用户可以使用继承将他们的接口组织成层次结构。 从那里我们可以通过将两个 PLink 嵌套到一个新的 FilterIO 包中来定义过滤器接口
class FilterIO extends Bundle {
val x new PLink().flip
val y new PLink()
} 其中flip递归地改变Bundle的“相性”将输入改变为输出和将输出改变为输入。 我们现在可以通过定义一个过滤器类继承模块来定义一个过滤器
class Filter extends Module {
val io new FilterIO()
...
} 其中io包含了FilterIO。
2、Bundle Vector 除了单个元素之外元素Vector可以组成更丰富的分层接口。 例如为了创建一个带有输入向量的交叉开关产生一个输出向量并由 UInt 输入选择我们使用 Vec 构造函数。
class CrossbarIo(n: Int) extends Bundle {val in Vec.fill(n){ new PLink().flip() } val sel UInt(INPUT, sizeof(n))val out Vec.fill(n){ new PLink() }
} 其中Vec用第一个参获取大小区块返回一个端口作为第二个参数。
3、批量连接 我们现在可以将两个过滤器组成一个过滤器块如下所示
class Block extends Module { val io new FilterIO()val f1 Module(new Filter()) val f2 Module(new Filter())f1.io.x io.x f1.io.y f2.io.x f2.io.y io.y
} 其中批量连接同级模块之间的相反接口或父/子模块之间的相同接口。批量连接将相同名称的端口彼此连接。在所有连接完成后Chisel警告用户端口是否只有一个到它们的连接。
4、接口视图 考虑一个由控制路径和数据路径子模块以及主机和内存接口组成的简单 CPU如图。 在这个 CPU 中我们可以看到控制路径和数据路径各自只连接到一部分指令和数据内存接口。Chisel 允许用户通过部分实现接口来做到这一点。 用户首先定义完整的 ROM 和 Mem 接口如下
class RomIo extends Bundle { val isVal Bool(INPUT)val raddr UInt(INPUT, 32) val rdata UInt(OUTPUT, 32)
}
class RamIo extends RomIo { val isWr Bool(INPUT)val wdata UInt(INPUT, 32)
} 现在控制逻辑可以根据这些接口构建接口
class CpathIo extends Bundle { val imem RomIo().flip() val dmem RamIo().flip()
} 而且控制和数据通路模块可以通过部分地分配来给这个接口来构建如下所示
class Cpath extends Module { val io new CpathIo();...io.imem.isVal : ...;io.dmem.isVal : ...; io.dmem.isWr : ...; ...
}
class Dpath extends Module { val io new DpathIo(); ...io.imem.raddr : ...; io.dmem.raddr : ...; io.dmem.wdata : ...;...
} 我们现在可以使用批量连接来连接CPU就像使用其他bundle一样
class Cpu extends Module {val io new CpuIo()val c Module(new CtlPath()) val d Module(new DatPath()) c.io.ctl d.io.ctlc.io.dat d.io.dat c.io.imem io.imemd.io.imem io.imemc.io.dmem io.dmemd.io.dmem io.dmemd.io.host io.host
}