Skip to content

Commit

Permalink
Import memory files inline for Verilog generation
Browse files Browse the repository at this point in the history
This annotation adds memory import with inline generation for the
emmiter.
Supports both readmemh and readmemb statements based on argument.
  • Loading branch information
carlosedp committed Mar 9, 2021
1 parent 2b5466c commit 78efd29
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ test_run_dir
*~
\#*\#
.\#*
.metals
.bloop
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ case class ChiselLoadMemoryAnnotation[T <: Data](
}


/** [[loadMemoryFromFile]] is an annotation generator that helps with loading a memory from a text file. This relies on
/** [[loadMemoryFromFile]] is an annotation generator that helps with loading a memory from a text file as a bind module. This relies on
* Verilator and Verilog's `\$readmemh` or `\$readmemb`. The [[https://github.com/freechipsproject/treadle Treadle
* backend]] can also recognize this annotation and load memory at run-time.
*
Expand Down Expand Up @@ -116,6 +116,87 @@ object loadMemoryFromFile {
}
}


/** [[loadMemoryFromFileInline]] is an annotation generator that helps with loading a memory from a text file inlined in
* the Verilog module. This relies on Verilator and Verilog's `\$readmemh` or `\$readmemb`.
* The [[https://github.com/freechipsproject/treadle Treadlebackend]] can also recognize this annotation and load memory at run-time.
*
* This annotation, when the FIRRTL compiler runs, triggers the [[MemoryFileInlineAnnotation]] that will add Verilog
* directives inlined to the module enabling the specified memories to be initialized from files.
* The module supports both `hex` and `bin` files by passing the appropriate [[MemoryLoadFileType.FileType]] argument with
* [[MemoryLoadFileType.Hex]] or [[MemoryLoadFileType.Binary]]. Hex is the default.
*
* ==Example module==
*
* Consider a simple Module containing a memory:
* {{{
* import chisel3._
* class UsesMem(memoryDepth: Int, memoryType: Data) extends Module {
* val io = IO(new Bundle {
* val address = Input(UInt(memoryType.getWidth.W))
* val value = Output(memoryType)
* })
* val memory = Mem(memoryDepth, memoryType)
* io.value := memory(io.address)
* }
* }}}
*
* ==Above module with annotation==
*
* To load this memory from the file `/workspace/workdir/mem1.hex.txt` just add an import and annotate the memory:
* {{{
* import chisel3._
* import chisel3.util.experimental.loadMemoryFromFileInline // <<-- new import here
* class UsesMem(memoryDepth: Int, memoryType: Data) extends Module {
* val io = IO(new Bundle {
* val address = Input(UInt(memoryType.getWidth.W))
* val value = Output(memoryType)
* })
* val memory = Mem(memoryDepth, memoryType)
* io.value := memory(io.address)
* loadMemoryFromFileInline(memory, "/workspace/workdir/mem1.hex.txt") // <<-- Note the annotation here
* }
* }}}
*
* ==Example file format==
*
* A memory file should consist of ASCII text in either hex or binary format. The following example shows such a
* file formatted to use hex:
* {{{
* 0
* 7
* d
* 15
* }}}
*
* A binary file can be similarly constructed.
* Chisel does not validate the file format or existance. It is supposed to be in a path accessible by the Synthesis
* tool together with the generated Verilog.
*
* @see Chisel3 Wiki entry on
* [[https://github.com/freechipsproject/chisel3/wiki/Chisel-Memories#loading-memories-in-simulation "Loading Memories
* in Simulation"]]
*/
object loadMemoryFromFileInline {


/** Annotate a memory such that it can be initialized inline using a file
* @param memory the memory
* @param fileName the file used for initialization
* @param hexOrBinary whether the file uses a hex or binary number representation
*/
def apply[T <: Data](
memory: MemBase[T],
fileName: String,
hexOrBinary: MemoryLoadFileType.FileType = MemoryLoadFileType.Hex
): Unit = {
annotate(new ChiselAnnotation {
override def toFirrtl = MemoryFileInlineAnnotation(memory.toTarget, fileName, hexOrBinary)
})
// annotate(ChiselLoadMemoryAnnotation(memory, fileName, hexOrBinary))
}
}

