Header menu logo issie

RamStore Module

 How a read/write memory's contents are held while a simulation runs.

 See [docs/dev/ramRepresentation.md] for why this exists and what it is worth. In short: the
 contents used to be a `Map` and a fresh snapshot of it was stored for every
 clock step, so a write cost an AVL path copy plus two boxed `BigInt`s, a read cost a descent
 with boxed comparisons, and a long waveform simulation retained every version it had ever
 made. Here

 - a read during simulation is an array index,
 - a write appends two numbers to shared flat arrays,
 - the value an address held at any past step is a binary search of that address's own writes,
   whose cost does not depend on how far the cursor moved, and
 - nothing at all is allocated per step.

 **Storage is CSR.** Every address that is ever written is given a *slot* number, and a slot's
 writes are one contiguous run of the shared `IStep`/`IVal` arrays, delimited by `Start`. The
 alternative - a growable list per address - costs a record and two list objects per address,
 which is around 230 bytes for what may be a single 8-byte write. That is affordable for one
 big RAM and is not for a hundred small ones, which between them have just as many addresses.
 Recent writes go to a small tail and are folded in by `compact`.

 **Where this file sits.** F# compile order is the dependency layering, and
 `SimGraphTypes.SimulationComponentState` names this type, so it has to be compiled before
 `SimGraphTypes.fs` and cannot live in the `FastSim` block with the rest of the simulator. It
 depends on nothing but `CommonTypes`, which is what makes that possible.

 **Mutability.** This is the same layer as the step arrays, which are already mutable, and
 [docs/mutableState.md] allows it for a measured performance reason - the whole point here is
 to stop allocating. Nothing mutable escapes: `Memory1` remains the immutable type everywhere
 outside the running simulation, and `toMemory` builds one on demand.

Types and nested modules

Type/Module Description

Constants

Addressing

How an address is turned into its slot number. This is the *only* place the strategies differ: everything after it works on slot numbers.

NodeOrLeaf

A 16-way trie over the address, used when the address space is too large to give every address a slot number in a flat array. It is grown in place and never copied: a slot is filled once and never repointed, which is what removes the per-write path copy the `Map` paid. A `Leaf` may sit above its natural depth - that path compression is the whole reason this is a union rather than a fixed-depth array of arrays, since depth follows from the address width and would otherwise be known - so the leaf carries its own address and it is checked on arrival.

Ram

A read/write memory as the simulator holds it.

Functions and values

Function or value Description

fixedOf mem

Full Usage: fixedOf mem

Parameters:
Returns: Ram

A read-only memory wrapped so that the one extraction path can serve ROMs and RAMs alike.

mem : Memory1
Returns: Ram

liveCountAt ram step

Full Usage: liveCountAt ram step

Parameters:
    ram : Ram
    step : int

Returns: int

How many words were non-zero at the end of `step`. Only diagnostics and tests want the exact number; the display wants `liveCountExceeds`.

ram : Ram
step : int
Returns: int

liveCountExceeds ram step limit

Full Usage: liveCountExceeds ram step limit

Parameters:
    ram : Ram
    step : int
    limit : int

Returns: bool

Whether more than `limit` words were non-zero at the end of `step`. This is all the RAM table needs: it chooses between listing every non-zero location and a windowed display, and the exact figure never appears. Asking the cheaper question means the walk stops as soon as the answer is settled, and usually it never starts - a memory that has never had more than `limit` addresses written cannot have more than `limit` non-zero.

ram : Ram
step : int
limit : int
Returns: bool

ofMemory window mem

Full Usage: ofMemory window mem

Parameters:
Returns: Ram

Build the store for one read/write memory. `window` is the simulation's `MaxArraySize`: the number of past steps that can still be asked about.

window : int
mem : Memory1
Returns: Ram

readAddrBigIntDataBigInt ram addr

Full Usage: readAddrBigIntDataBigInt ram addr

Parameters:
    ram : Ram
    addr : bigint

Returns: bigint
ram : Ram
addr : bigint
Returns: bigint

readAddrBigIntDataUInt32 ram addr

Full Usage: readAddrBigIntDataUInt32 ram addr

Parameters:
    ram : Ram
    addr : bigint

