転職でバタバタしてて、気づけば最後に更新してから早2ヶ月。。。
間が空いたけど、前回からの続きでChisel3.3.0の変更点を確認していく。
今回は#1183のSyncReadMem
の件について
#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
ということで、リード/ライトのアクセス競合時の挙動を指定子たい場合にはReadFirst
かWriteFirst
を指定すれば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
を指定した時の波形
SyncReadMem.WriteFirst
/SyncReadMem.Undefined
を指定した時の波形
ということで、SyncReadMem
に引数が追加された話でした。