상태 레지스터를 RegInit(0.U(2.W))처럼 숫자로 만들면 한 파일은 3.U를, 다른 파일은
2.U를 "완료" 상태로 써도 아무 오류가 나지 않습니다. 숫자에는 이름도 타입도 없기
때문입니다. Chisel의 ChiselEnum은 값마다 이름을 붙이고, 인코딩과 폭을 정하고, 값의
집합을 독립된 하드웨어 타입으로 만듭니다. 이 장은 공식 Chisel 문서 Explanations의
"Enumerations"와, 열거형 값으로 분기할 때 쓰는 switch/is를 다룹니다.
먼저 알아 두면 좋은 것.
Mux·when·Wire 장의
when/.otherwise와 Wire,
레지스터와 step 장의
RegInit과 레지스터가 클록 에지에서 갱신되는 규칙을 사용합니다. 포트와 Bundle은
Bundle과 Vec 장에서 배운 방식을 그대로
사용합니다.
열거형은 두 단계에 걸쳐 존재합니다. 선언은 elaboration 중에 한 번 실행되는 Scala
코드이며, 이때 이름과 번호와 폭이 모두 확정됩니다. 열거형 값은 하드웨어입니다. 열거형
타입의 레지스터는 같은 폭의 일반 레지스터이고, 시뮬레이터는 비트만 다룹니다.
1. 열거형 선언
1.1 ChiselEnum과 Value
열거형은 ChiselEnum을 상속하는 Scala object로 선언합니다. object 안에서 Value를
한 번 호출할 때마다 이름 있는 값이 하나 생깁니다.
형식
object 열거형_이름 extends ChiselEnum {
val 이름1, 이름2, 이름3 = Value
}import chisel3._
object Color extends ChiselEnum {
val red, green, blue = Value
}| 코드 | 의미 |
|---|---|
object Color extends ChiselEnum | 열거형 Color를 선언합니다. chisel3.util을 들여오지 않아도 됩니다 |
val red, green, blue = Value | 값 세 개를 선언합니다. 적은 순서대로 0, 1, 2번이 됩니다 |
Color.red | 값이 0인 Color.Type 하드웨어 리터럴입니다 |
Color.all | 모든 값을 번호 순서로 담은 Scala Seq입니다 |
Color.getWidth | Chisel이 정한 폭입니다. 여기서는 2입니다 |
1.2 번호와 폭이 정해지는 규칙
인자 없는 Value는 0부터 1씩 증가하는 번호를 받습니다. 폭은 가장 큰 번호를 담을 수
있는 최소 비트 수이며, 사용자가 직접 지정하지 않습니다.
| 선언한 값의 수 | 가장 큰 번호 | 폭 |
|---|---|---|
| 2 | 1 | 1비트 |
| 3 또는 4 | 2 또는 3 | 2비트 |
| 5부터 8 | 4부터 7 | 3비트 |
참고. 상태가 네 개인 FSM에 다섯 번째 상태를 추가하면 그 열거형 타입을 쓰는 모든 레지스터와 포트가 2비트에서 3비트로 넓어집니다. 기존 값의 번호는 그대로이므로 이름으로 값을 쓰는 코드는 고칠 필요가 없습니다.
1.3 인코딩 명시하기
명령어 opcode나 프로토콜 필드처럼 비트 패턴이 외부에서 정해진 경우에는 Value에 번호를
직접 전달합니다.
형식
val 이름 = Value(번호.U)object Cmd extends ChiselEnum {
val nop = Value(0.U)
val read = Value(4.U)
val write = Value // 4 다음이므로 5
}| 규칙 | 위 코드에서 |
|---|---|
| 명시한 번호는 선언 순서대로 엄격히 증가해야 합니다 | 0 다음에 4 |
명시한 값 뒤의 인자 없는 Value는 직전 번호에 1을 더합니다 | write는 5 |
| 폭은 여전히 가장 큰 번호에 맞춰집니다 | 5를 담으려면 3비트 |
| 선언한 번호 사이의 수는 열거형 값이 아닙니다 | 1, 2, 3, 6, 7은 Cmd 값이 아닙니다 |
주의. 명시한 번호의 순서가 틀려도 컴파일 오류는 나지 않습니다. object를 처음
사용하는 순간 java.lang.ExceptionInInitializerError가 발생하며, 원인으로
Enums must be strictly increasing: Cmd가 출력됩니다.
2. 하드웨어에서 열거형 사용하기
2.1 포트, wire, 레지스터의 타입
괄호를 붙인 Color()는 UInt(2.W)와 같은 Chisel 타입입니다. Chisel 타입을 받는 곳이면
어디에나 쓸 수 있습니다. 괄호 없는 Color.red는 0.U 같은 값입니다.
형식
Input(열거형_이름()) // 열거형 타입의 포트
Wire(열거형_이름()) // 열거형 타입의 wire
RegInit(열거형_이름.값) // 한 값으로 초기화하는 레지스터class Light extends Module {
val io = IO(new Bundle {
val color = Output(Color())
})
val state = RegInit(Color.red)
io.color := state
}| 표기 | 정체 | 비슷한 것 |
|---|---|---|
Color() | 2비트 Chisel 타입 | UInt(2.W) |
Color.red | 하드웨어 리터럴 | 0.U(2.W) |
Color.Type | 모든 Color 값의 Scala 타입. 함수 매개변수에 씁니다 | Scala 타입으로서의 UInt |
Bundle 필드도 같습니다. Bundle 안에 val color = Color()라고 쓰면 Color 타입의
2비트 필드가 생깁니다.
2.2 열거형 값 비교하기
===와 =/=는 같은 열거형의 두 값을 비교해 Bool을 반환합니다. isOneOf는 값이 목록
가운데 하나인지 검사합니다.
형식
x === 열거형_이름.값
x =/= 열거형_이름.값
x.isOneOf(열거형_이름.값1, 열거형_이름.값2)val stop = state === Color.red
val ready = state.isOneOf(Color.green, Color.blue)비교 대상은 반드시 같은 열거형이어야 합니다. state === 2.U처럼 UInt와 비교하면
Scala 컴파일 단계에서 type mismatch; found: chisel3.UInt, required: chisel3.EnumType
오류가 발생합니다. 지금은 2번이 blue라 해도 번호는 상태를 추가할 때 바뀔 수 있으므로,
타입 검사가 숫자와의 비교를 막습니다.
2.3 switch와 is로 분기하기
switch와 is는 chisel3.util에 있습니다. 신호 하나를 상수 값들과 비교하고 일치하는
is의 블록을 실행합니다. 조건이 모두 x === 상수 형태인 when/.elsewhen 연쇄를
짧게 쓴 것과 같습니다.
형식
import chisel3.util._
switch(신호) {
is(값1) { ... }
is(값2, 값3) { ... } // 여러 값이 한 분기를 공유
}val next = WireDefault(Color.red)
switch(state) {
is(Color.red) { next := Color.green }
is(Color.green) { next := Color.blue }
}| 구성 요소 | 의미 |
|---|---|
switch(state) | 모든 is가 비교할 신호입니다 |
is(Color.red) { ... } | state === Color.red일 때 실행됩니다 |
is(a, b) { ... } | 신호가 나열한 값 가운데 하나와 같을 때 실행됩니다 |
일치하는 is가 없을 때 | switch 안의 어떤 대입도 실행되지 않습니다. 레지스터는 값을 유지하고 wire는 기본값을 유지합니다 |
위 코드에는 blue 분기가 없으므로 state가 blue이면 next는 WireDefault의
기본값 Color.red가 됩니다.
주의. 기본값 없는 Wire를 일부 값만 처리하는 switch에서 대입하면 오류가
발생합니다. Wire(Color())를 is(Color.red) 안에서만 대입하면 시뮬레이션 전에
Reference next is not fully initialized.로 중단됩니다. wire는 WireDefault로
기본값을 주거나 switch 앞에서 먼저 대입하세요. 레지스터는 대입하는 분기가 없으면 값을
유지하므로 같은 오류가 나지 않습니다.
참고. 이전 유틸리티 chisel3.util.Enum(n)은 일반 UInt 리터럴의 목록을
반환합니다. val sIdle :: sRun :: Nil = Enum(2)는 1비트 값 0과 1을 만듭니다. 이름은
있지만 별도의 타입이 없으므로 아무 UInt와 비교해도 컴파일됩니다. 새 코드에서는
ChiselEnum을 쓰세요.
2.4 예제: 타이머 상태 기계
Timer.scala는 Chisel에서 FSM을 작성하는 전형적인 형태입니다. 다음 순서로 읽으세요.
object State는 번호를 명시하지 않은 상태 세 개를 선언합니다.state는sIdle로 초기화되는 레지스터이고,left는 2비트 하강 카운터입니다.switch에는 상태마다is가 하나씩 있습니다.- 출력
done은state와State.sDone의 비교 결과입니다.
| 상태 | 동작 |
|---|---|
sIdle | start가 1이면 sCount로 전이하고 left에 2를 적재합니다 |
sCount | left가 0이 아니면 1 감소시키고, 0이면 sDone으로 전이합니다 |
sDone | 한 사이클 뒤 sIdle로 돌아갑니다 |
import chisel3._
import chisel3.util._
// 이름 있는 상태 세 개. 인코딩과 폭은 Chisel이 정한다.
object State extends ChiselEnum {
val sIdle, sCount, sDone = Value
}
class Timer extends Module {
val io = IO(new Bundle {
val start = Input(Bool())
val state = Output(State())
val done = Output(Bool())
})
val state = RegInit(State.sIdle)
val left = RegInit(0.U(2.W))
switch(state) {
is(State.sIdle) {
when(io.start) {
state := State.sCount
left := 2.U
}
}
is(State.sCount) {
when(left === 0.U) {
state := State.sDone
}.otherwise {
left := left - 1.U
}
}
is(State.sDone) {
state := State.sIdle
}
}
io.state := state
io.done := state === State.sDone
}Tb.scala는 elaboration 중에 열거형의 값과 폭을 출력합니다. 이어서 0 사이클에만
start를 1로 인가하고, 클록 에지 직전마다 상태와 done을 출력합니다. 열거형 포트를
peek한 값은 열거형 이름 뒤 괄호 안에 번호와 값 이름이 함께 표시되므로 번호 표 없이도
추적 결과를 읽을 수 있습니다.
import chisel3._
import chiseltest._
import chiseltest.RawTester.test
object Main extends App {
println(s"ENCODING ${State.all.mkString(" ")} width=${State.getWidth}")
test(new Timer) { dut =>
for (cycle <- 0 until 7) {
// start를 한 사이클 동안만 1로 인가한다.
dut.io.start.poke((cycle == 0).B)
println(s"cycle=$cycle state=${dut.io.state.peek()} done=${dut.io.done.peek().litValue}")
dut.clock.step()
}
}
}이 개념 실습하기
다음 단계로 짧은 Chisel 문제에 이 개념을 적용해 보세요.
실행하기 전에 예측해 보세요.
ENCODING줄이 보고하는 폭은 얼마이고,sDone은 몇 번일까요?start가 1인 0 사이클에 상태는 이미sCount일까요?done이 처음 1이 되는 사이클은 언제이고, 몇 사이클 동안 1일까요?
다음 추적표로 직접 계산해 보세요.
| 사이클 | 그 사이클의 상태 | left | 클록 에지에서 일어나는 일 |
|---|---|---|---|
| 0 | sIdle | 0 | start가 1이므로 sCount로 전이하고 2를 적재 |
| 1 | sCount | 2 | 0이 아니므로 감소 |
| 2 | sCount | 1 | 0이 아니므로 감소 |
| 3 | sCount | 0 | sDone으로 전이 |
| 4 | sDone | 0 | sIdle로 전이 |
| 5–6 | sIdle | 0 | start가 0이므로 유지 |
결과 확인.
| 출력 줄 | 확인할 내용 |
|---|---|
ENCODING | 세 값이 선언 순서대로 0, 1, 2번이고 폭은 2입니다 |
cycle=0 | 아직 sIdle입니다. 레지스터는 start를 본 사이클이 끝나는 에지에서 갱신됩니다 |
cycle=1부터 cycle=3 | left가 2, 1, 0으로 줄어드는 세 사이클 동안 sCount입니다 |
cycle=4 | sDone이고 done=1입니다. done이 1인 사이클은 이 한 번뿐입니다 |
cycle=5, cycle=6 | start가 0이므로 sIdle에서 기다립니다 |
직접 바꿔 보세요.
sDone뒤에 쓰지 않는 상태sWait,sError를 추가해 보세요.ENCODING줄에 값 다섯 개가 나오고 폭이 3이 됩니다.sWait만 추가하면 폭은 2 그대로입니다. 기존 상태의 번호가 바뀌지 않으므로 두 경우 모두 추적 결과는 같습니다.- 모든 사이클에서
start를 1로 유지해 보세요(dut.io.start.poke(true.B)). 5 사이클까지는 같지만 6 사이클에 다시sCount가 됩니다.sIdle이start를 보자마자 전이하기 때문입니다. is(State.sDone)분기를 지워 보세요.sDone에서state를 대입하는 분기가 없으므로 레지스터가 값을 유지합니다. 4 사이클부터sDone에 머물고done이 계속 1입니다. 분기가 빠진 레지스터는 오류가 아니라 값 유지로 동작합니다.
3. 열거형과 비트 사이의 변환
3.1 열거형에서 비트로: asUInt
asUInt는 열거형의 비트를 같은 폭의 UInt로 반환합니다. 선언된 값인지 검사하지는
않으므로, 원시 입력을 해독할 때는 safe가 반환한 유효 플래그를 사용하세요.
형식
열거형_값.asUIntval code = state.asUInt // Color.blue이면 2비트 2.U3.2 비트에서 열거형으로: 일반 형변환과 safe
반대 방향은 단순한 변환이 아니라 "이 비트는 선언된 값이다"라는 주장입니다. 원시 UInt는
선언되지 않은 패턴을 담을 수 있기 때문입니다. Chisel은 두 가지 형태를 제공합니다.
형식
열거형_이름(비트) // 일반 형변환
val (값, 유효) = 열거형_이름.safe(비트) // 형변환과 유효 플래그val (color, valid) = Color.safe(io.raw) // io.raw는 UInt(2.W)| 형태 | 반환 | io.raw가 3일 때 |
|---|---|---|
Color(io.raw) | Color.Type | 3이 표시 없이 통과합니다. elaboration 중에 [W001] 경고가 출력됩니다 |
Color.safe(io.raw) | (Color.Type, Bool) | 비트는 같고 valid가 0입니다 |
safe는 비트를 바꾸지 않습니다. 입력이 선언된 번호 가운데 하나와 같을 때만 1을 출력하는
비교기를 추가할 뿐입니다.
주의. 변환할 UInt가 열거형보다 넓으면 안 됩니다. 7비트 값을 6비트 열거형으로
변환하면 일반 형변환이든 safe든 The UInt being cast to Opcode is wider than Opcode's width (6) 메시지와 함께 elaboration이 중단됩니다.
3.3 예제: safe로 opcode 해독하기
Decode.scala는 인코딩이 고정된 opcode 필드를 다룹니다. load는 0x03, imm은 0x13,
store는 0x23입니다. 다음 세 질문을 염두에 두고 읽으세요.
- 가장 큰 인코딩은 35입니다.
Opcode의 폭은 몇 비트이고,raw는 그 폭으로 선언되었을까요? Opcode.safe(io.raw)가 반환하는 두 값은 각각 어느 출력으로 연결될까요?memory는valid와isOneOf(load, store)의 AND입니다.valid를 함께 쓰는 이유는 무엇일까요?
import chisel3._
// 명시한 인코딩은 증가해야 한다. 사이의 값은 합법 opcode가 아니다.
object Opcode extends ChiselEnum {
val load = Value(0x03.U)
val imm = Value(0x13.U)
val store = Value(0x23.U)
}
class Decode extends Module {
val io = IO(new Bundle {
val raw = Input(UInt(6.W))
val valid = Output(Bool())
val bits = Output(UInt(6.W))
val memory = Output(Bool())
})
// safe는 enum 값과 함께, 선언된 값일 때만 참인 플래그를 반환한다.
val (op, valid) = Opcode.safe(io.raw)
io.valid := valid
io.bits := op.asUInt
io.memory := valid && op.isOneOf(Opcode.load, Opcode.store)
}Tb.scala는 elaboration 중에 폭과 Opcode.all의 번호를 출력합니다. 이어서 합법
인코딩 세 개와 불법 패턴 0x05를 인가합니다. 해독한 열거형은 일부러 io.bits를 통해
UInt 비트로 읽는데, 그 이유는 결과 확인 표 아래에 있습니다.
import chisel3._
import chiseltest._
import chiseltest.RawTester.test
object Main extends App {
println(s"OPCODE width=${Opcode.getWidth} values=${Opcode.all.map(_.litValue).mkString(",")}")
test(new Decode) { dut =>
for (raw <- Seq(0x03, 0x13, 0x23, 0x05)) {
dut.io.raw.poke(raw.U)
println(f"raw=0x$raw%02x valid=${dut.io.valid.peek().litValue} bits=0x${dut.io.bits.peek().litValue}%02x memory=${dut.io.memory.peek().litValue}")
}
}
}이 개념 실습하기
다음 단계로 짧은 Chisel 문제에 이 개념을 적용해 보세요.
실행하기 전에 예측해 보세요.
OPCODE줄에 나오는 폭과 번호 세 개는 무엇일까요?- 0x05에 대한
valid,bits,memory는 각각 얼마일까요? safe가 0x05를 가까운 합법 opcode로 바꿀까요?
결과 확인.
| 출력 줄 | 확인할 내용 |
|---|---|
OPCODE | 폭은 6이고 번호는 3, 19, 35입니다. 명시한 인코딩을 10진수로 쓴 값입니다 |
raw=0x03, raw=0x23 | 메모리 opcode입니다. valid=1, 비트는 그대로, memory=1입니다 |
raw=0x13 | 합법이지만 메모리 opcode가 아니므로 memory=0입니다 |
raw=0x05 | valid=0이지만 bits는 여전히 0x05입니다. safe는 보고만 하고 고치지 않습니다 |
0x05에서 memory가 0인 이유는 모듈이 isOneOf의 결과를 valid와 AND하기 때문입니다.
테스트벤치가 열거형 포트 대신 io.bits를 읽는 데도 이유가 있습니다. 선언되지 않은 패턴을
담은 열거형 포트를 peek()하면 붙일 이름이 없으므로
java.util.NoSuchElementException: None.get이 발생합니다.
직접 바꿔 보세요.
Opcode.safe(io.raw)를 일반 형변환Opcode(io.raw)로 바꾸고, 모듈이 컴파일되도록io.valid := true.B로 두세요. 실행은 성공하지만 시뮬레이션 출력 앞에[W001] Casting non-literal UInt to Opcode. You can use Opcode.safe to cast without this warning.경고가 나오고, 0x05도valid=1로 보고됩니다.raw를UInt(7.W)로 넓혀 보세요.ChiselException과 함께The UInt being cast to Opcode is wider than Opcode's width (6)가 출력되고elaboration이 중단됩니다.store뒤에val next = Value를 추가해 보세요.next는store보다 1 큰 36이 되고,OPCODE줄에는 폭 6과 번호 네 개가 나옵니다.
참고. 합법 값만 저장하는 메모리에서 읽은 Bundle처럼 원시 값의 유효성이 이미
보장되면 suppressEnumCastWarning { ... }으로 해당 구간의 경고를 끌 수 있습니다. 그 밖의
경우에는 safe를 쓰세요. 플래그는 비교기 하나로 구현되고 불법 입력을 드러냅니다.
4. 자주 하는 실수
4.1 열거형을 숫자와 비교하기
// 잘못된 코드: 2.U는 State 값이 아니라 UInt
io.done := state === 2.U
// 바른 코드: 이름 있는 값과 비교
io.done := state === State.sDone잘못된 코드는 Scala 컴파일 단계에서 type mismatch; found: chisel3.UInt, required: chisel3.EnumType 오류가 발생합니다. 상태를 추가하면 번호는 바뀔 수 있지만 이름은 바뀌지
않습니다.
4.2 switch를 쓰면서 chisel3.util을 들여오지 않기
// 잘못된 코드: chisel3._만 들여옴
import chisel3._
switch(state) { is(State.sIdle) { ... } }
// 바른 코드
import chisel3._
import chisel3.util._
switch(state) { is(State.sIdle) { ... } }ChiselEnum은 chisel3에 있지만 switch와 is는 chisel3.util에 있습니다. 두 번째
import가 없으면 not found: value switch와 not found: value is 컴파일 오류가
발생합니다.
4.3 safe 없이 원시 비트 형변환하기
// 잘못된 코드: 불법 패턴이 표시 없이 통과
val op = Opcode(io.raw)
// 바른 코드: 유효 플래그를 받아 사용
val (op, valid) = Opcode.safe(io.raw)일반 형변환은 [W001] 경고만 출력하고 elaboration을 마칩니다. 하드웨어에는 불법 입력과
합법 입력을 구분하는 신호가 남지 않습니다.
4.4 명시한 인코딩의 순서가 틀림
// 잘못된 코드: 0x13이 0x03보다 먼저
object Opcode extends ChiselEnum {
val imm = Value(0x13.U)
val load = Value(0x03.U)
}
// 바른 코드: 증가 순서로 선언
object Opcode extends ChiselEnum {
val load = Value(0x03.U)
val imm = Value(0x13.U)
}잘못된 코드는 컴파일은 되지만 처음 사용할 때 ExceptionInInitializerError가 발생하고,
원인으로 Enums must be strictly increasing: Opcode가 출력됩니다.
4.5 일부 분기에서만 대입하는 wire
// 잘못된 코드: state가 green이나 blue이면 next에 값이 없음
val next = Wire(Color())
switch(state) { is(Color.red) { next := Color.green } }
// 바른 코드: wire에 기본값 지정
val next = WireDefault(Color.red)
switch(state) { is(Color.red) { next := Color.green } }잘못된 코드는 시뮬레이션 전에 Reference next is not fully initialized. 오류로
중단됩니다. 레지스터는 대입하는 분기가 없으면 이전 값을 유지하지만, wire에는 유지할 값이
없습니다.
5. 하나만 바꾸기: 분기를 없애고, 상태 둘을 더해 보세요
실행 패널에서 타이머의 is(State.sDone) { ... } 분기 전체를 지우고 다시 실행하세요.
확인 문제
sDone 분기가 없으면 사이클 4부터 무슨 일이 생깁니까?
분기를 되돌린 뒤 선언에서 sDone 뒤에 멤버 둘을 더하세요. 예를 들어
val sIdle, sCount, sDone, sWait, sError = Value입니다.
확인 문제
멤버가 다섯이면 ENCODING 줄은 폭을 얼마로 보고합니까?
6. 적용: 노란불 단계
아래 채점 실습 문제는 tick이 1인 사이클마다 한 단계 나아가는, 빨강과 초록 두 상태의
신호등입니다. 초록과 빨강 사이에 노랑을 더하세요. 선언에 멤버 하나, 바뀌는 전이 하나, 새 is
분기 하나, 그리고 빨강과 노랑에서 1인 stop 출력입니다. 상태는 Light 멤버와만 비교하세요.
isOneOf는 여러 멤버를 한 번에 검사합니다. 실행 전에 여덟 사이클 출력을 적어 보세요. 어느
사이클에서 본 tick은 다음 사이클에 새 상태로 나타납니다.
7. 정리
| 주제 | 핵심 |
|---|---|
| 선언 | object E extends ChiselEnum { val a, b = Value }. 번호는 0부터 시작합니다 |
| 폭 | 가장 큰 번호를 담는 최소 폭입니다. 직접 지정하지 않습니다 |
| 인코딩 명시 | Value(n.U). 번호는 엄격히 증가해야 하고, 인자 없는 Value는 직전 번호에 1을 더합니다 |
| 타입과 값 | E()는 포트, wire, 필드에 쓰는 Chisel 타입이고 E.a는 리터럴입니다 |
| 비교 | ===, =/=, isOneOf는 같은 열거형끼리만 씁니다. UInt와 비교하면 컴파일 오류입니다 |
switch/is | chisel3.util에 있습니다. 일치하는 분기가 없으면 레지스터는 값을 유지합니다 |
| 열거형에서 비트로 | asUInt는 항상 성공합니다 |
| 비트에서 열거형으로 | E(u)는 경고를 출력하고, E.safe(u)는 값과 유효 Bool을 반환합니다 |
8. 확인 문제
-
네 번째 상태를 추가해도 폭은 2이지만 다섯 번째 상태를 추가하면 폭이 바뀝니다. 왜 그럴까요?
정답 보기
값 네 개의 번호는 0부터 3까지이므로 2비트에 들어갑니다. 다섯 번째 값은 4번이 되는데, 4를 담으려면 3비트가 필요합니다. 폭은 항상 가장 큰 번호를 담는 최소 비트 수입니다.
-
FSM 추적에서
start가 1인 0 사이클의 상태가 여전히sIdle인 이유는 무엇일까요?정답 보기
state는 레지스터입니다.switch가 0 사이클 동안 다음 상태를 계산하고, 레지스터는 0 사이클이 끝나는 클록 에지에서 그 값을 받습니다. 새 상태sCount는 1 사이클에 나타납니다. -
Opcode.safe는 입력 비트에서 무엇을 바꾸고 무엇을 추가할까요?정답 보기
비트는 바꾸지 않습니다. 0x05는 0x05 그대로입니다. 대신 비트가 선언된 인코딩 가운데 하나와 같을 때만 1인
Bool을 추가로 반환합니다. -
state === 2.U, 6비트raw에 대한Opcode(io.raw), 7비트raw에 대한Opcode.safe(io.raw)가운데 Scala 컴파일에서 실패하는 것,elaboration에서 실패하는 것, 경고만 출력하는 것은 각각 무엇일까요?정답 보기
state === 2.U는 타입 불일치로 Scala 컴파일 오류가 발생합니다. 6비트 입력의Opcode(io.raw)는elaboration을 마치고[W001]경고만 출력합니다. 7비트 입력의safe는 입력이 열거형보다 넓으므로elaboration이 중단됩니다. -
is(State.sDone)분기를 지웠더니 타이머가sDone에서 멈췄습니다. Chisel 오류일까요?정답 보기
아닙니다. 일치하는 분기가 없으면
state레지스터에 아무것도 대입되지 않으므로 값을 유지합니다. 레지스터의 분기 누락은 값 유지로 동작하는 합법적인 코드이지만 의도와 다른 회로입니다.