Returns: uint32
ram : Ram
addr : bigint
Returns: uint32

readAddrUInt32DataBigInt ram addr

Full Usage: readAddrUInt32DataBigInt ram addr

Parameters:
    ram : Ram
    addr : uint32

Returns: bigint
ram : Ram
addr : uint32
Returns: bigint

readAddrUInt32DataUInt32 ram addr

Full Usage: readAddrUInt32DataUInt32 ram addr

Parameters:
    ram : Ram
    addr : uint32

Returns: uint32

The four combinations the evaluators need, named as the `Memory1` versions they replace so that the call sites read the same.

ram : Ram
addr : uint32
Returns: uint32

readBigVal ram addr

Full Usage: readBigVal ram addr

Parameters:
    ram : Ram
    addr : uint32

Returns: bigint

Current contents at `addr`, on the bigint-value path. A store keeps values in one of two forms, chosen once by word width, and the caller has to ask for the one it holds - the evaluators do, via `BigIntState`. Asking for the other would otherwise read an array that was never filled, so it is redirected rather than trusted.

ram : Ram
addr : uint32
Returns: bigint

readU ram addr

Full Usage: readU ram addr

Parameters:
    ram : Ram
    addr : uint32

Returns: uint32

Current contents at `addr`, on the uint32 path. One array index in the dense case, which is the case every RAM a design can actually simulate falls into.

ram : Ram
addr : uint32
Returns: uint32

reset ram

Full Usage: reset ram

Parameters:

Put the memory back to the contents it was built with, discarding all history. Called when a simulation restarts, which is the one thing the old representation got wrong: it read the state left by the wrapped-out end of the previous run instead.

ram : Ram

toMemory ram step

Full Usage: toMemory ram step

Parameters:
    ram : Ram
    step : int

Returns: Memory1

The whole memory as of `step`, as the immutable type the rest of Issie uses. Only for the places that genuinely want all of it - a memory diff, a golden file - never per render.

ram : Ram
step : int
Returns: Memory1

wordAt ram step addr

Full Usage: wordAt ram step addr

Parameters:
    ram : Ram
    step : int
    addr : bigint

Returns: bigint

What one address held at the end of `step`.

ram : Ram
step : int
addr : bigint
Returns: bigint

writeAddrBigIntDataBigInt ram step addr value

Full Usage: writeAddrBigIntDataBigInt ram step addr value

Parameters:
    ram : Ram
    step : int
    addr : bigint
    value : bigint

ram : Ram
step : int
addr : bigint
value : bigint

writeAddrBigIntDataUInt32 ram step addr value

Full Usage: writeAddrBigIntDataUInt32 ram step addr value

Parameters:
    ram : Ram
    step : int
    addr : bigint
    value : uint32

ram : Ram
step : int
addr : bigint
value : uint32

writeAddrUInt32DataBigInt ram step addr value

Full Usage: writeAddrUInt32DataBigInt ram step addr value

Parameters:
    ram : Ram
    step : int
    addr : uint32
    value : bigint

ram : Ram
step : int
addr : uint32
value : bigint

writeAddrUInt32DataUInt32 ram step addr value

Full Usage: writeAddrUInt32DataUInt32 ram step addr value

Parameters:
    ram : Ram
    step : int
    addr : uint32
    value : uint32

ram : Ram
step : int
addr : uint32
value : uint32

writeBigVal ram step addr value

Full Usage: writeBigVal ram step addr value

Parameters:
    ram : Ram
    step : int
    addr : uint32
    value : bigint

ram : Ram
step : int
addr : uint32
value : bigint

writeU ram step addr value

Full Usage: writeU ram step addr value

Parameters:
    ram : Ram
    step : int
    addr : uint32
    value : uint32

Record a write, unless it changes nothing. A write of the value already at that address is not a write: skipping it costs one comparison on a value the reducer has already read (it is the old contents, which a RAM returns as its output) and saves an entry, which every later search and every later compaction then does not have to walk. Nothing observable changes - the contents are identical by definition, and the RAM table's read/write highlighting comes from the input step arrays rather than from here.

ram : Ram
step : int
addr : uint32
value : uint32

Type something to start searching.