<jupyter_start><jupyter_text>Module 3.5: Object Oriented Programming | 面向对象编程**Prev:上一小节： [Functional Programming | 函数式编程](3.4_functional_programming.ipynb)****Next:下一小节： [Types | 类型](3.6_types.ipynb)** Motivation | 动机Scala and Chisel are object-oriented programming languages, meaning code may be compartmentalized into objects.Scala, which is built on Java, inherits many of Java's object-oriented features.However, as we'll see below, there are some differences. Chisel's hardware modules are similar to Verilog's modules, in that they can be instantiated and wired up as single or multiple instances.Scala 以及 Chisel 都是面向对象编程语言，这意味着代码可以划分为对象。Scala 建立在 Java 之上，传承了很多 Java 的面向对象特性。然而，这也有一些区别。我们将在后面看到。Chisel的硬件模块在被声明、单个或多个连接方面，与 Verilog 的模块相似。 Setup | 设置<jupyter_code>val path = System.getProperty("user.dir") + "/source/load-ivy.sc"
interp.load.module(ammonite.ops.Path(java.nio.file.FileSystems.getDefault().getPath(path)))
import chisel3._
import chisel3.util._
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
import chisel3.experimental._<jupyter_output><empty_output><jupyter_text>--- Object Oriented Programming | 面向对象编程This section outlines how Scala implements the object-oriented programming paradigm. So far you have already seen classes, but Scala also has the following features: 这个小节简单描述了 Scala 是怎样实现面向对象编程的范例。虽然到目前为止，你已经见到了不少类，但是 Scala 也有如下的特指：- [Abstract classes | 抽象类](abstract)- [Traits | 特质](traits)- [Objects | 对象](objects)- [Companion Objects | 伴生对象](compobj)- [Case Classes | 案例类](caseclass) Abstract Classes | 抽象类Abstract classes are just like other programming language implementations. They can define many unimplemented values that subclasses must implement. Any object can only directly inherit from one parent abstract class. 抽象类与其他编程语言实现相同，他们可以定义很多还没有实现的值，但是它的子类需要实现。任何一个对象都只能直接地继承于一个父抽象类。**Example: Abstract Class | 举例：抽象类**<jupyter_code>abstract class MyAbstractClass {
  def myFunction(i: Int): Int
  val myValue: String
}
class ConcreteClass extends MyAbstractClass {
  def myFunction(i: Int): Int = i + 1
  val myValue = "Hello World!"
}
// Uncomment below to test! 
// 取消下面的注释再试试看！
// val abstractClass = new MyAbstractClass() // Illegal! Cannot instantiate an abstract class 
                                             // 非法！不能实例化一个抽象类
val concreteClass = new ConcreteClass()      // Legal! 合法！<jupyter_output><empty_output><jupyter_text>Traits | 特质Traits are very similar to abstract classes in that they can define unimplemented values. However, they differ in two ways: - a class can inherit from multiple traits - a trait cannot have constructor parameters 特质在定义未实现的值方面与抽象类很像。但是，他们在两方面有区别：- 一个类可以继承多个特质- 一个特质不能有构造变量**Example: Traits and Multiple Inheritance | 举例：特质和多继承**Traits are how Scala implements multiple inheritance, as shown in the example below. `MyClass` extends from both traits `HasFunction` and `HasValue`: 特质是在 Scala 中实现多继承的方法，就像下面的例子一样，`MyClass` 继承了 `HasFunction` 和 `HasValue` 两个特质<jupyter_code>trait HasFunction {
  def myFunction(i: Int): Int
}
trait HasValue {
  val myValue: String
  val myOtherValue = 100
}
class MyClass extends HasFunction with HasValue {
  override def myFunction(i: Int): Int = i + 1
  val myValue = "Hello World!"
}
// Uncomment below to test! 取消下面的注释再试试看！
// val myTraitFunction = new HasFunction() // Illegal! Cannot instantiate a trait 
                                           // 非法！不能实例化一个特质
// val myTraitValue = new HasValue()       // Illegal! Cannot instantiate a trait 
                                           //非法！不能实例化一个特质
