1. Chisel 모듈은 회로를 만드는 Scala 프로그램입니다
Chisel은 Scala 안에 구현된 하드웨어 구성 언어입니다. Chisel 설계는 평범한 Scala 프로그램이며, 프로그램을 실행하면 회로가 만들어지고 그 회로를 시뮬레이션하거나 Verilog로 변환합니다. Scala를 미리 알 필요는 없습니다. 이 과정은 Scala 문법을 각 장에서 처음 쓰기 직전에 필요한 만큼만 설명합니다. Chisel을 처음 배울 때 겪는 혼란은 대부분 "회로를 만드는 프로그램"과 "만들어진 회로"를 구분하지 않는 데서 생기므로, 이 장은 그 구분부터 설명합니다.
Chisel은 두 가지를 분리합니다. 하드웨어 타입과 연산자는 모두 합성 가능한 이진 논리를 기술하고,
그것을 조립하는 프로그램은 Scala입니다. printf, assert, stop 같은 시뮬레이션 전용 구문도
있지만 하드웨어 구문과 뚜렷이 구분됩니다. 그리고 Scala의 반복문, 함수, 클래스, 컬렉션을 어떤
회로를 만들지 결정하는 데 그대로 사용할 수 있습니다. 매개변수에 따라 서로 다른 설계를 만드는
Chisel 프로그램을 회로 생성기라고 합니다.
1.1 elaboration과 시뮬레이션
Chisel 실행은 두 단계로 나뉘며, 두 단계는 겹치지 않습니다.
| 단계 | 실행 대상 | 결과 |
|---|---|---|
elaboration | 사용자가 작성한 Scala 코드. 모듈 인스턴스마다 한 번 실행됩니다 | 포트, 연산자, wire, 레지스터로 이루어진 회로 그래프 |
| 시뮬레이션 | 생성된 회로. 사이클 단위로 실행됩니다 | 테스트벤치가 관찰하는 포트 값 |
elaboration 중에 val sum = a + b는 두 수를 더하지 않습니다. 입력이 a와 b인 덧셈기 노드를
회로에 추가하고, 그 출력에 Scala 이름 sum을 결합합니다. 이 문장을 Scala for 반복문으로 감싸면
반복 횟수만큼 덧셈기가 생깁니다. elaboration이 끝나면 반복문은 사라지고 회로에는 생성된 노드만
남습니다. 시뮬레이션은 그 뒤에 시작되고, 이때부터 비로소 신호에 값이 흐릅니다.
참고. 공식 문서는 Chisel의 대상을 이진 동기 논리로 정합니다. 삼상태 버스처럼 방향이 없는 네트를
위한 Analog 타입이 있지만 표준 설계 흐름에서 지원이 약하며, 이 과정에서는 사용하지 않습니다.
1.2 Scala 값과 Chisel 값
두 단계에 맞춰 타입도 두 종류로 나뉩니다.
| 종류 | 예 | 값이 존재하는 시점 |
|---|---|---|
| Scala 타입 | Int, Boolean, Seq, String | elaboration 중. 생성기가 바로 계산에 사용합니다 |
| Chisel 타입 | UInt, SInt, Bool | 하드웨어 노드의 값은 회로가 동작할 때, 즉 이 과정에서는 시뮬레이션 중에만 존재합니다. 3.U 같은 리터럴은 elaboration 중에도 값을 가집니다 |
elaboration은 Chisel 값의 타입과 폭을 확인할 수 있지만, wire가 전달할 값은 읽을 수 없습니다. 그
값은 아직 존재하지 않습니다. 이 장 뒤쪽의 실패 예제 두 개는 모두 이 표의 구분을 어긴 코드입니다.
2. Chisel 모듈의 구조
2.1 모듈 정의
하드웨어 블록 하나는 Module을 상속한 Scala 클래스입니다. 클래스 본문은 elaboration 중에 한 번
실행되며, 본문의 문장마다 포트, 노드, 연결이 회로에 추가됩니다.
형식
import chisel3._
class 모듈_이름(매개변수) extends Module {
val io = IO(new Bundle {
// 포트 선언
})
// 회로 기술
}다음은 입력 두 개의 AND를 출력하는 가장 작은 모듈입니다.
import chisel3._
class AndGate extends Module {
val io = IO(new Bundle {
val a = Input(Bool())
val b = Input(Bool())
val y = Output(Bool())
})
io.y := io.a && io.b
}| 코드 | Scala에서의 뜻 | 이 코드에서 하는 일 |
|---|---|---|
import chisel3._ | 패키지의 이름을 가져옵니다 | Module, IO, UInt, Bool, := 등 Chisel 구문을 사용할 수 있게 합니다 |
class AndGate extends Module | Module을 상속하는 클래스를 선언합니다 | AndGate라는 하드웨어 블록을 선언합니다. 본문은 elaboration 중 한 번 실행됩니다 |
val io = IO(...) | val은 값에 이름을 한 번 붙이는 선언입니다 | 모듈의 포트를 선언합니다. 이름은 관례적으로 io를 씁니다 |
new Bundle { ... } | 나열한 필드를 가진 객체를 만듭니다 | 이름 붙은 포트 여러 개를 값 하나로 묶습니다. io.a, io.y로 읽습니다 |
Input(...), Output(...) | 평범한 함수 호출입니다 | 포트의 방향을 정합니다 |
io.y := io.a && io.b | io.y의 method 호출입니다 | 두 입력의 AND를 출력에 연결합니다 |
참고. 모든 Module은 암묵적인 클록과 리셋을 가집니다. AND 게이트는 이 둘을 사용하지 않으며,
레지스터·RegInit·클록 진행 장의 레지스터가 사용합니다.
2.2 포트: IO, Input, Output
포트는 방향을 붙인 Chisel 타입이며, IO가 그 타입을 모듈 경계에 결합합니다.
형식
val 이름 = Input(Chisel_타입)
val 이름 = Output(Chisel_타입)val io = IO(new Bundle {
val data = Input(UInt(8.W)) // 8비트 부호 없는 입력
val valid = Input(Bool()) // 1비트 입력
val out = Output(UInt(8.W)) // 8비트 부호 없는 출력
})UInt(8.W)는 "폭이 8비트인 부호 없는 정수"라는 뜻입니다. .W는 Scala Int 8을 Chisel 폭으로
변환합니다. Bool()은 비트 하나입니다. 모듈 안에서는 io.data, io.out처럼 io를 거쳐 입력을 읽고
출력을 구동합니다. Bool, UInt와 그 연산자는
Bool·UInt와 연산자 장에서, 폭과 SInt와 리터럴은
폭·SInt·산술 장에서 자세히 다룹니다.
2.3 :=로 연결하기
연결 연산자 :=는 "오른쪽이 왼쪽을 구동한다"는 연결을 회로 그래프에 기록합니다. 회로에 놓인
선처럼 주는 쪽이 받는 쪽을 항상 구동합니다.
형식
대상 := 구동원io.out := io.data // 입력을 그대로 출력
val doubled = io.data + io.data
io.out := doubled // 같은 대상에 나중에 쓴 연결이 적용됨| 구문 | 동작 | 단계 |
|---|---|---|
val 이름 = 식 | 식의 하드웨어를 만들고 Scala 이름을 결합합니다. 그 자체로는 배선이 아닙니다 | elaboration |
대상 := 구동원 | 구동원에서 대상으로 가는 연결을 추가합니다 | elaboration에 기록되고 회로에서 동작합니다 |
:=의 왼쪽에는 모듈이 구동할 수 있는 하드웨어, 즉 출력 포트나 wire가 와야 합니다. Scala의 =는
회로에 아무것도 추가하지 않고 이름만 결합합니다. val x = io.in + io.in은 덧셈기 하나를 만들고 그
출력을 x라고 부릅니다. 두 연결이 같은 대상을 구동하면 소스에서 나중에 쓴 연결이 적용됩니다.
Mux·when·Wire와 모든 경로의 기본값 장에서 이 규칙을
자세히 사용합니다.
2.4 생성기 매개변수
모듈은 Scala 클래스이므로 생성자 매개변수, 즉 클래스 이름 뒤 괄호 안의 이름도 평범한 Scala
값입니다. 매개변수는 elaboration 중에만 존재하며, 어떤 하드웨어를 만들지 결정합니다.
형식
class 모듈_이름(매개변수: Scala_타입) extends Module { ... }class Buffer(width: Int, withParity: Boolean) extends Module {
val io = IO(new Bundle {
val in = Input(UInt(width.W))
val out = Output(UInt(width.W))
})
io.out := io.in
if (withParity) {
println("building a parity port") // elaboration 중에만 실행
}
}new Buffer(16, false)와 new Buffer(8, true)는 같은 소스에서 서로 다른 회로를 만듭니다.
withParity는 생성기가 실행되는 동안 값이 정해진 Scala Boolean이므로 Scala if로 분기합니다.
2.5 가장 작은 테스트벤치
이 과정의 예제는 chiseltest로 모듈을 구동합니다. 테스트벤치는 Scala object Main extends App입니다.
object는 인스턴스가 하나뿐인 클래스이고, extends App은 그 본문을 실행이 시작되면 바로 실행되는
프로그램으로 만듭니다. 본문은 모듈을 elaboration한 뒤 시뮬레이션합니다.
형식
test(new 모듈_이름(...)) { dut =>
dut.io.포트.poke(값)
dut.io.포트.peek()
}import chisel3._
import chiseltest._
import chiseltest.RawTester.test
object Main extends App {
test(new AndGate) { dut =>
dut.io.a.poke(true.B)
dut.io.b.poke(false.B)
println(s"y=${dut.io.y.peek().litValue}")
}
}| 호출 | 역할 |
|---|---|
test(new AndGate) { dut => ... } | AndGate를 elaboration하고 시뮬레이터를 시작한 뒤, 중괄호 안의 블록을 검사 대상 회로 dut와 함께 실행합니다 |
poke(값) | true.B, 3.U 같은 하드웨어 리터럴로 입력 포트를 구동합니다 |
peek() | 포트의 현재 값을 읽습니다 |
.litValue | 읽은 리터럴을 출력할 수 있는 Scala BigInt로 변환합니다 |
s"y=${...}" | $ 자리에 값을 끼워 넣는 Scala 문자열입니다. ${식}은 어떤 식의 값이든 끼워 넣습니다 |
참고. poke와 peek는 시뮬레이션 중에 실행됩니다. 모듈 본문의 나머지 문장은 println을
포함해 모두 그보다 먼저 elaboration 중에 실행되었습니다.
3. 실행 예제의 빌드 과정
이 과정의 모든 예제는 같은 명령을 사용합니다. 명령은 .scala 파일을 모두 컴파일한 뒤 Main을
실행합니다.
CP=$(printf '%s:' $CHISEL_JARS/*.jar)
java -cp "$CP" scala.tools.nsc.Main -classpath "$CP" \
-Xplugin:$(ls $CHISEL_JARS/chisel-plugin*.jar) -language:reflectiveCalls \
-d classes *.scala
java -cp "$CP:classes" Main| 명령 | 역할 |
|---|---|
첫 번째 java | Scala 컴파일러입니다. 샌드박스에 scalac 실행 스크립트가 없으므로 컴파일러 main 클래스를 직접 호출합니다 |
-Xplugin:...chisel-plugin... | Chisel 컴파일러 플러그인입니다. 생성 회로에서 val 이름을 복원합니다 |
-language:reflectiveCalls | 익명 new Bundle { ... } 포트 목록의 필드 접근을 허용합니다 |
두 번째 java | Main을 실행합니다. chiseltest가 모듈을 elaboration하고, JVM 시뮬레이터 treadle에서 테스트 본문을 실행합니다 |
오류는 세 단계에서 발생할 수 있으며, 메시지 형태로 단계를 구분합니다.
| 메시지 형태 | 실패 단계 |
|---|---|
File.scala:12: error: ... | Scala 컴파일 |
모듈을 구성하는 중 Exception in thread "main" | elaboration |
expect 실패나 출력의 틀린 값 | 시뮬레이션 |
4. 예제: 생성기 하나를 두 단계로 추적하기
Doubler는 Scala Int 매개변수 stages를 받습니다. 생성자 안의 var x는 처음에 입력 포트를
가리킵니다. var는 val과 달리 다른 대상으로 옮겨 붙일 수 있는 이름입니다.
for (i <- 1 to stages)는 본문을 i = 1, 2, …, stages에 대해 반복합니다. 반복할 때마다 직전 노드를
두 입력으로 받는 덧셈기가 x = x + x로 새로 생기고, x는 그 덧셈기의 출력을 가리키므로 덧셈기
stages개가 사슬처럼 이어집니다. 반복문 안의 println은 평범한 Scala 문장이므로 반복문이 실행되는
동안 출력됩니다. 모듈을 먼저 읽고, 이어서 Doubler(3)을 만들어 입력 세 개를 인가하는 테스트벤치를
읽으세요. for (value <- Seq(1, 3, 5))는 나열한 세 값에 대해 블록을 한 번씩 실행합니다.
import chisel3._
class Doubler(stages: Int) extends Module {
val io = IO(new Bundle {
val in = Input(UInt(8.W))
val out = Output(UInt(8.W))
})
// Scala var는 실행 중의 값이 아니라 하드웨어 노드에 대한 참조를 저장한다.
var x = io.in
for (i <- 1 to stages) {
println(s"ELABORATE adder $i")
x = x + x
}
io.out := x
}import chisel3._
import chiseltest._
import chiseltest.RawTester.test
object Main extends App {
test(new Doubler(3)) { dut =>
for (value <- Seq(1, 3, 5)) {
dut.io.in.poke(value.U)
println(s"SIMULATE in=$value out=${dut.io.out.peek().litValue}")
}
}
}이 개념 실습하기
다음 단계로 짧은 Chisel 문제에 이 개념을 적용해 보세요.
배선은 마지막 줄 io.out := x 하나입니다. 그 앞의 코드는 모두 무엇을 배선할지 결정하는 코드입니다.
실행하기 전에 예측해 보세요. Doubler의 반복문은 덧셈기 하나마다 ELABORATE 줄을 하나
출력하고, 테스트벤치는 넣은 값마다 SIMULATE 줄을 하나 출력합니다.
확인 문제
ELABORATE 줄은 몇 개이고 어디에 나옵니까?
확인 문제
stages = 3일 때 in = 3에 대한 out은 무엇입니까?
결과 확인. 실행을 누르세요. 실행 패널이 두 파일을 컴파일하고 Doubler(3)을 elaboration한
뒤 시뮬레이션합니다. 출력을 다음 표와 비교하세요.
| 출력 | 확인할 내용 |
|---|---|
맨 앞의 ELABORATE 세 줄 | new Doubler(3)을 구성하는 동안 Scala 반복문이 한 번 실행되어 덧셈기 세 개를 만들었습니다 |
SIMULATE 줄 사이에 ELABORATE가 없음 | 모든 poke가 같은 덧셈기 세 개를 사용합니다. 반복문은 다시 실행되지 않습니다 |
| 입력 1, 3, 5에 대해 출력 8, 24, 40 | 두 배를 세 번 거치므로 입력에 8을 곱한 값입니다 |
주의. 출력이 나왔다는 사실은 chiseltest의 treadle 시뮬레이터에서 회로가 올바르게
시뮬레이션되었다는 뜻입니다. Chisel의 하드웨어 구문은 합성 가능한 논리를 기술하지만, 이 과정은
시뮬레이션만 하고 합성은 실행하지 않습니다. println은 회로를 만들고 검사하는 프로그램에만 있고
회로 안에는 없습니다.
직접 바꿔 보세요. 먼저 Tb.scala의 new Doubler(3)을 new Doubler(2)로 바꾸고 다시
실행하세요. 하드웨어 문법이 아니라 Scala 숫자 하나를 바꿨는데 다른 회로가 만들어집니다.
확인 문제
stages = 2일 때 in = 3에 대한 out은 무엇입니까?
이어서 두 가지를 더 바꿔 보세요.
- 매개변수를
Doubler(5)로 바꾸세요.ELABORATE줄이 다섯 개가 되고 출력은 입력의 32배인 32, 96, 160이 됩니다. Doubler(5)를 유지한 채 인가 값 목록에 10을 추가하세요. 곱 320은 8비트 출력에 담기지 않으므로 포트에는 64가 나타납니다.+의 결과 폭은 넓은 피연산자의 폭과 같으므로 덧셈기마다 256을 법으로 값이 순환합니다. 이 폭 규칙은 폭·SInt·산술 장에서 다룹니다.
바꾼 코드는 패널에 남습니다. 페이지의 실행을 다시 누르면 예제를 새로 불러옵니다.
5. 하드웨어 값과 Scala 제어문
Scala if는 생성기 코드의 두 분기 가운데 하나를 고르며, elaboration 중에 Boolean 값을
요구합니다. Bool 포트는 그 시점에 값이 없습니다. 이후 매 사이클 0 또는 1을 전달할 wire를 가리킬
뿐입니다.
형식 — 두 종류의 선택
if (Scala_Boolean) { ... } else { ... } // 만들 회로를 고름
when (하드웨어_Bool) { ... } .otherwise { ... } // 실행 중에 고르는 회로를 만듦if (withParity) { ... } // 올바름: withParity는 Scala Boolean
if (io.sel) { ... } // 잘못됨: io.sel은 하드웨어 Bool다음 예제는 일부러 컴파일 오류를 발생시킵니다. Select는 하드웨어 포트 io.sel로 Scala 분기를
결정하려 합니다. 실행을 누르기 전에 3절의 세 단계 가운데 어느 단계에서 오류가 발생할지 정해
보세요.
확인 문제
ScalaIf.scala의 오류는 어느 단계에서 보고됩니까?
import chisel3._
class Select extends Module {
val io = IO(new Bundle {
val sel = Input(Bool())
val a = Input(UInt(8.W))
val b = Input(UInt(8.W))
val out = Output(UInt(8.W))
})
// Scala if는 모듈을 구성하는 동안 Scala Boolean 값을 요구한다.
if (io.sel) {
io.out := io.a
} else {
io.out := io.b
}
}
object Main extends App {
println(circt.stage.ChiselStage.emitCHIRRTL(new Select))
}| 확인 항목 | 내용 |
|---|---|
| 잘못된 코드 | if (io.sel)이 Bool 포트를 Scala if의 조건으로 사용합니다 |
| 실패 단계 | Scala 컴파일입니다. 회로가 만들어지기 전입니다 |
| 오류 메시지 | type mismatch; found: chisel3.Bool, required: Boolean |
| 고치는 방법 | Scala 분기 대신 when/.otherwise나 Mux(sel, a, b)로 하드웨어 멀티플렉서를 기술합니다 |
오류 메시지는 1.2절의 표를 그대로 보여 줍니다. 조건은 하드웨어 타입인데 Scala 제어문은 Scala
타입을 요구합니다. when과 Mux는
Mux·when·Wire와 모든 경로의 기본값 장에서 다룹니다.
6. 연결과 대입의 차이
Chisel 하드웨어 객체는 Scala val에 결합되며, val은 다시 결합할 수 없습니다. 연결 연산자 :=는
왼쪽 하드웨어 객체의 method이며, 무언가를 바꾸지 않고 그래프에 연결을 추가합니다.
| 문장 | 단계 | 의미 |
|---|---|---|
val y = a + b | elaboration | 덧셈기를 만들고 Scala 이름 y를 결합합니다 |
var x에 대한 x = x + x | elaboration | Scala 변수 x가 새 노드를 가리키게 합니다 |
io.out := x | 회로 | 노드 x가 출력 포트를 구동하게 합니다 |
다음 예제도 일부러 컴파일 오류를 발생시킵니다. 하드웨어 연결 자리에 Scala =을 썼습니다.
이번에도 실행을 누르기 전에 실패 단계를 예측해 보세요.
확인 문제
Assign.scala의 오류는 어느 단계에서 보고됩니까?
import chisel3._
class Passthrough extends Module {
val io = IO(new Bundle {
val in = Input(UInt(8.W))
val out = Output(UInt(8.W))
})
// Scala =은 val을 다시 결합하려 한다. 하드웨어 연결에는 :=을 쓴다.
io.out = io.in
}
object Main extends App {
println(circt.stage.ChiselStage.emitCHIRRTL(new Passthrough))
}| 확인 항목 | 내용 |
|---|---|
| 잘못된 코드 | io.out = io.in이 val io의 필드에 =을 사용합니다 |
| 실패 단계 | Scala 컴파일 |
| 오류 메시지 | reassignment to val |
| 고치는 방법 | io.out := io.in으로 쓰면 입력을 그대로 출력하는 올바른 모듈이 됩니다 |
Doubler 예제는 두 연산자를 모두 올바르게 사용했습니다. x = x + x는 elaboration 중에 Scala
var를 옮기고, io.out := x는 마지막 노드를 출력에 연결합니다.
7. 자주 하는 실수
7.1 하드웨어 값으로 Scala if 분기하기
// 잘못된 코드: io.sel은 하드웨어이고 Scala는 지금 Boolean이 필요함
if (io.sel) { io.out := io.a } else { io.out := io.b }
// 바른 코드: 멀티플렉서를 만듦
when (io.sel) { io.out := io.a } .otherwise { io.out := io.b }Scala if, while, match는 elaboration 중에 실행됩니다. 컴파일러는
found: chisel3.Bool, required: Boolean을 보고합니다. Scala if는 생성기 매개변수로 분기할 때만
사용하세요.
7.2 연결에 = 쓰기
// 잘못된 코드: val의 필드를 다시 결합하려 함
io.out = io.in
// 바른 코드: 연결을 기록함
io.out := io.in=은 Scala 이름에, :=는 회로 연결에 씁니다. 컴파일러는 reassignment to val을 보고합니다.
7.3 Scala 숫자를 포트에 연결하기
// 잘못된 코드: 5는 Scala Int이고 하드웨어가 아님
io.out := 5
// 바른 코드: 하드웨어 리터럴로 변환함
io.out := 5.U:=의 오른쪽에는 하드웨어가 와야 합니다. Scala 컴파일러는
type mismatch; found: Int(5), required: chisel3.Data를 보고합니다. .U는 부호 없는 리터럴을
만들며, 다른 리터럴 형식은
폭·SInt·산술 장에서
다룹니다.
7.4 println으로 시뮬레이션 값을 기대하기
// 잘못된 코드: elaboration 중에 한 번 실행되고 포트 설명을 출력함
println(io.in)
// 바른 코드: 시뮬레이션 중에 테스트벤치에서 값을 읽음
println(dut.io.in.peek().litValue)모듈 본문의 println은 회로를 구성하는 동안 실행됩니다. 출력되는 내용은
모듈_이름.io.in: IO[UInt<8>] 형태의 포트 이름과 타입이며 숫자가 아닙니다. 값은 시뮬레이션이
시작된 뒤에만 존재합니다.
8. 정리
| 주제 | 핵심 |
|---|---|
| Chisel 프로그램 | 회로를 만드는 Scala 프로그램입니다. 고정된 넷리스트가 아니라 생성기입니다 |
elaboration | 모듈 인스턴스마다 Scala 코드가 한 번 실행되어 회로 그래프를 남깁니다 |
| 시뮬레이션 | 그래프가 사이클 단위로 실행됩니다. poke와 peek가 이 단계에서 동작합니다 |
| Scala 타입과 Chisel 타입 | Int, Boolean은 생성 과정을 제어하고 UInt, SInt, Bool은 하드웨어를 기술합니다 |
| 모듈 | class X extends Module로 선언하고 포트는 IO(new Bundle { ... })에 Input, Output으로 둡니다 |
val 이름 = 식 | 식을 만들고 결과에 이름을 붙입니다. 그 자체로는 배선이 아닙니다 |
:= | 연결을 기록합니다. Scala =은 이름만 다시 결합합니다 |
test(new X) { dut => ... } | elaboration 뒤 시뮬레이션합니다. poke로 넣고 peek로 읽습니다 |
| 오류 | File.scala:N: error:는 컴파일, 모듈 구성 중 예외는 elaboration 오류입니다 |
9. 확인 문제
-
모든
ELABORATE줄이 첫 번째SIMULATE줄보다 먼저 출력되는 이유는 무엇일까요?정답 보기
ELABORATE를 출력하는 반복문은 모듈 생성자 안의 Scala 코드이며,new Doubler(3)을elaboration할 때 한 번 실행됩니다. 시뮬레이션은 회로가 완성된 뒤에 시작되고, 모든poke가 같은 덧셈기를 사용합니다. -
Scala
if가stages > 2는 검사할 수 있지만io.sel은 검사할 수 없는 이유는 무엇일까요?정답 보기
stages는 ScalaInt이므로stages > 2는elaboration중에 값이 정해진 ScalaBoolean입니다.io.sel은 시뮬레이션 중에만 값이 있는 하드웨어Bool이므로 Scala가 분기할 값이 없습니다. -
io.out := x가 회로에 추가하는 것 가운데x = x + x가 추가하지 않는 것은 무엇일까요?정답 보기
노드
x에서 출력 포트로 가는 연결입니다.x = x + x의 덧셈 식은 덧셈기를 만들지만 대입 자체는 Scala 변수를 옮길 뿐입니다.:=가 없으면 그 덧셈기는 아무것도 구동하지 않습니다. -
모듈 본문에
println(io.in)이 있고 테스트벤치가 값 열 개를 인가합니다. 이println은 몇 줄을 출력하며, 내용은 무엇일까요?정답 보기
elaboration중에 한 줄을 출력합니다. 내용은 포트 이름과UInt<8>같은 타입이며 시뮬레이션 값은 들어 있지 않습니다. -
stages를Tb.scala에 적지 않고 설정 파일에서 읽도록 바꾸면, 파일을 읽는 코드는 어느 단계에서 실행될까요?정답 보기
elaboration입니다. 생성기 매개변수는 Scala 값이므로, 그 값을 계산하는 코드는 회로가 만들어지기 전에 실행됩니다.
아래 실습 문제는 이 장의 elaboration 모델을 적용합니다. 덧셈기로 곱을 만들어야 하는 상수 곱셈기가
지금은 컴파일과 실행은 되면서 모든 입력에 0을 출력합니다. 배율에 대한 Scala if가 상수에 맞는
덧셈기만 만들도록 생성기 반복문을 완성하세요.