Help us understand the problem. What is going on with this article?

Chiselを使って4bit CPUのTD4をFPGAボード上に実装する

Chiselとは?

Chiselとは、VHDLやVerilog等と同じく、ディジタル回路設計用のハードウェア記述言語(HDL)の一種です(ただし、ハードウェアの設計をアジャイルに行う事を主目的としているので、シミュレーション等の機能は弱い)。Chiselで書かれたコードはVerilogのコードに変換され、そのVerilogコードを使って、FPGAをプログラムしたり、ASICの製造に用います。

有名な用途としては、オープンソースのCPU ISAのRISC-Vの実装でもっとも有名なものの一つのRocket Chipや、GoogleのEdge TPUがあります(講演の動画はこちら)

Chiselの文法について知りたい方は、Chisel入門書「Digital Design with Chisel」1章の勉強記録から始まる記事を参照してください。

TD4とは?

TD4とは、CPUの創りかたにて、設計されている4bit CPUです。実用的なCPUではありません(ラーメンタイマーを作るのが精一杯…)ですが、CPUの基本的な構成要素は実装されているので、CPUの設計の学習には十分なものとなっています。

書籍では、手を動かして、ユニバーサル基板にICをハンダ付けしていきながら、電子工作自体も学習する事も目的としています。FPGAを使って実装するのは書籍の趣旨に反するのですが、HDLで簡単なCPUを実装するのはHDLの学習にも適しているので、Chiselで実装してみました。

本記事ではTD4自体の解説は行いません。詳細を知りたい場合はぜひ書籍を購入してみてください。

TD4の実装

書籍での解説順に実装していきます。
(最終の実装結果を見たい場合は、GitHubのリポジトリを参照してください)

リセットとクロック

Chiselでは、Moduleクラスを継承したクラスを定義していく事で、回路設計を行っていきます。Moduleクラスを継承したクラスはio(I/O)のメンバーを実装する必要があり、IOメソッドを呼び出して入出力を定義します。

package td4

import chisel3._
import chisel3.util._
import chisel3.core.withClockAndReset

/**
  * TD4のトップモジュール
  */
class TD4Top extends Module {
  val io = IO(new Bundle {
    // ここに、モジュールへの入出力が定義される
  })
}

また、Chiselのコードから、Verilogのコードを出力するために以下のオブジェクトを定義します。

/**
  * TD4のVerilogファイルを生成するためのオブジェクト
  */
object TD4Top extends App {
  chisel3.Driver.execute(args, () => new TD4Top)
}

リセットやクロックの定義は以下のようにします。

class TD4Top extends Module {
  val io = IO(new Bundle {
    val cpuReset      = Input(Bool()) // CPU RESETボタンに接続
    val isManualClock = Input(Bool()) // クロック信号をマニュアル操作するか
    val manualClock   = Input(Bool()) // マニュアル・クロック信号
    val isHz10        = Input(Bool()) // 10Hzのクロックで動作するか? 偽の場合は1Hzで動作します。
  })

  // 1Hz, 10Hzのパルスを生成してクロック信号の代わりにする
  val clockFrequency = 100000000    // 使用するFPGAボードの周波数(Hz)
  val (clockCount, hz10Pulse) = Counter(true.B, clockFrequency / 10 / 2)
  val hz10Clock = RegInit(true.B)
  hz10Clock := Mux(hz10Pulse, ~hz10Clock, hz10Clock)
  val (helz10Count, hz1Pulse) = Counter(hz10Pulse, 10)
  val hz1Clock = RegInit(true.B)
  hz1Clock := Mux(hz1Pulse, ~hz1Clock, hz1Clock)

  // マニュアル・クロック用のボタンのチャタリングを除去
  val manualClock = Debounce(io.manualClock, clockFrequency)
  val td4Clock = Mux(io.isManualClock,
    io.manualClock,
    Mux(io.isHz10, hz10Clock, hz1Clock)).asClock

  // CPU RESETボタンは負論理なので反転する。
  withClockAndReset(td4Clock, ~io.cpuReset) {
    // TODO: 実装する
  }
}

/**
  *  プッシュボタン用デバンウンス
  */
class Debounce(hz: Int) extends Module {
  val io = IO(new Bundle{
    val in = Input(Bool())
    val out = Output(Bool())
  })

  val (count, enable) = Counter(true.B, hz / 10) // 0.1秒間隔で値を取り込む