val myClass = new MyClass()                // Legal! 合法！<jupyter_output><empty_output><jupyter_text>To inherit multiple traits, chain them like 如果要继承多个类，像下面这样把它们串起来```scalaclass MyClass extends HasTrait1 with HasTrait2 with HasTrait3 ...```In general, always use traits over abstract classes, unless you are certain you want to enforce the single-inheritance restriction of abstract classes. 总的来说，除非你确认你想要使用抽象类的单个继承约束，不然建议使用特质而不是抽象类。 Objects | 对象Scala has a language feature for these singleton classes, called objects. You cannot instantiate an object **(no need to call `new`)**; you can simply directly reference it. That makes them similar to Java static classes. 对于这些单独的类，Scala 有一个语言特性，叫做对象。你不能实例化一个对象 **不需要声明 `new` **；你可以简单地引用它。这就与 Java 的静态类很相似。**Example: Objects | 举例：对象**<jupyter_code>object MyObject {
  def hi: String = "Hello World!"
  def apply(msg: String) = msg
}
println(MyObject.hi)
println(MyObject("This message is important! 这个信息很重要！")) // equivalent to MyObject.apply(msg) 等同于 MyObject.apply(msg)<jupyter_output><empty_output><jupyter_text>Companion Objects | 伴生对象When a class and an object share the same name and defined in the same file, the object is called a **companion object**. When you use `new` before the class/object name, it will instantiate the class. If you don't use `new`, it will reference the object: 当一个类与一个对象名字相同并且在同一个文件里面定义时，对象被称作**伴生对象**。当你在这个类/对象的名字前使用 `new` 时，它将会实例化类。如果你不使用 `new`，那么这将会引用对象。**Example: Companion Object | 举例：伴生对象**<jupyter_code>object Lion {
    def roar(): Unit = println("I'M AN OBJECT!")
}
class Lion {
    def roar(): Unit = println("I'M A CLASS!")
}
new Lion().roar()
Lion.roar()<jupyter_output><empty_output><jupyter_text>Companion objects are usually used for the following reasons:   1. to contain constants related to the class  2. to execute code before/after the class constructor  3. to create multiple constructors for a class  In the example below, we will instantiate a number of instances of Animal. We want each animal to have a name, and to know its order within all instantiations. Finally, if no name is given, it should get a default name.   伴生对象经常由于下列的原因使用：  1. 包含类相关的常数  2. 在类构造体之前/之后执行一些代码  3. 为一个类创造多个构造体在下面这个例子中，我们将会实例化一些动物的实例。我们想要让每一个动物都有一个名字，并且知道它在所有实例化中的顺序。最后，如果它没有名字的话，那么它应该得到一个默认名字。<jupyter_code>object Animal {
    val defaultName = "Bigfoot"
    private var numberOfAnimals = 0
    def apply(name: String): Animal = {
        numberOfAnimals += 1
        new Animal(name, numberOfAnimals)
    }
    def apply(): Animal = apply(defaultName)
}
class Animal(name: String, order: Int) {
  def info: String = s"Hi my name is $name, and I'm $order in line!"
}

