ハードウェアの気になるあれこれ

技術的に興味のあることを調べて書いてくブログ。主にハードウェアがネタ。

Chisel3.3.0のリリースノートを確認した(3) - SyncReadMemに追加された引数

スポンサーリンク

転職でバタバタしてて、気づけば最後に更新してから早2ヶ月。。。
間が空いたけど、前回からの続きでChisel3.3.0の変更点を確認していく。
今回は#1183SyncReadMemの件について

#1183 SyncReadMemに同時アクセス時の挙動を指定するパラメータが追加された

内容としては、タイトルのまんまだけどSyncReadMemに「リード/ライトが同時に来た場合のデータの扱いをどうするかを選択する引数」が追加になった。 FPGAのRAMのジェネレータについてるようなやつをイメージしてもらえればOK。

下記のようにSyncReadMemのオブジェクトのapplyメソッドと、クラスのクラスパラメータにそれぞれruwというフィールドが 追加された。

object SyncReadMem {
  // ~省略~
  def apply[T <: Data](size: Int, t: T, ruw: ReadUnderWrite): SyncReadMem[T] =
    macro MemTransform.apply_ruw[T]
sealed class SyncReadMem[T <: Data] private (
  t: T, n: BigInt, val readUnderWrite: SyncReadMem.ReadUnderWrite)
   extends MemBase[T](t, n) {

引数の型はSyncReadMem.ReadUnderWriteというもので、object SyncReadMem内に、次の3つが定義されている。

  • Undefined
  • ReadFirst
  • WriteFirst

ということで、リード/ライトのアクセス競合時の挙動を指定子たい場合にはReadFirstWriteFirstを指定すればOK。 指定しない場合はUndefinedが設定される。

RTLと波形を使って確認

生成されるRTLの違いを確認するために、以下のようなコードを書いて、RTLを生成してみた。

import chisel3._
import chisel3.iotesters._
import chisel3.stage.ChiselStage

class SyncReadMemAccCollision(ruw: SyncReadMem.ReadUnderWrite)
  extends Module {
  val io = IO(new Bundle {
    val wren = Input(Bool())
    val wraddr = Input(UInt(4.W))
    val wrdata = Input(UInt(8.W))
    val rdaddr = Input(UInt(4.W))
    val rden = Input(Bool())
    val rddata = Output(UInt(8.W))
  })

  val m_sync_read_mem = SyncReadMem(10, UInt(8.W), ruw)

  when (io.wren) {
    m_sync_read_mem.write(io.wraddr, io.wrdata)
  }

  io.rddata := m_sync_read_mem.read(io.rdaddr, io.rden)
}

object ElaborateSRMA extends App {

  val ruwList = List(
    SyncReadMem.ReadFirst,
    SyncReadMem.WriteFirst,
    SyncReadMem.Undefined)

  ruwList.foreach {
    ruw =>
      (new ChiselStage).emitVerilog(
        new SyncReadMemAccCollision(ruw),
        Array(
          "-td=rtl",
         s"-o=SyncReadMemAccCollision_${ruw.toString}")
      )
  }
}

このコードのElaborateSRMAを実行して、生成されたRTLの必要な部分を抜粋すると次のようになる。

  • SyncReadMem.ReadFirstを指定したRTL
    • 入力されたアドレスがそのままメモリの参照に使われるため、リードアクセス時点のメモリのデータが読める
module SyncReadMemAccCollision(
  input        clock,
  input        reset,
  input        io_wren,
  input  [3:0] io_wraddr,
  input  [7:0] io_wrdata,
  input  [3:0] io_rdaddr,
  input        io_rden,
  output [7:0] io_rddata
);

// 省略

  assign m_sync_read_mem__T_4_addr = io_rdaddr;
  `ifndef RANDOMIZE_GARBAGE_ASSIGN
  assign m_sync_read_mem__T_4_data = m_sync_read_mem[m_sync_read_mem__T_4_addr]; // @[SyncReadMemAccCollision.scala 18:36]
  `else
  assign m_sync_read_mem__T_4_data = m_sync_read_mem__T_4_addr >= 4'ha ? _RAND_1[7:0] : m_sync_read_mem[m_sync_read_mem__T_4_addr]; // @[SyncReadMemAccCollision.scala 18:36]
  `endif // RANDOMIZE_GARBAGE_ASSIGN

// 省略

  always @(posedge clock) begin
    if(m_sync_read_mem__T_en & m_sync_read_mem__T_mask) begin
      m_sync_read_mem[m_sync_read_mem__T_addr] <= m_sync_read_mem__T_data; // @[SyncReadMemAccCollision.scala 18:36]
    end
    // io_rdenが1'b1になった時のデータが1cycle遅れで出力される
    if (io_rden) begin
      m_sync_read_mem__T_4_data_pipe_0 <= m_sync_read_mem__T_4_data;
    end
  end
endmodule
  • SyncReadMem.WriteFirst/SyncReadMem.Undefinedを指定したRTL
    • 入力されたアドレスをFFで叩いたものがメモリの参照に使われるため、ライト後のデータが読める
module SyncReadMemAccCollision(
  input        clock,
  input        reset,
  input        io_wren,
  input  [3:0] io_wraddr,
  input  [7:0] io_wrdata,
  input  [3:0] io_rdaddr,
  input        io_rden,
  output [7:0] io_rddata
);

// 省略

  `ifndef RANDOMIZE_GARBAGE_ASSIGN
  assign m_sync_read_mem__T_4_data = m_sync_read_mem[m_sync_read_mem__T_4_addr]; // @[SyncReadMemAccCollision.scala 18:36]
  `else
  assign m_sync_read_mem__T_4_data = m_sync_read_mem__T_4_addr >= 4'ha ? _RAND_1[7:0] : m_sync_read_mem[m_sync_read_mem__T_4_addr]; // @[SyncReadMemAccCollision.scala 18:36]
  `endif // RANDOMIZE_GARBAGE_ASSIGN

// 省略

  always @(posedge clock) begin
    if(m_sync_read_mem__T_en & m_sync_read_mem__T_mask) begin
      m_sync_read_mem[m_sync_read_mem__T_addr] <= m_sync_read_mem__T_data; // @[SyncReadMemAccCollision.scala 18:36]
    end
    m_sync_read_mem__T_4_en_pipe_0 <= io_rden;
    // データではなく、io_rdaddrがレジスタに格納される
    // このため競合時にはライト後のデータが出力される
    if (io_rden) begin
      m_sync_read_mem__T_4_addr_pipe_0 <= io_rdaddr;
    end
  end
endmodule

一応、波形も取得して確認してみたが、以下の画像の通りでRTLと一致する挙動が確認できた。

  • SyncReadMem.ReadFirstを指定した時の波形

f:id:diningyo-kpuku-jougeki:20200726225345p:plain
SyncReadMem.ReadFirst指定時の波形

  • SyncReadMem.WriteFirst/SyncReadMem.Undefinedを指定した時の波形

f:id:diningyo-kpuku-jougeki:20200726225424p:plain
SyncReadMem.WriteFirst指定時の波形

ということで、SyncReadMemに引数が追加された話でした。