Skip to content

Commit

Permalink
fix waveform simulation continuity problems
Browse files Browse the repository at this point in the history
  • Loading branch information
tomcl committed Sep 7, 2024
1 parent 6ff0e5d commit 12d7407
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 52 deletions.
5 changes: 5 additions & 0 deletions src/Renderer/Common/CommonTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -861,9 +861,14 @@ type WaveIndexT = {
}

type WSConfig = {
/// This is the last clock cycle number possibly needed by a waveform simulation
LastClock: int
/// currently this is always 0
/// TODO (maybe): implement simulation windows allowing this to be non-zero
FirstClock: int
/// The size of the waveform sdispaly font
FontSize: int
/// The weight of the waveform display font: 300 = normal, 600 = bold.
FontWeight: int
}

Expand Down
4 changes: 3 additions & 1 deletion src/Renderer/UI/ModelHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ open Optics.Operators

module Constants =
/// Needed to prevent possible overrun of simulation arrays
let multipliers = [1;2;5;10;20;50;100;200;500;1000]
let maxStepsOverflow = 3
let waveSimRequiredArraySize wsModel = wsModel.WSConfig.LastClock + maxStepsOverflow + List.last multipliers
let defaultWSConfig = {
LastClock = 2000; // Simulation array limit during wave simulation
FirstClock = 0; // first clock accessible - limits scroll range. NOT IMPLEMENTED
Expand Down Expand Up @@ -449,7 +451,7 @@ let resimulateWaveSimForErrors (model: Model) : Result<SimulationData, Simulatio
let ws = getWSModel model
let simSize =
match ws.State with
| Success -> ws.WSConfig.LastClock + Constants.maxStepsOverflow
| Success | Loading -> Constants.waveSimRequiredArraySize ws
| _ -> 10 // small value does not matter what it is.
simulateModel true model.WaveSimSheet simSize canv model
|> fst
Expand Down
2 changes: 1 addition & 1 deletion src/Renderer/UI/Update.fs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ let update (msg : Msg) oldModel =
{model with RunAfterRender = Some fn}, Cmd.none

| ChangeWaveSimMultiplier key ->
let table = WaveSimStyle.Constants.multipliers
let table = Constants.multipliers
if key < 0 || key >= table.Length then
printf $"Warning: Can't change multiplier to key = {key}"
model, Cmd.none
Expand Down
2 changes: 1 addition & 1 deletion src/Renderer/UI/UpdateHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ let shortDWSM (ws: WaveSimModel) =
let shortDisplayMsg (msg:Msg) =
match msg with
| ChangeWaveSimMultiplier n ->
List.tryItem n WaveSimStyle.Constants.multipliers
List.tryItem n Constants.multipliers
|> Option.map (fun n -> $"Set WS multiplier to {n}")
|> Option.defaultValue $"Invalid Ws mult key of {n}"
|> Some
Expand Down
1 change: 1 addition & 0 deletions src/Renderer/UI/WaveSim/WaveSimNavigation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ let clkCycleButtons (wsModel: WaveSimModel) (dispatch: Msg -> unit) : ReactEleme
// Text input box for manual selection of clock cycle
Input.number [
Input.Props clkCycleInputProps
Input.Id "clkCycleInput"
Input.Value (
match wsModel.ClkCycleBoxIsEmpty with
| true -> ""
Expand Down
62 changes: 34 additions & 28 deletions src/Renderer/UI/WaveSim/WaveSimSVGs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,15 @@ let nonBinaryFillPoints (startCycle: int) (clkCycleWidth: float) (gap: Gap): arr
let calculateBinaryTransitionsUInt32 (waveValues: array<uint32>) (startCycle: int) (shownCycles: int) (multiplier: int)
: array<BinaryTransition> =
let getBit bit = int32 bit
match startCycle, startCycle + shownCycles + 1 with
| startCyc, endCyc when startCyc = 0 && startCyc < endCyc && endCyc*multiplier < Array.length waveValues ->
match startCycle, startCycle + shownCycles - 1 with
| startCyc, endCyc when startCyc = 0 && startCyc <= endCyc && endCyc*multiplier < Array.length waveValues ->
// in this case, we need to add a 0 value to the start of the array
// we start on the first sample, end one after the last sample.
subSamp waveValues startCyc (endCyc-startCyc+1) multiplier
|> Array.append [| waveValues[0] |]
| startCyc, endCyc when 0 < startCyc && startCyc < endCyc && endCyc*multiplier < Array.length waveValues ->
subSamp waveValues (startCyc-1) (endCyc-startCyc+1) multiplier
| startCyc, endCyc when 0 < startCyc && startCyc <= endCyc && endCyc*multiplier < Array.length waveValues ->
// in this case we can start one before the first data sample, end one after the last sample, to work out the first transition
subSamp waveValues (startCyc-1) (endCyc-startCyc+2) multiplier
| _ ->
printfn $"Before Bin failure: waveValues.Length {waveValues.Length} \
e*m: {(startCycle+shownCycles-1)*multiplier} start {startCycle} shown {shownCycles} mult: {multiplier}"
Expand Down Expand Up @@ -509,31 +512,34 @@ let makeWaveformsWithTimeOut
(ws: WaveSimModel)
(wavesToBeMade: list<WaveIndexT>)
: {| WSM: WaveSimModel ; NumberDone: int ; TimeTaken: option<float> |}=

let start = TimeHelpers.getTimeMs()
let allWaves, numberDone, timeTaken =
((ws.AllWaves, 0, None), wavesToBeMade)
||> List.fold (fun (all,n, _) wi ->
match timeOut, TimeHelpers.getTimeMs() - start with
| Some timeOut, timeSoFar when timeOut < timeSoFar ->
all, n, Some timeSoFar
| _ ->
(Map.change wi (Option.map (generateWaveform ws wi)) all), n+1, None)
let finish = TimeHelpers.getTimeMs()
if Constants.showPerfLogs then
let countWavesWithWidthRange lowerLim upperLim =
wavesToBeMade
|> List.map (fun wi -> (Map.find wi allWaves).Width)
|> List.filter (fun width -> lowerLim <= width && width <= upperLim)
|> List.length
try
let start = TimeHelpers.getTimeMs()
let allWaves, numberDone, timeTaken =
((ws.AllWaves, 0, None), wavesToBeMade)
||> List.fold (fun (all,n, _) wi ->
match timeOut, TimeHelpers.getTimeMs() - start with
| Some timeOut, timeSoFar when timeOut < timeSoFar ->
all, n, Some timeSoFar
| _ ->
(Map.change wi (Option.map (generateWaveform ws wi)) all), n+1, None)
let finish = TimeHelpers.getTimeMs()
if Constants.showPerfLogs then
let countWavesWithWidthRange lowerLim upperLim =
wavesToBeMade
|> List.map (fun wi -> (Map.find wi allWaves).Width)
|> List.filter (fun width -> lowerLim <= width && width <= upperLim)
|> List.length

printfn "PERF:makeWaveformsWithTimeOut: generating visible only: %b" Constants.generateVisibleOnly
printfn "PERF:makeWaveformsWithTimeOut: making %d/%d waveforms" (List.length wavesToBeMade) (Map.count allWaves)
printfn "PERF:makeWaveformsWithTimeOut: binary = %d" (countWavesWithWidthRange 1 1)
printfn "PERF:makeWaveformsWithTimeOut: int32 = %d" (countWavesWithWidthRange 2 32)
printfn "PERF:makeWaveformsWithTimeOut: process took %.2fms" (finish-start)

{| WSM={ws with AllWaves = allWaves}; NumberDone=numberDone; TimeTaken = timeTaken|}
printfn "PERF:makeWaveformsWithTimeOut: generating visible only: %b" Constants.generateVisibleOnly
printfn "PERF:makeWaveformsWithTimeOut: making %d/%d waveforms" (List.length wavesToBeMade) (Map.count allWaves)
printfn "PERF:makeWaveformsWithTimeOut: binary = %d" (countWavesWithWidthRange 1 1)
printfn "PERF:makeWaveformsWithTimeOut: int32 = %d" (countWavesWithWidthRange 2 32)
printfn "PERF:makeWaveformsWithTimeOut: process took %.2fms" (finish-start)
{| WSM={ws with AllWaves = allWaves}; NumberDone=numberDone; TimeTaken = timeTaken|}
with
| ex ->
printfn $"Error in makeWaveformsWithTimeOut: {ex.Message}"
{| WSM=ws; NumberDone=0; TimeTaken= None |}



Expand Down
1 change: 0 additions & 1 deletion src/Renderer/UI/WaveSim/WaveSimStyle.fs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ module Constants =

let waveLegendMaxChars = 35
let valueColumnMaxChars = 35
let multipliers = [1;2;5;10;20;50;100;200;500;1000]
let maxRamRowsDisplayed = 50
let maxRamLocsWithSparseDisplay = 100

Expand Down
56 changes: 36 additions & 20 deletions src/Renderer/UI/WaveSim/WaveSimTop.fs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ module Refresh =
/// Make sure we always have consistent parameters. They will be written back to model after this function terminates.
/// the validation may be done more than onece because this function is recursive, but that is OK.
/// validateSimparas is idempotent unless model changes.
/// Payload for Spinner to use when refreshing waveforms with progress bar.
let getRefreshSimPayload model =
let wsModel = getWSModel model
fst (refreshWaveSim false wsModel model) // get model after refreshWaveSim has run
|> (fun model ->
// After waveform generation if possible focus must return to the clock cycle box
// since typing in this causes waveform generation.
let el = Browser.Dom.document.getElementById "clkCycleInput"
if el <> null then el.focus()
model)

let wsModel =
let createWaves (wsModel: WaveSimModel) =
{wsModel with AllWaves = WaveSimSVGs.getWaves wsModel (Simulator.getFastSim())}
Expand All @@ -79,25 +91,30 @@ module Refresh =
// special case if simulation is empty there is nothing to do. Not sure why this is needed.
let fs = Simulator.getFastSim()
if fs.NumStepArrays = 0 then
model, Elmish.Cmd.none
model, Elmish.Cmd.none
else
// The simulation must be run to the last cycle needed for the current view.
// This may require no work, in which case runFastSimulation will return immediately.
// NB during waveform simulation the simulation buffer is NOT used as a circular buffer. Simulation
// length is therefore limited to the size of the buffer.
// All date from time = 0 is stored.

let lastCycleNeeded =
((wsModel.ShownCycles + wsModel.StartCycle - 1)*wsModel.SamplingZoom + //last shown cycle
wsModel.SamplingZoom - 1) // last real cycle within this range
|> min wsModel.WSConfig.LastClock // cannot go beyond the last clock cycle
/// This is the highest simulation cycle that might be required in this simulation
/// as determined by the current WSConfig.
let cycleLimit =
(wsModel.ShownCycles + wsModel.StartCycle)*wsModel.SamplingZoom //last shown cycle + 1, to get transitions
|> min (wsModel.WSConfig.LastClock + Constants.maxStepsOverflow - 1 + wsModel.SamplingZoom) // cannot go beyond the array

if lastCycleNeeded >= fs.MaxArraySize then
failwithf $"Sanity check failed: lastCycleNeeded = {lastCycleNeeded} >= fs.MaxArraySize = {fs.MaxArraySize}"
if cycleLimit >= fs.MaxArraySize then
failwithf $"Sanity check failed: lastCycleNeeded = {cycleLimit} >= fs.MaxArraySize = {fs.MaxArraySize}"

let lastCycleNeeded =
(wsModel.ShownCycles + wsModel.StartCycle)*wsModel.SamplingZoom + 1
|> min cycleLimit

FastRun.runFastSimulation (Some Constants.initSimulationTime) lastCycleNeeded fs
|> (fun speedOpt -> // if not None the simulation has timed out and has not yet completed
let cyclesToDo = lastCycleNeeded - fs.ClockTick // may be negative
let cyclesToDo = cycleLimit - fs.ClockTick // may be negative
let action =
match speedOpt, Option.isSome model.Spinner with
| None, _ ->
Expand All @@ -110,9 +127,9 @@ module Refresh =
ContinueSimWithSpinner
match action with
| ContinueSimAfterStartingSpinner ->
// long simulation, set spinner on and dispatch another refresh
let spinnerFunc = fun dispatch model ->
fst (refreshWaveSim false wsModel model)
// long simulation, set spinner on and dispatch another refresh
printfn "dispatching continuation..."
let spinnerFunc = fun _dispatch model -> getRefreshSimPayload model
let model =
model
|> updateSpinner "Extending Circuit Simulation..." spinnerFunc cyclesToDo
Expand All @@ -123,7 +140,7 @@ module Refresh =
| FinishedSim ->
if action = FinishSimNow || action = ContinueSimWithSpinner then
// force simulation to finish now
FastRun.runFastSimulation None lastCycleNeeded fs |> ignore
FastRun.runFastSimulation None cycleLimit fs |> ignore

// simulation has now always finished so can generate waves

Expand All @@ -133,8 +150,7 @@ module Refresh =
// redo waves based on simulation data which is now correct
let allWaves = wsModel.AllWaves

let simulationIsUptodate = Simulator.getFastSim().ClockTick > lastCycleNeeded

let simulationIsUptodate = Simulator.getFastSim().ClockTick >= lastCycleNeeded

// need to use isSameWave here because array index may have changed
let wavesToBeMade =
Expand All @@ -160,15 +176,15 @@ module Refresh =
WaveSimSVGs.makeWaveformsWithTimeOut (Some Constants.initSimulationTime) wsModel wavesToBeMade
|> (fun res ->
match wavesToBeMade.Length - res.NumberDone, res.TimeTaken with
| n, None ->
| _, None | 0, _->
{| WSM=res.WSM; SpinnerPayload=None; NumToDo=numToDo|} // finished
| _ when res.NumberDone = 0 ->
failwithf "What? makewaveformsWithTimeOut must make at least one waveform"
| numToDo, Some t when float numToDo * t / float res.NumberDone < Constants.maxSimulationTimeWithoutSpinner ->
let res2 = WaveSimSVGs.makeWaveformsWithTimeOut None res.WSM wavesToBeMade
{| WSM= res2.WSM; SpinnerPayload=None; NumToDo = numToDo - res2.NumberDone|}
| numToDo, _ ->
let payload = Some ("Updating Waveform Display: select fewer waveforms for better scroll performance", refreshWaveSim false res.WSM >> fst)
if res.NumberDone = 0 && numToDo > 0 then
printf $"No waves completed when {numToDo} are required. This is probably an error. Retrying refreshWaveSim"
let payload = Some ("Updating Waveform Display", refreshWaveSim false res.WSM >> fst)
{| WSM=res.WSM; SpinnerPayload=payload; NumToDo=numToDo|})

let ramComps =
Expand Down Expand Up @@ -242,10 +258,11 @@ let refreshButtonAction canvasState model dispatch = fun _ ->
let wsModel = getWSModel model
//printfn $"simSheet={wsSheet}, wsModel sheet = {wsModel.TopSheet},{wsModel.FastSim.SimulatedTopSheet}, state={wsModel.State}"
let simRes =
// Here is where the new fast simulation is created
ModelHelpers.simulateModel
true
model.WaveSimSheet
(wsModel.WSConfig.LastClock + ModelHelpers.Constants.maxStepsOverflow)
(Constants.waveSimRequiredArraySize wsModel)
canvasState model

match simRes with
Expand All @@ -262,7 +279,6 @@ let refreshButtonAction canvasState model dispatch = fun _ ->
dispatch <| UpdateWSModel (fun wsModel -> {wsModel with DefaultCursor = Default})
else
dispatch <| SetWSModelAndSheet ({ wsModel with State = NonSequential}, wsSheet)

dispatch <| RunAfterRender(dispatch, fun dispatch model -> startWaveSimulation dispatch model; model)


Expand Down

0 comments on commit 12d7407

Please sign in to comment.