From e0da5ae47f3674bdd2018a672028290c927274e1 Mon Sep 17 00:00:00 2001
From: Carlos Eduardo
Date: Wed, 7 Apr 2021 16:01:51 -0300
Subject: Add documentation guide about memory initialization (#1850)
* Add documentation guide about memory initialization
* Move information to experimental and add ref---
docs/src/appendix/experimental-features.md | 150 +++++++++++++++++++++++------
docs/src/explanations/memories.md | 15 ++-
2 files changed, 135 insertions(+), 30 deletions(-)
diff --git a/docs/src/appendix/experimental-features.md b/docs/src/appendix/experimental-features.md
index c989ec97..363f7a06 100644
--- a/docs/src/appendix/experimental-features.md
+++ b/docs/src/appendix/experimental-features.md
@@ -10,7 +10,7 @@ Chisel has a number of new features that are worth checking out. This page is a
- [Module Variants](#module-variants)
- [Module Variants](#bundle-literals)
- [Interval Type](#interval-type)
-- [Loading Memories in Simulation](#loading-memories)
+- [Loading Memories for simulation or FPGA initialization](#loading-memories)
### FixedPoint
FixedPoint numbers are basic *Data* type along side of UInt, SInt, etc. Most common math and logic operations
@@ -143,49 +143,142 @@ 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 for 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 its 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()
-```
-```scala
-val memory = Mem(memoryDepth, memoryType)
+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 }
+ }
+}
```
-At the top of your file just add the import
+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 additional import.
+
+By default, the inline initialization will generate the memory `readmem` statements inside an `ifndef SYNTHESIS` block, which suits ASIC workflow.
+
+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.util.experimental.loadMemoryFromFile
-```
-Now just add the memory annotation using
-```scala
- loadMemoryFromFile(memory, "/workspace/workdir/mem1.txt")
+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 }
+ }
+}
```
-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.
+
+#### SystemVerilog Bind Initialization
+
+Chisel can also 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 firrtl.annotations.MemoryLoadFileType
+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)
+}
```
-```scala
- loadMemoryFromFile(memory, "/workspace/workdir/mem1.txt", MemoryLoadFileType.Binary)
+
+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(.*);
```
-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.
+
+There is no simple answer to where to put the `hex` or `bin` file with the initial contents. 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://github.com/freechipsproject/chisel-testers/blob/master/src/test/scala/examples/ComplexMemoryLoadingSpec.scala) and
+[LoadMemoryFromFileSpec.scala](https://github.com/freechipsproject/chisel-testers/blob/master/src/test/scala/examples/LoadMemoryFromFileSpec.scala)
+for working examples.
+
+
### 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 +286,7 @@ 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 792d176e..09ac4c8d 100644
--- a/docs/src/explanations/memories.md
+++ b/docs/src/explanations/memories.md
@@ -11,9 +11,11 @@ Chisel provides facilities for creating both read only and read/write memories.
## ROM
Users can define read only memories with a `Vec`:
+
```scala mdoc:invisible
import chisel3._
```
+
``` scala mdoc:compile-only
VecInit(inits: Seq[T])
VecInit(elt0: T, elts: T*)
@@ -56,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
@@ -79,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.
-
+
+
#### 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
@@ -110,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):
-
+
### `Mem`: combinational/asynchronous-read, sequential/synchronous-write
@@ -174,3 +179,9 @@ class MaskedRWSmem extends Module {
}
```
+### Memory Initialization
+
+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.
+
+For more information, check the experimental docs on [Loading Memories](../appendix/experimental-features#loading-memories) feature.
+
--
cgit v1.2.3