diff --git a/docs/src/appendix/experimental-features.md b/docs/src/appendix/experimental-features.md index c989ec97a93..1a6605ae644 100644 --- a/docs/src/appendix/experimental-features.md +++ b/docs/src/appendix/experimental-features.md @@ -143,49 +143,173 @@ Consider a Interval with a binary point of 3: aaa.bbb | shiftLeftBinaryPoint(2) | a.aabbb | 5 | X | X | increase the precision | | shiftRighBinaryPoint(2) | aaaa.b | 1 | X | X | reduce the precision | -## Loading Memories in simulation +## Loading Memories in simulation or FPGA initialization -Chisel now supports an experimental method for annotating memories to be loaded from a text file containing -hex or binary numbers. When using verilog simulation it uses the `$readmemh` or `$readmemb` -verilog extension. The treadle simulator can also load memories using the same annotation. +Chisel supports multiple experimental methods for annotating memories to be loaded from a text file containing hex or binary data. When using verilog simulation it uses the `$readmemh` or `$readmemb` verilog extension. The treadle simulator can also load memories using the same annotation. -### How to annotate -Assuming you have a memory in a Module -```scala mdoc:invisible +### Inline initialization with external file + +Memories can be initialized by generating inline `readmemh` or `readmemb` statements in the output Verilog. + +The function `loadMemoryFromFileInline` from `chisel3.util.experimental` allows the memory to be initialized by the synthesis software from the specified file. Chisel does not validate the file contents nor it's location. Both the memory initialization file and the Verilog source should be accessible for the toolchain. + +```scala mdoc:silent import chisel3._ -val memoryDepth = 8 -val memoryType = Bool() +import chisel3.util.experimental.loadMemoryFromFileInline + +class InitMemInline(memoryFile: String = "") extends Module { + val width: Int = 32 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(width.W)) + val dataOut = Output(UInt(width.W)) + }) + + val mem = SyncReadMem(1024, UInt(width.W)) + // Initialize memory + if (memoryFile.trim().nonEmpty) { + loadMemoryFromFileInline(mem, memoryFile) + } + io.dataOut := DontCare + when(io.enable) { + val rdwrPort = mem(io.addr) + when (io.write) { rdwrPort := io.dataIn } + .otherwise { io.dataOut := rdwrPort } + } +} ``` -```scala -val memory = Mem(memoryDepth, memoryType) + +The default is to use `$readmemh` (which assumes all numbers in the file are in ascii hex), +but to use ascii binary there is an optional `hexOrBinary` argument which can be set to `MemoryLoadFileType.Hex` or `MemoryLoadFileType.Binary`. You will need to add an the additional import. + +The inline initialization can generate the memory `readmem` statement inside a `ifndef SYNTHESIS` block, which suits ASIC workflow or outside the block suiting FPGA workflows. + +Some synthesis tools (like Synplify and Yosys) define `SYNTHESIS` so the `readmem` statement is not read when inside this block. + +To control this, one can use the `MemoryNoSynthInit` and `MemorySynthInit` annotations from `firrtl.annotations`. The former which is the default setting when no annotation is present generates `readmem` inside the block. Using the latter, the statement are generated outside the `ifndef` block so it can be used by FPGA synthesis tools. + +Below an example for initialization suited for FPGA workflows: + +```scala mdoc:silent +import chisel3._ +import chisel3.util.experimental.loadMemoryFromFileInline +import chisel3.experimental.{annotate, ChiselAnnotation} +import firrtl.annotations.MemorySynthInit + +class InitMemInlineFPGA(memoryFile: String = "") extends Module { + val width: Int = 32 + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(width.W)) + val dataOut = Output(UInt(width.W)) + }) + + // Notice the annotation below + annotate(new ChiselAnnotation { + override def toFirrtl = + MemorySynthInit + }) + + val mem = SyncReadMem(1024, UInt(width.W)) + if (memoryFile.trim().nonEmpty) { + loadMemoryFromFileInline(mem, memoryFile) + } + io.dataOut := DontCare + when(io.enable) { + val rdwrPort = mem(io.addr) + when (io.write) { rdwrPort := io.dataIn } + .otherwise { io.dataOut := rdwrPort } + } +} ``` -At the top of your file just add the import +#### SystemVerilog Bind Initialization + +Chisel also can initialize memories by generating a SV bind module with `readmemh` or `readmemb` statements by using the function `loadMemoryFromFile` from `chisel3.util.experimental`. + ```scala mdoc:silent +import chisel3._ import chisel3.util.experimental.loadMemoryFromFile + +class InitMemBind(val bits: Int, val size: Int, filename: String) extends Module { + val io = IO(new Bundle { + val nia = Input(UInt(bits.W)) + val insn = Output(UInt(32.W)) + }) + + val memory = Mem(size, UInt(32.W)) + io.insn := memory(io.nia >> 2); + loadMemoryFromFile(memory, filename) +} ``` -Now just add the memory annotation using -```scala - loadMemoryFromFile(memory, "/workspace/workdir/mem1.txt") + +Which generates the bind module: + +```verilog +module BindsTo_0_Foo( + input clock, + input reset, + input [31:0] io_nia, + output [31:0] io_insn +); + +initial begin + $readmemh("test.hex", Foo.memory); +end +endmodule + +bind Foo BindsTo_0_Foo BindsTo_0_Foo_Inst(.*); ``` -The default is to use `$readmemh` (which assumes all numbers in the file are in ascii hex), -bu to use ascii binary there is an optional third argument. You will need to add an additional import. -```scala mdoc:silent -import firrtl.annotations.MemoryLoadFileType + +#### Inline initialization with embedded file contents + +Memories can also be initialized embedding the file contents in the generated Verilog. + +Data can be read from a single `BigInt` or an array of `BigInt` by using the following annotations: + +```scala mdoc:invisible +import scala.io.Source +import chisel3._ +import chisel3.experimental.{annotate, ChiselAnnotation} +import firrtl.annotations.MemoryScalarInitAnnotation ``` -```scala - loadMemoryFromFile(memory, "/workspace/workdir/mem1.txt", MemoryLoadFileType.Binary) +```scala mdoc:silent +// Read data into a BigInt +val src = Source.fromFile("filename") +val lines = src.getLines().toList +src.close() +val data = lines.map(_.trim).filter(_.nonEmpty).map(BigInt(_, radix)) +val memory = Mem(32, UInt(32.W)) + +// Uses the above `data` BigInt +annotate(new ChiselAnnotation { + override def toFirrtl = MemoryScalarInitAnnotation(memory.toTarget, data) +}) + +// Or read from an array +annotate(new ChiselAnnotation { + override def toFirrtl = MemoryArrayInitAnnotation(memory.toTarget, Seq[0]) +}) ``` + +### Notes on files + +There is no simple answer to where to put this file. It's probably best to create a resource directory somewhere and reference that through a full path or place the file beside the generated Verilog. Another option is adding the path to the memory file in the synthesis tool path. Because these files may be large, Chisel does not copy them. +> Don't forget there is no decimal option, so a 10 in an input file will be 16 decimal + See: [ComplexMemoryLoadingSpec.scala](https://freechipsproject/chisel-testers/src/test/scala/examples/ComplexMemoryLoadingSpec.scala) and [LoadMemoryFromFileSpec.scala](https://github.com/freechipsproject/chisel-testers/src/test/scala/examples/LoadMemoryFromFileSpec.scala) for working examples. -### Notes on files -There is no simple answer to where to put this file. It's probably best to create a resource directory somewhere and reference that through a full path. Because these files may be large, we did not want to copy them. -> Don't forget there is no decimal option, so a 10 in an input file will be 16 decimal ### Aggregate memories + Aggregate memories are supported but in bit of a clunky way. Since they will be split up into a memory per field, the following convention was adopted. When specifying the file for such a memory the file name should be regarded as a template. If the memory is a Bundle e.g. + ```scala mdoc:compile-only class MemDataType extends Bundle { val a = UInt(16.W) @@ -193,6 +317,13 @@ class MemDataType extends Bundle { val c = Bool() } ``` + The memory will be split into `memory_a`, `memory_b`, and `memory_c`. Similarly if a load file is specified as `"memory-load.txt"` the simulation will expect that there will be three files, `"memory-load_a.txt"`, `"memory-load_b.txt"`, `"memory-load_c.txt"` > Note: The use of `_` and that the memory field name is added before any file suffix. The suffix is optional but if present is considered to be the text after the last `.` in the file name. + + + + + + diff --git a/docs/src/explanations/memories.md b/docs/src/explanations/memories.md index 9cac74a2f18..7c10ee08b55 100644 --- a/docs/src/explanations/memories.md +++ b/docs/src/explanations/memories.md @@ -30,7 +30,7 @@ where `inits` is a sequence of initial `Data` literals that initialize the ROM. We can create an *n* value sine lookup table using a ROM initialized as follows: -``` scala mdoc:silent +```scala mdoc:silent def sinTable(amp: Double, n: Int) = { val times = (0 until n).map(i => (i*2*Pi)/(n.toDouble-1) - Pi) @@ -58,6 +58,7 @@ If the same memory address is both written and sequentially read on the same clo Values on the read data port are not guaranteed to be held until the next read cycle. If that is the desired behavior, external logic to hold the last read value must be added. #### Read port/write port + Ports into `SyncReadMem`s are created by applying a `UInt` index. A 1024-entry SRAM with one write port and one read port might be expressed as follows: ```scala mdoc:silent @@ -81,9 +82,11 @@ class ReadWriteSmem extends Module { Below is an example waveform of the one write port/one read port `SyncReadMem` with [masks](#masks). Note that the signal names will differ from the exact wire names generated for the `SyncReadMem`. With masking, it is also possible that multiple RTL arrays will be generated with the behavior below. -![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/tut/chisel3/memories_waveforms/smem_read_write.json) +![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/resources/json/smem_read_write.json) + #### Single-ported + Single-ported SRAMs can be inferred when the read and write conditions are mutually exclusive in the same `when` chain: ```scala mdoc:silent @@ -112,7 +115,7 @@ class RWSmem extends Module { Here is an example single read/write port waveform, with [masks](#masks) (again, generated signal names and number of arrays may differ): -![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/tut/chisel3/memories_waveforms/smem_rw.json) +![read/write ports example waveform](https://svg.wavedrom.com/github/freechipsproject/www.chisel-lang.org/master/docs/src/main/resources/json/smem_rw.json) ### `Mem`: combinational/asynchronous-read, sequential/synchronous-write @@ -178,144 +181,7 @@ class MaskedRWSmem extends Module { ### Memory Initialization -Chisel memories can be initialized from an external binary or hex file emitting proper Verilog. There are multiple modes of initialization. - -#### Inline initialization with external file - -Memories can be initialized by generating inline `readmemh` or `readmemb` statements in the output Verilog. - -The function `loadMemoryFromFileInline` from `chisel3.util.experimental` allows the memory to be initialized by the synthesis software from the specified file. Chisel does not validate the file contents nor it's location. Both the memory initialization file and the Verilog source should be accessible for the toolchain. - -```scala mdoc:silent -import chisel3._ -import chisel3.util.experimental.loadMemoryFromFileInline -class InitMemInline(memoryFile: String = "") extends Module { - val width: Int = 32 - val io = IO(new Bundle { - val enable = Input(Bool()) - val write = Input(Bool()) - val addr = Input(UInt(10.W)) - val dataIn = Input(UInt(width.W)) - val dataOut = Output(UInt(width.W)) - }) - - val mem = SyncReadMem(1024, UInt(width.W)) - // Initialize memory - if (memoryFile.trim().nonEmpty) { - loadMemoryFromFileInline(mem, memoryFile) - } - io.dataOut := DontCare - when(io.enable) { - val rdwrPort = mem(io.addr) - when (io.write) { rdwrPort := io.dataIn } - .otherwise { io.dataOut := rdwrPort } - } -} -``` - -The inline initialization can generate the memory `readmem` statement inside a `ifndef SYNTHESIS` block, which suits ASIC workflow or outside the block suiting FPGA workflows. - -Some synthesis tools (like Synplify and Yosys) define `SYNTHESIS` so the `readmem` statement is not read when inside this block. - -To control this, one can use the `MemoryNoSynthInit` and `MemorySynthInit` annotations from `firrtl.annotations`. The former which is the default setting when no annotation is present generates `readmem` inside the block. Using the latter, the statement are generated outside the `ifndef` block so it can be used by FPGA synthesis tools. - -Below an example for initialization suited for FPGA workflows: - -```scala mdoc:silent -import chisel3._ -import chisel3.util.experimental.loadMemoryFromFileInline -import chisel3.experimental.{annotate, ChiselAnnotation} -import firrtl.annotations.MemorySynthInit - -class InitMemInlineFPGA(memoryFile: String = "") extends Module { - val width: Int = 32 - val io = IO(new Bundle { - val enable = Input(Bool()) - val write = Input(Bool()) - val addr = Input(UInt(10.W)) - val dataIn = Input(UInt(width.W)) - val dataOut = Output(UInt(width.W)) - }) - - // Notice the annotation below - annotate(new ChiselAnnotation { - override def toFirrtl = - MemorySynthInit - }) - - val mem = SyncReadMem(1024, UInt(width.W)) - if (memoryFile.trim().nonEmpty) { - loadMemoryFromFileInline(mem, memoryFile) - } - io.dataOut := DontCare - when(io.enable) { - val rdwrPort = mem(io.addr) - when (io.write) { rdwrPort := io.dataIn } - .otherwise { io.dataOut := rdwrPort } - } -} -``` - -#### SystemVerilog Bind Initialization - -Chisel also can initialize memories by generating a SV bind module with `readmemh` or `readmemb` statements by using the function `loadMemoryFromFile` from `chisel3.util.experimental`. +Chisel memories can be initialized from an external `binary` or `hex` file emitting proper Verilog for synthesis or simulation. There are multiple modes of initialization. -```scala mdoc:silent -import chisel3._ -import chisel3.util.experimental.loadMemoryFromFile - -class InitMemBind(val bits: Int, val size: Int, filename: String) extends Module { - val io = IO(new Bundle { - val nia = Input(UInt(bits.W)) - val insn = Output(UInt(32.W)) - }) - - val memory = Mem(size, UInt(32.W)) - io.insn := memory(io.nia >> 2); - loadMemoryFromFile(memory, filename) -} -``` - -Which generates the bind module: - -```verilog -module BindsTo_0_Foo( - input clock, - input reset, - input [31:0] io_nia, - output [31:0] io_insn -); - -initial begin - $readmemh("test.hex", Foo.memory); -end -endmodule - -bind Foo BindsTo_0_Foo BindsTo_0_Foo_Inst(.*); -``` - -#### Inline initialization with embedded file contents - -Memories can also be initialized embedding the file contents in the generated Verilog. - -Data can be read from a single `BigInt` or an array of `BigInt` by using the following annotations: - -```scala -// Read data into a BigInt -val src = Source.fromFile("filename") -val lines = src.getLines().toList -src.close() -val data = lines.map(_.trim).filter(_.nonEmpty).map(BigInt(_, radix)) -val memory = Mem(32, UInt(32.W)) - -// Uses the above `data` BigInt -annotate(new ChiselAnnotation { - override def toFirrtl = MemoryScalarInitAnnotation(memory.toTarget, data) -}) - -// Or read from an array -annotate(new ChiselAnnotation { - override def toFirrtl = MemoryArrayInitAnnotation(memory.toTarget, Seq[0]) -}) -``` +For more information, check the experimental docs on [Loading Memories](../appendix/experimental-features#loading-memories) feature.