1. 기본 타입
Chisel 회로의 포트, wire, 레지스터는 모두 Chisel 데이터 타입의 값을 전달합니다. 데이터 타입은 값이
차지하는 비트 수와 그 비트를 읽는 방법, 즉 부호 없는 수, 2의 보수, 참/거짓 가운데 무엇으로 읽는지를
정합니다. 폭을 잘못 예측하면 오류 메시지 없이 비트가 사라지므로, 이 장의 폭 규칙을 정확히 익혀 두면
이후 장에서 디버깅 시간을 크게 줄일 수 있습니다. 이 장의 예제는 elaboration 중에 WIDTH 줄을,
테스트벤치에서 SIMULATE 줄을 출력합니다. 두 단계의 구분은
첫 장의 구분을
그대로 사용합니다.
1.1 UInt, SInt, Bool
Chisel 기본 타입은 비트 벡터와 그 비트를 읽는 규칙으로 이루어집니다.
형식
UInt(폭.W) // 부호 없는 정수
SInt(폭.W) // 2의 보수 부호 있는 정수
Bool() // 1비트 참/거짓val io = IO(new Bundle {
val count = Output(UInt(4.W)) // 0부터 15까지
val offset = Input(SInt(4.W)) // -8부터 7까지
val enable = Input(Bool()) // true 또는 false
})| 타입 | 비트 수 | 값의 범위 |
|---|---|---|
UInt(n.W) | n | 0부터 2n−1까지 |
SInt(n.W) | n | −2n−1부터 2n−1−1까지. 2의 보수 |
Bool() | 1 | true.B 또는 false.B. Bool은 1비트 UInt이기도 합니다 |
1.2 .W로 쓰는 폭
폭 인자는 평범한 Int가 아닙니다. .W가 Scala Int를 Chisel 폭으로 변환하며, UInt(...)는 이
폭만 받습니다. UInt(8.W)는 "부호 없는 8비트"라는 뜻입니다.
형식
n.W // Scala Int n을 Chisel 폭으로 변환val width = 12 // elaboration 중에 값이 정해진 Scala Int
val bus = UInt(width.W) // 12비트 부호 없는 타입폭은 Scala가 계산하므로 생성기 매개변수에서 가져올 수 있습니다.
class Buffer(width: Int) extends Module로 선언한 클래스는 만들어질 때 width를 받습니다. 클래스
이름 뒤 괄호 안의 이름이 생성자 매개변수이고 Int는 그 Scala 타입입니다. 클래스 안에서
UInt(width.W)로 그 폭의 포트를 만들면 new Buffer(8)과 new Buffer(16)은 같은 소스에서 나온 서로
다른 회로입니다.
주의. 모듈 내부의 wire는 폭을 생략하고 연결로부터 추론하게 할 수 있습니다. 하지만
Output(UInt())처럼 폭 없이 선언한 포트는 테스트벤치에서 Width of ... is unknown! 오류를
일으키므로, 포트 폭은 모두 명시하세요.
1.3 같은 비트, 다른 해석
비트만으로는 어떤 수인지 정해지지 않습니다. 4비트 1101은 UInt로 읽으면 13이고 SInt로 읽으면
−3입니다. 해석은 타입이 결정합니다. 이 장에서는 이 구분이 계속 쓰입니다. 형변환은 해석만 바꾸고,
확장과 절단은 비트 수를 바꿉니다.
2. 리터럴
2.1 리터럴 형식
리터럴은 Scala 값을 하드웨어 상수로 변환합니다.
형식
값.U // 부호 없는 리터럴, 최소 폭
값.U(n.W) // 부호 없는 리터럴, 폭 n
값.S // 부호 있는 리터럴, 최소 폭
값.S(n.W) // 부호 있는 리터럴, 폭 n
"hXX".U // 16진 문자열. 2진은 "b...", 8진은 "o..."
true.B / false.B // Bool 리터럴val limit = 200.U(8.W)
val mask = "hf0".U
val minus = -3.S(4.W)
val on = true.Bsuffix | 만들어지는 타입 |
|---|---|
.U | UInt |
.S | SInt |
.B | Bool |
값은 Scala에서 오므로 (width * 2).U처럼 생성기가 계산한 값도 리터럴로 만들 수 있습니다.
2.2 리터럴의 폭
폭을 적지 않은 리터럴은 값을 담는 데 필요한 최소 비트 수를 폭으로 가집니다. 부호 있는 리터럴은 부호 비트를 포함합니다. 문자열 리터럴은 수로 저장되므로 문자열의 앞자리 0은 폭에 들어가지 않습니다.
| 리터럴 | 폭 | 이유 |
|---|---|---|
0.U | 1 | 0도 1비트가 필요합니다 |
5.U | 3 | 101 |
255.U | 8 | 1111_1111 |
256.U | 9 | 1_0000_0000 |
5.S | 4 | 0101. 부호 비트를 포함합니다 |
3.S | 3 | 011 |
-1.S | 1 | 2의 보수에서는 비트 1 하나가 −1입니다 |
-8.S | 4 | 1000 |
"b0001".U | 1 | 값은 1이며 앞자리 0은 버려집니다 |
"h00ff".U | 8 | 값은 255입니다 |
8.U(6.W) | 6 | 적은 폭을 그대로 쓰고 0으로 확장합니다 |
2.3 지정한 폭의 검사
리터럴에 폭을 적으면 Chisel이 그 폭을 검사합니다.
8.U(6.W) // 올바름: 001000
8.U(3.W) // elaboration 오류: 8에는 최소 4비트가 필요함검사는 Scala 컴파일 단계가 아니라 elaboration 중에 일어납니다. Chisel이 리터럴을 만들기 전까지
8은 평범한 Scala Int입니다. 이 실패는 8절에서 직접 실행합니다.
3. 폭과 해석 바꾸기
3.1 연결에서 일어나는 확장과 절단
폭이 고정되어 있고 부호 해석이 같은 대상, 즉 UInt는 UInt에, SInt는 SInt에 폭이 다른 값을
연결하면 경고 없이 폭이 맞춰집니다. Wire(UInt())처럼 폭이 아직 추론 중인 대상은 절단하지 않고
연결된 값의 폭을 받으며, UInt를 SInt에 연결하는 것은 폭 조정이 아니라 타입 오류입니다.
| 경우 | 동작 | 예: 4비트 값을 8비트 대상에 연결 |
|---|---|---|
좁은 UInt를 넓은 대상에 | 0 확장. 새 상위 비트는 0입니다 | 1101이 0000_1101이 됩니다 |
좁은 SInt를 넓은 대상에 | 부호 확장. 새 상위 비트는 부호 비트를 복사합니다 | 1101이 1111_1101이 됩니다 |
| 넓은 값을 좁은 대상에 | 절단. 상위 비트를 버립니다 | 8비트 1100_1000을 4비트에 연결하면 1000이 남습니다 |
고정 폭 연결의 세 경우 모두 경고가 없습니다. 편리하지만 비트가 사라지는 가장 흔한 원인이기도 하므로, 포트 폭은 명시하고 값이 이상하면 폭부터 확인하세요.
3.2 식에서 폭을 명시적으로 바꾸기
식 안에서 폭을 직접 바꿀 수도 있습니다.
형식
x.pad(n) // n비트로 확장. x가 이미 n비트 이상이면 그대로
x(hi, lo) // 비트 hi부터 lo까지 선택
u.zext // UInt를 한 비트 넓은 SInt로. 값은 그대로val x = io.x // 8비트 UInt
x.pad(12) // 12비트, 0 확장
x.pad(4) // 여전히 8비트
x(3, 0) // 하위 4비트
io.nibble.zext // 4비트 UInt 13이 5비트 SInt 13이 됨pad는 타입을 따릅니다. UInt에서는 0 확장, SInt에서는 부호 확장을 합니다. 비트 선택
x(hi, lo)는 Bool·UInt와 연산자
장에서 다른 비트 필드 연산자와 함께 다룹니다.
3.3 형변환과 비트 해석
asUInt와 asSInt는 폭과 모든 비트를 유지하고 해석만 바꿉니다.
형식
x.asUInt // x의 비트를 부호 없이 읽음
x.asSInt // x의 비트를 부호 있게 읽음val raw = "hc5".U(8.W) // 1100_0101 = 197
raw.asSInt // 같은 8비트를 -59로 읽음형변환은 폭 인자를 받지 않습니다. 다른 폭이 필요하면 결과를 그 폭의 대상에 연결하거나 pad를
사용하세요. 확장이나 절단은 그 단계에서 일어납니다.
주의. 확장과 형변환의 순서에 따라 값이 달라집니다. SInt를 확장한 뒤 형변환한 값과, 형변환한
뒤 확장한 값은 서로 다릅니다. 4비트 −3을 UInt로 형변환한 뒤 넓히면 13이고, 먼저 8비트 부호 있는
값으로 넓힌 뒤 형변환하면 253입니다. 다음 예제에서 두 순서를 모두 확인합니다.
4. 예제: 리터럴 폭과 확장
Literals.scala는 두 부분으로 되어 있습니다. lits는 리터럴 표기와 리터럴을 짝지은 목록이며,
반복문이 Chisel이 정한 폭을 출력합니다. .getWidth는 하드웨어 값의 폭을 Scala 숫자로 반환합니다.
아래쪽에서는 4비트 SInt 입력 in이 출력 세 개를 구동합니다. 8비트 SInt 출력 wide, asUInt를
거친 4비트 UInt 출력 bits, 같은 형변환을 거친 8비트 UInt 출력 widebits입니다. 테스트벤치는
in에 5, −3, −8을 차례로 인가합니다.
import chisel3._
class Literals extends Module {
val io = IO(new Bundle {
val in = Input(SInt(4.W))
val wide = Output(SInt(8.W))
val bits = Output(UInt(4.W))
val widebits = Output(UInt(8.W))
})
// 리터럴 폭은 생성기가 실행되는 동안 결정된다.
val lits = Seq(
"5.U" -> 5.U,
"5.S" -> 5.S,
"-8.S" -> -8.S,
"\"b1010\".U" -> "b1010".U,
"\"h00ff\".U" -> "h00ff".U,
"8.U(6.W)" -> 8.U(6.W),
"true.B" -> true.B
)
for ((text, lit) <- lits) {
println(s"WIDTH $text = ${lit.getWidth}")
}
// 좁은 SInt를 넓은 SInt에 연결하면 부호 확장된다.
io.wide := io.in
// asUInt는 비트와 폭을 그대로 유지한다.
io.bits := io.in.asUInt
// 좁은 UInt를 넓은 UInt에 연결하면 0으로 확장된다.
io.widebits := io.in.asUInt
}import chisel3._
import chiseltest._
import chiseltest.RawTester.test
object Main extends App {
test(new Literals) { dut =>
for (value <- Seq(5, -3, -8)) {
dut.io.in.poke(value.S)
val wide = dut.io.wide.peek().litValue
val bits = dut.io.bits.peek().litValue
val widebits = dut.io.widebits.peek().litValue
println(s"SIMULATE in=$value wide=$wide bits=$bits widebits=$widebits")
}
}
}이 개념 실습하기
다음 단계로 짧은 Chisel 문제에 이 개념을 적용해 보세요.
실행하기 전에 예측해 보세요. WIDTH 일곱 줄의 값은 각각 무엇이고, WIDTH 줄은 SIMULATE
줄보다 먼저 나올까요, 나중에 나올까요? 입력이 −3이면 wide, bits, widebits는 각각 어떤
값일까요?
확인 문제
리터럴 -8.S의 폭은 몇 비트입니까?
확인 문제
in = -3일 때 bits와 widebits는 각각 무엇입니까?
결과 확인. 실행을 누르고 WIDTH 줄 일곱 개를 2.2절의 표와 맞춰 읽은 뒤, −3이 회로를 지나는
과정을 따라가며 출력과 비교하세요.
| 단계 | 비트 | 해석한 값 |
|---|---|---|
in, 4비트 SInt | 1101 | −3 |
wide, 8비트로 부호 확장 | 1111_1101 | −3 |
in.asUInt를 bits에 연결 | 1101 | 13 |
in.asUInt를 widebits에 연결, 0 확장 | 0000_1101 | 13 |
| 출력 줄 | 확인할 내용 |
|---|---|
WIDTH 줄 | 2.2절 표의 폭 3, 4, 4, 4, 8, 6, 1입니다. 폭은 elaboration 중에 정해지므로 맨 앞에 출력됩니다 |
in=5 | 양수는 세 경로를 모두 거쳐도 값이 그대로입니다 |
in=-3 | wide는 −3을 유지하고, 부호 없는 두 출력은 13입니다 |
in=-8 | 1000을 부호 없이 읽으면 8이므로 bits와 widebits는 8입니다 |
widebits는 bits보다 넓지만, 도착하는 값이 이미 UInt이므로 연결은 0으로 채우고 값은 바뀌지
않습니다.
직접 바꿔 보세요.
- 마지막 연결을
io.widebits := io.wide.asUInt로 바꾸세요. 이번에는 부호 확장 뒤에 형변환하므로 8비트 패턴1111_1101을 다시 해석합니다.widebits는 −3에서 253, −8에서 248이 되고,bits는 13과 8을 유지합니다. 입력 비트는 그대로이고 확장과 형변환의 순서만 바뀌었습니다. lits목록에"b0001".U를 추가하세요. 문자열의 앞자리 0은 값에 포함되지 않으므로WIDTH줄은 1을 보고합니다.
5. 연산자의 결과 폭
연산자마다 결과 폭을 정하는 규칙이 있으며, elaboration 중에 적용됩니다.
| 식 | 결과 폭 |
|---|---|
x + y, x - y | max(w(x), w(y)) |
x +& y, x -& y | max(w(x), w(y)) + 1 |
x * y | w(x) + w(y) |
x & y, x | y, x ^ y | max(w(x), w(y)) |
Scala Int n에 대한 x << n | w(x) + n |
Scala Int n에 대한 x >> n | max(w(x) - n, 0). n >= w(x)이면 0 |
x === y, x < y 등 비교 | 1 |
Cat(x, y) | w(x) + w(y) |
val x = io.x // 8비트
x + x // 8비트: 자리올림을 버림
x +& x // 9비트: 자리올림을 보존
x * x // 16비트
x << 3 // 11비트: 0xc5 << 3은 1576
x >> 3 // 5비트: 0xc5 >> 3은 24+는 2최대 폭을 법으로 순환합니다. 프로세서의 덧셈기는 데이터 경로와 폭이 같고 넘친
비트를 버리므로, Chisel도 이 동작을 기본으로 정했습니다. 확장 덧셈 +&는 자리올림을 비트 하나에
보존합니다. -와 -&도 같은 규칙을 따릅니다. := 왼쪽의 포트를 넓혀도 자리올림은 복원되지
않습니다. 연결이 값을 보기 전에 연산자가 이미 좁은 결과를 만들었습니다.
6. 예제: +, +&, 절단 비교하기
Adder.scala는 같은 합을 두 번 계산합니다. plainSum은 +, expandSum은 +&를 사용하며, 둘 다
9비트 출력 포트에 연결됩니다. 세 번째 출력 low는 4비트이며 8비트 입력 a를 그대로 받습니다.
println은 elaboration 중에 두 합의 폭을 출력합니다.
import chisel3._
class Adder extends Module {
val io = IO(new Bundle {
val a = Input(UInt(8.W))
val b = Input(UInt(8.W))
val plain = Output(UInt(9.W))
val expand = Output(UInt(9.W))
val low = Output(UInt(4.W))
})
val plainSum = io.a + io.b
val expandSum = io.a +& io.b
// 연산자 결과 폭은 elaboration 중에 결정된다.
println(s"WIDTH a+b=${plainSum.getWidth} a+&b=${expandSum.getWidth}")
// +가 이미 버린 자리올림은 9비트 포트로도 복원되지 않는다.
io.plain := plainSum
io.expand := expandSum
// 8비트를 4비트 포트에 연결하면 하위 비트만 남는다.
io.low := io.a
}import chisel3._
import chiseltest._
import chiseltest.RawTester.test
object Main extends App {
test(new Adder) { dut =>
for ((a, b) <- Seq((100, 27), (200, 100), (0xab, 0x10))) {
dut.io.a.poke(a.U)
dut.io.b.poke(b.U)
val plain = dut.io.plain.peek().litValue
val expand = dut.io.expand.peek().litValue
val low = dut.io.low.peek().litValue
println(s"SIMULATE a=$a b=$b plain=$plain expand=$expand low=$low")
}
}
}이 개념 실습하기
다음 단계로 짧은 Chisel 문제에 이 개념을 적용해 보세요.
실행하기 전에 예측해 보세요. 두 합 포트는 모두 9비트입니다. WIDTH 줄은 a + b와 a +& b의
폭을 각각 얼마로 보고할까요? 이어서 a = 200, b = 100인 행을 예측하세요. 참값 300은
1_0010_1100입니다.
확인 문제
200 + 100에 대해 plain은 무엇을 보입니까?
확인 문제
low는 a에서 구동되는 UInt(4.W)입니다. a = 200일 때 무엇을 보입니까?
결과 확인.
| 출력 | 값 | 이유 |
|---|---|---|
WIDTH | 8과 9 | 5절의 폭 규칙입니다 |
plain | 44 | +가 하위 8비트 0010_1100만 만들었습니다. 9비트 포트는 44 앞에 0을 채웠고, 자리올림은 포트에 도달하기 전에 이미 사라졌습니다 |
expand | 300 | +&가 9비트를 모두 만들었습니다 |
low | 8 | 200은 1100_1000이고, 4비트로 절단하면 1000이 남습니다 |
다른 행도 같은 규칙을 따릅니다. 100 + 27은 8비트에 담기므로 plain과 expand가 모두 127입니다.
0xab의 low는 하위 4비트 0xb, 즉 11입니다. 어느 행에서도 경고는 출력되지 않습니다. 회로는
적법하고 비트를 버릴 뿐입니다.
직접 바꿔 보세요. 먼저 io.a + io.b를 io.a +& io.b로 바꿔 두 합이 모두 확장 덧셈을 쓰게 하고
다시 실행하세요. 바꾸기 전에 어느 행이 달라질지 정하세요.
확인 문제
두 합이 모두 +&일 때 plain 값이 이전과 달라지는 행은 어느 것입니까?
이어서 두 가지를 더 바꿔 보세요.
+를-로,+&를-&로 바꾸고 테스트벤치에 입력 쌍(3, 5)를 추가하세요.3 − 5는 음수이지만 피연산자가UInt입니다.-는 8비트 안에서 순환해 254를,-&는 9비트 결과 510을 출력합니다. 결과가 여전히 부호 없는 값이므로 추가된 비트는 부호가 아닙니다. 음수 결과가 필요하면 피연산자를SInt로 선언하세요.low를UInt(8.W)로 바꾸세요. 버려지는 비트가 없으므로 모든 행에서low가a와 같아집니다.
7. Chisel 타입과 하드웨어
UInt(8.W)는 부호 없는 8비트를 기술하는 Scala 객체입니다. 다음 구문 가운데 하나가 타입을 회로에
결합해야 하드웨어가 됩니다.
| 코드 | 정체 |
|---|---|
UInt(8.W) | Chisel 타입. 회로의 어떤 노드도 아닙니다 |
IO(Output(UInt(8.W))) | 포트 |
Wire(UInt(8.W)) | wire. Mux·when·Wire와 모든 경로의 기본값 장에서 다룹니다 |
0.U(8.W) | 값이 정해진 상수 노드 |
:=의 양쪽에는 하드웨어만 올 수 있습니다. 그런데 UInt(8.W)와 0.U(8.W)는 Scala 타입이 모두
UInt이므로 Scala 컴파일러는 둘을 구분하지 못합니다. 따라서 타입과 하드웨어를 혼동한 코드는
컴파일을 통과하고 elaboration 중에 오류가 검출됩니다.
8. 실패 예제: 폭이 부족한 리터럴
다음 예제는 일부러 elaboration 오류를 발생시킵니다. 값 8을 3비트 리터럴로 요구합니다. 실행을
누르기 전에 Scala 컴파일러와 elaboration 가운데 어느 쪽이 오류를 검출할지 정해 보세요.
확인 문제
TooNarrow.scala의 오류는 어느 단계에서 보고됩니까?
import chisel3._
class TooNarrow extends Module {
val io = IO(new Bundle {
val out = Output(UInt(3.W))
})
// 8에는 4비트가 필요하므로 3비트 리터럴에 담을 수 없다.
io.out := 8.U(3.W)
}
object Main extends App {
println(circt.stage.ChiselStage.emitCHIRRTL(new TooNarrow))
}| 확인 항목 | 내용 |
|---|---|
| 잘못된 코드 | 8.U(3.W). 값 8에는 최소 4비트가 필요합니다 |
| 실패 단계 | elaboration입니다. 8.U(3.W)는 타입이 맞는 Scala이므로 컴파일은 통과하고, 스택 추적은 모듈 안의 연결 줄을 가리킵니다 |
| 오류 메시지 | IllegalArgumentException: requirement failed: The literal value 8 was elaborated with a specified width of 3 bits, but at least 4 bits are required |
| 고치는 방법 | 8.U(4.W)로 씁니다 |
참고. 고친 4비트 리터럴을 3비트 출력에 연결하는 것은 허용되며, 경고 없이 절단되어 출력은 0이
됩니다. 6절의 low와 같은 규칙입니다. 리터럴에 적은 폭은 검사되고, 연결에서 생기는 폭 차이는
검사되지 않습니다.
9. 실패 예제: 하드웨어 대신 사용한 Chisel 타입
다음 예제도 일부러 elaboration 오류를 발생시킵니다. UInt(8.W)를 Scala val에 결합한 뒤
출력에 연결합니다.
import chisel3._
class BareType extends Module {
val io = IO(new Bundle {
val out = Output(UInt(8.W))
})
// UInt(8.W)는 타입을 기술할 뿐이며 회로의 어떤 노드도 아니다.
val zero = UInt(8.W)
io.out := zero
}
object Main extends App {
println(circt.stage.ChiselStage.emitCHIRRTL(new BareType))
}| 확인 항목 | 내용 |
|---|---|
| 잘못된 코드 | val zero = UInt(8.W) 뒤의 io.out := zero |
| 실패 단계 | elaboration입니다. val 선언은 평범한 Scala이므로 컴파일됩니다 |
| 오류 메시지 | ExpectedHardwareException: data to be connected 'UInt<8>' must be hardware, not a bare Chisel type. 메시지는 Wire(_)나 IO(_)로 감싸라고 제안합니다 |
| 고치는 방법 | 값이 정해진 하드웨어인 리터럴 0.U(8.W)를 사용합니다 |
포트 선언도 같은 규칙을 따릅니다. Output(UInt(8.W))는 IO(...)가 결합하기 전까지 타입일 뿐입니다.
10. 적용: 자리올림 보존하기
아래 채점 실습 문제는 4비트 덧셈기입니다. 5비트 sum 포트가 있는데 참값이 16 이상이면 정확히 16
작게 나옵니다. 포트는 충분히 넓고 연산자가 좁습니다. 이 페이지의 둘째 문제인 부호 있는 오프셋은
3.3절의 형변환 순서를 주소 계산에 적용하는 문제입니다. 음수인 4비트 오프셋이 넓어지기 전에 부호
없는 값으로 읽히므로 확장이 0으로 채워지고 주소가 앞으로 이동합니다. 채점 문제를 마친 뒤 도전하세요.
11. 자주 하는 실수
11.1 .W 없이 폭 쓰기
// 잘못된 코드: 8은 폭이 아니라 Scala Int
val a = Input(UInt(8))
// 바른 코드
val a = Input(UInt(8.W))Scala 컴파일러는 type mismatch; found: Int(8), required: chisel3.Width를 보고합니다.
11.2 5.U(3.W) 대신 5.U(3) 쓰기
// 잘못된 코드: 컴파일되지만 "5의 비트 3"이라는 1비트 값 0이 됨
io.out := 5.U(3)
// 바른 코드: 폭이 3인 리터럴 5
io.out := 5.U(3.W)이 실수는 컴파일 오류가 나지 않습니다. UInt에 Int를 적용하면 비트 선택이 되므로 5.U(3)은
101의 비트 3, 즉 0입니다. 컴파일러는 Passing an Int to .U is usually a mistake로 시작하는 경고와
함께 .U(3.W)를 제안합니다. 실수를 알려 주는 단서는 이 경고뿐이므로 컴파일 경고를 꼭 읽으세요.
11.3 넓은 포트가 자리올림을 살려 주리라 기대하기
// 잘못된 코드: +의 결과는 이미 8비트이고 9비트 포트는 위에 0을 채움
io.sum9 := io.a + io.b
// 바른 코드: +&가 자리올림을 보존함
io.sum9 := io.a +& io.b결과 폭은 구동하는 포트가 아니라 연산자가 정합니다. 합이 정확히 2n만큼 작게 나오면
+&여야 할 +를 찾아보세요.
11.4 부호 확장 뒤에 형변환하기
// -3에서 253: 부호 확장된 1111_1101을 읽음
io.widebits := io.wide.asUInt
// -3에서 13: 원래 4비트 1101을 읽음
io.widebits := io.in.asUInt두 줄 모두 그 자체로 틀린 코드는 아니며 서로 다른 값을 계산합니다. 부호 있는 값이 필요한지, 원래 비트 패턴이 필요한지 먼저 정한 뒤 순서를 고르세요.
11.5 타입을 그대로 연결하기
// 잘못된 코드: 하드웨어가 아니라 타입
val zero = UInt(8.W)
io.out := zero
// 바른 코드: 리터럴은 하드웨어
io.out := 0.U(8.W)elaboration 예외는 타입 UInt<8>을 지목하고 하드웨어여야 한다고 알려 줍니다.
12. 정리
| 주제 | 핵심 |
|---|---|
| 기본 타입 | UInt(n.W), SInt(n.W), Bool(). 비트와 해석 규칙의 조합입니다 |
| 폭 | .W가 Scala Int를 폭으로 변환합니다. 포트와 리터럴 선언에 쓰고, 생성자 Int에서 가져올 수 있습니다 |
| 리터럴 | .U, .S, .B. 폭을 적지 않으면 최소 폭이고, 적은 폭은 elaboration 중에 검사됩니다 |
| 연결 | 고정 폭 대상에서 좁은 값은 확장되고(UInt는 0, SInt는 부호 비트) 넓은 값은 절단됩니다. 같은 부호 종류끼리만이며 경고는 없습니다 |
| 형변환 | asUInt, asSInt는 비트와 폭을 유지하고 해석만 바꿉니다 |
| 연산자 | +는 피연산자 폭을 유지하고 +&는 한 비트를 더하며, *는 폭을 합하고 비교는 1비트입니다 |
| 타입과 하드웨어 | UInt(8.W)는 타입이고, 포트, wire, 레지스터, 리터럴이 하드웨어입니다 |
13. 확인 문제
-
"h00ff".U의 폭이 16이 아니라 8인 이유는 무엇일까요?정답 보기
Chisel은 문자열 리터럴을 수로 저장하며, 수에는 앞자리 0이 없습니다. 값 255를 담으려면 8비트가 필요합니다.
-
−1을 담은 4비트
SInt를asUInt로 8비트UInt포트에 연결하면 어떤 값이 나타날까요? 8비트SInt를 먼저 거친 뒤 형변환하면 무엇이 달라질까요?정답 보기
−1은
1111입니다.asUInt는 이를 15로 읽고 8비트 포트가 0 확장하므로 포트에는 15가 나타납니다. 부호 확장을 먼저 하면1111_1111이 되고, 형변환은 이를 255로 읽습니다. -
8.U(3.W)는 실패하는데8.U(4.W)를 3비트 포트에 연결하는 것은 실패하지 않는 이유는 무엇일까요?정답 보기
리터럴에 적은 폭은
elaboration중에 검사되고, 8은 3비트에 담기지 않습니다. 연결에서 생기는 폭 차이는 검사되지 않으므로 4비트 리터럴은 하위 3비트인 0으로 절단됩니다. -
x,y,z가 모두 8비트UInt일 때x * y + z의 폭은 얼마일까요?정답 보기
16비트입니다.
x * y는 8 + 8 = 16비트이고,+는 넓은 피연산자의 폭인max(16, 8)을 따릅니다. -
UInt(8.W)와0.U(8.W)는 Scala 타입이 모두UInt입니다. 어느 쪽이 하드웨어이며, 둘을 혼동하면 어떻게 검출될까요?정답 보기
0.U(8.W)가 상수 노드인 하드웨어이고UInt(8.W)는 타입일 뿐입니다. Scala 컴파일러는 둘을 구분하지 못하므로elaboration중에ExpectedHardwareException으로 검출됩니다.
아래 실습 문제 두 개는 이 장의 폭 규칙을 적용합니다. 채점 문제는 5비트 합 포트의 폭이 충분한데도 16 이상인 합이 모두 정확히 16만큼 작게 나옵니다. 다른 문제는 8비트 주소에 부호 있는 오프셋을 더합니다. 양수 오프셋은 올바르게 동작하지만 음수 오프셋을 더하면 주소가 뒤로 가지 않고 오히려 앞으로 이동합니다.