[chisel-tutorial] RealGCD.scala: DeqIO 및 bits, swap, Register reset 여부

YumeIroVillain·2023년 8월 8일
0

Chisel 독학

목록 보기
22/44
post-thumbnail

초장부터 좀 빡세보였다.

  1. io 선언부터 쉽지 않아보였다.
  val io  = IO(new Bundle {
    val in  = DeqIO(new RealGCDInput())
    val out = Output(Valid(UInt(16.W)))
  })

도대체 이게 뭐야;

  1. GCD는 유클리드 알고리즘을 써야 하는데, 이건 SW적으로 돌아가는 재귀문이잖아? 이걸 Verilog로 elaborate되어야 하는 chisel 언어로 작성하라고?

유클리드 호제법은 나눗셈으로도 구현할 수 있고,
뺄셈으로도 구현할 수 있다.(속도는 나눗셈이 훨씬 빠르다)
모듈러 연산시 문제가 발생할 수 있으니, 우선 뺄셈으로 하자.
원리는 대동소이하다.

Euclid GCD Algorithm 1

int gcd(int a, int b){
	int t;
    while(a!=0){
    	if(b > a){
        	`swap(a,b)
        }
        a = a-b;
    }
    return b;
}

풀이

Test Code

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

import chisel3.iotesters.PeekPokeTester

class RealGCDTests(c: RealGCD) extends PeekPokeTester(c) {
  val inputs = List( (48, 32), (7, 3), (100, 10) )
  val outputs = List( 16, 1, 10)

  var i = 0
  do {
    var transfer = false
    do {
      poke(c.io.in.bits.a, inputs(i)._1)
      poke(c.io.in.bits.b, inputs(i)._2)
      poke(c.io.in.valid,  1)
      transfer = peek(c.io.in.ready) == BigInt(1)
      step(1)
    } while (t < 100 && !transfer)

    do {
      poke(c.io.in.valid, 0)
      step(1)
    } while (t < 100 && (peek(c.io.out.valid) == BigInt(0)))

    expect(c.io.out.bits, outputs(i))
    i += 1
  } while (t < 100 && i < 3)
  if (t >= 100) fail
}

오답 1

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

import chisel3._
import chisel3.util.{Valid, DeqIO}

// Problem:
// Implement a GCD circuit (the greatest common divisor of two numbers).
// Input numbers are bundled as 'RealGCDInput' and communicated using the Decoupled handshake protocol
//
class RealGCDInput extends Bundle {
  val a = UInt(16.W)
  val b = UInt(16.W)
}

class RealGCD extends Module {
  val io  = IO(new Bundle {
    val in  = DeqIO(new RealGCDInput())
    val out = Output(Valid(UInt(16.W)))
  })

  // Implement below ----------
  def EuclidGCD(a:Int, b:Int):Int = {
    if(b == 0) {
      return a
    }
    else {
      return EuclidGCD(b, a%b)
    }
  }

  val aa = io.in.bits.a
  val bb = io.in.bits.b
  io.out := EuclidGCD(aa, bb).asUInt



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

}

  • C++에서 짜던 대로, 재귀문으로 짜려다가
  • 역시 문제에 봉착했다.
  • EuclidGCD 재귀함수의 인자로 들어오는 놈은 Int형인데,
  • peek() 함수로 chisel3.UInt 형을 Int로 casting하려면 PeekPokeTester를 import해야하는데, 이것은 바람직하지 않아보였다. simulation 용의 package를 elaborate용 source에 import한다? 무조건 문제가 발생할 거다.

엄...

  • Sequential하게 while문으로 짜야하나..?

오답 2

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

import chisel3._
import chisel3.util.{Valid, DeqIO}

// Problem:
// Implement a GCD circuit (the greatest common divisor of two numbers).
// Input numbers are bundled as 'RealGCDInput' and communicated using the Decoupled handshake protocol
//
class RealGCDInput extends Bundle {
  val a = UInt(16.W)
  val b = UInt(16.W)
}

class RealGCD extends Module {
  val io  = IO(new Bundle {
    val in  = DeqIO(new RealGCDInput())
    val out = Output(Valid(UInt(16.W)))
  })

  // Implement below ----------
  val a_reg = RegInit(0.U(16.W))
  val b_reg = RegInit(0.U(16.W))

  io.in.ready := true.B

  when (io.in.valid){
    a_reg := io.in.bits.a
    b_reg := io.in.bits.b
  } .elsewhen(a_reg === 0.U){ // return
    a_reg := a_reg
    b_reg := b_reg
  }.elsewhen(a_reg < b_reg) {
    a_reg := b_reg - a_reg
    b_reg := a_reg
  } .otherwise{
    a_reg := a_reg - b_reg
    b_reg := b_reg
  }

  io.out.bits := a_reg
  when(a_reg === 0.U){
    io.out.valid := true.B
  }.otherwise{
    io.out.valid := false.B
  }



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

}

  • Deterministic하게 1 Cycle이 걸리지 않는데, 무지성으로 input ready를 1로 tie 시켜놓은게 문제라고 보았다.
  • 그래서, working이라는 val을 둬서 현재 작업중인지의 여부를 물리적으로 유지하고, 그 반전값을 ready로 인가하도록 하였다.
  • a_reg == 0일때 리턴하면서, 출력값도 a_reg다.

정답

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

import chisel3._
import chisel3.util.{Valid, DeqIO}

// Problem:
// Implement a GCD circuit (the greatest common divisor of two numbers).
// Input numbers are bundled as 'RealGCDInput' and communicated using the Decoupled handshake protocol
//
class RealGCDInput extends Bundle {
  val a = UInt(16.W)
  val b = UInt(16.W)
}

class RealGCD extends Module {
  val io  = IO(new Bundle {
    val in  = DeqIO(new RealGCDInput())
    val out = Output(Valid(UInt(16.W)))
  })

  // Implement below ----------
  val a_reg = RegInit(0.U(16.W))
  val b_reg = RegInit(0.U(16.W))

  io.in.ready := true.B

  when (io.in.valid){
    a_reg := io.in.bits.a
    b_reg := io.in.bits.b
  } .elsewhen(a_reg === 0.U){ // return
    a_reg := a_reg
    b_reg := b_reg
  }.elsewhen(a_reg < b_reg) {
    a_reg := b_reg - a_reg
    b_reg := a_reg
  } .otherwise{
    a_reg := a_reg - b_reg
    b_reg := b_reg
  }

  io.out.bits := b_reg
  when(a_reg === 0.U){
    io.out.valid := true.B
  }.otherwise{
    io.out.valid := false.B
  }



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

}
  • 허탈하게도, 알고리즘 실수였다.
  • return b를 해야하는데, return a를 하도록 모듈을 짰으니...

모법답안

좀 더 깔끔하다.
다만, Verilog를 짜던 사람 입장에서, 하나의 val을 두 개의 when 문에서 write 접근한다는 점이 영-불편하긴 하다.

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

import chisel3._
import chisel3.util.{Valid, DeqIO}

// Problem:
// Implement a GCD circuit (the greatest common divisor of two numbers).
// Input numbers are bundled as 'RealGCDInput' and communicated using the Decoupled handshake protocol
//
class RealGCDInput extends Bundle {
  val a = UInt(16.W)
  val b = UInt(16.W)
}

class RealGCD extends Module {
  val io  = IO(new Bundle {
    val in  = DeqIO(new RealGCDInput())
    val out = Output(Valid(UInt(16.W)))
  })

  // Implement below ----------
  val a_reg = RegInit(0.U(16.W))
  val b_reg = RegInit(0.U(16.W))
  // val a_reg = Reg(UInt())
  // val b_reg = Reg(UInt())
  val working_reg = RegInit(false.B)

  io.in.ready := !working_reg

  when (io.in.valid && !working_reg){
    a_reg := io.in.bits.a
    b_reg := io.in.bits.b
    working_reg := true.B
  }
  
  when(working_reg){
    when(a_reg > b_reg) {
      a_reg := b_reg
      b_reg := a_reg
    } .otherwise{
      b_reg := b_reg - a_reg
    }
  }
  
  io.out.bits := a_reg
  io.out.valid := b_reg === 0.U && working_reg
  when(io.out.valid){
    working_reg := false.B
  }

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

}

What I learned

  • Chisel에서는 RegInit된 val이 두 개의 when 문에서 값을 변동시켜도 문제가 발생하지 않는다.
    ex)
  io.in.ready := !p

  when (io.in.valid && !p) {
    x := io.in.bits.a
    y := io.in.bits.b
    p := true.B
  } 

  when (p) {
    when (x > y)  { x := y; y := x } 
    .otherwise    { y := y - x }
  }
  • SWAP을 하고싶다면,
.elsewhen(a_reg === 0.U){ // return
    a_reg := a_reg
    b_reg := b_reg
    }

하면 .v에서 그렇게 된다.

  • DeqIO가 있고 Valid가 있다.
    각각의 경우, .bits.a 및 .bits.b 를 통해 Bundle 내부의 멤버변수를 접근할 수 있고
    또한 ready, valid 멤버변수 또한 존재한다.
  • RegInit시, 굳이 Init 할 필요가 없다면
val a_reg = RegInit(0.U(16.W))
val a_reg = Reg(UInt())

중 아무거나 선택해도 무방하다.
다만, 후자를 선택했을 때, BitWidth는 어떻게 설정되는지는 의문이 든다.
실제로는 둘 다 [15:0]의 width로 elaborate된다.
아, 차이가 있다!!!

a_reg = Reg(UInt()) 로 선언만 하였을 때의 결과


보다시피, a_reg 레지스터가 reset 변수의 영향을 받지 않도록 elaborate 된 것을 알 수 있다.

a_reg = RegInit(0.U(16.W)) 로 초기화까지 해 줬을 때의 결과


보다시피, Synchronous Reset의 영향을 받아 초기화되게 elab되었음을 확인할 수 있다.

profile
HW SW 둘다 공부하는 혼종의 넋두리 블로그 / SKKU SSE 17 / SWM 11th

0개의 댓글