val bunny = Animal.apply("Hopper") // Calls the Animal factory method 调用 Animal 工厂方法
println(bunny.info)
val cat = Animal("Whiskers")       // Calls the Animal factory method 调用 Animal 工厂方法
println(cat.info)
val yeti = Animal()                // Calls the Animal factory method 调用 Animal 工厂方法
println(yeti.info)<jupyter_output><empty_output><jupyter_text>*What's happening here? *- Our **Animal companion object** defines a constant relevant to ```class Animal```: ```scalaval defaultName = "Bigfoot"```- It also defines a private mutable integer to keep track of the order of Animal instances: ```scala private var numberOfAnimals = 0```- It defines two **apply** methods, which are known as **factory methods** in that they return instances of the **class Animal**.     - The first creates an instance of Animal using only one argument, ```name```, and uses ```numberOfAnimals``` as well to call the Animal class constructor.    ```scala    def apply(name: String): Animal = {                numberOfAnimals += 1                new Animal(name, numberOfAnimals)    }    ```    - The second factory method requires no argument, and instead uses the default name to call the other apply method.     ```scala    def apply(): Animal = apply(defaultName)    ```- These factory methods can be called naively like this ```scalaval bunny = Animal.apply("Hopper")```which eliminates the need to use the new keyword, but the real magic is that the compiler assumes the apply method any time it sees parentheses applied to an instance or object: ```scalaval cat = Animal("Whiskers")```- Factory methods, usually provided via companion objects, allow alternative ways to express instance creations, provide additional tests for constructor parameters, conversions, and eliminate the need to use the keyword ```new```. Note that you must call the companion object's `apply` method for `numberOfAnimals` to be incremented. **Chisel uses many companion objects, like Module.** When you write the following: ```scalaval myModule = Module(new MyModule)```you are calling the **Module companion object**, so Chisel can run background code before and after instantiating ```MyModule```.*这发生了什么呢? *- 我们的 **Animal 伴生对象**定义了一个与 ```class Animal``` 有关的常量：```scalaval defaultName = "Bigfoot"```- 这也定义了一个私有的可变整数以得到实例化的顺序：```scala private var numberOfAnimals = 0```- 它定义了两个 **apply** 方法，也称为**工厂方法**因为他们返回 **class Animal** 的实例。        - 第一个创建了一个使用了一个参数的立即声明，```name```，并且使用 ```numberOfAnimals``` 调用 Animal 类构造器。    ```scala    def apply(name: String): Animal = {                numberOfAnimals += 1                new Animal(name, numberOfAnimals)    }```        - 第二个工厂方法不需要参数，而是使用默认的名字去调用另外一个`apply`方法。    ```scala    def apply(): Animal = apply(defaultName)    ```- 这些工厂方法可以被简单地调用：```scalaval bunny = Animal.apply("Hopper")```这会忽略使用 `new` 关键词的需要，但是真正的魔力是编译器每看到一个括号被用在实例或者对象的时候，就会去假定使用的是 `apply` 方法：```scalaval cat = Animal("Whiskers")```- 工厂方法，常常由伴生对象提供，允许多种方式表达实例声明，为构造变量、转变并且忽略使用```new``` 关键词的需要。注意你必须调用伴生对象的 `apply` 方法才能够使得 `numberOfAnimals` 增加。**Chisel 使用很多伴生对象，比如 Module。**当你写如下代码的时候：```scalaval myModule = Module(new MyModule)```你就在调用 **Module 伴生对象**，所以 Chisel 可以在实例之前或者之后允许一些后台代码。```MyModule```.  Case Classes | 案例类Case classes are a special type of Scala class that provides some cool additional features. They are very common in Scala programming, so this section outlines some of their useful features: - Allows **external access** to the **class parameters** - **Eliminates** the need to use **`new`** when instantiating the class - Automatically creates an **unapply method** that supplies access to all of the class Parameters. - Cannot be subclassed from In the following example, we declare three different classes, `Nail`, `Screw`, and `Staple`. 案列类是 Scala 类中一个特殊的类，它提供了一下很酷的附加特性。他们在 Scala 编程中使用很多，所以这个小节简单描述了一些他们很有用的特性。- 允许 **外部获取类变量**- 当实例化类的时候可以**忽略**使用关键词 **`new`**- 自动生成一个 **unapply method** 以支持获取所有的类参数- 不能成为父类在下面这个例子中，我们声明了三种不同的类，`Nail`， `Screw`， 以及 `Staple`<jupyter_code>class Nail(length: Int) // Regular class 普通类
val nail = new Nail(10) // Requires the `new` keyword 需要关键词 `new`
// println(nail.length) // Illegal! Class constructor parameters are not by default externally visible 
                        // 非法！类的构造变量默认是不可以被外面获取的。

class Screw(val threadSpace: Int) // By using the `val` keyword, threadSpace is now externally visible 
                                  //通过使用关键词 `val`，threadSpace 现在就是对外可见的了
val screw = new Screw(2)          // Requires the `new` keyword 需要关键词 `new`
println(screw.threadSpace)

case class Staple(isClosed: Boolean) // Case class constructor parameters are, by default, externally visible 
                                     // 案列类的构造参数就是默认对外可见的
