1. when 아래에서 갱신하는 레지스터는 다른 사이클에서 값을 유지합니다
리셋이 비활성일 때, 레지스터의 연결을 when 안에 두면 조건이 성립하는 에지에서만 새 값을 받습니다. 다른 모든
사이클에서는 연결이 없으므로 현재 값을 유지합니다. 공통 기초의 enable이 바로 이것입니다. 빠진
경로가 오류가 되지 않는 유일한 경우이기도 합니다. 어떤 경로에서 구동되지 않는 wire는 시뮬레이션
전에 거절되지만, 레지스터는 그냥 유지하므로 기본값이나 .otherwise가 필요하지 않습니다.
Mux·when·Wire 장이 wire와 출력에 적용한 미연결 검사는
when 안에서만 연결한 레지스터에는 적용되지 않습니다.
형식
val r = RegInit(0.U(4.W))
when (io.en) {
r := io.in
}| 사이클 | en | 에지가 r에 하는 일 |
|---|---|---|
| 모두 | 1 | io.in을 저장 |
| 모두 | 0 | 현재 값 유지 |
2. 우선순위는 소스 순서로 씁니다
한 사이클에서 레지스터로 가는 연결 둘이 모두 적용될 수 있으면
Mux·when·Wire 장의 규칙이 정합니다. when/.elsewhen
사슬은 첫 번째 참 분기를 쓰고, 별개의 문장 사이에서는 나중 연결이 이깁니다. 카운트보다 앞서야
하는 load는 사슬의 첫 분기에 씁니다. 모든 것보다 앞서야 하는 clear는 사슬의 첫 분기에 쓰거나
마지막 별개 when으로 씁니다.
형식
when (io.load) {
count := io.cycles
} .elsewhen (count =/= 0.U) {
count := count - 1.U
}load와 0이 아닌 count가 모두 참이면 레지스터는 load합니다. 감소 분기에는 도달하지 않습니다. 둘 다
거짓이면 연결이 없어 레지스터는 유지합니다.
3. 리셋은 리셋 값을 선언한 레지스터만 바꿉니다
리셋은 전체를 지우는 신호가 아닙니다. 레지스터마다 선언에 속하며, 리셋 값을 선언한 레지스터에만 적용됩니다.
| 생성 방식 | 리셋 값 | 리셋이 1인 동안 에지에서 |
|---|---|---|
RegInit(v) | v | 다음 값 대신 v를 저장 |
RegNext(x, v) | v | 같음 |
Reg(t) | 없음 | 리셋 연결이 없음. 다음 값 논리를 그대로 따름 |
RegNext(x) | 없음 | 같음 |
리셋 값이 없는 레지스터는 시작 값도 정해져 있지 않습니다. 시뮬레이터 treadle은 이런 레지스터를 0으로 시작하지만 하드웨어는 그 값을 보장하지 않습니다. 플래그, valid 비트, 카운터 같은 제어 상태는 어디서 시작하는지가 정확성을 정하므로 리셋 값이 필요합니다.
최상위 모듈의 암묵적 reset은 동기 리셋입니다. 1이 되는 순간이 아니라 클록 에지에서 작용합니다.
chiseltest는 본문이 실행되기 전에 리셋을 한 사이클 인가하므로, 본문의 첫 peek에서 리셋 값이 있는
레지스터는 이미 리셋 값을 저장하고 있습니다. 테스트벤치는 dut.reset.poke(true.B)로 리셋을 보통
입력처럼 구동할 수도 있으며, 그 뒤 step이 있기 전까지는 아무것도 바뀌지 않습니다.
4. 리셋 하나를 지나는 레지스터 네 개
ResetDemo는 3절의 네 방식으로 만든 레지스터 넷을 가집니다.
| 레지스터 | 생성 | 다음 값 |
|---|---|---|
withInit | RegInit(5.U(4.W)) | en이 1일 때 in |
noInit | Reg(UInt(4.W)) | en이 1일 때 in |
next | RegNext(io.in) | 매 사이클 in |
nextInit | RegNext(io.in, 0.U(4.W)) | 매 사이클 in |
import chisel3._
class ResetDemo extends Module {
val io = IO(new Bundle {
val en = Input(Bool())
val in = Input(UInt(4.W))
val withInit = Output(UInt(4.W))
val noInit = Output(UInt(4.W))
val next = Output(UInt(4.W))
val nextInit = Output(UInt(4.W))
})
// 리셋이 이 레지스터에 5를 저장한다.
val withInit = RegInit(5.U(4.W))
// 리셋 값이 없으므로 리셋이 이 레지스터를 바꾸지 않는다.
val noInit = Reg(UInt(4.W))
when(io.en) {
withInit := io.in
noInit := io.in
}
io.withInit := withInit
io.noInit := noInit
io.next := RegNext(io.in)
io.nextInit := RegNext(io.in, 0.U(4.W))
}테스트벤치는 처음에 withInit만 출력합니다. 리셋 값이 없는 레지스터의 시작 값에 의존하지 않기
위해서입니다. 이어서 모든 레지스터에 9를 저장하고, en을 0, in을 6으로 바꾸고 reset을 1로
넣은 뒤 에지 전에 출력하고, 한 번 step한 뒤 다시 출력하고, 마지막으로 리셋을 풀고 한 번 더
step합니다.
import chisel3._
import chiseltest._
import chiseltest.RawTester.test
object Main extends App {
test(new ResetDemo) { dut =>
def show(phase: String): Unit = {
val v = Seq(dut.io.withInit, dut.io.noInit, dut.io.next, dut.io.nextInit).map(_.peek().litValue)
println(s"$phase withInit=${v(0)} noInit=${v(1)} next=${v(2)} nextInit=${v(3)}")
}
println(s"start withInit=${dut.io.withInit.peek().litValue}")
// 모든 레지스터에 9를 저장한다.
dut.io.en.poke(true.B)
dut.io.in.poke(9.U)
dut.clock.step()
show("loaded")
// in을 6, en을 0으로 두고 에지 하나 동안 리셋을 인가한다.
dut.io.en.poke(false.B)
dut.io.in.poke(6.U)
dut.reset.poke(true.B)
show("reset-before-edge")
dut.clock.step()
show("reset-after-edge")
dut.reset.poke(false.B)
dut.clock.step()
show("released")
}
}이 개념 실습하기
다음 단계로 짧은 Chisel 문제에 이 개념을 적용해 보세요.
실행하기 전에 예측해 보세요. start 줄의 withInit은 무엇일까요? 이어서 reset을 1로 바꾼
직후, 에지 전에 출력되는 reset-before-edge 줄과 reset-after-edge 줄의 네 값을 예측하세요.
확인 문제
reset을 1로 넣은 직후이고 에지는 없었습니다. reset-before-edge 줄은 무엇을 보입니까?
확인 문제
reset이 1이고 en이 0인 에지 뒤에 noInit은 무엇을 보입니까?
확인 문제
같은 reset-after-edge 줄에서 next와 nextInit은 무엇을 보입니까?
결과 확인. 실행을 누르고 다섯 줄을 비교하세요.
| 출력 줄 | 확인할 내용 |
|---|---|
start | chiseltest가 본문 전에 리셋을 인가했으므로 withInit은 5입니다 |
loaded | en이 1인 에지를 지나 네 레지스터 모두 9입니다 |
reset-before-edge | 네 값 모두 9 그대로입니다. 리셋을 인가하기만 해서는 레지스터가 바뀌지 않습니다 |
reset-after-edge | withInit은 리셋 값 5, noInit은 en이 0이라 9를 유지, next는 리셋과 무관하게 in을 따라 6, nextInit은 리셋 값 0입니다 |
released | withInit과 noInit은 en이 0이라 유지되고, nextInit은 다시 in을 따라 6이 됩니다 |
reset-before-edge 줄에서 아무 값도 바뀌지 않았으므로 이 모듈의 리셋은 클록 에지에서만
적용되는 동기 리셋입니다.
5. 리셋 값 하나를 없애 보세요
실행 패널에서 withInit의 생성을 RegInit(5.U(4.W))에서 Reg(UInt(4.W))로 바꾸고, 다시 실행하기
전에 reset-after-edge 줄을 예측해 보세요.
확인 문제
withInit을 Reg(UInt(4.W))로 만들면 reset-after-edge 줄에서 무엇을 보입니까?
6. 리셋 타입
6.1 세 가지 리셋 타입
Chisel의 리셋 타입은 세 가지이며 모두 trait Reset을 구현합니다.
| 타입 | 생성 | 생성되는 레지스터 |
|---|---|---|
Bool | Bool() | 동기 리셋 레지스터 |
AsyncReset | AsyncReset() | 비동기 리셋 레지스터 |
Reset | Reset() | 추상 리셋입니다. FIRRTL 컴파일 중 구체 타입이 추론됩니다 |
Module의 암묵적 reset은 추상 리셋이며, 그 리셋을 구동하는 신호에서 구체 타입이 추론됩니다.
| 추상 리셋을 구동하는 신호 | 추론 결과 |
|---|---|
AsyncReset, 추상 Reset, DontCare만 | AsyncReset |
Bool과 AsyncReset이 함께 | 오류 |
| 그 밖의 경우 | Bool |
최상위 모듈의 리셋은 아무것도 구동하지 않으므로 Bool이 됩니다. 그래서 최상위로 쓴 ResetDemo의
리셋은 동기 리셋이고, 출력한 CHIRRTL은 input reset : UInt<1>을 선언합니다. 추상 리셋 덕분에 같은
블록을 수정 없이 동기 리셋 설계와 비동기 리셋 설계 양쪽에 재사용할 수 있습니다.
6.2 리셋 타입 고정하기
모듈에 trait를 섞으면 암묵적 리셋의 타입을 고정할 수 있습니다.
형식
class 모듈_이름 extends Module with RequireSyncReset // reset은 Bool
class 모듈_이름 extends Module with RequireAsyncReset // reset은 AsyncResetResetDemo의 선언을 class ResetDemo extends Module with RequireAsyncReset으로 바꾸고 CHIRRTL을
출력하면 포트가 input reset : AsyncReset으로 바뀌고 레지스터 선언은 그대로 남습니다. 이 상태에서
테스트벤치를 실행하면 dut.reset.poke(true.B)에서
chiseltest.LiteralTypeException: don't know how to poke 오류로 멈춥니다. 포트 타입이 실제로
AsyncReset이 되었으므로 Bool 값을 인가할 수 없습니다.
6.3 withReset으로 일부 영역의 리셋 바꾸기
withReset은 블록 안에서 만든 레지스터가 암묵적 리셋 대신 지정한 신호를 리셋으로 쓰게 합니다.
형식
withReset(리셋_신호) {
// 이 블록 안에서 만든 레지스터는 리셋_신호로 리셋됩니다
}val r = withReset(io.clear) { RegInit(0.U(8.W)) }
r := r + 1.Uio.clear가 Bool이므로 r은 동기 리셋 레지스터가 됩니다. clear를 1로 바꿔도 r은 바로 0이
되지 않고, 다음 에지를 지난 뒤 0이 됩니다.
주의. reset.asBool과 reset.asAsyncReset 형변환은 컴파일러가 검사하지 않습니다. 리셋의 실제
방식을 알고 있을 때만 사용하세요.
7. 자주 하는 실수
7.1 리셋을 인가하자마자 값이 바뀌기를 기대하기
// 잘못된 기대: 동기 리셋은 아직 적용되지 않았습니다
dut.reset.poke(true.B)
dut.io.withInit.peek() // 이전 값 그대로
// 바른 순서: 에지를 하나 지난 뒤 확인
dut.reset.poke(true.B)
dut.clock.step()
dut.io.withInit.peek() // 리셋 값동기 리셋은 클록 에지에서만 적용됩니다. 테스트벤치에서 리셋 효과를 확인하려면 리셋을 인가한
상태로 clock.step()을 한 번 실행하세요.
8. 적용: 리셋 뒤에도 남는 플래그
아래 채점 실습 문제에는 오류 카운터 errors 옆에 오류 플래그 seen이 있습니다. 리셋 뒤 카운터는
0인데 플래그는 켜진 채이고, 첫 오류 전에는 플래그 값이 정해져 있지 않습니다. 두 선언을 3절의 표와
대조하세요. 바꿔야 할 선언은 하나입니다. 실행을 누르기 전에 출력되는 다섯 단계의 사이클별 표를
먼저 써 보세요.
9. 정리
| 규칙 | 결과 |
|---|---|
when 안에서만 연결한 레지스터 | 조건이 거짓인 사이클마다 유지. 기본값 불필요 |
| 우선순위 | 사슬의 첫 참 분기. 별개 문장은 나중 것이 이김 |
RegInit(v), RegNext(x, v) | 리셋이 1인 에지에서 v로 |
Reg(t), RegNext(x) | 리셋과 무관. 시작 값 미정 |
| 최상위의 암묵적 리셋 | 동기 리셋. poke한 뒤 step해야 효과가 보임 |
| 리셋 타입 | 암묵적 리셋은 추상 Reset이며 최상위에서는 Bool로 추론됨. RequireSyncReset과 RequireAsyncReset으로 고정하고, withReset으로 일부 영역만 바꿈 |
10. 확인 문제
-
리셋이 비동기라면
reset-before-edge줄에서 어떤 레지스터가 바뀌어야 하며, 지금의 테스트벤치로 그 동작을 직접 보일 수 없는 이유는 무엇일까요?정답 보기
비동기 리셋은 에지를 기다리지 않으므로 리셋 값이 있는
withInit과nextInit이 각각 5와 0으로 바로 바뀌어야 합니다. 하지만RequireAsyncReset으로 리셋 타입을 바꾸면 테스트벤치의dut.reset.poke(true.B)가LiteralTypeException으로 멈추므로 그 줄까지 실행되지 않습니다.