/** This transform only is activated if Verilog is being generated (determined by presence of the proper emit
* annotation) when activated it creates additional Verilog files that contain modules bound to the modules that
* contain an initializable memory
Expand Down
53 changes: 52 additions & 1 deletion src/test/scala/chiselTests/LoadMemoryFromFileSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import java.io.File

import chisel3._
import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage}
import chisel3.util.experimental.loadMemoryFromFile
import chisel3.util.experimental.{loadMemoryFromFile,loadMemoryFromFileInline}
import chisel3.util.log2Ceil
import firrtl.FirrtlExecutionSuccess
import firrtl.annotations.MemoryLoadFileType
Expand All @@ -33,6 +33,26 @@ class UsesThreeMems(memoryDepth: Int, memoryType: Data) extends Module {
io.value3 := memory3(io.address)
}

class UsesThreeMemsInline(memoryDepth: Int, memoryType: Data, memoryFile: String, hexOrBinary: MemoryLoadFileType.FileType) extends Module {
val io = IO(new Bundle {
val address = Input(UInt(memoryType.getWidth.W))
val value1 = Output(memoryType)
val value2 = Output(memoryType)
val value3 = Output(memoryType)
})

val memory1 = Mem(memoryDepth, memoryType)
val memory2 = Mem(memoryDepth, memoryType)
val memory3 = Mem(memoryDepth, memoryType)
loadMemoryFromFileInline(memory1, memoryFile, hexOrBinary)
loadMemoryFromFileInline(memory2, memoryFile, hexOrBinary)
loadMemoryFromFileInline(memory3, memoryFile, hexOrBinary)

io.value1 := memory1(io.address)
io.value2 := memory2(io.address)
io.value3 := memory3(io.address)
}

class UsesMem(memoryDepth: Int, memoryType: Data) extends Module {
val io = IO(new Bundle {
val address = Input(UInt(memoryType.getWidth.W))
Expand Down Expand Up @@ -205,4 +225,35 @@ class LoadMemoryFromFileSpec extends AnyFreeSpec with Matchers {
file.delete()
}

"Module with more than one hex memory inline should work" in {
val testDirName = "test_run_dir/load_three_memory_spec_inline"

val result = (new ChiselStage).execute(
args = Array("-X", "verilog", "--target-dir", testDirName),
annotations = Seq(ChiselGeneratorAnnotation(() => new UsesThreeMemsInline(memoryDepth = 8, memoryType = UInt(16.W), "./testmem.h", MemoryLoadFileType.Hex)))
)
val dir = new File(testDirName)
val file = new File(dir, s"UsesThreeMemsInline.v")
file.exists() should be (true)
val fileText = io.Source.fromFile(file).getLines().mkString("\n")
fileText should include (s"""$$readmemh("./testmem.h", memory1);""")
fileText should include (s"""$$readmemh("./testmem.h", memory2);""")
fileText should include (s"""$$readmemh("./testmem.h", memory3);""")
}

"Module with more than one bin memory inline should work" in {
val testDirName = "test_run_dir/load_three_memory_spec_inline"

val result = (new ChiselStage).execute(
args = Array("-X", "verilog", "--target-dir", testDirName),
annotations = Seq(ChiselGeneratorAnnotation(() => new UsesThreeMemsInline(memoryDepth = 8, memoryType = UInt(16.W), "testmem.bin", MemoryLoadFileType.Binary)))
)
val dir = new File(testDirName)
val file = new File(dir, s"UsesThreeMemsInline.v")
file.exists() should be (true)
val fileText = io.Source.fromFile(file).getLines().mkString("\n")
fileText should include (s"""$$readmemb("testmem.bin", memory1);""")
fileText should include (s"""$$readmemb("testmem.bin", memory2);""")
fileText should include (s"""$$readmemb("testmem.bin", memory3);""")
}
}

0 comments on commit 78efd29

Please # to comment.