1. Mux는 식으로 쓴 멀티플렉서입니다
Mux(조건, 참일_때_값, 거짓일_때_값)은 공통 기초의 2입력 멀티플렉서를 만듭니다. 조건은 하드웨어
Bool이어야 하고, 결과의 폭은 두 값 가운데 넓은 쪽입니다. 식이므로 :=의 오른쪽에 두거나 더 큰 식
안에 넣을 수 있습니다. 값 하나를 만드는 단일 선택에는 Mux가 알맞습니다.
형식
Mux(조건, 참일_때_값, 거짓일_때_값)val larger = Mux(io.a > io.b, io.a, io.b)
val small = Mux(io.sel, io.x, 1.U(4.W)) // 8비트 x와 4비트 리터럴: 8비트첫 장의
규칙을 다시 적용할 자리입니다. Scala if는 회로를 만드는 동안 알 수 있는 Scala Boolean을 받아
어떤 하드웨어를 만들지 고르고, 게이트를 남기지 않습니다. Mux와 when은 하드웨어 Bool을 받아
회로가 동작하는 동안 고르는 회로를 만듭니다. if (io.sel)은 컴파일되지 않습니다. elaboration
중에 io.sel에는 값이 없습니다.
| 작성 | 결정 시점 | 회로에 남는 것 |
|---|---|---|
if (Scala_Boolean) ... else ... | elaboration | 선택된 분기만 |
Mux(bool, a, b) | 시뮬레이션의 매 순간 | 멀티플렉서 |
when (bool) { ... } | 시뮬레이션의 매 순간 | 안에서 연결한 받는 쪽마다 멀티플렉서 |
조건 목록, 조회 표, 선택 신호 벡터로 고르는 더 넓은 선택은 chisel3.util의 함수로 만들며,
MuxCase·MuxLookup·PriorityMux·Mux1H로 선택하기 장에서
다룹니다.
2. Wire: :=로 구동하는 이름 있는 노드
Wire는 :=로 구동하는 이름 있는 노드입니다. 식 하나가 아니라 여러 연결로 값을 정할 때 사용합니다.
형식
val 이름 = Wire(Chisel_타입)
val 이름 = WireDefault(초깃값)val level = Wire(UInt(2.W)) // 아직 구동원이 없음
val alarm = WireDefault(false.B) // 다른 연결이 없으면 false로 구동됨Wire(UInt(2.W))는 노드만 만듭니다. 5절에서 설명하듯이 모든 경로에서 무언가가 이 노드를 구동해야
합니다. WireDefault(v)는 노드를 만들고 v를 연결하는 일을 한 번에 합니다.
3. when, .elsewhen, .otherwise
when 블록은 블록 안의 모든 연결에 조건을 붙입니다. 블록 안의 모든 :=가 조건부 연결이 됩니다.
.elsewhen은 앞의 조건이 모두 거짓일 때만 검사하는 분기를 더하고, .otherwise는 나머지 모든 경우를
맡습니다.
형식
when (조건1) {
// 조건1이 참일 때 적용되는 연결
} .elsewhen (조건2) {
// 조건1이 거짓이고 조건2가 참일 때 적용
} .otherwise {
// 위 조건이 모두 거짓일 때 적용
}val speed = Wire(UInt(2.W))
when (io.temp > 80.U) {
speed := 3.U
} .elsewhen (io.temp > 50.U) {
speed := 1.U
} .otherwise {
speed := 0.U
}| 구문 | 의미 |
|---|---|
when (c) { ... } | c가 참인 동안에만 블록 안의 연결이 적용됩니다 |
.elsewhen (c2) { ... } | 체인의 앞선 조건이 모두 거짓일 때만 검사합니다 |
.otherwise { ... } | 남은 경우를 모두 덮습니다 |
Wire(UInt(2.W))는 구동원이 아직 없는 노드를 만들고, when 체인이 그 노드를 구동합니다. 체인은
처음으로 참인 조건 하나만 선택합니다. temp = 90이면 > 80과 > 50이 모두 참이지만 speed는
3입니다.
4. 마지막 연결 우선 규칙
두 연결이 같은 대상을 구동하고 두 조건이 모두 참이면, 소스에서 나중에 쓴 연결이 적용됩니다. 연결이 둘 이상 적용될 수 있을 때 받는 쪽의 값은 두 규칙이 정합니다.
| 규칙 | 뜻 |
|---|---|
| 체인은 처음으로 참인 조건을 택합니다 | 하나가 성립하면 뒤의 .elsewhen은 건너뜁니다 |
| 별개의 문장 사이에서는 마지막 연결이 우선합니다 | 둘 다 성립하면 소스에서 나중에 쓴 연결이 앞의 연결을 덮습니다 |
조건 없는 연결 뒤에 조건부 연결을 두면 기본값을 표현할 수 있습니다.
val bigger = WireDefault(0.U(8.W)) // 기본값 0
when (io.x > 100.U) {
bigger := io.x // 나중에 썼으므로 x > 100인 동안 우선
}x = 197이면 bigger는 197이고, x = 3이나 x = 40이면 when이 거짓이므로 0을 유지합니다.
우선순위는 조건의 중요도가 아니라 소스에서 연결을 쓴 위치가 정합니다.
5. 모든 경로의 구동
출력과 wire는 모든 조건 조합에서 값이 있어야 합니다. 어떤 경로에서 연결이 없으면 Chisel은 임의의
값을 채우지 않으며, 시뮬레이션 전에 실행되는 검사가 RefNotInitializedException과 함께 그 대상을
not fully initialized로 보고하고 실행을 멈춥니다. 메시지는 멀티플렉서 체인을 보여 주며 빠진 연결
자리에 VOID를 적습니다.
| 모든 경로를 구동하는 방법 | 예 |
|---|---|
when 앞에 기본 연결 | io.out := 0.U |
.otherwise로 남은 경우 처리 | .otherwise { io.out := 0.U } |
| 선언에서 기본값 지정 | WireDefault(0.U(8.W)) |
| 값이 중요하지 않음을 명시 | io.out := DontCare |
주의. DontCare는 값을 정하지 않습니다. 그 상태에서 신호를 읽는 쪽이 없을 때만 사용하세요.
6. 예제: Wire, when, 마지막 연결 추적하기
Grade.scala는 8비트 level을 네 등급으로 나눕니다. 네 갈래 when 체인이 grade wire를 구동하고,
그 뒤의 별도 when (io.force)가 grade를 다시 구동합니다. alarm은 WireDefault(false.B)로
시작하며, when 하나가 등급 3일 때 1로 바꿉니다.
import chisel3._
class Grade extends Module {
val io = IO(new Bundle {
val level = Input(UInt(8.W))
val force = Input(Bool())
val grade = Output(UInt(2.W))
val alarm = Output(Bool())
})
val grade = Wire(UInt(2.W))
// 체인에서 처음으로 참인 조건이 연결을 선택한다.
when (io.level > 200.U) {
grade := 3.U
} .elsewhen (io.level > 100.U) {
grade := 2.U
} .elsewhen (io.level > 0.U) {
grade := 1.U
} .otherwise {
grade := 0.U
}
// force가 1이면 뒤에 쓴 연결이 체인보다 우선한다.
when (io.force) {
grade := 0.U
}
io.grade := grade
// WireDefault는 다른 연결이 없는 모든 경로에서 wire를 구동한다.
val alarm = WireDefault(false.B)
when (grade === 3.U) {
alarm := true.B
}
io.alarm := alarm
}import chisel3._
import chiseltest._
import chiseltest.RawTester.test
object Main extends App {
test(new Grade) { dut =>
for ((level, force) <- Seq((0, false), (50, false), (150, false), (250, false), (250, true))) {
dut.io.level.poke(level.U)
dut.io.force.poke(force.B)
val grade = dut.io.grade.peek().litValue
val alarm = dut.io.alarm.peek().litValue
println(s"SIMULATE level=$level force=$force grade=$grade alarm=$alarm")
}
}
}이 개념 실습하기
다음 단계로 짧은 Chisel 문제에 이 개념을 적용해 보세요.
실행하기 전에 예측해 보세요. force가 0일 때 level이 0, 50, 150, 250이면 등급은 각각
무엇일까요? 마지막 행은 level = 250이고 force가 1입니다. grade에 연결하는 블록이 두 개인데
어느 쪽이 적용되고, alarm은 체인이 고른 값과 최종 grade 가운데 무엇을 비교할까요?
확인 문제
level = 250, force = true일 때 grade와 alarm은 무엇입니까?
결과 확인. 실행을 누르고 마지막 행을 단계별로 추적하세요.
| 단계 | 판정 | grade 후보 |
|---|---|---|
체인의 level > 200 | 참 | 3 |
.elsewhen 분기 | 체인은 처음으로 참인 조건만 쓰므로 건너뜁니다 | 3 |
별도의 when (io.force) | 참이며 나중에 쓴 연결입니다 | 0 |
나중에 쓴 연결이 우선하므로 grade는 0입니다. alarm은 체인의 중간 선택이 아니라 최종 grade를
비교하므로 기본값 0을 유지합니다. 처음 네 행은 등급 0, 1, 2, 3을 보여 주고 넷째 행에서만 경보가
1입니다. 넷째 행과 다섯째 행은 level이 같지만, 넷째 행은 등급 3과 경보 1, 다섯째 행은 등급 0과
경보 0입니다.
직접 바꿔 보세요. 먼저 when (io.force) { ... } 블록 전체를 체인 위로 옮기고 다시 실행하세요.
확인 문제
force 블록을 체인보다 앞에 쓰면 마지막 행은 무엇을 보입니까?
.otherwise가 남은 경우를 모두 덮으므로 체인은 항상 무언가를 연결하고, force 블록보다 나중에 쓴
체인의 연결이 언제나 우선합니다. 이어서 반대 편집도 해 보세요. 순서를 되돌린 뒤 .otherwise 분기를
지웁니다.
확인 문제
.otherwise가 없으면 level = 0이고 force가 0인 첫 행에서 무슨 일이 생깁니까?
시뮬레이션 전에 RefNotInitializedException과 Reference grade is not fully initialized가
발생하고, 메시지의 멀티플렉서 체인에서 빠진 경우가 VOID로 표시됩니다. 같은 오류를 포트에서
일으키는 예제는 7절에서 다룹니다.
7. 실패 예제: 모든 경로에서 구동되지 않은 출력
다음 예제는 일부러 오류를 발생시킵니다. Gate는 when (io.en) 안에서만 io.out을 구동하므로
en이 0일 때는 io.out을 구동하는 연결이 없습니다. 실행을 누르기 전에 어느 단계에서 오류가
보고될지 정해 보세요.
확인 문제
Gate.scala의 오류는 어느 단계에서 보고됩니까?
import chisel3._
import chiseltest._
import chiseltest.RawTester.test
class Gate extends Module {
val io = IO(new Bundle {
val en = Input(Bool())
val in = Input(UInt(8.W))
val out = Output(UInt(8.W))
})
// en이 0이면 out을 구동하는 연결이 없으므로 회로가 불완전하다.
when (io.en) {
io.out := io.in
}
}
object Main extends App {
test(new Gate) { dut =>
dut.io.en.poke(true.B)
}
}| 확인 항목 | 내용 |
|---|---|
| 잘못된 코드 | io.en이 0인 경로에 io.out 연결이 없습니다 |
| 실패 단계 | 시뮬레이션 전 초기화 검사입니다. elaboration은 성공하며, 생성된 CHIRRTL에는 when io.en 연결 하나만 있습니다 |
| 오류 메시지 | RefNotInitializedException과 Reference io is not fully initialized. 마지막 줄은 io.out이 io.en, io.in, VOID의 mux로 구동된다고 보여 줍니다 |
| 고치는 방법 | when 앞에 기본값 io.out := 0.U를 추가하거나, en이 0일 때 출력을 읽는 쪽이 없다면 io.out := DontCare를 추가합니다 |
오류 메시지는 아래에서 위로 읽으세요. VOID가 연결이 없는 경로이고, 첫 줄은 모듈 이름과 잘못된
필드가 들어 있는 집합체 io를 알려 줍니다. 이 오류는 new Gate를 구성할 때가 아니라 test(...)가
시작될 때 발생합니다.
8. 적용: 덮어쓰지 못하는 덮어쓰기
아래 채점 실습 문제는 팬 제어기입니다. 온도 체인이 속도를 고르고, fault 입력이 켜지면 온도와
상관없이 속도 3을 강제해야 합니다. 시작 코드의 fault 블록은 효과가 없습니다. 6절의 첫 편집이 진단의
전부입니다. 고친 설계는 공개 실행이 출력하지 않는 행, 즉 온도가 높고 fault가 켜진 행에서도
3이어야 합니다.
9. 자주 하는 실수
9.1 기본값 없는 when
// 잘못된 코드: en이 0이면 out을 구동하는 연결이 없음
when (io.en) { io.out := io.in }
// 바른 코드: 기본값을 먼저 연결함
io.out := 0.U
when (io.en) { io.out := io.in }시뮬레이션 전 검사가 mux 안의 VOID와 함께 not fully initialized를 보고합니다. 모든 대상은 모든
경로에서 구동원이 있어야 합니다.
9.2 우선해야 할 연결을 체인보다 먼저 쓰기
// 잘못된 코드: 체인을 나중에 썼으므로 체인이 항상 우선함
when (io.force) { grade := 0.U }
when (io.level > 200.U) { grade := 3.U } .otherwise { grade := 1.U }
// 바른 코드: 우선해야 할 연결을 마지막에 씀
when (io.level > 200.U) { grade := 3.U } .otherwise { grade := 1.U }
when (io.force) { grade := 0.U }마지막 연결이 우선합니다. 다른 연결보다 우선해야 하는 연결은 그 연결들보다 뒤에 써야 합니다.
10. 정리
| 주제 | 핵심 |
|---|---|
Mux(c, a, b) | 식으로 쓴 멀티플렉서입니다. 조건은 하드웨어 Bool이고 폭은 넓은 값을 따릅니다 |
Scala if | elaboration 중에 만들 회로를 고르고 게이트를 남기지 않습니다. Bool 포트를 읽을 수 없습니다 |
Wire(t), WireDefault(v) | :=로 구동하는 이름 있는 노드입니다. WireDefault는 모든 경로에 기본값을 줍니다 |
when / .elsewhen / .otherwise | 조건부 연결입니다. 체인은 처음으로 참인 조건을 씁니다 |
| 우선순위 규칙 | 별개의 문장 사이에서는 참인 연결 가운데 마지막 연결이 우선합니다. 덮어쓰기는 마지막에 씁니다 |
| 완전성 | 기본 연결, .otherwise, WireDefault, DontCare 가운데 하나로 모든 경로를 구동합니다 |
| 구동되지 않는 경로 | 시뮬레이션 전에 not fully initialized로 거절되며 빠진 경우가 VOID로 표시됩니다 |
11. 확인 문제
-
wire를 조건 없이 1에 연결한 뒤
when (c)안에서 2에 연결했습니다.c가 거짓일 때와 참일 때 wire의 값은 각각 무엇일까요?정답 보기
c가 거짓이면 조건 없는 연결만 적용되므로 1입니다.c가 참이면 나중에 쓴 연결이 우선하므로 2입니다. -
Grade에서.otherwise를 지우면 실행이 실패하지만,alarm에는.otherwise가 전혀 없습니다.alarm에 문제가 없는 이유는 무엇일까요?정답 보기
alarm은WireDefault(false.B)로 선언되어 다른 연결이 없는 모든 경로에서 구동됩니다.grade는 평범한Wire이므로.otherwise가 없으면 구동되지 않는 경로가 생깁니다. -
io.x가 8비트UInt일 때Mux(io.sel, io.x, 1.U(4.W))의 폭은 얼마이며,io.sel은 무엇이어야 할까요?정답 보기
8비트입니다. 결과는 두 값 가운데 넓은 쪽을 따르고, 4비트 리터럴은 선택될 때 0으로 확장됩니다.
io.sel은 하드웨어Bool이어야 합니다. ScalaBoolean을 쓰면 컴파일되지 않습니다. -
Gate의 구동되지 않은 출력이 Scala 컴파일과elaboration을 통과하는 이유는 무엇이며, 어느 단계가 오류를 보고할까요?정답 보기
줄 하나하나는 그 자체로 적법합니다. 연결 하나가 든
when은 올바른 Chisel이므로 컴파일과elaboration은 성공하고 회로 그래프가 만들어집니다. chiseltest가 시뮬레이션 전에 실행하는 초기화 검사만이 모든 경로를 따라가VOID인 경우를 찾아내며, 오류는test(...)가 시작될 때 발생합니다.
아래 실습 문제는 온도로 팬 속도를 정합니다. 센서 고장 신호가 1이면 온도와 상관없이 최고 속도여야 하지만, 고장 행에서 낮은 온도에 해당하는 속도가 출력됩니다.