[CSE228A] Lecture 7 - Decoupling

YumeIroVillain·2023년 8월 11일
0

Chisel 독학

목록 보기
36/44

Lecture 7 - Decoupling

강의 전의 의문

  • Valid, Decouple이 있더라도 결국 단순히 interface처럼, 선언 시의 편의만 제공하고 구체적인 Valid high, ready high시의 동작은 일일히 기술해야한다는 점은 똑같지 않을까?(잘 모름)

Scala Case Classes

  • Scala가 지원하는 특별한 Class, 몇몇 기능을 지원한다.
  1. Companion Object이다. 즉, new가 없어도 instance화가 가능하다.
  2. 모든 Parameter가 기본적으로 public하게 지정된다. 즉, val 할 필요가 없다는 것이다.
    이전 예제에서, argument에 val 붙이면 외부에서 참조가 되고, 안붙이면 로컬에서만 돌고 삭제된다는 얘기를 한 적이 있는데 그렇게 되지 않는다는 얘기이다.
  3. toString, equals, copy를 자동적으로 구현해준다.
  4. 나중에 공부할 것이라고 하는데, pattern matching에 유리하다고 한다.
case class Movie(name: String, year: Int, genre: String) {
    def decade(): String = (year - year%10) + "s"
}

val m1 = Movie("Gattaca", 1997, "drama")
m1.genre
val m2 = Movie("The Avengers", 1998, "action")
m2.copy(year=2012)
m2.decade

출력
defined class Movie
m1: Movie = Movie("Gattaca", 1997, "drama")
res2_2: String = "drama"
m2: Movie = Movie("The Avengers", 1998, "action")
res2_4: Movie = Movie("The Avengers", 2012, "action")
res2_5: String = "1990s"

Ready-Valid Protocol

  • 3개의 field가 있다.
    • valid
    • ready
    • bits
  • Transfer는 ready&valid가 동일한 cycle에 high일때만 그 직후 clock posedge에서 일어난다.
  • Chisel은 내부적으로 Ready-Valid Handshake를 지원한다.

Valid

  • 오로지 Valid 신호만 있음.
  • Receiver는 sent된 신호를 무조건 consume해야 함.
  • 단순히, Data가 유효한지를 나타냄. Scala의 Option과 유사.

Decouple

  • Consumer가 Backpressure를 인가할 수 있음
    • backpressure: Backpressure is when the progress of turning that input to output is resisted in some way. In most cases that resistance is computational speed...출처
  • Combinational loop에 유의해야 한다. Ready-Valid 신호를 combinationally하게 제작하는 것은 버그를 유발할 가능성이 있다.

Ready-Valid Combinational Loop (해서는 안되는) 예제

class LoopyCounter(w: Int) extends Module {
    val io = IO(new Bundle {
        val count = Output(UInt(w.W))
    })
    io.count := io.count + 1.U
//     io.count := RegNext(io.count + 1.U)
}
println(getVerilog(new LoopyCounter(4)))

class MakeValid(w: Int) extends Module {
    val io = IO(new Bundle {
        val en  = Input(Bool())
        val in  = Input(UInt(w.W))
        val out = Valid(UInt(w.W))
    })
    io.out.valid := io.en
    io.out.bits := io.in
}

println(getVerilog(new MakeValid(4)))


실제로, Chisel은 Combinational Loop가 존재한다며 오류를 뱉는다.


이런 꼴의 루프가 있다면,
가능한 한 최대한의 속도로 무한히 1이 더해질 것이다.
전력낭비에 불과하고, 의미가 없다.
그래서, Chisel Compiler가 거절한다.

Valid 예제

MakeValid Module

class MakeValid(w: Int) extends Module {
    val io = IO(new Bundle {
        val en  = Input(Bool())
        val in  = Input(UInt(w.W))
        val out = Valid(UInt(w.W))
    })
    io.out.valid := io.en
    io.out.bits := io.in
}

println(getVerilog(new MakeValid(4)))

MakeValid test

class ValidReceiver(w: Int) extends Module {
    val io = IO(new Bundle {
        val in = Flipped(Valid(UInt(w.W)))
    })
    when (io.in.valid) {
        printf("  received %d\n", io.in.bits)
    }
}

// println(getVerilog(new ValidReceiver(4)))
test(new ValidReceiver(4)) { c =>
    for (cycle <- 0 until 8) {
        c.io.in.bits.poke(cycle.U)
        println(s"cycle: $cycle")
        c.io.in.valid.poke((cycle%2 == 0).B)
        c.clock.step()
    }
}
  • Tx를 테스트하려면, Rx와 testbench가 필요하다.
  • Rx는 단순히 Valid를 Flipped하여 선언할 수 있으며,
  • 동작은..in.valid가 HIGH이면 그저 bits를 출력하도록 하면 된다.
  • 짝수 사이클이 될 때마다 valid를 인가한다.

