|
SimulationBudget
|
How much memory a simulation may take, and of which kind.
Here rather than beside the code that spends it because two different parts of the simulator
spend it: GraphMerger, which expands the design into a graph, and FastCreate, which allocates
the step arrays. Both come out of the same memory and GraphMerger is compiled first.
Sizes are float and not int64 on purpose. Fable compiles int64 to BigInt, so every comparison
here would allocate one - a poor thing to spend on deciding whether a design is too big. A float
carries integers exactly to 2^53 and the largest number reached here is a few hundred GB.
|
|
BigIntState
|
Used for efficiency reasons.
For a given normal simulation these arrays show whether the corresponding
component input or output is a bigint or a unint32 type bus, and therefore
show IOArray array is used for the data.
|
|
Driver
|
Convenience array used so that waveform simulation can access
component outputs (drivers) without a Map lookup
|
|
FData
|
|
|
FastComponent
|
FastComponent represents a physical component in a simulation. Because sheets can be
instantiated in multiple places a given sheet component can have multiple FastComponents
in the simulation.
Arrays on FastComponent are filled up with simulation data per clock step as a clocked
simulation progresses.
Equality is by reference: a FastComponent is a mutable object with an identity, holding
step arrays that can run to megabytes, so comparing two of them field by field would be
both meaningless and ruinous. It also carries its own reducers, which are functions and
so have no structural equality at all.
|
|
FastSimulation
|
|
|
GatherData
|
Scaffolding for building a FastSimulation, and alive only while one is built.
It used to be four `Map`s: the flattened design indexed the several ways the phases after the
flatten needed it, all of them keyed structurally by (ComponentId, access path). Every one of
those keys cost a boxed comparison per tree level of every lookup, and the build does millions
of lookups - a measured fifth of a 480,000-component build went on one of them. They are now a
single index space instead: the flatten creates each FastComponent, stamps it with its position
in `Comps`, and expresses every link it finds as those indices. Nothing here is keyed by
anything but an int, and what a built simulation offers the rest of the program - the store in
gather order, and one map from a design-time name into it - is made once at the end, from this.
One store and one index space, holding the FastComponents themselves: custom against ordinary
is a PREDICATE over it, never a second store. Splitting them is the obvious tidy-up and it is
what would break this - the indices the links carry would then mean two different things.
Deliberately not kept after the build. It holds a SimulationComponent per component INSTANCE
through the FastComponents it stores, so on a large design it is one of the biggest things the
simulator ever allocates, and a FastSimulation left holding one made every later edit slower by
giving each major GC all of it to trace.
|
|
IOArray
|
This type represents an array of time steps of simulation data.
In any simulation, for a given IOArray, only one of the three step stores will be used.
For (very strong) efficiency reasons this cannot be implemented as a disjoint union:
the code that reads and writes IOArray elements will access the appropriate store.
Truthtable simulations use FDataStep everywhere.
Normal simulations use the UInt32 or BigInt store according to the size of the relevant bus.
The uint32 and bigint stores are REGIONS OF SHARED SLABS, not arrays of their own: step s of
this IO lives at `slab[StepBase + s]`, and `StepLength` steps belong to it. FastCreate's
arena packs every step region of a build into a few large slabs (under both runtimes), so a
design has dozens of step allocations rather than one per output port, and a port's data is
named by an integer offset. Always go through the members below - indexing a slab without
adding StepBase reads another port's data, which no bounds check will ever catch.
|
|
RunOutcome
|
What a run of a fast simulation did.
Not a rate. It used to return cycles per millisecond, with None meaning both "nothing to do" and
"finished" - so a caller could not tell those apart, and the one caller that used the number
divided by it to guess how long the rest would take. Guessing elapsed time from work done is
what breaks when a machine sleeps mid-run: the rate collapses towards zero and instant work is
predicted to take minutes. This says what happened and nothing more.
|
|
SheetPort
|
Type used to tie component ports to simulation data
for advanved wavefor simulation features.
|
|
SimulatedDesign
|
The design a simulation was built FROM, as opposed to the simulation built from it.
**Everything here is the size of the design somebody drew.** A design of seven sheets is seven
sheets here however many instances it expands to - and a design that expands to 49,152 sheet
instances is exactly the case this exists for. The renderer needs the design to draw a
selector, a hierarchy and a set of names; it does not need the expansion, and holding one to
get at the other is what made the renderer's memory grow with a simulation running elsewhere.
The instance queries below are the point of the type. An instance is an `InstancePath`, a list
of the custom-component ids entered on the way down to it, and every question anyone asks about
one - which sheet is it a copy of, what is it labelled, what is inside it - is answered by
walking that path through the DESIGN. Each step is a lookup in one sheet's components, so a
query costs the depth of the path and the size of a sheet, and never the expansion. They used
to be answered from maps built over every instance in the simulation: 208,896 entries on
largeTest, for questions only ever asked about the handful of instances on screen.
The design the simulation was BUILT from, not the one on the canvas now. An edit to the
schematic is meant to change nothing in a running waveform simulation until it is restarted or
refreshed, and reading the live project instead moved half the selector and left the rest.
|
|
SimulationData
|
- Top level data tracking a simulation
|
|
SimulationRunStatus
|
document current status of a simulation as used by waveform simulator
|
|
StepArray<'T>
|
Wrapper to allow arrays to be resized for longer simulations while keeping the links between inputs
and outputs
|
|
StepCost
|
What one clock cycle of a design costs in step arrays, kept apart by which memory it comes from.
The two are not interchangeable, and measurably so - though not in the way performance.memory
suggests. usedJSHeapSize counts Uint32Arrays at every size, so it cannot tell the two apart;
what separates them is the LIMIT. Uint32Array allocation ran to 15.5GB on a 32GB machine
against a jsHeapSizeLimit of 3.7GB, so those are bounded by the machine and not by V8's pointer
compression cage. Buses wider than 32 bits are held as a plain array of BigInt, which is
ordinary heap - 400MB of values cost 454MB - inside that 4GB cage, shared with the model, the
design and the waveforms. So a design can be refused for the second while nowhere near the first.
FastCreate.stepBytesForWidth works out one port's share; FastCreate.stepCostOfGraph totals it.
|
|
StepIndex
|
Where one clock step sits in the circular simulation arrays: the step number itself, its
index into the arrays, and the index of the step before it. All three follow from the step
number, so the simulation loop works them out once per step and hands the same value to
every component - numStep % maxArraySize is an integer division, and it used to be redone
for every component of every step. A struct so that passing it costs nothing.
|
|
WaveTables
|
Whether a build makes the tables only a waveform VIEWER needs: WaveComps, Drivers and
WaveIndex.
Nothing that RUNS a simulation reads any of them - not the run loop, not the reducers, not
FastExtract, not RamView - so a simulator that only runs and is read over a wire can leave them
out. They are sized by the EXPANSION, one entry per port of every instance, which is exactly
what a remote simulator exists to keep out of the process that draws.
The custom-component port linking that used to be part of the same phase is not optional and
happens either way: it re-points a custom component's ports at the arrays of the Input and
Output components inside it, which is what makes those ports readable at all.
|