资讯详情

Chisel教程 2.1:您的第一个Chisel模块

本节的目的

通过上一章的学习,你应该处理Scala我们已经知道了,我们可以开始看看如何实现硬件。Chisel全称嵌入Scala硬件结构语言(onstructingardwaren acalambeddedanguage)。这意味着它是Scala领域专用语言(DSL),允许您同时使用同一代码Scala和Chisel编程。理解什么代码Scala什么代码是Chisel以后再讨论这一点很重要。在这一节中,我们只需要Chisel看作是实现Verilog更好的方法。在这一节中,我们将看到一个完整的部分Chisel我们现在只需要大致掌握模块及其测试的一些要点,然后我们就会看到更多的例子。

val path = System.getProperty("user.dir")   "/source/load-ivy.sc" interp.load.module(ammonite.ops.Path(java.nio.file.FileSystems.getDefault().getPath(path)))

如上一章所述,下面的句子在Scala中导入Chisel:

import chisel3._ import chisel3.util._ import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}    import chisel3._  import chisel3.util._  import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}

第一个Chisel模块

本节将介绍实现一个简单的硬件模块、测试用例以及如何操作它。它可能包含很多你不理解的东西,不需要太担心细节,我们将继续回到这个例子来加强你所学到的东西。

**例子:模块** 类似于Verilog,我们可以在Chisel中定义模块(module)。下面的例子是一个名字Passthrough的ChiselModule,它有一个4比特输入,名称为in,还有一个4比特的输出out。输入该模块的组合电路in连接到输出out,所以outin驱动。

// Chisel代码:定义一个模块 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 }    defined class Passthrough

上面的代码包含了很多东西我们将详细解释代码的每一行:

