Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ChiselでシンプルなRISC-Vコアを作った話

diningyo
February 24, 2020

 ChiselでシンプルなRISC-Vコアを作った話

第1回 自作CPUもくもく会で発表で使う資料

diningyo

February 24, 2020
Tweet

Other Decks in Technology

Transcript

  1. Chisel ・UCBerkeleyが作ったハードウェア記述言語 ・Scalaの内部DSLとして実装されている ・ChiselのモジュールはFIRRTLという中間表現を経てVerilogのRTLに変換される class MyModule extends Module { val

    io = IO(new Bundle { val in = Input(Bool()) val out = Output(Bool())}) // wire宣言 val w_in = Wire(Bool()) // reg宣言 val r_out = RegInit(false.B) // 接続は”:=” w_in := io.in r_out := w_in io.out := r_out } module MyModule ( input clk ,input reset ,input in ,output reg out ); wire w_in; assign w_in = in; always @(posedge clk) begin if (reset) out <= 1'b0; else out <= w_in; end endmodule : MyModule
  2. 作ったRISC-Vコア(dirv) ・RV32Iのみ ・2-stageパイプライン(F-DEMW) ・User-Level ISA version 2.2 ・Privileged ISA version

    1.10 ・割り込みは未サポート ・上記だけだと面白くないので、追加で以下の要素も  ・NIC  ・UART ・タイトルからも分かる通りChiselで作成
  3. IFU ・ChiselのIFUモジュール宣言部分 ・Moduleクラスを継承して任意のモジュールを実装していく class Ifu(implicit cfg: Config) extends Module {

    val io = IO(new IfuIO()) val sIdle :: sFetch :: Nil = Enum(2) // 以下はリセット解除後の初期命令フェッチのトリガー val initPcSeq = Seq.fill(3)(RegInit(false.B)) val initPc = !initPcSeq(2) && initPcSeq(1) // Seqの要素を1つずつずらしていく when (!initPcSeq.reduce(_ && _)) { (Seq(true.B) ++ initPcSeq).zip(initPcSeq).foreach{case (c, n) => n := c} }
  4. IDU ・RISC-Vの命令デコード記述の抜粋  → 実際には140行くらいのコード ・論理を実体化する部分 class Idu(implicit cfg: Config) extends

    Module with InstInfoRV32 { val io = IO(new IduIO()) val inst = Wire(new InstRV32()) // InsrRV32のクラスで定義したメソッドを呼んでデコード論理を実体化する。 inst.decode(io.ifu2idu.valid && io.ifu2idu.ready, io.ifu2idu.inst) def decode(dataValid: Bool, data: UInt): Unit = { funct7 := data(funct7Msb, funct7Lsb) rs2 := data(rs2Msb, rs2Lsb) rs1 := data(rs1Msb, rs1Lsb) funct3 := data(funct3Msb, funct3Lsb) rd := data(rdMsb, rdLsb) opcode := data(opCodeMsb, opCodeLsb) jalr := opcode === "b1100111".U この部分の記述
  5. EXUの中のALU ・ALUの中のrv32iの命令の処理部分  → rv32iAluの命令のみを固めてScalaのSeqに実装。  →M-extensionとかをサポートする際にはval aluにM用のSeqを足せばOK val rv32iAlu = scala.collection.mutable.Seq(

    (add || aluThrough) -> (rs1 + rs2), (inst.slti || inst.slt) -> (rs1.asSInt() < rs2.asSInt()).asUInt(), (inst.sltiu || inst.sltu) -> (rs1 < rs2), inst.sub -> (rs1 - rs2), (inst.andi || inst.and) -> (rs1 & rs2), (inst.ori || inst.or) -> (rs1 | rs2), (inst.xori || inst.xor) -> (rs1 ^ rs2), (inst.slli || inst.sll) -> (rs1 << shamt)(cfg.arch.xlen - 1, 0), (inst.srli || inst.srl) -> (rs1 >> shamt), (inst.srai || inst.sra) -> (rs1.asSInt() >> shamt).asUInt(), inst.lui -> inst.immU) val alu = rv32iAlu // ++ Seq(他のextension)でALUを拡張できる
  6. LSU ・LSUのミスアラインチェック用のメソッド  → Verilogのfunction文と同様のイメージ def setMaAddr(dataReq: Bool, addr: UInt, size:

    UInt): ExcMa = { val uaMsb = log2Ceil(cfg.arch.xlen / 8) val uaAddr = addr(uaMsb - 1, 0) val excReq = MuxCase(false.B, Seq( (size === MbusSize.half.U) -> uaAddr(0).toBool(), (size === MbusSize.word.U) -> (uaAddr =/= "b00".U) )) val ret = Wire(new ExcMa()) ret.excReq := excReq && dataReq ret.excAddr := addr ret } io.lsu2exu.excRdMa := setMaAddr(inst.loadValid, io.exu2lsu.memAddr, size) io.lsu2exu.excWrMa := setMaAddr(inst.storeValid, io.exu2lsu.memAddr, size)
  7. NIC ・ChiselのArbiterとDecoderを利用して作成 ・渡したアドレスマップに従ってデコードが行われ、要求がアービターに出力 // Tmp. // 0x0000 - 0x7fff :

    Memory // 0x8000 - 0x8100 : Uart val base_p = MbusICParams( MbusRW, // マスタの設定、タプルにしたけど現状意味なし。。。 Seq((0x0, 0x1000), (0x0, 0x1000)), // ここがメモリマップの設定 // Seqのサイズに応じた個数のアービターが作られる Seq((0x0, 32 * 1024),// MemTop (32KBytes) (32 * 1024, 0x100) // Uart ), 32)
  8. NIC ・DecoderとArbiterのインスタンスと接続処理は以下の感じ  → パラメータで色々渡せるのは便利 class MbusIC(p: MbusICParams) extends Module {

    val io = IO(new MbusICIO(p)) // パラメータに従ってデコーダとアービターを必要数インスタンスする val m_decs = p.decParams.map( dp => Module(new MbusDecoder(dp))) val m_arbs = p.arbParams.map( ap => Module(new MbusArbiter(ap))) // io.in(N) <-> dec(N).io.in for ((in , m_dec) <- io.in zip m_decs) { m_dec.io.in <> in } // dec.io.out(M) <-> arb.io.in(N) for ((m_dec, i) <- m_decs.zipWithIndex; (m_arb, j) <- m_arbs.zipWithIndex) { m_dec.io.out(j) <> m_arb.io.in(i) } // io.out(N) <-> arb.io.out(N) for ((out , arb) <- io.out zip m_arbs) { out <> arb.io.out } }
  9. Uart ・XilinxのUartLiteマクロの仕様書をベースに実装  →コードは制御部のポートのパラメタライズ部分 https://japan.xilinx.com/products/intellectual-property/axi_uartlite.html // directionでTX/RXを切り替え class Ctrl(direction: UartDirection, durationCount:

    Int) extends Module { val io = IO(new Bundle { val uart = direction match { case UartTx => Output(UInt(1.W)) case UartRx => Input(UInt(1.W)) } val reg = direction match { case UartTx => Flipped(new FifoRdIO) case UartRx => Flipped(new FifoWrIO) } })
  10. まとめ ・Chiselの習熟も兼ねてRISC-VのISAで一番シンプルなrv32iのコアを作ってみた  → コア単体を作るのに1週間位  → そのあとNIC+Uartでまた1週間位  → Chiselの記述を色々試しながら作ったので、少し時間がかかった。 ・Chiselで書くのも楽しいので、興味があれば是非!! ・以下の3つが実装する上でものすごくありがたかった。。。

     → コンパイラやエミュレータ等のSWが充実していること  → 基本的な動作を書いたテストが存在していること(riscv-tests)  → 参考になる実装がgithub等で色々公開されていること ・何もわからん!!から手探りで始めてもなんとか作れる!!  → HW/SWの両面の知識が得られるのでお得感が高い!