  val reg0 = RegEnable(io.in, false.B, enable)
  val reg1 = RegEnable(reg0,  false.B, enable)

  io.out := reg0 && !reg1 && enable // enableの時だけ変化を見るようにして、1クロックのパルスにする
}

/**
  * プッシュボタン用デバンウンスのコンパニオン・オブジェクト
  */
object Debounce {
  def apply(in: Bool, hz: Int): Bool = {
    val debounce = Module(new Debounce(hz))
    debounce.io.in := in
    debounce.io.out
  }
}

ROMを作る

書籍で解説されている内容で、実際に基板を使って作成するのが一番大変なのがROMですが、HDLで実装すると簡単です。
ここでは、プログラムの内容は決まっていないので、ROMの中身は全て0になっています。ROMは、しばらく使わないのでどこにも接続していません。

/**
  * ROM
  */
class ROM extends Module {
  val io = IO(new Bundle() {
    val addr = Input(UInt(4.W)) // アドレス
    val data = Output(UInt(8.W)) // データ
  })

   // ROMの中身
  val rom = VecInit(List(
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  ).map(_.asUInt(8.W)))

   io.data := rom(io.addr)
}

演算できるようにする(NOT Aを実装)

書籍で最初に解説されている命令は、MOV A, Aであり、「迷ったらココに戻って来るコト」などと言われていますが、変化が全然なくて退屈なので、NOT Aから実装していきます。
CPUのコアの部分は独自のクラスに実装しています。

/**
  * TD4 CPUコア
  */
class TD4 extends Module {
  val io = IO(new Bundle() {
    val out = Output(UInt(1.W))
  })

   // 1bitのAレジスタ(0で初期化)
  val regA = RegInit(1.U(1.W))

   // NOT A, A
  regA := ~regA

   // Aレジスタの内容を出力しておく
  io.out := regA
}

class TD4Top extends Module {
  val io = IO(new Bundle {
    val cpuReset      = Input(Bool()) // CPU RESETボタンに接続
    val isManualClock = Input(Bool()) // クロック信号をマニュアル操作するか
    val manualClock   = Input(Bool()) // マニュアル・クロック信号
    val isHz10        = Input(Bool()) // 10Hzのクロックで動作するか? 偽の場合は1Hzで動作します。

    val out           = Output(UInt(1.W)) // LEDへの出力
  })

// 中略

  withClockAndReset(td4Clock, ~io.cpuReset) {
    val core = Module(new TD4())
    io.out := core.io.out
  }
}

FPGAボードの制約ファイルを作成すれば(ここでは、Digilent社のNexys4 DDRの制約ファイル)、Aレジスタの変化をLEDの点滅として見る事ができます。

## Clock signal
set_property -dict { PACKAGE_PIN E3    IOSTANDARD LVCMOS33 } [get_ports { clock }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {clock}];

##Switches