val staple = Staple(false)           // No `new` keyword required 不需要关键词 `new` 
println(staple.isClosed)<jupyter_output><empty_output><jupyter_text>`Nail` is a regular class, and its parameters are not externally visible because we did not use the `val` keyword in the argument list. It also requires the `new` keyword when declaring an instance of `Nail`.   `Screw` is declared similarly to `Nail`, but includes `val` in the argument list. This allows its parameter, `threadSpace`, to be visible externally.  By using a case class, `Staple` gets the benefit of all its parameters being externally visible (without needing the `val` keyword). In addition, `Staple` does not require using `new` when declaring a case class. This is because the Scala compiler automatically creates a companion object for every case class in your code, which contains an apply method for the case class. Case classes are nice containers for generators with lots of parameters. The constructor gives you a good place to define derived parameters and validate input. `Nail`是一个普通的类，它的变量不能被外面获取，因为外面没有在参数列表中使用关键词 `val`。当声明实例 `Nail`的时候，它也需要关键词  `new` `Screw` 的声明与 `Nail` 类似，但是包括了一个关键词 `val`。这使得它的参数 `threadSpace` 是对外可见的。通过使用一个案例类，`Staple` 的所有参数都可以通过外部获取到 ( 而不需要使用关键词 `val` )除此以外，在你每个案例类中实例化伴生对象的时候，`Staple` 也不需要使用关键词 `new`，并且案例类也包含了一个 `apply` 方法。案例类对带有很多参数的生成器非常友好。你可以在构造体内定义衍生参数和有效的输入。<jupyter_code>case class SomeGeneratorParameters(
    someWidth: Int,
    someOtherWidth: Int = 10,
    pipelineMe: Boolean = false
) {
    require(someWidth >= 0)
    require(someOtherWidth >= 0)
    val totalWidth = someWidth + someOtherWidth
}<jupyter_output><empty_output><jupyter_text>--- Inheritance with Chisel | 从 Chisel 继承You've seen `Module`s and `Bundle`s before, but it's important to realize what's really going on. Every Chisel module you make is a class extending the base type `Module`. Every Chisel IO you make is a class extending the base type `Bundle` (or, in some special cases, `Bundle`'s supertype [`Record`](https://github.com/freechipsproject/chisel3/blob/v3.0.0/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scalaL415)). Chisel hardware types like `UInt` or `Bundle` all have `Data` as a supertype. We'll explore using object oriented programming to create hierarchical hardware blocks and explore object reuse. You'll learn more about types and `Data` in the next Module on type generic generators. 你已经在之前看到过 `Module` 和 `Bundle` 了，但是意识到真正发生了什么是很重要的。你声明的每一个 Chisel 模块都是一个继承自基础类型 `Module` 的类。每一个你声明的 IO 都是一个继承自基础类型 `Bundle` 的类 (或者有时候是一个超级类型 [`Record`](https://github.com/freechipsproject/chisel3/blob/v3.0.0/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scalaL415))硬件类型比如 `UInt` 或者 `Bundle` 都有一个 `Data` 的超级类型。我们将探索使用面向对象编程去创造一个分级的硬件模块并且探索对象重复使用。你将在下一个单元的类型通用生成器中学到更多类型和 `Data`。 Module | 模块名Whenever you want to create a hardware object in Chisel, it needs to have `Module` as a superclass. Inheritance might not always be the right tool for reuse ([composition over inheritance](https://en.wikipedia.org/wiki/Composition_over_inheritance) is a common principle), but inheritance is still a powerful tool. Below is an example of creating a `Module` and connecting multiple instantiations of them together hierarchically. 不论什么时候你想在 Chisel 中创造一个硬件对象，这个对象都需要把 `Module` 当作超级类。继承可能并不总是正确复用的工具 ([超越继承的组成](https://en.wikipedia.org/wiki/Composition_over_inheritance) 但是它确实是很有效的一个工具。下面是一个创造 `Module` 并且分级地连接多个实例。 **Example: Gray Encoder and Decoder | 举例：格雷码编码、解码器**We'll create a hardware Gray encoder/decoder. The encode or decode operation choice is hardware programmable. 我们将创造一个硬件格雷码编码、解码器。编码或者解码操作是硬件可控的。<jupyter_code>class NoGlitchCounterIO(bitwidth: Int) extends Bundle {
    val en  = Input(Bool())
    val out = Output(UInt(bitwidth.W))
}

abstract class NoGlitchCounter(val maxCount: Int) extends Module {
    val bitwidth: Int
    val io = IO(new NoGlitchCounterIO(bitwidth))
}

abstract class AsyncFIFO(depth: Int) extends Module {
    val io = IO(new Bundle{
         // write inputs 写输入
         val write_clock = Input(Clock())
         val write_enable = Input(Bool())
         val write_data = Input(UInt(32.W))

         // read inputs/outputs 读输入/输出
         val read_clock = Input(Clock())
         val read_enable = Input(Bool())
         val read_data = Output(UInt(32.W))

         // FIFO status      FIFO 的状态
         val full = Output(Bool())
         val empty = Output(Bool())
    })
    
    def makeCounter(maxCount: Int): NoGlitchCounter

    // add extra bit to counter to check for fully/empty status 为计数器添加一个额外的位来检查满/空状态
    assert(isPow2(depth), "AsyncFIFO needs a power-of-two depth!")
    val write_counter = withClock(io.write_clock) {
        val count = makeCounter(depth * 2)
        count.io.en := io.write_enable && !io.full
        count.io.out
    }
    val read_counter = withClock(io.read_clock) {
        val count = makeCounter(depth * 2)
        count.io.en := io.read_enable && !io.empty
        count.io.out
    }

    // synchronize 同步
    val sync = withClock(io.read_clock) { ShiftRegister(write_counter, 2) }

    // status logic goes here
}
class GrayCounter(val bitwidth: Int) extends NoGlitchCounter(bitwidth) {
    // todo
}

class RingCounter(maxCount: Int) extends NoGlitchCounter(maxCount) {
    // todo
}
import scala.math.pow

