Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation guide about memory initialization #1850

Merged
merged 2 commits into from
Apr 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 122 additions & 28 deletions docs/src/appendix/experimental-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a name="fixed-point"></a>
FixedPoint numbers are basic *Data* type along side of UInt, SInt, etc. Most common math and logic operations
Expand Down Expand Up @@ -143,56 +143,150 @@ 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 <a name="loading-memories"></a>
## Loading Memories for simulation or FPGA initialization <a name="loading-memories"></a>

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)
val b = UInt(32.W)
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.
15 changes: 13 additions & 2 deletions docs/src/explanations/memories.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*)
Expand Down Expand Up @@ -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
Expand All @@ -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.

![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
Expand Down Expand Up @@ -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):

![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)
carlosedp marked this conversation as resolved.
Show resolved Hide resolved

### `Mem`: combinational/asynchronous-read, sequential/synchronous-write

Expand Down Expand Up @@ -174,3 +179,9 @@ class MaskedRWSmem extends Module {
}
```

### Memory Initialization
carlosedp marked this conversation as resolved.
Show resolved Hide resolved

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.