[chisel-tutorial] Mul.scala:

YumeIroVillain·2023년 8월 8일
0

Chisel 독학

목록 보기
20/44
post-custom-banner

4*4 곱셈을, Multiplier가 아니라 Lookup Table을 256가지 모든 경우의 수에 대해 만들어서 구현하라는 문제이다.
Verilog로 하면 5분컷이지만, Chisel로 하니까 낯설었다.

테스트 코드

// See LICENSE.txt for license details.
package problems

import chisel3.iotesters.PeekPokeTester

class MulTests(c: Mul) extends PeekPokeTester(c) {
  val maxInt  = 1 << 4
  for (i <- 0 until 10) {
    val x = rnd.nextInt(maxInt)
    val y = rnd.nextInt(maxInt)
    poke(c.io.x, x)
    poke(c.io.y, y)
    step(1)
    expect(c.io.z, x * y)
  }
}
  • 간단하게, 랜덤변수 x,y를 받아 입력한 뒤, 한 사이클 뒤에 출력되는 z 출력을 Chisel 값과 비교하는 방식으로 구성되어 있다.

오답 1

일단 직관적으로 짜봤다.

// See LICENSE.txt for license details.
package problems

import chisel3._
import scala.collection.mutable.ArrayBuffer

// Problem:
//
// Implement a four-by-four multiplier using a look-up table.
//
class Mul extends Module {
  val io = IO(new Bundle {
    val x   = Input(UInt(4.W))
    val y   = Input(UInt(4.W))
    val z   = Output(UInt(8.W))
  })
  val mulsValues = new ArrayBuffer[UInt]()

  // Calculate io.z = io.x * io.y by generating a table of values for mulsValues
  for(x <- 0 until 1<<4-1){
    for(y<-0 until 1<<4-1){
      mulsValues(x,y) := x*y
    }
  }

  // Implement below ----------
  z := mulsValues(io.x,io.y)

  // Implement above ----------
}

틀린 부분이 보인다.
1. until은 등호없는 for이기에, 16-1이 아니라 16으로 해야한다.
2. 2차원 ArrayBuffer는 콤마로 접근해서 Write하는게 아니다. +=를 써서 Append한다.
뺄 때는 myList -= "Welcome"; 처럼, -=를 쓰면 된다.
즉, index로 접근하여 직접 건드는게 아니라, value로 넣고 value로 뺀다.
(어 그러면 연속성 깨지지 않나..? 왜 이렇게 구현했지)
3. mulsValues를 W할때 말고 R 할때도, (io.x, io.y) 로 인덱싱하여 접근하면 안된다. VecInit로 Scala형으로 바꾼 뒤에, 그것을 RHS에 둬서 assign해야한다.

오답 2

// See LICENSE.txt for license details.
package problems

import chisel3._
import scala.collection.mutable.ArrayBuffer

// Problem:
//
// Implement a four-by-four multiplier using a look-up table.
//
class Mul extends Module {
  val io = IO(new Bundle {
    val x   = Input(UInt(4.W))
    val y   = Input(UInt(4.W))
    val z   = Output(UInt(8.W))
  })
  val mulsValues = new ArrayBuffer[UInt]()

  // Calculate io.z = io.x * io.y by generating a table of values for mulsValues
  for(x <- 0 until 1<<4){
    for(y<-0 until 1<<4){
      // mulsValues(x,y) := x*y
      mulsValues += (x*y)
    }
  }

  val LUT = VecInit(mulsValues)
  // println(LUT.mkString(" "))
  // printf(cf"LUT = $LUT")

  // Implement below ----------
  // z := mulsValues(io.x,io.y)
  io.z := LUT((io.x << 4.U) | io.y)

  // Implement above ----------
}

  • x+y 부분에서 chisel3.UInt를 기대하는데, Int가 왔다는 형오류가 나왔다.

  • 여담) VecInit된, Chisel형을 담는 Vector를 print하기 위해서는 cf를 써야하는데, 왜 없다고 나오지? 2.12라서 Scala 버전이 부족하진 않을텐데, 올해나온거라.

cf custom string interpolation 출처

정답

// See LICENSE.txt for license details.
package problems

import chisel3._
import scala.collection.mutable.ArrayBuffer

// Problem:
//
// Implement a four-by-four multiplier using a look-up table.
//
class Mul extends Module {
  val io = IO(new Bundle {
    val x   = Input(UInt(4.W))
    val y   = Input(UInt(4.W))
    val z   = Output(UInt(8.W))
  })
  val mulsValues = new ArrayBuffer[UInt]()

  // Calculate io.z = io.x * io.y by generating a table of values for mulsValues
  for(x <- 0 until 1<<4){
    for(y<-0 until 1<<4){
      // mulsValues(x,y) := x*y
      mulsValues += (x*y).asUInt(8.W)
    }
  }

  val LUT = VecInit(mulsValues)
  // println(LUT.mkString(" "))
  // printf(cf"LUT = $LUT")

  // Implement below ----------
  // z := mulsValues(io.x,io.y)
  io.z := LUT((io.x << 4.U) | io.y)

  // Implement above ----------
}
  • mulsValues += (x*y).asUInt(8.W) 로, chisel3.UInt 형으로 바꿔주었다.
  • LUT의 width는 뭐야? 8비트인지, 아닌지 아예 모르지않나..?

  • Elaborate 결과, Tree형의 Mux가 아니라, Cascade형의 Mux가 나온다. 좋을게 하나도 없을텐데, 왜 그렇게 Elaborate 했지?

What I learned

  • Scala의 ArrayBuffer는 Vec과 다르다.
  1. ArrayBuffer가 훨씬 빠르다.
    (초당 Append가능한 횟수 - 출처)
    mutable.ArrayBuffer -- 125M
    immutable.Vector -- 8M
    즉, ArrayBuffer가 Vector보다 빠르다. 이것은 Vector가 내부적으로 트리형식으로 구성되어있기 때문이다.
  2. ArrayBuffer는 Size를 유연하게 조정할 수 있는 Array이다.
    Array는 초기 사이즈를 전부 활용할 일이 없어지더라도 크기를 줄이지 못한다.
    ArrauBuffer는 크기를 줄일 수 있다.
    반대로, 크기가 늘어날 수도 있다. store 하기 전에 항상 크기를 검사하고, 초과할 경우 새로운 larger array를 할당한 후 모든 원소를 복사하고 append를 수행한다 출처.
  3. ArrayBuffer는 Arrays에 속한다. 반면 Vector는 Lists에 속한다.
  4. Vector는 Tree형식이지만 Random 접근을 허용한다. 거대한 규모의 데이터에 사용된다(ArrayBuffer와 다르게, 연속된 주소를 요구하지 않는 것으로 추정된다.) 출처
  • for(i<-0 until 16)은 <= 16이 아니라, < 16을 의미한다.
  • ArrayBuffer에 원소 첨가는 +=을 통해 가능하다.
  • ArrayBuffer는 Chisel형이 아니라 Scala형이기 때문에, 그자체로는 elaborate할 수 없고 Chisel형으로 Casting을 거쳐줘야 한다.
  • VecInit을 통해 ArrayBuffer를 Chisel형으로 변환가능하다.
    출처
  • Chisel Cookbook - 읽어볼 것
  • LUT 만든다고 해도, Tree형이 아니라 Cascade 형이 될 수 있다.
  • 아무튼, ROM을 만들 일이 있다면 VecInit을 활용하자.출처
profile
HW SW 둘다 공부하는 혼종의 넋두리 블로그 / SKKU SSE 17 / SWM 11th
post-custom-banner

0개의 댓글