class Passthrough extends Module {     
      

我们定义了一个叫做模块的模块PassthroughModule是一个Chisel所有的硬件模块都必须从内部建筑中继承。

val io = IO(...)

一个叫我们io所有输入输出端口都在常量中声明。这个常量的名称必须称为io,而且必须是类IO对象或实例。这一类需要实例化Bundle:IO(_instantiated_bundle_)

new Bundle {     val in = Input(...)     val out = Output(...) }

我们声明了一种新的硬件结构类型(Bundle),它包含信号inout,这两个信号分别有方向Input和Output。

UInt(4.W)

我们声明了一个信号的硬件类型,它是宽度为4的无符号整数。

io.out := io.in

在这里连接输入端口和输出端口io.in驱动io.out。注意:=操作符是一个操作符表示右边的信号驱动左边的信号,具有方向性。

硬件结构语言(HCL)巧妙之处在于,我们可以使用底层编程语言Scala作为脚本语言。例如,上述定义Chisel我们可以在模块后使用它Scala调用Chisel编译器来将Chisel的Passthrough描述转换成Verilog的Passthrough描述。这一步叫

// Scala代码:将Chisel设计展开成Verilog设计 //下面生成的代码比较复杂。如果你不明白,你不必太担心 println(getVerilog(new Passthrough))
[info] [0.002] Elaborating design... [info] [0.605] Done elaborating. Total FIRRTL Compile Time: 476.9 ms module cmd2HelperPassthrough(   input        clock,   input        reset,   input  [3:0] io_in,   output [3:0] io_out );   assign io_out = io_in; // @[cmd2.sc 6:10] endmodule

注意上面生成的Verilog模块叫做cmd<#>HelperPassthrough,这是因为在jupyter里面的关系。在正常环境为Passthrough。但这是一个重要的教训:尽管Chisel硬件模块或其他硬件组件的名称将尽可能保留,但不能保证总是这样。

**例:模块生成器(Generator)** 如果我们将Scala我们可以在这个例子中看到知识的应用Chisel模块是由Scala类别实现。就像其他任何东西一样Scala类一样,我们可以使用一些参数来构造Chisel模块。在这个例子中,我们定义了一个叫做PassthroughGenerator有一种类叫做width用于描述输入输出端口的宽度:

// 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 }  // 我们现在可以用它来生成不同宽度的模块 println(getVerilog(new PassthroughGenerator(10))) println(getVerilog(new PassthroughGenerator(20)))    [info] [0.000] Elaborating design... [info] [0.077] Done elaborating. Total FIRRTL Compile Time: 29.1 ms module cmd4HelperPassthroughGenerator(   input        clock,   input        reset,   input  [9:0] io_in,   output [9:0] io_out );   assign io_out = io_in; // @[cmd4.sc 6:10] endmodule  [info] [0.000] Elaborating design... [info] [0.005] Done elaborating. Total FIRRTL Compile Time: 27.8 ms module cmd4HelperPassthroughGenerator(   input         clock,   input         reset,   input  [19:0] io_in,   output [19:0] io_out );   assign io_out = io_in; // @[cmd4.sc 6:10] endmodule  defined class PassthroughGenerator

注意上面生成的Verilog会根据width参数值使用不同的输入/输出位宽。我们可以深入了解这是如何工作的,因为Chisel模块是普通的Scala所以我们可以使用Scala类的构造函数来参数化我们的设计。

这里的可以这样参数化的原因是因为我们使用了Scala,而不是Chisel。Chisel并没有别的API来使得我们可以参数化一个设计。然而我们却可以依赖Scala来使得参数化成为可能。

在这里,因为PassthroughGenerator并不只是用来描述一个设计,它其实描述了以width为参数的一系列设计,所以我们将Passthrough称为一个

测试您的硬件

没有测试,任何的硬件模块或生成器都不能称为一个完整的设计。本教程也会介绍Chisel内置的测试功能。下面的例子是一个Chisel测试,它将值传递给模块Passthrough的输入端口in,并检查输出端口out上是否能看到相同的值。

**例子:一个测试** 这里有一些高级的Scala语法。但是,除了pokeexpect语句之外,您不需要理解其他任何东西。您可以将其余代码视为简单的样板来编写这些简单的测试。

// Scala代码:调用驱动(Driver)来实例化 Passthrough 和 PeekPokeTester,并且运行测试。
// 如果不理解下面代码的话不要担心;这是非常复杂的Scala代码。
// 可以简单地把它想象成运行Chisel测试的样板
val testResult = Driver(() => new Passthrough()) {
  c => new PeekPokeTester(c) {
    poke(c.io.in, 0)     // 将输入设置成 0
    expect(c.io.out, 0)  // 检查输出是否为 0
    poke(c.io.in, 1)     // 将输入设置成 1
    expect(c.io.out, 1)  // 检查输出是否为 1
    poke(c.io.in, 2)     // 将输入设置成 2
    expect(c.io.out, 2)  // 检查输出是否为 2
  }
}
assert(testResult)   // Scala代码:如果testResult等于false的话,,这里会抛出异常
println("SUCCESS!!") // Scala代码:到这里,我们的测试已经通过了



[info] [0.000] Elaborating design...
[info] [0.068] Done elaborating.
Total FIRRTL Compile Time: 19.0 ms
Total FIRRTL Compile Time: 10.5 ms
End of dependency graph
Circuit state created
[info] [0.001] SEED 1561161438550
test cmd2HelperPassthrough Success: 3 tests passed in 5 cycles taking 0.017532 seconds
[info] [0.007] RAN 0 CYCLES PASSED
SUCCESS!!
testResult: Boolean = true

上面发生了什么?这个测试接受一个类型为Passthrough的模块,驱动它的输入,并且检查它的输出。我们调用poke来驱动输入,调用expect来检查输出。如果不使用expect来比较期望值的话(没有断言的话),也可以使用peek来读出输出值。

如果所有的expect语句都为真,上面的代码会返回真(见testResult)。

**练习:写一个您自己的测试** 写两个测试,一个用来测试位宽为10的PassthroughGenerator,另一个测试位宽为20的PassthroughGenerator。至少检查两个值:0和最大值。注意这里三个问号???在Scala中有特殊含义。直接运行下面的代码会有未被实现NotImplementedError的错误。将下面的???替换成您自己的代码:

val test10result = ???

val test20result = ???

assert((test10result == true) && (test20result == true))
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!

 

val test10result = Driver(() => new PassthroughGenerator(10)) {
  c => new PeekPokeTester(c) {
    poke(c.io.in, 0)
    expect(c.io.out, 0)
    poke(c.io.in, 1023)
    expect(c.io.out, 1023)
  }
}

val test20result = Driver(() => new PassthroughGenerator(20)) {
  c => new PeekPokeTester(c) {
    poke(c.io.in, 0)
    expect(c.io.out, 0)
    poke(c.io.in, 1048575)
    expect(c.io.out, 1048575)
  }
}

看一看生成的Verilog/FIRRTL

如果您无法理解Chisel描述的硬件并且习惯于阅读Verilog或FIRRTL(Chisel的中间语言相当于Verilog的可综合子集),那么您可以尝试看看所生成的Verilog。

以下是生成Verilog(之前已经看过),以及FIRRTL的例子:

// 查看生成的 Verilog 代码
println(getVerilog(new Passthrough))


[info] [0.000] Elaborating design...
[info] [0.004] Done elaborating.
Total FIRRTL Compile Time: 20.5 ms
module cmd2HelperPassthrough(
  input        clock,
  input        reset,
  input  [3:0] io_in,
  output [3:0] io_out
);
  assign io_out = io_in; // @[cmd2.sc 6:10]
endmodule
// 查看生成的 FIRRTL 代码
println(getFirrtl(new Passthrough))

[info] [0.000] Elaborating design...
[info] [0.070] Done elaborating.
;buildInfoPackage: chisel3, version: 3.2-SNAPSHOT, scalaVersion: 2.12.6, sbtVersion: 1.2.7
circuit cmd2HelperPassthrough : 
  module cmd2HelperPassthrough : 
    input clock : Clock
    input reset : UInt<1>
    output io : {flip in : UInt<4>, out : UInt<4>}
    