// create a module 创造一个单元
class GrayCoder(bitwidth: Int) extends Module {
  val io = IO(new Bundle{
    val in = Input(UInt(bitwidth.W))
    val out = Output(UInt(bitwidth.W))
    val encode = Input(Bool()) // decode on false 如果是 false 那就解码
  })
  
  when (io.encode) { //encode 编码
    io.out := io.in ^ (io.in >> 1.U)
  } .otherwise { // decode, much more complicated 解码要稍微复杂一点
    io.out := Seq.fill(log2Ceil(bitwidth))(Wire(UInt(bitwidth.W))).zipWithIndex.fold((io.in, 0)){
      case ((w1: UInt, i1: Int), (w2: UInt, i2: Int)) => {
        w2 := w1 ^ (w1 >> pow(2, log2Ceil(bitwidth)-i2-1).toInt.U)
        (w2, i1)
      }
    }._1
  }
}<jupyter_output><empty_output><jupyter_text>Give it a whirl!<jupyter_code>// test our gray coder 测试我们的格雷码编码器
val bitwidth = 4
Driver(() => new GrayCoder(bitwidth)) {
  c => new PeekPokeTester(c) {
  
    def toBinary(i: Int, digits: Int = 8) =
      String.format("%" + digits + "s", i.toBinaryString).replace(' ', '0')

    println("Encoding:")
    for (i <- 0 until pow(2, bitwidth).toInt) {
      poke(c.io.in, i)
      poke(c.io.encode, true)
      step(1)
      println(s"In = ${toBinary(i, bitwidth)}, Out = ${toBinary(peek(c.io.out).toInt, bitwidth)}")
    }
    
    println("Decoding:")
    for (i <- 0 until pow(2, bitwidth).toInt) {
      poke(c.io.in, i)
      poke(c.io.encode, false)
      step(1)
      println(s"In = ${toBinary(i, bitwidth)}, Out = ${toBinary(peek(c.io.out).toInt, bitwidth)}")
    }
  }
}<jupyter_output><empty_output><jupyter_text>Gray codes are often used in asynchronous interfaces. Usually Gray counters are used rather than fully-featured encoders/decoders, but we'll the above module to simplify things. Below is an example AsyncFIFO, built using the above Gray coder. The control logic and tester is left as an exercise for later on. For now, look at how the Gray coder is instantiated multiple times and connected. 格雷码通常被用于异步接口。通常会使用格雷码计数器而不是功能齐全的编码器、解码器，但是上面的单元是一个简化模型。下面是一个异步 FIFO 的例子，使用了上述的格雷码编码器。控制逻辑以及测试被留作后面的练习。现在，我们来看看格雷码编码器是怎么样被多次实例化和连接的。<jupyter_code>class AsyncFIFO(depth: Int = 16) extends Module {
  val io = IO(new Bundle{
    // write inputs 写输入
    val write_clock = Input(Clock())
    val write_enable = Input(Bool())
    val write_data = Input(UInt(32.W))
    
    // read inputs/outputs 读输入/输出
    val read_clock = Input(Clock())
    val read_enable = Input(Bool())
    val read_data = Output(UInt(32.W))
    
    // FIFO status      FIFO 的状态
    val full = Output(Bool())
    val empty = Output(Bool())
  })
  
  // add extra bit to counter to check for fully/empty status 为计数器添加一个额外的位来检查满/空状态
  assert(isPow2(depth), "AsyncFIFO needs a power-of-two depth!")
  val write_counter = withClock(io.write_clock) { Counter(io.write_enable && !io.full, depth*2)._1 }
  val read_counter = withClock(io.read_clock) { Counter(io.read_enable && !io.empty, depth*2)._1 }
  
  // encode 编码
  val encoder = new GrayCoder(write_counter.getWidth)
  encoder.io.in := write_counter
  encoder.io.encode := true.B
  
  // synchronize 同步
  val sync = withClock(io.read_clock) { ShiftRegister(encoder.io.out, 2) }
  
  // decode 解码
  val decoder = new GrayCoder(read_counter.getWidth)
  decoder.io.in := sync
  decoder.io.encode := false.B
  
  // status logic goes here 下面是状态逻辑
  
}<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10351030477463123, 'fraction_numerical': 0.005088143084750989, 'mean_word_length': 4.2674607471575525, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 54, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '162783', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 7366, 'n_tokens_neox': 7419, 'n_words': 1988, 'path': '3.5_object_oriented_programming.ipynb', 'repo_name': 'SingularityKChen/chisel-bootcamp', 'size': '19457'}