Chisel: How the "read" function (or macro) is implemented for the SyncReadMem class?

Viewed 105

Can anyone help me to understand how "read" macro is implemented? I have the feeling that "do_read" function below is actually called, but could not figure out how that is done. I'm intrigued by the "SourceInfoTransform" class. Can anyone give me a hint on its usage? The "SyncReadMem" implementation is listed below.

Thanks in advance for any help!

Best regards,

-Fei

sealed class SyncReadMem[T <: Data] private (t: T, n: BigInt, val readUnderWrite: SyncReadMem.ReadUnderWrite) extends MemBase[T](t, n) {
  def read(x: UInt, en: Bool): T = macro SourceInfoTransform.xEnArg

  /** @group SourceInfoTransformMacro */
  def do_read(addr: UInt, enable: Bool)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = {
    val a = Wire(UInt())
    a := DontCare
    var port: Option[T] = None
    when (enable) {
      a := addr
      port = Some(read(a))
    }
    port.get
  }
}
2 Answers

The SourceInfoTransform is a scala macro that transforms the def read into the def do_read. The code for the macro is in src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala of github.com/chipsalliance/chisel3. In that file there are a lot of transform classes for handling different chisel constructs with different numbers of arguments. The main use of the SourceInfoTransform is to get the line number of the Chisel/Scala source so it can be reported in Exceptions and in generated Firrtl and Verilog. Here is an article on macros, there are many more available. Good luck.

Chick's answer is mostly correct--it's correct on the "how" and where to look, but slightly wrong on the "why":

The main use of the SourceInfoTransform is to get the line number of the Chisel/Scala source

This is not quite true, you can get source locators with merely having implicit sourceInfo: SourceInfo, and you could have that on the original def read if you wanted to. What the SourceInfoTransform macros do is resolve ambiguity when you want to do a bit extraction immediately following invoking the read method, eg.

myMem.read(addr, en)(16, 0)

If we had defined read with the implicits directly, the compiler would think you're trying to pass 16 and 0 as the implicit arguments, when in reality, you're trying to call .apply on the resulting T (if it's a subtype of Bits). The macro resolves the ambiguity so that the compiler knowns you're not trying to pass the implicits.

Here's a link to a talk where I describe this (warning while the little part about source locators and the macro are correct, much of this talk is out-of-date): https://youtu.be/2-ZiXNd9wbc?t=2756

Related