    io.out <= io.in @[cmd2.sc 6:10]

解释一下用“printf”来调试

并不总是最好的办法,但是通常可以作为调试的第一步。

因为Chisel生成器是用来生成硬件的程序,需要好好区分是在生成电路的时候打印还是在运行电路的时候打印。

下面是想要打印的三种常见的场景:

  • Chisel生成器在生成电路的时候打印
  • 在运行电路仿真的时候打印
  • 在测试的时候打印

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 的被解析的字符串有自己的语法
    printf(p"Print during simulation: IO is $io\n")

    println(s"Print during generation: Input is ${io.in}")
}


class PrintingModuleTester(c: PrintingModule) extends PeekPokeTester(c) {
    poke(c.io.in, 3)
    step(5) // 每次运行Chisel电路会打印
    
    println(s"Print during testing: Input is ${peek(c.io.in)}")
}
chisel3.iotesters.Driver( () => new PrintingModule ) { c => new PrintingModuleTester(c) }


[info] [0.000] Elaborating design...
Print during generation: Input is UInt<4>(IO in unelaborated cmd9$Helper$PrintingModule)
[info] [0.027] Done elaborating.
Total FIRRTL Compile Time: 30.7 ms
Total FIRRTL Compile Time: 18.8 ms
End of dependency graph
Circuit state created
[info] [0.000] SEED 1561161468619
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)
[info] [0.013] Print during testing: Input is 3
test cmd9HelperPrintingModule Success: 0 tests passed in 10 cycles taking 0.022628 seconds
[info] [0.014] RAN 5 CYCLES PASSED



defined class PrintingModule
defined class PrintingModuleTester
res9_2: Boolean = true

标签: 068连接器连接器poke

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台