Decoupled의 Helper Function

Decoupled.enq(data)

  • Set Valid to TRUE && send DATA(don't check READY)

Decoupled.noenq

  • Set Valid to FALSE

Decoupled.deq

  • enq와 비슷하나, Rx용

Decoupled.nodeq

  • noenq와 비슷하나, Rx용

Decoupled.fire

  • Ready && Valid 모두 HIGH 일 때 TRUE
class CountWhenReady(maxVal: Int) extends Module {
    val io = IO(new Bundle {
        val en  = Input(Bool())
        val out = Decoupled(UInt())
    })
    val (count, wrap) = Counter(io.out.fire, maxVal)
    when (io.en) {
        io.out.enq(count)
//         io.out.bits := count
//         io.out.valid := true.B
    } .otherwise {
        io.out.noenq()
//         io.out.bits := DontCare
//         io.out.valid := false.B
    }
}

// println(getVerilog(new CountWhenReady(3)))

test(new CountWhenReady(3)) { c =>
    c.io.en.poke(true.B)
    for (cycle <- 0 until 7) {
        c.io.out.ready.poke((cycle%2 == 1).B)
        println(s"cycle: $cycle, count: ${c.io.out.bits.peek()}")
        c.clock.step()
    }
}

Using Queues to Handle Backpressure

  • 앞뒷단의 속도차가 나면, Buffer 역할의 FIFO가 필요하다.
  • 사실, Chisel은 Queue Generator를 제공한다.

Chisel's Queue Helper Function

  • 선언법: Queue(UInt(4.W), 8) 처럼, 자료형과 entry 수를 기입
  • pipe: full이면, enqueue와 dequeue를 동시에 할 수 있도록 구현함
  • empty: empty이면, queueing하지말고 즉시 dequeue를 할 수 있게 구현함(레이턴시이득)
class CountIntoQueue(maxVal: Int, numEntries: Int, pipe: Boolean, flow: Boolean) extends Module {
    val io = IO(new Bundle {
        val en  = Input(Bool())
        val out = Decoupled(UInt())
        val count = Output(UInt())
    })
    val q = Module(new Queue(UInt(), numEntries, pipe=pipe, flow=flow))
    val (count, wrap) = Counter(q.io.enq.fire, maxVal)
    q.io.enq.valid := io.en
    q.io.enq.bits := count
    io.out <> q.io.deq
    io.count := count // for visibility
}

// println(getVerilog(new CountIntoQueue(3,1,false,false)))
  • Queue 인스턴스화 시, numEntries 및 pipe, flow 정보가 들어가는 것을 확인할 수 있다.
  • val out은 단순한 output이 아니라 decopled이므로, q.io.deq와 bulk connect 된다.

Queue 테스트코드

test(new CountIntoQueue(4,3,pipe=false,flow=false)) { c =>
    c.io.en.poke(true.B)
    c.io.out.ready.poke(false.B)
    for (cycle <- 0 until 4) {   // Fill up queue
        println(s"f count:${c.io.count.peek()} out:${c.io.out.bits.peek()} v:${c.io.out.valid.peek()}")
        c.clock.step()
    }
    println()
    c.io.en.poke(false.B)
    c.io.out.ready.poke(true.B)
    for (cycle <- 0 until 4) {   // Drain queue
        println(s"d count:${c.io.count.peek()} out:${c.io.out.bits.peek()} v:${c.io.out.valid.peek()}")
        c.clock.step()
    }
    println()
    c.io.en.poke(true.B)
    for (cycle <- 0 until 4) {   // Simultaneous
        println(s"s count:${c.io.count.peek()} out:${c.io.out.bits.peek()} v:${c.io.out.valid.peek()}")
        c.clock.step()
    }
}
f count:UInt<1>(0) out:UInt<1>(0) v:Bool(false)
f count:UInt<1>(1) out:UInt<1>(0) v:Bool(true)
f count:UInt<2>(2) out:UInt<1>(0) v:Bool(true)
f count:UInt<2>(3) out:UInt<1>(0) v:Bool(true)

d count:UInt<2>(3) out:UInt<1>(0) v:Bool(true)
d count:UInt<2>(3) out:UInt<1>(1) v:Bool(true)
d count:UInt<2>(3) out:UInt<2>(2) v:Bool(true)
d count:UInt<2>(3) out:UInt<1>(0) v:Bool(false)

s count:UInt<2>(3) out:UInt<1>(0) v:Bool(false)
s count:UInt<1>(0) out:UInt<2>(3) v:Bool(true)
s count:UInt<1>(1) out:UInt<1>(0) v:Bool(true)
s count:UInt<2>(2) out:UInt<1>(1) v:Bool(true)
test CountIntoQueue Success: 0 tests passed in 14 cycles in 0.017046 seconds 821.30 Hz

fill과 drain이 제대로 동작하는 것을 출력을 통해 확인할 수 있다.

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

0개의 댓글