set_property -dict { PACKAGE_PIN J15   IOSTANDARD LVCMOS33 } [get_ports { io_isManualClock }]; #IO_L24N_T3_RS0_15 Sch=sw[0]
set_property -dict { PACKAGE_PIN L16   IOSTANDARD LVCMOS33 } [get_ports { io_isHz10 }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=sw[1]

## LEDs

set_property -dict { PACKAGE_PIN H17   IOSTANDARD LVCMOS33 } [get_ports { io_out }]; #IO_L18P_T2_A24_15 Sch=led[0]

##Buttons

set_property -dict { PACKAGE_PIN C12   IOSTANDARD LVCMOS33 } [get_ports { io_cpuReset }]; #IO_L3P_T0_DQS_AD1P_15 Sch=cpu_resetn

set_property -dict { PACKAGE_PIN N17   IOSTANDARD LVCMOS33 } [get_ports { io_manualClock }]; #IO_L9P_T1_DQS_14 Sch=btnc

命令にしたがって処理を変更する(MOV A, AとNOT Aを切り替える)

CPUコアを以下のように変更します。

class TD4 extends Module {
  val io = IO(new Bundle() {
    val inst = Input(Bool())    // 命令(現在は、真ならMOV A, A、偽ならNOT A)
    val out = Output(UInt(1.W)) // Aレジスタの内容
  })

  // 1bitのAレジスタ(0で初期化)
  val regA = RegInit(1.U(1.W))

  when (io.inst) {
    regA := regA    // MOV A, A
  } .otherwise {
    regA := ~regA  // NOT A
  }

  // Aレジスタの内容を出力しておく
  io.out := regA
}

TOPモジュールも以下のように変更します。(instをFPGAボードのスイッチに接続すれば、命令の切り替えを外部から入力出来ます)

class TD4Top extends Module {
  val io = IO(new Bundle {
    val cpuReset      = Input(Bool()) // CPU RESETボタンに接続
    val isManualClock = Input(Bool()) // クロック信号をマニュアル操作するか
    val manualClock   = Input(Bool()) // マニュアル・クロック信号
    val isHz10        = Input(Bool()) // 10Hzのクロックで動作するか? 偽の場合は1Hzで動作します。
    val inst          = Input(Bool()) // 命令(現在は、真ならMOV A, A、偽ならNOT A)

    val out           = Output(UInt(1.W)) // LEDへの出力
  })

// 中略

  // CPU RESETボタンは負論理なので反転する。
  withClockAndReset(td4Clock, ~io.cpuReset) {
    val core = Module(new TD4())
    core.io.inst := io.inst
    io.out := core.io.out
  }
}

レジスタの数を増やす(MOV A, B)

レジスタ間のデータ転送を実装します。

class TD4 extends Module {
  val io = IO(new Bundle() {
    val select = Input(UInt(2.W)) // レジスタ・セレクタ
    val load   = Input(Vec(4, Bool())) // 真の位置のレジスタに値をロードする
    val out    = Output(UInt(4.W)) // Aレジスタの内容
  })

  // 4bitのレジスタを4つ作成。レジスタの番号のビット位置のビットを立てる
  val regs = RegInit(Vec(1.U(4.W), 2.U(4.W), 4.U(4.W), 8.U(4.W)))

  // MOV X, X
  val selectedVal = regs(io.select)
  for (i <- 0 until regs.size) {
    regs(i) := Mux(io.load(i), selectedVal, regs(i))
  }

  // Aレジスタの内容を出力しておく
  io.out := regs(0)
}

class TD4Top extends Module {
  val io = IO(new Bundle {
    val cpuReset      = Input(Bool()) // CPU RESETボタンに接続
    val isManualClock = Input(Bool()) // クロック信号をマニュアル操作するか
    val manualClock   = Input(Bool()) // マニュアル・クロック信号
    val isHz10        = Input(Bool()) // 10Hzのクロックで動作するか? 偽の場合は1Hzで動作します。
    val select        = Input(UInt(2.W)) // レジスタ・セレクタ
    val load          = Input(Vec(4, Bool())) // 真の位置のレジスタに値をロードする

    val out           = Output(UInt(4.W)) // LEDへの出力
  })

// 中略

  withClockAndReset(td4Clock, ~io.cpuReset) {
    val core = Module(new TD4())
    core.io.select := io.select
    core.io.load := io.load
    io.out := core.io.out
  }
}

加算回路を追加(ADD X,Im)

class TD4 extends Module {
  val io = IO(new Bundle() {
    val select = Input(UInt(2.W)) // レジスタ・セレクタ
    val load   = Input(Vec(4, Bool())) // 真の位置のレジスタに値をロードする
    val imData = Input(UInt(4.W)) // 即値データ
    val out    = Output(UInt(4.W)) // Aレジスタの内容
  })

  // 4bitのレジスタを4つ作成。レジスタの番号のビット位置のビットを立てる
  val regs = RegInit(Vec(1.U(4.W), 2.U(4.W), 4.U(4.W), 8.U(4.W)))

  val selectedVal = regs(io.select)
  val addedVal = selectedVal + io.imData
  for (i <- 0 until regs.size) {
    regs(i) := Mux(io.load(i), addedVal, regs(i))
  }

  // Aレジスタの内容を出力しておく
  io.out := regs(0)
}

class TD4Top extends Module {
  val io = IO(new Bundle {
    val cpuReset      = Input(Bool()) // CPU RESETボタンに接続
    val isManualClock = Input(Bool()) // クロック信号をマニュアル操作するか
    val manualClock   = Input(Bool()) // マニュアル・クロック信号
    val isHz10        = Input(Bool()) // 10Hzのクロックで動作するか? 偽の場合は1Hzで動作します。
    val select        = Input(UInt(2.W)) // レジスタ・セレクタ
    val load          = Input(Vec(4, Bool())) // 真の位置のレジスタに値をロードする
    val imData        = Input(UInt(4.W)) // 即値データ

    val out           = Output(UInt(4.W)) // LEDへの出力
  })

// 中略

  withClockAndReset(td4Clock, ~io.cpuReset) {
    val core = Module(new TD4())
    core.io.select := io.select
    core.io.load := io.load
    core.io.imData := io.imData
    io.out := core.io.out
  }
}

MOV X,Im

以下のように変更します。

class TD4 extends Module {
  val io = IO(new Bundle() {
    val select = Input(UInt(2.W)) // レジスタ・セレクタ
    val load   = Input(Vec(4, Bool())) // 真の位置のレジスタに値をロードする
    val imData = Input(UInt(4.W)) // 即値データ
    val out    = Output(UInt(4.W)) // Aレジスタの内容
  })

  // 4bitのレジスタを4つ作成。レジスタの番号のビット位置のビットを立てる
  val regs = RegInit(Vec(1.U(4.W), 2.U(4.W), 4.U(4.W), 8.U(4.W)))

  val selectedVal = MuxLookup(io.select, 0.U(4.W), Seq(
    (0.U(4.W) -> regs(0)),
    (1.U(4.W) -> regs(1)),
    (2.U(4.W) -> regs(2)) // 3.Uの分はデフォルト値で代用
  ))
  val addedVal = selectedVal + io.imData
  for (i <- 0 until regs.size) {
    regs(i) := Mux(io.load(i), addedVal, regs(i))
  }

  // Aレジスタの内容を出力しておく
  io.out := regs(0)
}

キャリーフラグを実装

class TD4 extends Module {
  val io = IO(new Bundle() {
    val select = Input(UInt(2.W)) // レジスタ・セレクタ
    val load   = Input(Vec(4, Bool())) // 真の位置のレジスタに値をロードする
    val imData = Input(UInt(4.W)) // 即値データ
    val out    = Output(UInt(4.W)) // Aレジスタの内容
  })

  // 4bitのレジスタを4つ作成。レジスタの番号のビット位置のビットを立てる
  val regs = RegInit(Vec(1.U(4.W), 2.U(4.W), 4.U(4.W), 8.U(4.W)))
  val carryFlag = RegInit(false.B)

  val selectedVal = MuxLookup(io.select, 0.U(4.W), Seq(
    (0.U(4.W) -> regs(0)),
    (1.U(4.W) -> regs(1)),
    (2.U(4.W) -> regs(2)) // 3.Uの分はデフォルト値で代用
  ))
  val addedVal = selectedVal +& io.imData
  carryFlag := addedVal(4)
  for (i <- 0 until regs.size) {
    regs(i) := Mux(io.load(i), addedVal(3, 0), regs(i))
  }

  // Aレジスタの内容を出力しておく
  io.out := regs(0)
}

プログラム・カウンタを追加

プログラムカウンタを追加して、ROMから命令データを読み込んで行きます。ただし、命令を解釈する部分(デコーダ)がないので、意味のある処理はできません。

class TD4 extends Module {
  val io = IO(new Bundle() {
    val iAddr  = Output(UInt(4.W)) // 命令アドレス
    val iData  = Input(UInt(8.W))  // 命令データ
    val select = Input(UInt(2.W)) // レジスタ・セレクタ
    val load   = Input(Vec(4, Bool())) // 真の位置のレジスタに値をロードする

    val out    = Output(UInt(4.W)) // テスト用出力値
  })

  // 汎用レジスタ
  val regA = RegInit(1.U(4.W)) // Aレジスタ
  val regB = RegInit(2.U(4.W)) // Bレジスタ
  val regC = RegInit(4.U(4.W)) // (Cレジスタ。最終的には別のものになる)
  val programCounter = RegInit(0.U(4.W)) // プログラム・カウンター

  val carryFlag = RegInit(false.B)

  val selectedVal = MuxLookup(io.select, 0.U(4.W), Seq(
    (0.U(4.W) -> regA),
    (1.U(4.W) -> regB),
    (2.U(4.W) -> regC) // 3.Uの分はデフォルト値で代用
  ))
  val addedVal = selectedVal +& io.iData(3, 0)
  carryFlag := addedVal(4)
  when (io.load(0)) {
    regA := addedVal(3, 0)
  }
  when (io.load(1)) {
    regB := addedVal(3, 0)
  }
  when (io.load(2)) {
    regC := addedVal(3, 0)
  }
  when (io.load(3)) {
    programCounter := addedVal
  } .otherwise {
    programCounter := programCounter + 1.U
  }

  io.iAddr := programCounter
  // 即値を出しておく
  io.out := io.iData(3, 0)
}

class ROM extends Module {
  val io = IO(new Bundle() {
    val addr = Input(UInt(4.W)) // アドレス
    val data = Output(UInt(8.W)) // データ
  })

  // ROMの中身
  val rom = VecInit(List(
    0x00, 0x01, 0x02, 0x03,
    0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0A, 0x0B,
    0x0C, 0x0D, 0x0E, 0x0F
  ).map(_.asUInt(8.W)))

  io.data := rom(io.addr)
}

class TD4Top extends Module {

// 中略

  withClockAndReset(td4Clock, ~io.cpuReset) {
    val core = Module(new TD4())
    core.io.select := io.select
    core.io.load := io.load
    io.out := core.io.out

    val rom = Module(new ROM())
    rom.io.addr := core.io.iAddr
    core.io.iData := rom.io.data
  }
}

I/Oポート

以下のように、入出力ポートを追加します。

class TD4 extends Module {
  val io = IO(new Bundle() {
    val iAddr  = Output(UInt(4.W)) // 命令アドレス
    val iData  = Input(UInt(8.W))  // 命令データ
    val select = Input(UInt(2.W)) // レジスタ・セレクタ
    val load   = Input(Vec(4, Bool())) // 真の位置のレジスタに値をロードする

    val in     = Input(UInt(4.W))  // 入力ポートへ
    val out    = Output(UInt(4.W)) // 出力ポートへ
  })

  // 汎用レジスタ
  val regA = RegInit(1.U(4.W)) // Aレジスタ
  val regB = RegInit(2.U(4.W)) // Bレジスタ
  val regOut = RegInit(4.U(4.W)) // 出力ポート用レジスタ
  val programCounter = RegInit(0.U(4.W)) // プログラム・カウンター

  val carryFlag = RegInit(false.B)

  val selectedVal = MuxLookup(io.select, 0.U(4.W), Seq(
    (0.U(4.W) -> regA),
    (1.U(4.W) -> regB),
    (2.U(4.W) -> io.in) // 3.Uの分はデフォルト値で代用
  ))
  val addedVal = selectedVal +& io.iData(3, 0)
  carryFlag := addedVal(4)
  when (io.load(0)) {
    regA := addedVal(3, 0)
  }
  when (io.load(1)) {
    regB := addedVal(3, 0)
  }
  when (io.load(2)) {
    regOut := addedVal(3, 0)
  }
  when (io.load(3)) {
    programCounter := addedVal
  } .otherwise {
    programCounter := programCounter + 1.U
  }

  // 出力
  io.iAddr := programCounter
  io.out := regOut
}

class TD4Top extends Module {
  val io = IO(new Bundle {
    val cpuReset      = Input(Bool()) // CPU RESETボタンに接続
    val isManualClock = Input(Bool()) // クロック信号をマニュアル操作するか
    val manualClock   = Input(Bool()) // マニュアル・クロック信号
    val isHz10        = Input(Bool()) // 10Hzのクロックで動作するか? 偽の場合は1Hzで動作します。
    val select        = Input(UInt(2.W)) // レジスタ・セレクタ
    val load          = Input(Vec(4, Bool())) // 真の位置のレジスタに値をロードする

    val in            = Input(UInt(4.W))  // 入力ポート
    val out           = Output(UInt(4.W)) // 出力ポート
  })

// 中略

  withClockAndReset(td4Clock, ~io.cpuReset) {
    val core = Module(new TD4())
    core.io.select := io.select
    core.io.load := io.load
    core.io.in := io.in
    io.out := core.io.out

    val rom = Module(new ROM())
    rom.io.addr := core.io.iAddr
    core.io.iData := rom.io.data
  }
}

命令デコーダを実装し、Lチカのプログラムを実行する

class TD4 extends Module {
  val io = IO(new Bundle() {
    val iAddr  = Output(UInt(4.W)) // 命令アドレス
    val iData  = Input(UInt(8.W))  // 命令データ
    val in     = Input(UInt(4.W))  // 入力ポートへ
    val out    = Output(UInt(4.W)) // 出力ポートへ
  })

  /*
   * レジスタ定義
   */
  // 汎用レジスタ
  val regA = RegInit(0.U(4.W)) // Aレジスタ
  val regB = RegInit(0.U(4.W)) // Bレジスタ

  // 出力ポート用レジスタ
  val regOut = RegInit(0.U(4.W))

  // プログラム・カウンタ
  val programCounter = RegInit(0.U(4.W))

  // キャリーフラグ
  val carryFlag = RegInit(false.B)

  /*
   * 命令デコーダ
   */
  val opData = Cat(io.iData(7, 4), carryFlag)
  val ctrlSig = MuxCase(0.U(6.W), Seq(
    // 本のデコーダの設計の最初の真理値表
    ((opData === BitPat("b0000?")) -> "b000111".U), // ADD A,Im
    ((opData === BitPat("b0001?")) -> "b010111".U), // MOV A,B
    ((opData === BitPat("b0010?")) -> "b100111".U), // IN  A
    ((opData === BitPat("b0011?")) -> "b110111".U), // MOV A,Im
    ((opData === BitPat("b0100?")) -> "b001011".U), // MOV B,A
    ((opData === BitPat("b0101?")) -> "b011011".U), // ADD B,Im
    ((opData === BitPat("b0110?")) -> "b101011".U), // IN  B
    ((opData === BitPat("b0111?")) -> "b111011".U), // MOV B,Im
    ((opData === BitPat("b1001?")) -> "b011101".U), // OUT B
    ((opData === BitPat("b1011?")) -> "b111101".U), // OUT Im
    ((opData === BitPat("b11100")) -> "b111110".U), // JNC(C=0)
    ((opData === BitPat("b11101")) -> "b111111".U), // JNC(C=1)"b??1111"
    ((opData === BitPat("b1111?")) -> "b111110".U)  // JMP
  ))
  val select = ctrlSig(5, 4)
  // 本はloadの値は、負論理なので否定して、順序も逆なのでReverseする
  val load   = Reverse(~ctrlSig(3, 0))

  /*
   * レジスタ読み込み
   */
  val selectedVal = MuxLookup(select, 0.U(4.W), Seq(
    (0.U(4.W) -> regA),
    (1.U(4.W) -> regB),
    (2.U(4.W) -> io.in) // 3.Uの分はデフォルト値で代用
  ))

  /*
   * 演算処理(ALU)
   */
  val addedVal = selectedVal +& io.iData(3, 0)
  carryFlag := addedVal(4)

  /*
   * レジスタ書き戻し
   */
  when (load(0)) {
    regA := addedVal(3, 0)
  }
  when (load(1)) {
    regB := addedVal(3, 0)
  }
  when (load(2)) {
    regOut := addedVal(3, 0)
  }
  when (load(3)) {
    programCounter := addedVal
  } .otherwise {
    programCounter := programCounter + 1.U
  }

  // 出力
  io.iAddr := programCounter
  io.out := regOut
}

class ROM extends Module {
  val io = IO(new Bundle() {
    val addr = Input(UInt(4.W)) // アドレス
    val data = Output(UInt(8.W)) // データ
  })

  // ROMの中身
  val rom = VecInit(List(
    // Lチカプログラム
    0xB3, 0xB6, 0xBC, 0xB8,
    0xB8, 0xBC, 0xB6, 0xB3,
    0xB1, 0xF0, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00
  ).map(_.asUInt(8.W)))

  io.data := rom(io.addr)
}

class TD4Top extends Module {
  val io = IO(new Bundle {
    val cpuReset      = Input(Bool()) // CPU RESETボタンに接続
    val isManualClock = Input(Bool()) // クロック信号をマニュアル操作するか
    val manualClock   = Input(Bool()) // マニュアル・クロック信号
    val isHz10        = Input(Bool()) // 10Hzのクロックで動作するか? 偽の場合は1Hzで動作します。
    val in            = Input(UInt(4.W))  // 入力ポート
    val out           = Output(UInt(4.W)) // 出力ポート
  })

// 中略

  withClockAndReset(td4Clock, ~io.cpuReset) {
    val core = Module(new TD4())
    core.io.in := io.in
    io.out := core.io.out

    val rom = Module(new ROM())
    rom.io.addr := core.io.iAddr
    core.io.iData := rom.io.data
  }
}

全体としてのコードを見たい場合は、GitHubのリポジトリを参照してください

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away