diff --git a/src/Renderer/Renderer.fsproj b/src/Renderer/Renderer.fsproj
index 4d6dc50dc..729ac3748 100644
--- a/src/Renderer/Renderer.fsproj
+++ b/src/Renderer/Renderer.fsproj
@@ -103,9 +103,13 @@
-
+
+
+
+
+
diff --git a/src/Renderer/UI/MainView.fs b/src/Renderer/UI/MainView.fs
index 23884d18b..b759186a7 100644
--- a/src/Renderer/UI/MainView.fs
+++ b/src/Renderer/UI/MainView.fs
@@ -335,7 +335,7 @@ let displayView model dispatch =
newWidth
|> max minViewerWidth
|> min (windowX - minEditorWidth())
- setViewerWidthInWaveSim w dispatch
+ WaveSimNavigation.setViewerWidthInWaveSim w dispatch
dispatch <| SetDragMode DragModeOff
dispatch <| SetViewerWidth w
| _ -> ()
diff --git a/src/Renderer/UI/Update.fs b/src/Renderer/UI/Update.fs
index 3017ad8a4..22439e856 100644
--- a/src/Renderer/UI/Update.fs
+++ b/src/Renderer/UI/Update.fs
@@ -17,6 +17,7 @@ open Optics
open Optics.Optic
open Optics.Operators
+
//---------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------//
//---------------------------------- Update Model ---------------------------------------------//
@@ -67,7 +68,7 @@ let update (msg : Msg) oldModel =
match testMsg with
| ChangeWaveSimMultiplier key ->
- let table = WaveSimHelpers.Constants.multipliers
+ let table = WaveSimStyle.Constants.multipliers
if key < 0 || key >= table.Length then
printf $"Warning: Can't chnage multiplier to key = {key}"
model, Cmd.none
@@ -80,7 +81,7 @@ let update (msg : Msg) oldModel =
model
|> Optic.map waveSim_ (fun ws ->
let wsModel = ws[sheet]
- Map.add sheet (WaveSimHelpers.changeMultiplier (table[key]) wsModel) ws)
+ Map.add sheet (WaveSimNavigation.changeMultiplier (table[key]) wsModel) ws)
|> (fun m -> m, Cmd.none)
| CheckMemory ->
if JSHelpers.loggingMemory then
@@ -256,18 +257,18 @@ let update (msg : Msg) oldModel =
| SetWaveComponentSelectionOpen (fIdL, show) ->
model
- |> updateWSModel (fun ws -> WaveSimHelpers.setWaveComponentSelectionOpen ws fIdL show)
+ |> updateWSModel (fun ws -> WaveSimStyle.setWaveComponentSelectionOpen ws fIdL show)
|> withNoMsg
| SetWaveGroupSelectionOpen (fIdL, show) ->
model
- |> updateWSModel (fun ws -> WaveSimHelpers.setWaveGroupSelectionOpen ws fIdL show)
+ |> updateWSModel (fun ws -> WaveSimStyle.setWaveGroupSelectionOpen ws fIdL show)
|> withNoMsg
| SetWaveSheetSelectionOpen (fIdL, show) ->
model
- |> updateWSModel (fun ws -> WaveSimHelpers.setWaveSheetSelectionOpen ws fIdL show)
+ |> updateWSModel (fun ws -> WaveSimStyle.setWaveSheetSelectionOpen ws fIdL show)
|> withNoMsg
| TryStartSimulationAfterErrorFix simType ->
@@ -609,7 +610,7 @@ let update (msg : Msg) oldModel =
| ScrollbarMouseMsg (cursor: float, action: ScrollbarMouseAction, dispatch: Msg->unit) ->
let wsm = Map.find (Option.get model.WaveSimSheet) model.WaveSim
- WaveSim.updateScrollbar wsm dispatch cursor action
+ WaveSimNavigation.updateScrollbar wsm dispatch cursor action
model, Cmd.none
// Various messages here that are not implemented as yet, or are no longer used
diff --git a/src/Renderer/UI/UpdateHelpers.fs b/src/Renderer/UI/UpdateHelpers.fs
index d182ef573..4464b8a61 100644
--- a/src/Renderer/UI/UpdateHelpers.fs
+++ b/src/Renderer/UI/UpdateHelpers.fs
@@ -65,7 +65,7 @@ let shortDWSM (ws: WaveSimModel) =
let shortDisplayMsg (msg:Msg) =
match msg with
| ChangeWaveSimMultiplier n ->
- List.tryItem n WaveSimHelpers.Constants.multipliers
+ List.tryItem n WaveSimStyle.Constants.multipliers
|> Option.map (fun n -> $"Set WS multiplier to {n}")
|> Option.defaultValue $"Invalid Ws mult key of {n}"
|> Some
diff --git a/src/Renderer/UI/WaveSim/WaveSim.fs b/src/Renderer/UI/WaveSim/WaveSim.fs
index 87b3efa8e..f7c7fe189 100644
--- a/src/Renderer/UI/WaveSim/WaveSim.fs
+++ b/src/Renderer/UI/WaveSim/WaveSim.fs
@@ -13,1078 +13,11 @@ open TopMenuView
open SimulatorTypes
open NumberHelpers
open DrawModelType
-open WaveSimSelect
+open WaveSimNavigation
+//open WaveSimSelect
open DiagramStyle
-
-module Constants =
- /// Config variable to choose whether to generate the full 1000 cycles of SVG.
- let generateVisibleOnly = true
- /// Config variable to choose whether to print performance analysis info to console.
- let showPerfLogs = false
- let inlineNoWrap = WhiteSpace WhiteSpaceOptions.Nowrap
-
-open Constants
-
-
-/// Generates SVG to display non-binary values on waveforms.
-/// Should be refactored together with displayBigIntOnWave.
-let displayUInt32OnWave
- (wsModel: WaveSimModel)
- (width: int)
- (waveValues: array)
- (transitions: array)
- : list =
- // find all clock cycles where there is a NonBinaryTransition.Change
- let changeTransitions =
- transitions
- |> Array.indexed
- |> Array.filter (fun (_, x) -> x = Change)
- |> Array.map (fun (i, _) -> i)
-
- // find start and length of each gap between a Change transition
- let gaps: array =
- if Constants.generateVisibleOnly
- then
- // add dummy change at visible end, but need account for difference in changes:
- // e.g. if we are showing 3 cycles, a wave with a change in each would be 0, 1, 2, 3 and would be fine when
- // 4 is added; however, a wave with no change at all would be 0, and would produce an errorneous gap length
- // of 4 when 4 is added - we therefore add 3
- if changeTransitions[Array.length changeTransitions-1] <> wsModel.ShownCycles
- then Array.append changeTransitions [|wsModel.ShownCycles|]
- else Array.append changeTransitions [|wsModel.ShownCycles+1|]
- |> Array.map (fun loc -> loc+wsModel.StartCycle) // shift cycle to start cycle
- else
- Array.append changeTransitions [|wsModel.StartCycle+transitions.Length-1|] // add dummry change length end
- |> Array.pairwise
- |> Array.map (fun (i1, i2) -> {Start = i1; Length = i2-i1}) // get start and length of gap
-
- // utility functions for SVG generation
- /// Function to make polygon fill for a gap.
- /// Array of polyline points to fill.
- let makePolyfill (points: array) =
- let points = points |> Array.distinct
- polyline (wavePolyfillStyle points) []
-
- /// Function to make text element for a gap.
- /// Starting X location of element.
- let makeTextElement (start: float) (waveValue: string) =
- text (singleValueOnWaveProps start) [ str waveValue ]
-
- // create text element for every gap
- gaps
- |> Array.map (fun gap ->
- // generate string
- let waveValue = UInt32ToPaddedString Constants.waveLegendMaxChars wsModel.Radix width waveValues[gap.Start]
-
- // calculate display widths
- let cycleWidth = singleWaveWidth wsModel
- let gapWidth = (float gap.Length * cycleWidth) - 2. * Constants.nonBinaryTransLen
- let singleWidth = 1.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
- let doubleWidth = 2. * singleWidth + Constants.valueOnWavePadding
-
- match gapWidth with
- | w when (w < singleWidth) -> // display filled polygon
- let fillPoints = nonBinaryFillPoints cycleWidth gap
- let fill = makePolyfill fillPoints
- [ fill ]
- | w when (singleWidth <= w && w < doubleWidth) -> // diplay 1 copy at centre
- let gapCenterPadWidth = (float gap.Length * cycleWidth - singleWidth) / 2.
- let singleText = makeTextElement (float gap.Start * cycleWidth + gapCenterPadWidth) waveValue
- [ singleText ]
- | w when (doubleWidth <= w) -> // display 2 copies at end of gaps
- let singleCycleCenterPadWidth = // if a single cycle gap can include 2 copies, set arbitrary padding
- if cycleWidth < doubleWidth
- then (cycleWidth - singleWidth) / 2.
- else Constants.valueOnWaveEdgePadding
- let startPadWidth =
- if singleCycleCenterPadWidth < 0.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
- then 0.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
- else singleCycleCenterPadWidth
- let endPadWidth = (float gap.Length * cycleWidth - startPadWidth - singleWidth)
- let startText = makeTextElement (float gap.Start * cycleWidth + startPadWidth) waveValue
- let endText = makeTextElement (float gap.Start * cycleWidth + endPadWidth) waveValue
- [ startText; endText ]
- | _ -> // catch-all
- failwithf "displayUInt32OnWave: impossible case"
- )
- |> List.concat
-
-/// Generates SVG to display bigint values on waveforms.
-/// Should be refactored together with displayUInt32OnWave.
-let displayBigIntOnWave
- (wsModel: WaveSimModel)
- (width: int)
- (waveValues: array)
- (transitions: array)
- : list =
- // find all clock cycles where there is a NonBinaryTransition.Change
- let changeTransitions =
- transitions
- |> Array.indexed
- |> Array.filter (fun (_, x) -> x = Change)
- |> Array.map (fun (i, _) -> i)
-
- // find start and length of each gap between a Change transition
- let gaps: array =
- if Constants.generateVisibleOnly
- then
- // add dummy change at visible end, but need account for difference in changes:
- // e.g. if we are showing 3 cycles, a wave with a change in each would be 0, 1, 2, 3 and would be fine when
- // 4 is added; however, a wave with no change at all would be 0, and would produce an errorneous gap length
- // of 4 when 4 is added - we therefore add 3
- if changeTransitions[Array.length changeTransitions-1] <> wsModel.ShownCycles
- then Array.append changeTransitions [|wsModel.ShownCycles|]
- else Array.append changeTransitions [|wsModel.ShownCycles+1|]
- |> Array.map (fun loc -> loc+wsModel.StartCycle) // shift cycle to start cycle
- else
- Array.append changeTransitions [|wsModel.StartCycle+transitions.Length-1|] // add dummry change length end
- |> Array.pairwise
- |> Array.map (fun (i1, i2) -> {Start = i1; Length = i2-i1}) // get start and length of gap
-
- // utility functions for SVG generation
- /// Function to make polygon fill for a gap.
- /// Array of polyline points to fill.
- let makePolyfill (points: array) =
- let points = points |> Array.distinct
- polyline (wavePolyfillStyle points) []
-
- /// Function to make text element for a gap.
- /// Starting X location of element.
- let makeTextElement (start: float) (waveValue: string) =
- text (singleValueOnWaveProps start) [ str waveValue ]
-
- // create text element for every gap
- gaps
- |> Array.map (fun gap ->
- // generate string
- let waveValue = BigIntToPaddedString Constants.waveLegendMaxChars wsModel.Radix width waveValues[gap.Start]
-
- // calculate display widths
- let cycleWidth = singleWaveWidth wsModel
- let gapWidth = (float gap.Length * cycleWidth) - 2. * Constants.nonBinaryTransLen
- let singleWidth = 1.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
- let doubleWidth = 2. * singleWidth + Constants.valueOnWavePadding
-
- match gapWidth with
- | w when (w < singleWidth) -> // display filled polygon
- let fillPoints = nonBinaryFillPoints cycleWidth gap
- let fill = makePolyfill fillPoints
- [ fill ]
- | w when (singleWidth <= w && w < doubleWidth) -> // diplay 1 copy at centre
- let gapCenterPadWidth = (float gap.Length * cycleWidth - singleWidth) / 2.
- let singleText = makeTextElement (float gap.Start * cycleWidth + gapCenterPadWidth) waveValue
- [ singleText ]
- | w when (doubleWidth <= w) -> // display 2 copies at end of gaps
- let singleCycleCenterPadWidth = // if a single cycle gap can include 2 copies, set arbitrary padding
- if cycleWidth < doubleWidth
- then (cycleWidth - singleWidth) / 2.
- else Constants.valueOnWaveEdgePadding
- let startPadWidth =
- if singleCycleCenterPadWidth < 0.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
- then 0.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
- else singleCycleCenterPadWidth
- let endPadWidth = (float gap.Length * cycleWidth - startPadWidth - singleWidth)
- let startText = makeTextElement (float gap.Start * cycleWidth + startPadWidth) waveValue
- let endText = makeTextElement (float gap.Start * cycleWidth + endPadWidth) waveValue
- [ startText; endText ]
- | _ -> // catch-all
- failwithf "displayUInt32OnWave: impossible case"
- )
- |> List.concat
-
-/// Check if generated SVG is correct, based on existence, position,
-/// and zoom. Fast simulation data is assumed unchanged. Used to determine if
-/// generateWaveform is run.
-let waveformIsUptodate (ws: WaveSimModel) (wave: Wave): bool =
- wave.SVG <> None &&
- wave.ShownCycles = ws.ShownCycles &&
- wave.StartCycle = ws.StartCycle &&
- wave.CycleWidth = singleWaveWidth ws &&
- wave.Radix = ws.Radix &&
- wave.Multiplier = ws.CycleMultiplier
-
-/// Called when InitiateWaveSimulation message is dispatched and when wave
-/// simulator is refreshed. Generates or updates the SVG for a specific waveform
-/// whether needed or not. The SVG depends on cycle width as well as start/stop
-/// clocks and design. Assumes that the fast simulation data has not changed and
-/// has enough cycles.
-let generateWaveform (ws: WaveSimModel) (index: WaveIndexT) (wave: Wave): Wave =
- let makePolyline points =
- let points = points |> Array.concat |> Array.distinct
- polyline (wavePolylineStyle points) []
-
- let waveform =
- match wave.Width with
- | 0 ->
- failwithf "Cannot have wave of width 0"
-
- | 1 -> // binary waveform
- let transitions = calculateBinaryTransitionsUInt32 wave.WaveValues.UInt32Step ws.StartCycle ws.ShownCycles ws.CycleMultiplier
-
- let wavePoints =
- let waveWidth = singleWaveWidth ws
- let startCycle = if Constants.generateVisibleOnly then ws.StartCycle else 0
- Array.mapi (binaryWavePoints waveWidth startCycle) transitions
- |> Array.concat
- |> Array.distinct
-
- svg (waveRowProps ws) [ polyline (wavePolylineStyle wavePoints) [] ]
-
- | w when w <= 32 -> // non-binary waveform
- let transitions = calculateNonBinaryTransitions wave.WaveValues.UInt32Step ws.StartCycle ws.ShownCycles ws.CycleMultiplier
- let fstPoints, sndPoints =
- let waveWidth = singleWaveWidth ws
- let startCycle = if Constants.generateVisibleOnly then ws.StartCycle else 0
- Array.mapi (nonBinaryWavePoints waveWidth startCycle) transitions |> Array.unzip
-
- let valuesSVG = displayUInt32OnWave ws wave.Width wave.WaveValues.UInt32Step transitions
- let polyLines = [makePolyline fstPoints; makePolyline sndPoints]
-
- svg (waveRowProps ws) (List.append polyLines valuesSVG)
-
- | _ -> // non-binary waveform with width greather than 32
- let transitions = calculateNonBinaryTransitions wave.WaveValues.UInt32Step ws.StartCycle ws.ShownCycles ws.CycleMultiplier
-
- let fstPoints, sndPoints =
- Array.mapi (nonBinaryWavePoints (singleWaveWidth ws) 0) transitions |> Array.unzip
-
- let valuesSVG = displayBigIntOnWave ws wave.Width wave.WaveValues.BigIntStep transitions
-
- svg (waveRowProps ws) (List.append [makePolyline fstPoints; makePolyline sndPoints] valuesSVG)
- {wave with
- Radix = ws.Radix
- ShownCycles = ws.ShownCycles
- StartCycle = ws.StartCycle
- Multiplier = ws.CycleMultiplier
- CycleWidth = singleWaveWidth ws
- SVG = Some waveform}
-
-
-
-/// Set highlighted clock cycle number
-let private setClkCycle (wsModel: WaveSimModel) (dispatch: Msg -> unit) (newRealClkCycle: int) : unit =
- let start = TimeHelpers.getTimeMs ()
- let newDetail = max newRealClkCycle 0
- let mult = wsModel.CycleMultiplier
- let newClkCycle = newRealClkCycle / mult
- let newClkCycle = min Constants.maxLastClk newClkCycle |> max 0
-
- if newClkCycle <= endCycle wsModel then
- if newClkCycle < wsModel.StartCycle then
- dispatch <| GenerateWaveforms
- {wsModel with
- StartCycle = newClkCycle
- CurrClkCycle = newClkCycle
- ClkCycleBoxIsEmpty = false
- CurrClkCycleDetail = newDetail
- }
- else
- dispatch <| SetWSModel
- {wsModel with
- CurrClkCycle = newClkCycle
- ClkCycleBoxIsEmpty = false
- CurrClkCycleDetail = newDetail
- }
- else
- let newDetail = min newDetail maxLastClk
- let newClkCycle = newDetail / mult
- dispatch <| GenerateWaveforms
- {wsModel with
- StartCycle = newClkCycle - (wsModel.ShownCycles - 1)
- CurrClkCycle = newClkCycle
- ClkCycleBoxIsEmpty = false
- CurrClkCycleDetail = newDetail
- }
- |> TimeHelpers.instrumentInterval "setClkCycle" start
-
-/// Move waveform view window by closest integer number of cycles.
-/// Current clock cycle (WaveSimModel.CurrClkCycle) is set to beginning or end depending on direction of movement.
-/// Update is achieved by dispatching a GenerateWaveforms message.
-/// Note the side-effect of clearing the ScrollbarQueueIsEmpty counter.
-/// Target WaveSimModel.
-/// Dispatch function to send messages with.
-/// Number of non-integer cycles to move by.
-let setScrollbarTbByCycs (wsm: WaveSimModel) (dispatch: Msg->unit) (moveByCycs: float): unit =
- let moveWindowBy = int (System.Math.Round moveByCycs)
- let mult = wsm.CycleMultiplier
-
- /// Return target value when within min and max value, otherwise min or max.
- let bound (minV: int) (maxV: int) (tarV: int): int = tarV |> max minV |> min maxV
- let minSimCyc = 0
- let maxSimCyc = Constants.maxLastClk / mult
-
- let newStartCyc = (wsm.StartCycle+moveWindowBy) |> bound minSimCyc (maxSimCyc-wsm.ShownCycles+1)
- let newCurrCyc =
- let newEndCyc = newStartCyc+wsm.ShownCycles-1
- if newStartCyc <= wsm.CurrClkCycle && wsm.CurrClkCycle <= newEndCyc
- then
- wsm.CurrClkCycle
- else
- if abs (wsm.CurrClkCycle - newStartCyc) < abs (wsm.CurrClkCycle - newEndCyc)
- then newStartCyc
- else newEndCyc
- let detail = wsm.CurrClkCycleDetail
- let detail = if newCurrCyc <> detail / mult then newCurrCyc * mult else detail
- GenerateWaveforms {
- wsm with StartCycle = newStartCyc;
- CurrClkCycle = newCurrCyc;
- CurrClkCycleDetail = detail;
- ScrollbarQueueIsEmpty = true } |> dispatch
-
-/// Update WaveSimModel with new ScrollbarTbOffset.
-/// Used when starting or clearing scrollbar drag mode.
-/// Update is achieved by dispatching a GenerateWaveforms message.
-/// Target WaveSimModel.
-/// Dispatch function to send messages with.
-/// Offset option to be written to WaveSimModel.ScrollbarTbOffset.
-let setScrollbarOffset (wsm: WaveSimModel) (dispatch: Msg->unit) (offset: float option): unit =
- GenerateWaveforms { wsm with ScrollbarTbOffset = offset; ScrollbarQueueIsEmpty = true } |> dispatch
-
-/// Update WaveSimModel with new ScrollbarQueueIsEmpty.
-/// Used to update is-empty counter to coalesce scrollbar mouse events together.
-/// Update is achieved by dispatching a UpdateWSModel message, so as to not clog the queue with GenerateWaveforms messages.
-/// Target WaveSimModel.
-/// Dispatch function to send messages with.
-/// Bool to be written to WaveSimModel.ScrollbarQueueIsEmpty.
-let setScrollbarLastX (wsm: WaveSimModel) (dispatch: Msg->unit) (isEmpty: bool): unit =
- UpdateWSModel (fun _ -> { wsm with ScrollbarQueueIsEmpty = isEmpty }) |> dispatch
-
-/// If zoomIn, then increase width of clock cycles (i.e.reduce number of visible cycles).
-/// otherwise reduce width. GenerateWaveforms message will reconstitute SVGs after the change.
-let changeZoom (wsModel: WaveSimModel) (zoomIn: bool) (dispatch: Msg -> unit) =
- let start = TimeHelpers.getTimeMs ()
- let shownCycles =
- let wantedCycles = int (float wsModel.ShownCycles / Constants.zoomChangeFactor)
- if zoomIn then
- // try to reduce number of cycles displayed
- wantedCycles
- // If number of cycles after casting to int does not change
- |> (fun nc -> if nc = wsModel.ShownCycles then nc - 1 else nc )
- // Require a minimum of cycles
- |> (fun nc ->
- let minVis = min wsModel.ShownCycles Constants.minVisibleCycles
- max nc minVis)
- else
- let wantedCycles = int (float wsModel.ShownCycles * Constants.zoomChangeFactor)
- // try to increase number of cycles displayed
- wantedCycles
- // If number of cycles after casting to int does not change
- |> (fun nc -> if nc = wsModel.ShownCycles then nc + 1 else nc )
- |> (fun nc ->
- let maxNc = int (wsModel.WaveformColumnWidth / float Constants.minCycleWidth)
- max wsModel.ShownCycles (min nc maxNc))
- let startCycle =
- // preferred start cycle to keep centre of screen ok
- let sc = (wsModel.StartCycle - (shownCycles - wsModel.ShownCycles)/2)
- let cOffset = wsModel.CurrClkCycle - sc
- sc
- // try to keep cursor on screen
- |> (fun sc ->
- if cOffset > shownCycles - 1 then
- sc + cOffset - shownCycles + 1
- elif cOffset < 0 then
- (sc + cOffset)
- else
- sc)
- // final limits check so no cycle is outside allowed range
- |> min (Constants.maxLastClk / wsModel.CycleMultiplier - shownCycles)
- |> max 0
-
-
- dispatch <| GenerateWaveforms {
- wsModel with
- ShownCycles = shownCycles;
- StartCycle = startCycle
- }
- |> TimeHelpers.instrumentInterval "changeZoom" start
-
-/// Click on these buttons to change the number of visible clock cycles.
-let zoomButtons (wsModel: WaveSimModel) (dispatch: Msg -> unit) : ReactElement =
- div [ clkCycleButtonStyle ]
- [
- button [ Button.Props [clkCycleLeftStyle] ]
- (fun _ -> changeZoom wsModel false dispatch)
- zoomOutSVG
- button [ Button.Props [clkCycleRightStyle] ]
- (fun _ -> changeZoom wsModel true dispatch)
- zoomInSVG
- ]
-
-/// Click on these to change the highlighted clock cycle.
-let clkCycleButtons (wsModel: WaveSimModel) (dispatch: Msg -> unit) : ReactElement =
- /// Controls the number of cycles moved by the "◀◀" and "▶▶" buttons
- let mult = wsModel.CycleMultiplier
- let bigStepSize = max (2*mult) (wsModel.ShownCycles*mult / 2)
-
- let scrollWaveformsBy (numCycles: int) =
- setClkCycle wsModel dispatch (wsModel.CurrClkCycleDetail + numCycles)
-
- div [ clkCycleButtonStyle ]
- [
- // Move left by bigStepSize cycles
- button [ Button.Props [clkCycleLeftStyle] ]
- (fun _ -> scrollWaveformsBy -bigStepSize)
- (str "◀◀")
-
- // Move left by one cycle
- button [ Button.Props [clkCycleInnerStyle] ]
- (fun _ -> scrollWaveformsBy -1)
- (str "◀")
-
- // Text input box for manual selection of clock cycle
- Input.number [
- Input.Props clkCycleInputProps
-
- Input.Value (
- match wsModel.ClkCycleBoxIsEmpty with
- | true -> ""
- | false -> string (wsModel.CurrClkCycleDetail)
- )
- // TODO: Test more properly with invalid inputs (including negative numbers)
- Input.OnChange(fun c ->
- match System.Int32.TryParse c.Value with
- | true, n ->
- setClkCycle wsModel dispatch n
- | false, _ when c.Value = "" ->
- dispatch <| SetWSModel {wsModel with ClkCycleBoxIsEmpty = true}
- | _ ->
- dispatch <| SetWSModel {wsModel with ClkCycleBoxIsEmpty = false}
- )
- ]
-
- // Move right by one cycle
- button [ Button.Props [clkCycleInnerStyle] ]
- (fun _ -> scrollWaveformsBy 1)
- (str "▶")
-
- // Move right by bigStepSize cycles
- button [ Button.Props [clkCycleRightStyle] ]
- (fun _ -> scrollWaveformsBy bigStepSize)
- (str "▶▶")
- ]
-
-/// ReactElement of the tabs for changing displayed radix
-let private radixButtons (wsModel: WaveSimModel) (dispatch: Msg -> unit) : ReactElement =
- let radixString = [
- Bin, "Bin"
- Hex, "Hex"
- Dec, "uDec"
- SDec, "sDec"
- ]
-
- let radixTab (radix, radixStr) =
- Tabs.tab [
- Tabs.Tab.IsActive(wsModel.Radix = radix)
- Tabs.Tab.Props radixTabProps
- ] [ a [
- radixTabAStyle
- OnClick(fun _ -> dispatch <| GenerateWaveforms {wsModel with Radix = radix})
- ] [ str radixStr ]
- ]
-
- Tabs.tabs [
- Tabs.IsToggle
- Tabs.Props [ radixTabsStyle ]
- ] (List.map (radixTab) radixString)
-
-
-let highlightCircuit fs comps wave (dispatch: Msg -> Unit) =
- dispatch <| Sheet (SheetT.Msg.Wire (BusWireT.Msg.Symbol (SymbolT.SelectSymbols comps)))
- // Filter out any non-existent wires
- let conns = connsOfWave fs wave
- dispatch <| Sheet (SheetT.Msg.SelectWires conns)
-
-/// Create label of waveform name for each selected wave.
-/// Note that this is generated after calling selectedWaves. Any changes to this function
-/// must also be made to valueRows and waveRows, as the order of the waves matters here.
-/// This is because the wave viewer is comprised of three columns of many rows, rather
-/// than many rows of three columns.
-let nameRows (model: Model) (wsModel: WaveSimModel) dispatch: ReactElement list =
- selectedWaves wsModel
- |> List.map (fun wave ->
- let visibility =
- if wsModel.HoveredLabel = Some wave.WaveId then
- "visible"
- else "hidden"
-
- Level.level [
- Level.Level.Option.Props [
- nameRowLevelStyle (wsModel.HoveredLabel = Some wave.WaveId)
- let execWithModel (f: Model -> Unit) = ExecFuncInMessage((fun model _ -> f model), dispatch)
- OnMouseOver (fun _ -> dispatch <| execWithModel (fun model ->
- if wsModel.DraggedIndex = None then
- dispatch <| SetWSModel {wsModel with HoveredLabel = Some wave.WaveId}
- // Check if symbol exists on Canvas
- let symbols = model.Sheet.Wire.Symbol.Symbols
- match Map.tryFind (fst wave.WaveId.Id) symbols with
- | Some {Component={Type=IOLabel;Label=lab}} ->
- let labelComps =
- symbols
- |> Map.toList
- |> List.map (fun (_,sym) -> sym.Component)
- |> List.filter (function | {Type=IOLabel;Label = lab'} when lab' = lab -> true |_ -> false)
- |> List.map (fun comp -> ComponentId comp.Id)
- highlightCircuit wsModel.FastSim labelComps wave dispatch
- | Some sym ->
- highlightCircuit wsModel.FastSim [fst wave.WaveId.Id] wave dispatch
- | None -> ())
-
- )
- OnMouseOut (fun _ ->
- dispatch <| SetWSModel {wsModel with HoveredLabel = None}
- dispatch <| Sheet (SheetT.Msg.Wire (BusWireT.Msg.Symbol (SymbolT.SelectSymbols [])))
- dispatch <| Sheet (SheetT.Msg.UpdateSelectedWires (connsOfWave wsModel.FastSim wave, false))
- )
-
- Draggable true
-
- OnDragStart (fun ev ->
- ev.dataTransfer.effectAllowed <- "move"
- ev.dataTransfer.dropEffect <- "move"
- dispatch <| SetWSModel {
- wsModel with
- DraggedIndex = Some wave.WaveId
- PrevSelectedWaves = Some wsModel.SelectedWaves
- }
- )
-
- OnDrag (fun ev ->
- ev.dataTransfer.dropEffect <- "move"
- let nameColEl = Browser.Dom.document.getElementById "namesColumn"
- let bcr = nameColEl.getBoundingClientRect ()
-
- // If the user drags the label outside the bounds of the wave name column
- if ev.clientX < bcr.left || ev.clientX > bcr.right ||
- ev.clientY < bcr.top || ev.clientY > bcr.bottom
- then
- dispatch <| SetWSModel {
- wsModel with
- HoveredLabel = Some wave.WaveId
- // Use wsModel.SelectedValues if somehow PrevSelectedWaves not set
- SelectedWaves = Option.defaultValue wsModel.SelectedWaves wsModel.PrevSelectedWaves
- }
- )
-
- OnDragOver (fun ev -> ev.preventDefault ())
-
- OnDragEnter (fun ev ->
- ev.preventDefault ()
- ev.dataTransfer.dropEffect <- "move"
- let nameColEl = Browser.Dom.document.getElementById "namesColumn"
- let bcr = nameColEl.getBoundingClientRect ()
- let index = int (ev.clientY - bcr.top) / Constants.rowHeight - 1
- let draggedWave =
- match wsModel.DraggedIndex with
- | Some waveId -> [waveId]
- | None -> []
-
- let selectedWaves =
- wsModel.SelectedWaves
- |> List.except draggedWave
- |> List.insertManyAt index draggedWave
-
- dispatch <| SetWSModel {wsModel with SelectedWaves = selectedWaves}
- )
-
- OnDragEnd (fun _ ->
- dispatch <| SetWSModel {
- wsModel with
- DraggedIndex = None
- PrevSelectedWaves = None
- }
- )
- ]
- ] [ Level.left
- [ Props (nameRowLevelLeftProps visibility) ]
- [ Delete.delete [
- Delete.Option.Size IsSmall
- Delete.Option.Props [
- OnClick (fun _ ->
- let selectedWaves = List.except [wave.WaveId] wsModel.SelectedWaves
- dispatch <| SetWSModel {wsModel with SelectedWaves = selectedWaves}
- )
- ]
- ] []
- ]
- Level.right
- [ Props [ Style [ PaddingRight Constants.labelPadding ] ] ]
- [ label [ nameLabelStyle (wsModel.HoveredLabel = Some wave.WaveId) ] [ wave.ViewerDisplayName|> str ] ]
- ]
- )
-
-/// Create column of waveform names
-let namesColumn model wsModel dispatch : ReactElement =
- let start = TimeHelpers.getTimeMs ()
- let rows =
- nameRows model wsModel dispatch
- div (namesColumnProps wsModel)
- (List.concat [ topRow []; rows ])
- |> TimeHelpers.instrumentInterval "namesColumn" start
-
-
-/// Create label of waveform value for each selected wave at a given clk cycle.
-/// Note that this is generated after calling selectedWaves.
-/// Any changes to this function must also be made to nameRows
-/// and waveRows, as the order of the waves matters here. This is
-/// because the wave viewer is comprised of three columns of many
-/// rows, rather than many rows of three columns.
-/// Return required width of values column in pixels, and list of cloumn react elements.
-let valueRows (wsModel: WaveSimModel) =
- let valueColWidth, valueColNumChars =
- valuesColumnSize wsModel
- selectedWaves wsModel
- |> List.map (fun wave -> getWaveValue wsModel.CurrClkCycleDetail wave wave.Width)
- |> List.map (fun fd ->
- match fd.Width, fd.Dat with
- | 1, Word b -> $" {b}"
- | _ -> fastDataToPaddedString valueColNumChars wsModel.Radix fd)
- |> List.map (fun value -> label [ valueLabelStyle ] [ str value ])
- |> (fun rows -> valueColWidth, rows)
-
-
-/// Generate a row of numbers in the waveforms column.
-/// Numbers correspond to clock cycles multiplied by the current multiplier
-let clkCycleNumberRow (wsModel: WaveSimModel) =
- let makeClkCycleLabel i =
- let n = i * wsModel.CycleMultiplier
- match singleWaveWidth wsModel with
- | width when width < float Constants.clkCycleNarrowThreshold && i % 5 <> 0 ->
- []
- | width when n >= 1000 && width < (float Constants.clkCycleNarrowThreshold * 4. / 3.) && i % 10 <> 0 ->
- []
- | _ ->
- [ text (clkCycleText wsModel i) [str (string n)] ]
-
-
- [ wsModel.StartCycle .. endCycle wsModel]
- |> List.collect makeClkCycleLabel
- |> svg (clkCycleNumberRowProps wsModel)
-
-/// Create column of waveform values
-let private valuesColumn wsModel : ReactElement =
- let start = TimeHelpers.getTimeMs ()
- let width, rows = valueRows wsModel
- let cursorClkNum = wsModel.CurrClkCycleDetail
- let topRowNumber = [ text [Style [FontWeight "bold"; PaddingLeft "2pt"]] [str (string <| cursorClkNum)] ]
-
- div [ HTMLAttr.Id "ValuesCol" ; valuesColumnStyle width]
- (List.concat [ topRow topRowNumber ; rows ])
- |> TimeHelpers.instrumentInterval "valuesColumn" start
-
-/// Generate a column of waveforms corresponding to selected waves.
-let waveformColumn (wsModel: WaveSimModel) dispatch : ReactElement =
- let start = TimeHelpers.getTimeMs ()
- /// Note that this is generated after calling selectedWaves.
- /// Any changes to this function must also be made to nameRows
- /// and valueRows, as the order of the waves matters here. This is
- /// because the wave viewer is comprised of three columns of many
- /// rows, rather than many rows of three columns.
- let waves = selectedWaves wsModel
- if List.exists (fun wave -> wave.SVG = None) waves then
- dispatch <| GenerateCurrentWaveforms
- let waveRows : ReactElement list =
- waves
- |> List.map (fun wave ->
- match wave.SVG with
- | Some waveform ->
- waveform
- | None ->
- div [] [] // the GenerateCurrentWaveforms message will soon update this
- )
-
- div [ waveformColumnStyle ]
- [
- clkCycleHighlightSVG wsModel dispatch
- div [ waveRowsStyle <| wsModel.WaveformColumnWidth]
- ([ clkCycleNumberRow wsModel ] @
- waveRows
- )
- ]
- |> TimeHelpers.instrumentInterval "waveformColumn" start
-
-/// Display the names, waveforms, and values of selected waveforms
-let showWaveforms (model: Model) (wsModel: WaveSimModel) (dispatch: Msg -> unit) : ReactElement =
- if List.isEmpty wsModel.SelectedWaves then
- div [] [] // no waveforms
- else
- let wHeight = calcWaveformHeight wsModel
- let fixedHeight = Constants.softScrollBarWidth + Constants.topHalfHeight
- let cssHeight =
- if wsModel.SelectedRams.Count > 0 then
- $"min( calc(50vh - (0.5 * {fixedHeight}px)) , {wHeight}px)"
- else
- $"min( calc(100vh - {fixedHeight}px) , {wHeight}px)"
-
- div [ HTMLAttr.Id "Scroller"; Style [ Height cssHeight; Width "100%"; CSSProp.Custom("overflow", "auto")]] [
- div [ HTMLAttr.Id "WaveCols" ;showWaveformsStyle ]
- [
- namesColumn model wsModel dispatch
- waveformColumn wsModel dispatch
- valuesColumn wsModel
- ]
- ]
-
-/// Table row that shows the address and data of a RAM component.
-let ramTableRow ((addr, data,rowType): string * string * RamRowType): ReactElement =
-
- tr [ Style <| ramTableRowStyle rowType ] [
- td [] [ str addr ]
- td [] [ str data ]
- ]
-
-/// Table showing contents of a RAM component.
-let ramTable (wsModel: WaveSimModel) ((ramId, ramLabel): FComponentId * string) : ReactElement =
- let wanted = calcWaveformAndScrollBarHeight wsModel
- let maxHeight = max (screenHeight() - (min wanted (screenHeight()/2.)) - 300.) 30.
- let fs = wsModel.FastSim
- match Map.tryFind ramId wsModel.FastSim.FComps with
- | None -> div [] []
- | Some fc ->
- let step = wsModel.CurrClkCycle
- FastRun.runFastSimulation None step fs |> ignore // not sure why this is needed
-
- // in some cases fast sim is run for one cycle less than currClockCycle
- let memData =
- match fc.FType with
- | ROM1 mem
- | AsyncROM1 mem -> mem
- | RAM1 mem
- | AsyncRAM1 mem ->
- match FastRun.extractFastSimulationState fs wsModel.CurrClkCycle ramId with
- |RamState mem -> mem
- | x -> failwithf $"What? Unexpected state {x} from cycle {wsModel.CurrClkCycle} \
- in RAM component '{ramLabel}'. FastSim step = {fs.ClockTick}"
- | _ -> failwithf $"Given a component {fc.FType} which is not a vaild RAM"
- let aWidth,dWidth = memData.AddressWidth,memData.WordWidth
-
- let print w (a:int64) = NumberHelpers.valToPaddedString w wsModel.Radix (((1L <<< w) - 1L) &&& a)
-
- let lastLocation = int64 ((2 <<< memData.AddressWidth - 1) - 1)
-
- /// print a single 0 location as one table row
- let print1 (a:int64,b:int64,rw:RamRowType) = $"{print aWidth a}",$"{print dWidth b}",rw
- /// print a range of zero locations as one table row
-
- let print2 (a1:int64) (a2:int64) (d:int64) = $"{print aWidth (a1+1L)} ... {print aWidth (a2-1L)}", $"{print dWidth d}",RAMNormal
-
- /// output info for one table row filling the given zero memory gap or arbitrary size, or no line if there is no gap.
- let printGap (gStart:int64) (gEnd:int64) =
- match gEnd - gStart with
- | 1L -> []
- | 2L -> [print1 ((gEnd + gStart) / 2L, 0L,RAMNormal)]
- | n when n > 2L ->
- [print2 gStart gEnd 0L]
- | _ ->
- failwithf $"What? gEnd={gEnd},gStart={gStart}: negative or zero gaps are impossible..."
-
- /// transform Sparse RAM info into strings to print in a table, adding extra lines for zero gaps
- /// line styling is controlled by a RamRowtype value and added later when the table row react is generated
- let addGapLines (items: (int64*int64*RamRowType) list) =
- let startItem =
- match items[0] with
- | -1L,_,_ -> []
- | gStart,dStart,rw-> [print1 (gStart,dStart,rw)]
- List.pairwise items
- |> List.collect (fun ((gStart,_,_),(gEnd,dEnd,rwe)) ->
- let thisItem = if gEnd = lastLocation + 1L then [] else [print1 (gEnd,dEnd,rwe)]
- [printGap gStart gEnd; thisItem])
- |> List.concat
-
- /// Add a RAMNormal RamRowType value to every location in mem.
- /// Add in additional locations for read and/or write if needed.
- /// Set RamRowValue type to RAMWritten or RAMRead for thse locations.
- /// Write is always 1 cycle after WEN=1 and address.
- /// Read is 1 (0) cycles after address for sync (asynch) memories.
- let addReadWrite (fc:FastComponent) (step:int) (mem: Map) =
- let getInt64 (a: IOArray) step =
- let w = a.Width
- match w with
- | w when w > 32 -> int64 <| convertBigIntToUInt64 w a.BigIntStep[step]
- | _ -> int64 <| a.UInt32Step[step]
-
- let readStep =
- match fc.FType with
- | AsyncROM1 _ | AsyncRAM1 _ -> step
- | ROM1 _ | RAM1 _ -> step - 1
- | _ -> failwithf $"What? {fc.FullName} should be a memory component"
-
- let addrSteps step = getInt64 fc.InputLinks[0] step
-
- let readOpt =
- match step, fc.FType with
- | 0,ROM1 _ | 0, RAM1 _ -> None
- | _ ->
- addrSteps readStep
- |> Some
- let writeOpt =
- match step, fc.FType with
- | _, ROM1 _
- | _, AsyncROM1 _
- | 0, _ -> None
- | _, RAM1 _ | _, AsyncRAM1 _ when getInt64 fc.InputLinks[2] (step-1) = 1L ->
- addrSteps (step-1)
- |> Some
- | _ ->
- None
-
- /// Mark addr in memory map as being rType
- /// if addr does not exist - create it
- let addToMap rType addr mem:Map =
- match Map.tryFind addr mem with
- | Some (d,_) -> Map.add addr (d,rType) mem
- | None -> Map.add addr (0L,rType) mem
-
-
- Map.map (fun k v -> v,RAMNormal) mem
- |> (fun mem ->
- match readOpt with
- | Some addr -> addToMap RAMRead addr mem
- | None -> mem
- |> (fun mem ->
- match writeOpt with // overwrite RAMRead here is need be
- | Some addr -> addToMap RAMWritten addr mem
- | None -> mem))
-
-
- /// add fake locations beyong normal address range so that
- /// addGapLines fills these (if need be). These locations are then removed
- let addEndPoints (items:(int64*int64*RamRowType) list) =
- let ad (a,d,rw) = a
- match items.Length with
- | 0 -> [-1L,0L,RAMNormal; lastLocation,0L,RAMNormal]
- | _ ->
- if ad items[0] < 0L then items else List.insertAt 0 (-1L,-1L,RAMNormal) items
- |> (fun items ->
- if ad items[items.Length-1] = lastLocation then
- items else
- List.insertAt items.Length (lastLocation+1L,0L,RAMNormal) items)
-
-
- let lineItems =
- memData.Data
- |> addReadWrite fc step
- |> Map.toList
- |> List.map (fun (a,(d,rw)) -> a,d,rw)
- |> List.filter (fun (a,d,rw) -> d<>0L || rw <> RAMNormal)
- |> List.sort
- |> addEndPoints
- |> addGapLines
-
-
-
- Level.item [
- Level.Item.Option.Props ramTableLevelProps
- Level.Item.Option.HasTextCentered
- ] [
- Heading.h6 [
- Heading.Option.Props [ centerAlignStyle ]
- ] [ str ramLabel ]
- div [Style [MaxHeight maxHeight;OverflowY OverflowOptions.Auto]] [
- Table.table [
- Table.IsFullWidth
- Table.IsBordered
- ] [ thead [] [
- tr [] [
- th [ centerAlignStyle ] [ str "Address"]
- th [ centerAlignStyle ] [ str "Data"; sub [Style [MarginLeft "2px"; FontSize "10px"]] [str (string wsModel.CurrClkCycle)]]
- ]
- ]
- tbody []
- (List.map ramTableRow lineItems)
- ] ]
- br []
- ]
-
-/// Bulma Level component of tables showing RAM contents.
-let ramTables (wsModel: WaveSimModel) : ReactElement =
- let inlineStyle (styles:CSSProp list) = div [Style (Display DisplayOptions.Inline :: styles)]
- let start = TimeHelpers.getTimeMs ()
- let selectedRams = Map.toList wsModel.SelectedRams
- if List.length selectedRams > 0 then
- let tables =
- let headerRow =
- ["read", RAMRead; "overwritten",RAMWritten]
- |> List.map (fun (op, opStyle) -> inlineStyle [Margin "0px"] [inlineStyle (ramTableRowStyle opStyle) [str op]])
- |> function
- | [a;b] -> [str "Key: Memory location is " ; a; str ", or " ;b; str ". Click waveforms or use cursor control to change current cycle."]
- | _ -> failwithf "What? Can't happen!"
- List.map (fun ram -> td [Style [BorderColor "white"]] [ramTable wsModel ram]) selectedRams
- |> (fun tables -> [tbody [] [tr [] [th [ColSpan selectedRams.Length] [inlineStyle [] headerRow]]; tr [Style [Border "10px"]] tables]])
- |> Fulma.Table.table [
- Table.TableOption.Props ramTablesLevelProps;
- Table.IsFullWidth;
- Table.IsBordered;
- ]
- div [HTMLAttr.Id "TablesDiv"] [ hr [ Style [ Margin "5px"]]; br [ Style [ Margin "0px"]]; tables]
- else div [] []
- |> TimeHelpers.instrumentInterval "ramTables" start
-
-/// This function regenerates all the waveforms listed on wavesToBeMade .
-/// Generation is subject to timeout, so may not complete.
-/// This function have been augmented with performance monitoring function, turn Constants.showPerfLogs
-/// to print performance information to console.
-/// A tuple with the following information:
-/// a) allWaves (with new waveforms),
-/// b) numberDone (no of waveforms made), and
-/// c) timeToDo (Some timeTaken when greater than timeOut or None
-/// if completed with no time out).
-let makeWaveformsWithTimeOut
- (timeOut: option)
- (ws: WaveSimModel)
- (allWaves: Map)
- (wavesToBeMade: list)
- : Map * int * option =
- let start = TimeHelpers.getTimeMs()
- let allWaves, numberDone, timeToDo =
- ((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)
-
- allWaves, numberDone, timeToDo
-
-/// Generate scrollbar SVG info based on current WaveSimModel.
-/// Called in refreshWaveSim after WaveSimModel has been changed.
-/// Target WaveSimModel.
-/// Anonymous record contaning the information to be updated: thumb width,
-/// thumb position, and number of cycles the background represents.
-/// Note: bkg = background; tb = thumb.
-let generateScrollbarInfo (wsm: WaveSimModel): {| tbWidth: float; tbPos: float; bkgRep: int |} =
- let mult = wsm.CycleMultiplier
- let bkgWidth = wsm.ScrollbarBkgWidth - 60. // 60 = 2x width of buttons
-
- /// Return target value when within min and max value, otherwise min or max.
- let bound (minV: int) (maxV: int) (tarV: int): int = tarV |> max minV |> min maxV
- let currShownMaxCyc = wsm.StartCycle + wsm.ShownCycles
- let newBkgRep = [ wsm.ScrollbarBkgRepCycs; currShownMaxCyc; wsm.ShownCycles*2 ] |> List.max |> bound 0 (Constants.maxLastClk / mult)
-
- let tbCalcWidth = bkgWidth / (1. + (float newBkgRep / float wsm.ShownCycles))
- let tbWidth = max tbCalcWidth WaveSimStyle.Constants.scrollbarThumbMinWidth
-
- let tbMoveWidth = bkgWidth - tbWidth
- let tbPos = (float wsm.StartCycle) / (float newBkgRep - float wsm.ShownCycles) * tbMoveWidth
-
- // debug statements:
- // printfn "DEBUG:generateScrollbarInfo: Input -"
- // printfn "DEBUG:generateScrollbarInfo: wsm.CurrClkCycle = %d cycles" wsm.CurrClkCycle
- // printfn "DEBUG:generateScrollbarInfo: wsm.StartCycle = %d cycles" wsm.StartCycle
- // printfn "DEBUG:generateScrollbarInfo: wsm.ShownCycles = %d cycles" wsm.ShownCycles
- // printfn "DEBUG:generateScrollbarInfo: wsm.ScrollbarBkgRepCycs = %d cycles" wsm.ScrollbarBkgRepCycs
- // printfn "DEBUG:generateScrollbarInfo: bkgWidth = %.1f cycles" bkgWidth
- // printfn "DEBUG:generateScrollbarInfo: Output -"
- // printfn "DEBUG:generateScrollbarInfo: tbWidth = %.1fpx" tbWidth
- // printfn "DEBUG:generateScrollbarInfo: tbPos = %.1fpx" tbPos
- // printfn "DEBUG:generateScrollbarInfo: newBkgRep = %d cycles" newBkgRep
-
- {| tbWidth = tbWidth; tbPos = tbPos; bkgRep = newBkgRep |}
-
-/// Make scrollbar element based on information in WaveSimModel.
-/// Called in viewWaveSim, presumably after refreshWaveSim was called.
-/// Target WaveSimModel.
-/// Dispatch function to send messages with. Not used directly, but passed to tbMouseMoveOp.
-/// React element to be placed in to DOM.
-let makeScrollbar (wsm: WaveSimModel) (dispatch: Msg->unit): ReactElement =
- // button props
- let scrollWaveformViewBy (numCycles: float) = setScrollbarTbByCycs wsm dispatch numCycles
-
- // svg props
- let bkgWidth = wsm.ScrollbarBkgWidth - 60. // 60 = 2x width of buttons
-
- let tbMouseDownHandler (event: Browser.Types.MouseEvent): unit = // start drag
- ScrollbarMouseMsg (event.clientX, StartScrollbarDrag, dispatch) |> dispatch
-
- let tbMouseMoveHandler (event: Browser.Types.MouseEvent): unit = // if in drag, drag; otherwise do nothing
- if Option.isSome wsm.ScrollbarTbOffset
- then ScrollbarMouseMsg (event.clientX, InScrollbarDrag, dispatch) |> dispatch
- else ()
-
- let tbMouseUpHandler (event: Browser.Types.MouseEvent): unit = // if in drag, clear drag; otherwise do nothing
- if Option.isSome wsm.ScrollbarTbOffset
- then ScrollbarMouseMsg (event.clientX, ClearScrollbarDrag, dispatch) |> dispatch
- else ()
-
- let bkgPropList (width: float): List =
- [
- HTMLAttr.Id "scrollbarThumb";
- SVGAttr.X $"0px"; SVGAttr.Y "0.5px";
- SVGAttr.Width $"%.1f{width}px"; SVGAttr.Height $"%.1f{WaveSimStyle.Constants.softScrollBarWidth-1.0}px";
- SVGAttr.Fill "white"; SVGAttr.Stroke "gray"; SVGAttr.StrokeWidth "1px";
- ]
-
- let tbPropList (pos: float) (width: float): List =
- [
- HTMLAttr.Id "scrollbarBkg";
- Style [ Cursor "grab"];
- SVGAttr.X $"%.1f{pos}px"; SVGAttr.Y "0.5px";
- SVGAttr.Width $"%.1f{width}px"; SVGAttr.Height $"%.1f{WaveSimStyle.Constants.softScrollBarWidth-1.0}px";
- SVGAttr.Fill "lightgrey"; SVGAttr.Stroke "gray"; SVGAttr.StrokeWidth "1px";
- OnMouseDown tbMouseDownHandler; OnMouseUp tbMouseUpHandler; OnMouseMove tbMouseMoveHandler;
- ]
-
- div [ Style [ MarginTop "5px"; MarginBottom "5px"; Height "25px"]] [
- button [ Button.Props [scrollbarClkCycleLeftStyle] ]
- (fun _ -> scrollWaveformViewBy -1.0)
- (str "◀")
- svg
- [Style [Width $"{bkgWidth}"; Height $"{WaveSimStyle.Constants.softScrollBarWidth}px"];]
- [
- rect (bkgPropList bkgWidth) []; // background
- rect (tbPropList wsm.ScrollbarTbPos wsm.ScrollbarTbWidth) []; // thumb
- ]
- button [ Button.Props [scrollbarClkCycleRightStyle]]
- (fun _ -> scrollWaveformViewBy 1.0)
- (str "▶")
- ]
-
-/// Update waveform view information based on mouse postion in the X direction.
-/// Called in update when ScrollbarMouseMsg is dispatched.
-/// Target WaveSimModel.
-/// Dispatch function to send messages with, not used directly.
-/// Cursor postion in relation to the screen, i.e. event.clientX.
-/// Scrollbar action to do, see choices for more info, in type of ScrollbarMouseAction.
-/// Note that screenX does NOT scale with web zoom and will cause weird results!
-let updateScrollbar (wsm: WaveSimModel) (dispatch: Msg->unit) (cursor: float) (action: ScrollbarMouseAction): unit =
- /// Translate mouse movements in pixels to number of cycles to move by.
- /// Linear translator aims to allow scrollbar thumb to follow cursor.
- /// Number of pixels mouse has moved in X direction, obtained from MouseMove event.
- /// Number of cycles to move by.
- /// Swap this out with some other mouse-to-cycle translator for better user experience.
- let linearMouseToCycleTranslator (dx: float): float =
- let cycleToPixelRatio = float wsm.ScrollbarBkgRepCycs / (wsm.ScrollbarBkgWidth - wsm.ScrollbarTbWidth)
- dx*cycleToPixelRatio
- match action with
- | StartScrollbarDrag -> // record offset
- let offset = Some (wsm.ScrollbarTbPos-cursor)
- setScrollbarOffset wsm dispatch offset
- | InScrollbarDrag -> // in drag, unknown queue state: update counter, if queue is empty then dispatch ReleaseScrollQueue message
- let canDispatch = wsm.ScrollbarQueueIsEmpty
- setScrollbarLastX wsm dispatch false
- if canDispatch then ScrollbarMouseMsg (cursor, ReleaseScrollQueue, dispatch) |> dispatch
- | ReleaseScrollQueue -> // in drag, and queue is clear: update and set ScrollbarQueueIsEmpty to true
- match wsm.ScrollbarTbOffset with
- | Some puckOffset ->
- let dx = puckOffset + cursor - wsm.ScrollbarTbPos // offset + new cursor = new thumb; dx = new thumb - old thumb
- setScrollbarTbByCycs wsm dispatch (linearMouseToCycleTranslator dx)
- | None -> ()
- | ClearScrollbarDrag -> // clear offset
- setScrollbarOffset wsm dispatch None
+open WaveSimWaves.Constants
/// Start or update a spinner popup
let updateSpinner (name:string) payload (numToDo:int) (model: Model) =
@@ -1151,7 +84,7 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
let allWaves =
if newSimulation then
//printfn "making new waves..."
- getWaves wsModel fs
+ WaveSimWaves.getWaves wsModel fs
else wsModel.AllWaves
let model = updateWSModel (fun ws -> {ws with AllWaves = allWaves}) model
// redo viewer width (and therefore shown cycles etc) based on selected waves names
@@ -1172,7 +105,7 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
|> Map.filter (fun wi wave ->
// Only generate waveforms for selected waves.
// Regenerate waveforms whenever they have changed
- let hasChanged = not <| waveformIsUptodate wsModel wave
+ let hasChanged = not <| WaveSimWaves.waveformIsUptodate wsModel wave
//if List.contains index ws.SelectedWaves then
List.exists (fun wi' -> isSameWave wi wi') wsModel.SelectedWaves && hasChanged && simulationIsUptodate)
|> Map.toList
@@ -1181,7 +114,7 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
let model, allWaves, spinnerPayload, numToDo =
//printfn $"{wavesToBeMade.Length} waves to make."
let numToDo = wavesToBeMade.Length
- makeWaveformsWithTimeOut (Some Constants.initSimulationTime) wsModel allWaves wavesToBeMade
+ WaveSimWaves.makeWaveformsWithTimeOut (Some Constants.initSimulationTime) wsModel allWaves wavesToBeMade
|> (fun (allWaves, numDone, timeOpt) ->
match wavesToBeMade.Length - numDone, timeOpt with
| n, None ->
@@ -1190,7 +123,7 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
failwithf "What? makewaveformsWithTimeOut must make at least one waveform"
| numToDo, Some t when
float wavesToBeMade.Length * t / float numDone < Constants.maxSimulationTimeWithoutSpinner ->
- let (allWaves, numDone, timeOpt) = makeWaveformsWithTimeOut None wsModel allWaves wavesToBeMade
+ let (allWaves, numDone, timeOpt) = WaveSimWaves.makeWaveformsWithTimeOut None wsModel allWaves wavesToBeMade
model, allWaves, None, numToDo - numDone
| numToDo, _ ->
let payload = Some ("Making waves", refreshWaveSim false {wsModel with AllWaves = allWaves} >> fst)
@@ -1228,10 +161,7 @@ let rec refreshWaveSim (newSimulation: bool) (wsModel: WaveSimModel) (model: Mod
RamComps = ramComps
SelectedRams = selectedRams
FastSim = fs
- ScrollbarTbWidth = scrollbarInfo.tbWidth
- ScrollbarTbPos = scrollbarInfo.tbPos
- ScrollbarBkgRepCycs = scrollbarInfo.bkgRep
- }
+ } |> validateScrollBarInfo
let model =
match spinnerPayload with
@@ -1376,7 +306,7 @@ let topHalf canvasState (model: Model) dispatch : ReactElement * bool =
multiplierMenuButton wsModel dispatch
- radixButtons wsModel dispatch
+ WaveSimWaveforms.radixButtons wsModel dispatch
clkCycleButtons wsModel dispatch
]
@@ -1389,7 +319,9 @@ let topHalf canvasState (model: Model) dispatch : ReactElement * bool =
div [Style [MarginTop 20.; Display DisplayOptions.Flex; JustifyContent "space-between"]] [
refreshStartEndButton()
- div [Style [inlineNoWrap; Flex "0 1"]] [selectWavesButton wsModel dispatch; selectRamButton wsModel dispatch]
+ div [Style [inlineNoWrap; Flex "0 1"]] [
+ WaveSimSelect.selectWavesButton wsModel dispatch
+ WaveSimSelect.selectRamButton wsModel dispatch]
]
messageOrControlLine], needsBottomHalf
@@ -1406,16 +338,16 @@ let viewWaveSim canvasState (model: Model) dispatch : ReactElement =
let bottomHalf = // this has fixed height
div [HTMLAttr.Id "BottomHalf" ; showWaveformsAndRamStyle (if needsRAMs then screenHeight() else height)] (
if wsModel.SelectedWaves.Length > 0 then [
- showWaveforms model wsModel dispatch
+ WaveSimWaveforms.showWaveforms model wsModel dispatch
makeScrollbar wsModel dispatch ]
else []
@
- [ramTables wsModel]
+ [WaveSimRams.ramTables wsModel]
)
div [] [
- selectRamModal wsModel dispatch
- selectWavesModal wsModel dispatch
+ WaveSimSelect.selectRamModal wsModel dispatch
+ WaveSimSelect.selectWavesModal wsModel dispatch
div [ viewWaveSimStyle ]
[
//printfn $"WSmodel state: {wsModel.State}"
diff --git a/src/Renderer/UI/WaveSim/WaveSimHelpers.fs b/src/Renderer/UI/WaveSim/WaveSimHelpers.fs
index def059492..7a634c305 100644
--- a/src/Renderer/UI/WaveSim/WaveSimHelpers.fs
+++ b/src/Renderer/UI/WaveSim/WaveSimHelpers.fs
@@ -14,57 +14,9 @@ open FastRun
open NumberHelpers
+open WaveSimStyle
-module Constants =
-
- /// initial time running simulation without spinner to check speed (in ms)
- let initSimulationTime = 100.
- /// max estimated time to run simulation and not need a spinner (in ms)
- let maxSimulationTimeWithoutSpinner = 200.
- /// The horizontal length of a transition cross-hatch for non-binary waveforms
- let nonBinaryTransLen : float = 2.
-
- /// The height of the viewbox used for a wave's SVG. This is the same as the height
- /// of a label in the name and value columns.
- /// TODO: Combine this with WaveSimStyle.Constants.rowHeight?
- let viewBoxHeight : float = 30.0
-
- /// Height of a waveform
- let waveHeight : float = 0.8 * viewBoxHeight
- /// Vertical padding between top and bottom of each wave and the row it is in.
- let spacing : float = (viewBoxHeight - waveHeight) / 2.
-
- /// y-coordinate of the top of a waveform
- let yTop = spacing
- /// y-coordiante of the bottom of a waveform
- let yBot = waveHeight + spacing
-
- /// minium number of cycles on screen when zooming in
- let minVisibleCycles = 3
-
- /// Minimum number of visible clock cycles.
- let minCycleWidth = 5
-
- let zoomChangeFactor = 1.5
-
- /// If the width of a non-binary waveform is less than this value, display a cross-hatch
- /// to indicate a non-binary wave is rapidly changing value.
- let clkCycleNarrowThreshold = 20
-
- /// number of extra steps simulated beyond that used in simulation. Is this needed?
- let extraSimulatedSteps = 5
-
- let infoMessage =
- "Find ports by any part of their name. '.' = show all. '*' = show selected. '-' = collapse all"
-
- let outOfDateMessage = "Use refresh button to update waveforms. 'End' and then 'Start' to simulate a different sheet"
-
- let infoSignUnicode = "\U0001F6C8"
-
- let waveLegendMaxChars = 35
- let valueColumnMaxChars = 35
- let multipliers = [1;2;5;10;20;50]
@@ -76,15 +28,6 @@ module Constants =
let subSamp (arr: 'T array) (start:int) (count: int) (mult:int) =
Array.init count (fun n -> arr[start + n*mult])
-// maybe these should be defined earlier in compile order? Or added as list functions?
-
-let listMaxWithDef defaultValue lst =
- defaultValue :: lst
- |> List.max
-
-let listCollectSomes mapFn lst =
- lst
- |> List.collect (fun x -> match mapFn x with | Some r -> [r] | None -> [])
/// Determines whether a clock cycle is generated with a vertical bar at the beginning,
/// denoting that a waveform changes value at the start of that clock cycle. NB this
@@ -115,29 +58,6 @@ type Gap = {
Length: int
}
-let rec validateSimParas (ws: WaveSimModel) =
- if ws.StartCycle < 0 then
- printfn $"ERROR in Sim parameters: StartCycle {ws.StartCycle} < 0"
- validateSimParas {ws with StartCycle = 0}
- elif (ws.StartCycle + ws.ShownCycles-1)*ws.CycleMultiplier > Constants.maxLastClk then
- printfn $"Correcting sim paras by reducing StartCycle"
- {ws with StartCycle = Constants.maxLastClk / ws.CycleMultiplier - ws.ShownCycles}
- elif ws.CurrClkCycle < ws.StartCycle || ws.CurrClkCycle >= ws.StartCycle + ws.ShownCycles then
- printfn $"Resetting CurClkCycle which {ws.CurrClkCycle} was too large with multiplier = {ws.CycleMultiplier}"
- {ws with CurrClkCycle = ws.StartCycle; CurrClkCycleDetail = ws.StartCycle*ws.CycleMultiplier}
- else ws
-
-let changeMultiplier newMultiplier (ws: WaveSimModel) =
- let oldM = ws.CycleMultiplier
- printfn $"Old: {oldM} shown {ws.ShownCycles} start={ws.StartCycle} NewM={newMultiplier}"
- let sampsHalf = (float ws.ShownCycles - 1.) / 2.
- let newShown = min ws.ShownCycles (Constants.maxLastClk / newMultiplier)
- let newStart = int ((float ws.StartCycle + sampsHalf) * float oldM / float newMultiplier - (float newShown - 1.) / 2.)
- printfn $"New: shown={newShown} start = {newStart}"
- {ws with ShownCycles = newShown; StartCycle = newStart; CycleMultiplier = newMultiplier}
- |> validateSimParas
-
-
@@ -151,32 +71,12 @@ let xShift clkCycleWidth =
else Constants.nonBinaryTransLen
-/// Width of one clock cycle.
-let singleWaveWidth m = max 5.0 (float m.WaveformColumnWidth / float m.ShownCycles)
-
-/// Left-most coordinate of the SVG viewbox.
-let viewBoxMinX m = string (float m.StartCycle * singleWaveWidth m)
-
-/// Total width of the SVG viewbox.
-let viewBoxWidth m = string (max 5.0 (m.WaveformColumnWidth))
-/// Right-most visible clock cycle.
-let endCycle wsModel = wsModel.StartCycle + (wsModel.ShownCycles) - 1
/// Helper function to create Bulma buttons
let button options func label = Button.button (List.append options [ Button.OnClick func ]) [ label ]
-/// List of selected waves (of type Wave)
-let selectedWaves (wsModel: WaveSimModel) : Wave list =
- wsModel.SelectedWaves
- |> List.map (fun wi -> Map.tryFind wi wsModel.AllWaves |> Option.toList)
- |> List.concat
-/// Convert XYPos list to string
-let pointsToString (points: XYPos array) : string =
- Array.fold (fun str (point: XYPos) ->
- $"{str} %.1f{point.X},%.1f{point.Y} "
- ) "" points
/// Retrieve value of wave at given clock cycle as an int.
/// At extra (sampling) zoom this allows detail clock cycles within one sample
@@ -512,44 +412,7 @@ let wavesToIds (waves: Wave list) =
let tr1 react = tr [] [ react ]
let td1 react = td [] [ react ]
-//---------------------------Code for selector details state----------------------------------//
-
-// It would be better to do this with one subfunction and Optics!
-
-/// Sets or clears a subset of ShowSheetDetail
-let setWaveSheetSelectionOpen (wsModel: WaveSimModel) (subSheets: string list list) (show: bool) =
- let setChange = Set.ofList subSheets
- let newSelect =
- match show with
- | false -> Set.difference wsModel.ShowSheetDetail setChange
- | true -> Set.union setChange wsModel.ShowSheetDetail
- {wsModel with ShowSheetDetail = newSelect}
-
-/// Sets or clears a subset of ShowComponentDetail
-let setWaveComponentSelectionOpen (wsModel: WaveSimModel) (fIds: FComponentId list) (show: bool) =
- let fIdSet = Set.ofList fIds
- let newSelect =
- match show with
- | true -> Set.union fIdSet wsModel.ShowComponentDetail
- | false -> Set.difference wsModel.ShowComponentDetail fIdSet
- {wsModel with ShowComponentDetail = newSelect}
-
-
-/// Sets or clears a subset of ShowGroupDetail
-let setWaveGroupSelectionOpen (wsModel: WaveSimModel) (grps :(ComponentGroup*string list) list) (show: bool) =
- let grpSet = Set.ofList grps
- let newSelect =
- match show with
- | true -> Set.union grpSet wsModel.ShowGroupDetail
- | false -> Set.difference wsModel.ShowGroupDetail grpSet
- {wsModel with ShowGroupDetail = newSelect}
-
-let setSelectionOpen (wsModel: WaveSimModel) (cBox: CheckBoxStyle) (show:bool) =
- match cBox with
- | PortItem _ -> failwithf "What? setselectionopen cannot be called from a Port"
- | ComponentItem fc -> setWaveComponentSelectionOpen wsModel [fc.fId] show
- | GroupItem (grp,subSheet) -> setWaveGroupSelectionOpen wsModel [grp,subSheet] show
- | SheetItem subSheet -> setWaveSheetSelectionOpen wsModel [subSheet] show
+
/// get all waves electrically connected to a given wave
diff --git a/src/Renderer/UI/WaveSim/WaveSimNavigation.fs b/src/Renderer/UI/WaveSim/WaveSimNavigation.fs
new file mode 100644
index 000000000..e0a3005db
--- /dev/null
+++ b/src/Renderer/UI/WaveSim/WaveSimNavigation.fs
@@ -0,0 +1,464 @@
+module WaveSimNavigation
+
+open Fulma
+open Fulma.Extensions.Wikiki
+open Fable.React
+open Fable.React.Props
+
+open CommonTypes
+open ModelType
+open ModelHelpers
+open WaveSimStyle
+open WaveSimHelpers
+open TopMenuView
+open SimulatorTypes
+open WaveSimStyle.Constants
+
+/// Generate scrollbar SVG info based on current WaveSimModel.
+/// Called in refreshWaveSim after WaveSimModel has been changed.
+/// Target WaveSimModel.
+/// Anonymous record contaning the information to be updated: thumb width,
+/// thumb position, and number of cycles the background represents.
+/// Note: bkg = background; tb = thumb.
+let generateScrollbarInfo (wsm: WaveSimModel): {| tbWidth: float; tbPos: float; bkgRep: int |} =
+ let mult = wsm.CycleMultiplier
+ let bkgWidth = wsm.ScrollbarBkgWidth - 60. // 60 = 2x width of buttons
+
+ /// Return target value when within min and max value, otherwise min or max.
+ let bound (minV: int) (maxV: int) (tarV: int): int = tarV |> max minV |> min maxV
+ let currShownMaxCyc = wsm.StartCycle + wsm.ShownCycles
+ let newBkgRep = [ wsm.ScrollbarBkgRepCycs; currShownMaxCyc; wsm.ShownCycles*2 ] |> List.max |> bound 0 (Constants.maxLastClk / mult)
+
+ let tbCalcWidth = bkgWidth / (max 1. (float newBkgRep / float wsm.ShownCycles))
+ let tbWidth = max tbCalcWidth Constants.scrollbarThumbMinWidth
+
+ let tbMoveWidth = bkgWidth - tbWidth
+ let tbPos = (float wsm.StartCycle) / (float newBkgRep - float wsm.ShownCycles) * tbMoveWidth
+
+ // debug statements:
+ // printfn "DEBUG:generateScrollbarInfo: Input -"
+ // printfn "DEBUG:generateScrollbarInfo: wsm.CurrClkCycle = %d cycles" wsm.CurrClkCycle
+ // printfn "DEBUG:generateScrollbarInfo: wsm.StartCycle = %d cycles" wsm.StartCycle
+ // printfn "DEBUG:generateScrollbarInfo: wsm.ShownCycles = %d cycles" wsm.ShownCycles
+ // printfn "DEBUG:generateScrollbarInfo: wsm.ScrollbarBkgRepCycs = %d cycles" wsm.ScrollbarBkgRepCycs
+ // printfn "DEBUG:generateScrollbarInfo: bkgWidth = %.1f cycles" bkgWidth
+ // printfn "DEBUG:generateScrollbarInfo: Output -"
+ // printfn "DEBUG:generateScrollbarInfo: tbWidth = %.1fpx" tbWidth
+ // printfn "DEBUG:generateScrollbarInfo: tbPos = %.1fpx" tbPos
+ // printfn "DEBUG:generateScrollbarInfo: newBkgRep = %d cycles" newBkgRep
+
+ {| tbWidth = tbWidth; tbPos = tbPos; bkgRep = newBkgRep |}
+
+/// Make scrollbar parameters consistent with changed zoom. This asumes the scrollbar
+/// width has not changed, because that can only be calculated from viewer width in model.
+let validateScrollBarInfo (wsm: WaveSimModel) =
+ let scrollInfo = generateScrollbarInfo wsm
+ {wsm with ScrollbarTbPos = scrollInfo.tbPos
+ ScrollbarTbWidth = scrollInfo.tbWidth
+ ScrollbarBkgRepCycs = scrollInfo.bkgRep
+ }
+
+
+let inline updateViewerWidthInWaveSim w (model:Model) =
+ printfn "updateviewerWidthInWaveSim" // ***>
+ let wsModel = getWSModel model
+ //dispatch <| SetViewerWidth w
+ let namesColWidth = calcNamesColWidth wsModel
+
+ /// The extra is probably because of some unnacounted for padding etc (there is a weird 2px spacer to right of the divider)
+ /// It also allows space for a scroll bar (about 6 px)
+ let otherDivWidths = Constants.leftMargin + Constants.rightMargin + DiagramStyle.Constants.dividerBarWidth + Constants.scrollBarWidth + 8
+
+ /// This is what the overall waveform width must be
+ let valuesColumnWidth,_ = valuesColumnSize wsModel
+ let waveColWidth = w - otherDivWidths - namesColWidth - valuesColumnWidth
+
+ /// Require at least one visible clock cycle: otherwise choose number to get close to correct width of 1 cycle
+ let wholeCycles =
+ max 1 (int (float waveColWidth / singleWaveWidth wsModel))
+ |> min (Constants.maxLastClk / wsModel.CycleMultiplier) // make sure there can be no over-run when making viewer larger
+ let singleCycleWidth = float waveColWidth / float wholeCycles
+ let finalWavesColWidth = singleCycleWidth * float wholeCycles
+
+ /// Estimated length of scrollbar, adding three components together: names col, waveform port, and values col.
+ let scrollbarWidth = (float namesColWidth) + finalWavesColWidth + (float valuesColumnWidth)
+
+ // printfn "DEBUG:updateViewerWidthInWaveSim: Names Column Width = %Apx" (float namesColWidth)
+ // printfn "DEBUG:updateViewerWidthInWaveSim: Waves Column Width = %Apx" finalWavesColWidth
+ // printfn "DEBUG:updateViewerWidthInWaveSim: Values Column Width = %Apx" (float valuesColumnWidth)
+ // printfn "DEBUG:updateViewerWidthInWaveSim: Calculated Scrollbar Width = %Apx" scrollbarWidth
+
+
+ let updateFn wsModel =
+ {
+ wsModel with
+ ShownCycles = wholeCycles
+ StartCycle = min wsModel.StartCycle (Constants.maxLastClk - (wholeCycles - 1)*wsModel.CycleMultiplier)
+ CurrClkCycle = min wsModel.CurrClkCycle Constants.maxLastClk
+ WaveformColumnWidth = finalWavesColWidth
+ ScrollbarBkgWidth = scrollbarWidth
+ }
+ |> validateScrollBarInfo
+
+ {model with WaveSimViewerWidth = w}
+ |> ModelHelpers.updateWSModel updateFn
+
+
+
+let inline setViewerWidthInWaveSim w dispatch =
+ dispatch <| UpdateModel (updateViewerWidthInWaveSim w)
+ dispatch <| GenerateCurrentWaveforms
+
+let rec validateSimParas (ws: WaveSimModel) =
+ if ws.StartCycle < 0 then
+ printfn $"ERROR in Sim parameters: StartCycle {ws.StartCycle} < 0"
+ validateSimParas {ws with StartCycle = 0}
+ elif ws.CurrClkCycleDetail > Constants.maxLastClk then
+ validateSimParas {ws with CurrClkCycleDetail = Constants.maxLastClk}
+ elif (ws.StartCycle + ws.ShownCycles-1)*ws.CycleMultiplier > Constants.maxLastClk then
+ printfn $"Correcting sim paras by reducing StartCycle"
+ {ws with StartCycle = Constants.maxLastClk / ws.CycleMultiplier - ws.ShownCycles}
+ elif ws.CurrClkCycle < ws.StartCycle || ws.CurrClkCycle >= ws.StartCycle + ws.ShownCycles then
+ printfn $"Resetting CurClkCycle which {ws.CurrClkCycle} was too large with multiplier = {ws.CycleMultiplier}"
+ {ws with CurrClkCycle = ws.StartCycle; CurrClkCycleDetail = ws.StartCycle*ws.CycleMultiplier}
+ else ws
+ |> validateScrollBarInfo
+
+let changeMultiplier newMultiplier (ws: WaveSimModel) =
+ let oldM = ws.CycleMultiplier
+ printfn $"Old: {oldM} shown {ws.ShownCycles} start={ws.StartCycle} NewM={newMultiplier}"
+ let sampsHalf = (float ws.ShownCycles - 1.) / 2.
+ let newShown = min ws.ShownCycles (Constants.maxLastClk / newMultiplier)
+ let newStart = int ((float ws.StartCycle + sampsHalf) * float oldM / float newMultiplier - (float newShown - 1.) / 2.)
+ printfn $"New: shown={newShown} start = {newStart}"
+ {ws with ShownCycles = newShown; StartCycle = newStart; CycleMultiplier = newMultiplier}
+ |> validateSimParas
+
+
+
+
+/// Set highlighted clock cycle number
+let setClkCycle (wsModel: WaveSimModel) (dispatch: Msg -> unit) (newRealClkCycle: int) : unit =
+ let start = TimeHelpers.getTimeMs ()
+ let newDetail = min (max newRealClkCycle 0) Constants.maxLastClk
+ let mult = wsModel.CycleMultiplier
+ let newClkCycle = newRealClkCycle / mult
+ let newClkCycle = min Constants.maxLastClk newClkCycle |> max 0
+
+ if newClkCycle <= endCycle wsModel then
+ if newClkCycle < wsModel.StartCycle then
+ dispatch <| GenerateWaveforms
+ {wsModel with
+ StartCycle = newClkCycle
+ CurrClkCycle = newClkCycle
+ ClkCycleBoxIsEmpty = false
+ CurrClkCycleDetail = newDetail
+ }
+ else
+ dispatch <| SetWSModel
+ {wsModel with
+ CurrClkCycle = newClkCycle
+ ClkCycleBoxIsEmpty = false
+ CurrClkCycleDetail = newDetail
+ }
+ else
+ let newDetail = min newDetail Constants.maxLastClk
+ let newClkCycle = newDetail / mult
+ dispatch <| GenerateWaveforms
+ {wsModel with
+ StartCycle = newClkCycle - (wsModel.ShownCycles - 1)
+ CurrClkCycle = newClkCycle
+ ClkCycleBoxIsEmpty = false
+ CurrClkCycleDetail = newDetail
+ }
+ |> TimeHelpers.instrumentInterval "setClkCycle" start
+
+/// Move waveform view window by closest integer number of cycles.
+/// Current clock cycle (WaveSimModel.CurrClkCycle) is set to beginning or end depending on direction of movement.
+/// Update is achieved by dispatching a GenerateWaveforms message.
+/// Note the side-effect of clearing the ScrollbarQueueIsEmpty counter.
+/// Target WaveSimModel.
+/// Dispatch function to send messages with.
+/// Number of non-integer cycles to move by.
+let setScrollbarTbByCycs (wsm: WaveSimModel) (dispatch: Msg->unit) (moveByCycs: float): unit =
+ let moveWindowBy = int (System.Math.Round moveByCycs)
+ let mult = wsm.CycleMultiplier
+
+ /// Return target value when within min and max value, otherwise min or max.
+ let bound (minV: int) (maxV: int) (tarV: int): int = tarV |> max minV |> min maxV
+ let minSimCyc = 0
+ let maxSimCyc = Constants.maxLastClk / mult
+
+ let newStartCyc = (wsm.StartCycle+moveWindowBy) |> bound minSimCyc (maxSimCyc-wsm.ShownCycles+1)
+ let newCurrCyc =
+ let newEndCyc = newStartCyc+wsm.ShownCycles-1
+ if newStartCyc <= wsm.CurrClkCycle && wsm.CurrClkCycle <= newEndCyc
+ then
+ wsm.CurrClkCycle
+ else
+ if abs (wsm.CurrClkCycle - newStartCyc) < abs (wsm.CurrClkCycle - newEndCyc)
+ then newStartCyc
+ else newEndCyc
+ let detail = wsm.CurrClkCycleDetail
+ let detail = if newCurrCyc <> detail / mult then newCurrCyc * mult else detail
+ {
+ wsm with StartCycle = newStartCyc;
+ CurrClkCycle = newCurrCyc;
+ CurrClkCycleDetail = detail;
+ ScrollbarQueueIsEmpty = true
+ }
+ |> validateSimParas
+ |> (fun ws -> dispatch <| GenerateWaveforms ws)
+
+/// Update WaveSimModel with new ScrollbarTbOffset.
+/// Used when starting or clearing scrollbar drag mode.
+/// Update is achieved by dispatching a GenerateWaveforms message.
+/// Target WaveSimModel.
+/// Dispatch function to send messages with.
+/// Offset option to be written to WaveSimModel.ScrollbarTbOffset.
+let setScrollbarOffset (wsm: WaveSimModel) (dispatch: Msg->unit) (offset: float option): unit =
+ GenerateWaveforms { wsm with ScrollbarTbOffset = offset; ScrollbarQueueIsEmpty = true } |> dispatch
+
+/// Update WaveSimModel with new ScrollbarQueueIsEmpty.
+/// Used to update is-empty counter to coalesce scrollbar mouse events together.
+/// Update is achieved by dispatching a UpdateWSModel message, so as to not clog the queue with GenerateWaveforms messages.
+/// Target WaveSimModel.
+/// Dispatch function to send messages with.
+/// Bool to be written to WaveSimModel.ScrollbarQueueIsEmpty.
+let setScrollbarLastX (wsm: WaveSimModel) (dispatch: Msg->unit) (isEmpty: bool): unit =
+ UpdateWSModel (fun _ -> { wsm with ScrollbarQueueIsEmpty = isEmpty }) |> dispatch
+
+/// If zoomIn, then increase width of clock cycles (i.e.reduce number of visible cycles).
+/// otherwise reduce width. GenerateWaveforms message will reconstitute SVGs after the change.
+let changeZoom (wsModel: WaveSimModel) (zoomIn: bool) (dispatch: Msg -> unit) =
+ let start = TimeHelpers.getTimeMs ()
+ let shownCycles =
+ let wantedCycles = int (float wsModel.ShownCycles / Constants.zoomChangeFactor)
+ if zoomIn then
+ // try to reduce number of cycles displayed
+ wantedCycles
+ // If number of cycles after casting to int does not change
+ |> (fun nc -> if nc = wsModel.ShownCycles then nc - 1 else nc )
+ // Require a minimum of cycles
+ |> (fun nc ->
+ let minVis = min wsModel.ShownCycles Constants.minVisibleCycles
+ max nc minVis)
+ else
+ let wantedCycles = int (float wsModel.ShownCycles * Constants.zoomChangeFactor)
+ // try to increase number of cycles displayed
+ wantedCycles
+ // If number of cycles after casting to int does not change
+ |> (fun nc -> if nc = wsModel.ShownCycles then nc + 1 else nc )
+ |> (fun nc ->
+ let maxNc = int (wsModel.WaveformColumnWidth / float Constants.minCycleWidth)
+ max wsModel.ShownCycles (min nc maxNc))
+ let startCycle =
+ // preferred start cycle to keep centre of screen ok
+ let sc = (wsModel.StartCycle - (shownCycles - wsModel.ShownCycles)/2)
+ let cOffset = wsModel.CurrClkCycle - sc
+ sc
+ // try to keep cursor on screen
+ |> (fun sc ->
+ if cOffset > shownCycles - 1 then
+ sc + cOffset - shownCycles + 1
+ elif cOffset < 0 then
+ (sc + cOffset)
+ else
+ sc)
+ // final limits check so no cycle is outside allowed range
+ |> min (Constants.maxLastClk / wsModel.CycleMultiplier - shownCycles)
+ |> max 0
+
+
+ dispatch <| GenerateWaveforms {
+ wsModel with
+ ShownCycles = shownCycles;
+ StartCycle = startCycle
+ }
+ |> TimeHelpers.instrumentInterval "changeZoom" start
+
+/// Make scrollbar element based on information in WaveSimModel.
+/// Called in viewWaveSim, presumably after refreshWaveSim was called.
+/// Target WaveSimModel.
+/// Dispatch function to send messages with. Not used directly, but passed to tbMouseMoveOp.
+/// React element to be placed in to DOM.
+let makeScrollbar (wsm: WaveSimModel) (dispatch: Msg->unit): ReactElement =
+ // button props
+ let scrollWaveformViewBy (numCycles: float) = setScrollbarTbByCycs wsm dispatch numCycles
+
+ // svg props
+ let bkgWidth = wsm.ScrollbarBkgWidth - 60. // 60 = 2x width of buttons
+
+ let tbMouseDownHandler (event: Browser.Types.MouseEvent): unit = // start drag
+ ScrollbarMouseMsg (event.clientX, StartScrollbarDrag, dispatch) |> dispatch
+
+ let tbMouseMoveHandler (event: Browser.Types.MouseEvent): unit = // if in drag, drag; otherwise do nothing
+ if Option.isSome wsm.ScrollbarTbOffset
+ then ScrollbarMouseMsg (event.clientX, InScrollbarDrag, dispatch) |> dispatch
+ else ()
+
+ let tbMouseUpHandler (event: Browser.Types.MouseEvent): unit = // if in drag, clear drag; otherwise do nothing
+ if Option.isSome wsm.ScrollbarTbOffset
+ then ScrollbarMouseMsg (event.clientX, ClearScrollbarDrag, dispatch) |> dispatch
+ else ()
+
+ let bkgPropList (width: float): List =
+ [
+ HTMLAttr.Id "scrollbarThumb";
+ SVGAttr.X $"0px"; SVGAttr.Y "0.5px";
+ SVGAttr.Width $"%.1f{width}px"; SVGAttr.Height $"%.1f{WaveSimStyle.Constants.softScrollBarWidth-1.0}px";
+ SVGAttr.Fill "white"; SVGAttr.Stroke "gray"; SVGAttr.StrokeWidth "1px";
+ ]
+
+ let tbPropList (pos: float) (width: float): List =
+ [
+ HTMLAttr.Id "scrollbarBkg";
+ Style [ Cursor "grab"];
+ SVGAttr.X $"%.1f{pos}px"; SVGAttr.Y "0.5px";
+ SVGAttr.Width $"%.1f{width}px"; SVGAttr.Height $"%.1f{WaveSimStyle.Constants.softScrollBarWidth-1.0}px";
+ SVGAttr.Fill "lightgrey"; SVGAttr.Stroke "gray"; SVGAttr.StrokeWidth "1px";
+ OnMouseDown tbMouseDownHandler; OnMouseUp tbMouseUpHandler; OnMouseMove tbMouseMoveHandler;
+ ]
+
+ div [ Style [ MarginTop "5px"; MarginBottom "5px"; Height "25px"]] [
+ button [ Button.Props [scrollbarClkCycleLeftStyle] ]
+ (fun _ -> scrollWaveformViewBy -1.0)
+ (str "◀")
+ svg
+ [Style [Width $"{bkgWidth}"; Height $"{WaveSimStyle.Constants.softScrollBarWidth}px"];]
+ [
+ rect (bkgPropList bkgWidth) []; // background
+ rect (tbPropList wsm.ScrollbarTbPos wsm.ScrollbarTbWidth) []; // thumb
+ ]
+ button [ Button.Props [scrollbarClkCycleRightStyle]]
+ (fun _ -> scrollWaveformViewBy 1.0)
+ (str "▶")
+ ]
+
+/// Update waveform view information based on mouse postion in the X direction.
+/// Called in update when ScrollbarMouseMsg is dispatched.
+/// Target WaveSimModel.
+/// Dispatch function to send messages with, not used directly.
+/// Cursor postion in relation to the screen, i.e. event.clientX.
+/// Scrollbar action to do, see choices for more info, in type of ScrollbarMouseAction.
+/// Note that screenX does NOT scale with web zoom and will cause weird results!
+let updateScrollbar (wsm: WaveSimModel) (dispatch: Msg->unit) (cursor: float) (action: ScrollbarMouseAction): unit =
+ /// Translate mouse movements in pixels to number of cycles to move by.
+ /// Linear translator aims to allow scrollbar thumb to follow cursor.
+ /// Number of pixels mouse has moved in X direction, obtained from MouseMove event.
+ /// Number of cycles to move by.
+ /// Swap this out with some other mouse-to-cycle translator for better user experience.
+ let linearMouseToCycleTranslator (dx: float): float =
+ let cycleToPixelRatio = float wsm.ScrollbarBkgRepCycs / (wsm.ScrollbarBkgWidth - wsm.ScrollbarTbWidth)
+ dx*cycleToPixelRatio
+ match action with
+ | StartScrollbarDrag -> // record offset
+ let offset = Some (wsm.ScrollbarTbPos-cursor)
+ setScrollbarOffset wsm dispatch offset
+ | InScrollbarDrag -> // in drag, unknown queue state: update counter, if queue is empty then dispatch ReleaseScrollQueue message
+ let canDispatch = wsm.ScrollbarQueueIsEmpty
+ setScrollbarLastX wsm dispatch false
+ if canDispatch then ScrollbarMouseMsg (cursor, ReleaseScrollQueue, dispatch) |> dispatch
+ | ReleaseScrollQueue -> // in drag, and queue is clear: update and set ScrollbarQueueIsEmpty to true
+ match wsm.ScrollbarTbOffset with
+ | Some puckOffset ->
+ let dx = puckOffset + cursor - wsm.ScrollbarTbPos // offset + new cursor = new thumb; dx = new thumb - old thumb
+ setScrollbarTbByCycs wsm dispatch (linearMouseToCycleTranslator dx)
+ | None -> ()
+ | ClearScrollbarDrag -> // clear offset
+ setScrollbarOffset wsm dispatch None
+
+ /// Click on these buttons to change the number of visible clock cycles.
+let zoomButtons (wsModel: WaveSimModel) (dispatch: Msg -> unit) : ReactElement =
+ div [ clkCycleButtonStyle ]
+ [
+ button [ Button.Props [clkCycleLeftStyle] ]
+ (fun _ -> changeZoom wsModel false dispatch)
+ zoomOutSVG
+ button [ Button.Props [clkCycleRightStyle] ]
+ (fun _ -> changeZoom wsModel true dispatch)
+ zoomInSVG
+ ]
+
+/// Click on these to change the highlighted clock cycle.
+let clkCycleButtons (wsModel: WaveSimModel) (dispatch: Msg -> unit) : ReactElement =
+ /// Controls the number of cycles moved by the "◀◀" and "▶▶" buttons
+ let mult = wsModel.CycleMultiplier
+ let bigStepSize = max (2*mult) (wsModel.ShownCycles*mult / 2)
+
+ let scrollWaveformsBy (numCycles: int) =
+ setClkCycle wsModel dispatch (wsModel.CurrClkCycleDetail + numCycles)
+
+ div [ clkCycleButtonStyle ]
+ [
+ // Move left by bigStepSize cycles
+ button [ Button.Props [clkCycleLeftStyle] ]
+ (fun _ -> scrollWaveformsBy -bigStepSize)
+ (str "◀◀")
+
+ // Move left by one cycle
+ button [ Button.Props [clkCycleInnerStyle] ]
+ (fun _ -> scrollWaveformsBy -1)
+ (str "◀")
+
+ // Text input box for manual selection of clock cycle
+ Input.number [
+ Input.Props clkCycleInputProps
+
+ Input.Value (
+ match wsModel.ClkCycleBoxIsEmpty with
+ | true -> ""
+ | false -> string (wsModel.CurrClkCycleDetail)
+ )
+ // TODO: Test more properly with invalid inputs (including negative numbers)
+ Input.OnChange(fun c ->
+ match System.Int32.TryParse c.Value with
+ | true, n ->
+ setClkCycle wsModel dispatch n
+ | false, _ when c.Value = "" ->
+ dispatch <| SetWSModel {wsModel with ClkCycleBoxIsEmpty = true}
+ | _ ->
+ dispatch <| SetWSModel {wsModel with ClkCycleBoxIsEmpty = false}
+ )
+ ]
+
+ // Move right by one cycle
+ button [ Button.Props [clkCycleInnerStyle] ]
+ (fun _ -> scrollWaveformsBy 1)
+ (str "▶")
+
+ // Move right by bigStepSize cycles
+ button [ Button.Props [clkCycleRightStyle] ]
+ (fun _ -> scrollWaveformsBy bigStepSize)
+ (str "▶▶")
+ ]
+
+//-------------------------------------Popup menu for multiplier---------------------------------------------//
+
+let multiplierMenuButton(wsModel: WaveSimModel) (dispatch: Msg -> unit) =
+ /// key = 0 .. n-1 where there are n possible multipliers
+ let mulTable = Constants.multipliers
+ let menuItem (key) =
+ let itemLegend = str (match key with | 0 -> "Every Cycle" | _ -> $"Every {mulTable[key]} cycles")
+ Menu.Item.li
+ [ Menu.Item.IsActive (Constants.multipliers[key] = wsModel.CycleMultiplier)
+ Menu.Item.OnClick (fun _ ->
+ dispatch <| ChangeWaveSimMultiplier (key);
+ dispatch ClosePopup)
+ ]
+ [ itemLegend ]
+ let menu =
+ div []
+ [
+ h5 [Style[Color IColor.IsDanger]] [str "Warning: extra zoom view greater than X1 will sample the waveform and so lose information about fast-changing outputs"]
+ br []
+ h5 [] [str "It should only be used to observe slow-chnaging signals, when it is necessary to see large clock cycle range, \
+ and the normal - zoom function is not enough."]
+ br []
+ Menu.menu [] [ Menu.list [] (List.map menuItem [0 .. mulTable.Length - 1 ])]
+ ]
+
+ let buttonClick = Button.OnClick (fun _ ->
+ printfn $"Mul={wsModel.CycleMultiplier}"
+ dispatch <| ShowStaticInfoPopup("Multiplier",menu,dispatch ))
+ Button.button ( buttonClick :: topHalfButtonProps IColor.IsDanger "ZoomButton" false) [str $"Extra zoom X{wsModel.CycleMultiplier}"]
diff --git a/src/Renderer/UI/WaveSim/WaveSimRams.fs b/src/Renderer/UI/WaveSim/WaveSimRams.fs
new file mode 100644
index 000000000..48d4b23f8
--- /dev/null
+++ b/src/Renderer/UI/WaveSim/WaveSimRams.fs
@@ -0,0 +1,216 @@
+module WaveSimRams
+
+open Fulma
+open Fable.React
+open Fable.React.Props
+
+open CommonTypes
+open ModelType
+open ModelHelpers
+open WaveSimStyle
+open WaveSimHelpers
+open TopMenuView
+open SimulatorTypes
+open NumberHelpers
+open DrawModelType
+open WaveSimNavigation
+open WaveSimSelect
+open DiagramStyle
+
+
+/// Table row that shows the address and data of a RAM component.
+let ramTableRow ((addr, data,rowType): string * string * RamRowType): ReactElement =
+
+ tr [ Style <| ramTableRowStyle rowType ] [
+ td [] [ str addr ]
+ td [] [ str data ]
+ ]
+
+/// Table showing contents of a RAM component.
+let ramTable (wsModel: WaveSimModel) ((ramId, ramLabel): FComponentId * string) : ReactElement =
+ let wanted = calcWaveformAndScrollBarHeight wsModel
+ let maxHeight = max (screenHeight() - (min wanted (screenHeight()/2.)) - 300.) 30.
+ let fs = wsModel.FastSim
+ match Map.tryFind ramId wsModel.FastSim.FComps with
+ | None -> div [] []
+ | Some fc ->
+ let step = wsModel.CurrClkCycle
+ FastRun.runFastSimulation None step fs |> ignore // not sure why this is needed
+
+ // in some cases fast sim is run for one cycle less than currClockCycle
+ let memData =
+ match fc.FType with
+ | ROM1 mem
+ | AsyncROM1 mem -> mem
+ | RAM1 mem
+ | AsyncRAM1 mem ->
+ match FastRun.extractFastSimulationState fs wsModel.CurrClkCycle ramId with
+ |RamState mem -> mem
+ | x -> failwithf $"What? Unexpected state {x} from cycle {wsModel.CurrClkCycle} \
+ in RAM component '{ramLabel}'. FastSim step = {fs.ClockTick}"
+ | _ -> failwithf $"Given a component {fc.FType} which is not a vaild RAM"
+ let aWidth,dWidth = memData.AddressWidth,memData.WordWidth
+
+ let print w (a:int64) = NumberHelpers.valToPaddedString w wsModel.Radix (((1L <<< w) - 1L) &&& a)
+
+ let lastLocation = int64 ((2 <<< memData.AddressWidth - 1) - 1)
+
+ /// print a single 0 location as one table row
+ let print1 (a:int64,b:int64,rw:RamRowType) = $"{print aWidth a}",$"{print dWidth b}",rw
+ /// print a range of zero locations as one table row
+
+ let print2 (a1:int64) (a2:int64) (d:int64) = $"{print aWidth (a1+1L)} ... {print aWidth (a2-1L)}", $"{print dWidth d}",RAMNormal
+
+ /// output info for one table row filling the given zero memory gap or arbitrary size, or no line if there is no gap.
+ let printGap (gStart:int64) (gEnd:int64) =
+ match gEnd - gStart with
+ | 1L -> []
+ | 2L -> [print1 ((gEnd + gStart) / 2L, 0L,RAMNormal)]
+ | n when n > 2L ->
+ [print2 gStart gEnd 0L]
+ | _ ->
+ failwithf $"What? gEnd={gEnd},gStart={gStart}: negative or zero gaps are impossible..."
+
+ /// transform Sparse RAM info into strings to print in a table, adding extra lines for zero gaps
+ /// line styling is controlled by a RamRowtype value and added later when the table row react is generated
+ let addGapLines (items: (int64*int64*RamRowType) list) =
+ let startItem =
+ match items[0] with
+ | -1L,_,_ -> []
+ | gStart,dStart,rw-> [print1 (gStart,dStart,rw)]
+ List.pairwise items
+ |> List.collect (fun ((gStart,_,_),(gEnd,dEnd,rwe)) ->
+ let thisItem = if gEnd = lastLocation + 1L then [] else [print1 (gEnd,dEnd,rwe)]
+ [printGap gStart gEnd; thisItem])
+ |> List.concat
+
+ /// Add a RAMNormal RamRowType value to every location in mem.
+ /// Add in additional locations for read and/or write if needed.
+ /// Set RamRowValue type to RAMWritten or RAMRead for thse locations.
+ /// Write is always 1 cycle after WEN=1 and address.
+ /// Read is 1 (0) cycles after address for sync (asynch) memories.
+ let addReadWrite (fc:FastComponent) (step:int) (mem: Map) =
+ let getInt64 (a: IOArray) step =
+ let w = a.Width
+ match w with
+ | w when w > 32 -> int64 <| convertBigIntToUInt64 w a.BigIntStep[step]
+ | _ -> int64 <| a.UInt32Step[step]
+
+ let readStep =
+ match fc.FType with
+ | AsyncROM1 _ | AsyncRAM1 _ -> step
+ | ROM1 _ | RAM1 _ -> step - 1
+ | _ -> failwithf $"What? {fc.FullName} should be a memory component"
+
+ let addrSteps step = getInt64 fc.InputLinks[0] step
+
+ let readOpt =
+ match step, fc.FType with
+ | 0,ROM1 _ | 0, RAM1 _ -> None
+ | _ ->
+ addrSteps readStep
+ |> Some
+ let writeOpt =
+ match step, fc.FType with
+ | _, ROM1 _
+ | _, AsyncROM1 _
+ | 0, _ -> None
+ | _, RAM1 _ | _, AsyncRAM1 _ when getInt64 fc.InputLinks[2] (step-1) = 1L ->
+ addrSteps (step-1)
+ |> Some
+ | _ ->
+ None
+
+ /// Mark addr in memory map as being rType
+ /// if addr does not exist - create it
+ let addToMap rType addr mem:Map =
+ match Map.tryFind addr mem with
+ | Some (d,_) -> Map.add addr (d,rType) mem
+ | None -> Map.add addr (0L,rType) mem
+
+
+ Map.map (fun k v -> v,RAMNormal) mem
+ |> (fun mem ->
+ match readOpt with
+ | Some addr -> addToMap RAMRead addr mem
+ | None -> mem
+ |> (fun mem ->
+ match writeOpt with // overwrite RAMRead here is need be
+ | Some addr -> addToMap RAMWritten addr mem
+ | None -> mem))
+
+
+ /// add fake locations beyong normal address range so that
+ /// addGapLines fills these (if need be). These locations are then removed
+ let addEndPoints (items:(int64*int64*RamRowType) list) =
+ let ad (a,d,rw) = a
+ match items.Length with
+ | 0 -> [-1L,0L,RAMNormal; lastLocation,0L,RAMNormal]
+ | _ ->
+ if ad items[0] < 0L then items else List.insertAt 0 (-1L,-1L,RAMNormal) items
+ |> (fun items ->
+ if ad items[items.Length-1] = lastLocation then
+ items else
+ List.insertAt items.Length (lastLocation+1L,0L,RAMNormal) items)
+
+
+ let lineItems =
+ memData.Data
+ |> addReadWrite fc step
+ |> Map.toList
+ |> List.map (fun (a,(d,rw)) -> a,d,rw)
+ |> List.filter (fun (a,d,rw) -> d<>0L || rw <> RAMNormal)
+ |> List.sort
+ |> addEndPoints
+ |> addGapLines
+
+
+
+ Level.item [
+ Level.Item.Option.Props ramTableLevelProps
+ Level.Item.Option.HasTextCentered
+ ] [
+ Heading.h6 [
+ Heading.Option.Props [ centerAlignStyle ]
+ ] [ str ramLabel ]
+ div [Style [MaxHeight maxHeight;OverflowY OverflowOptions.Auto]] [
+ Table.table [
+ Table.IsFullWidth
+ Table.IsBordered
+ ] [ thead [] [
+ tr [] [
+ th [ centerAlignStyle ] [ str "Address"]
+ th [ centerAlignStyle ] [ str "Data"; sub [Style [MarginLeft "2px"; FontSize "10px"]] [str (string wsModel.CurrClkCycle)]]
+ ]
+ ]
+ tbody []
+ (List.map ramTableRow lineItems)
+ ] ]
+ br []
+ ]
+
+/// Bulma Level component of tables showing RAM contents.
+let ramTables (wsModel: WaveSimModel) : ReactElement =
+ let inlineStyle (styles:CSSProp list) = div [Style (Display DisplayOptions.Inline :: styles)]
+ let start = TimeHelpers.getTimeMs ()
+ let selectedRams = Map.toList wsModel.SelectedRams
+ if List.length selectedRams > 0 then
+ let tables =
+ let headerRow =
+ ["read", RAMRead; "overwritten",RAMWritten]
+ |> List.map (fun (op, opStyle) -> inlineStyle [Margin "0px"] [inlineStyle (ramTableRowStyle opStyle) [str op]])
+ |> function
+ | [a;b] -> [str "Key: Memory location is " ; a; str ", or " ;b; str ". Click waveforms or use cursor control to change current cycle."]
+ | _ -> failwithf "What? Can't happen!"
+ List.map (fun ram -> td [Style [BorderColor "white"]] [ramTable wsModel ram]) selectedRams
+ |> (fun tables -> [tbody [] [tr [] [th [ColSpan selectedRams.Length] [inlineStyle [] headerRow]]; tr [Style [Border "10px"]] tables]])
+ |> Fulma.Table.table [
+ Table.TableOption.Props ramTablesLevelProps;
+ Table.IsFullWidth;
+ Table.IsBordered;
+ ]
+ div [HTMLAttr.Id "TablesDiv"] [ hr [ Style [ Margin "5px"]]; br [ Style [ Margin "0px"]]; tables]
+ else div [] []
+ |> TimeHelpers.instrumentInterval "ramTables" start
+
+
diff --git a/src/Renderer/UI/WaveSim/WaveSimSelect.fs b/src/Renderer/UI/WaveSim/WaveSimSelect.fs
index 09665be25..55e01d995 100644
--- a/src/Renderer/UI/WaveSim/WaveSimSelect.fs
+++ b/src/Renderer/UI/WaveSim/WaveSimSelect.fs
@@ -300,20 +300,6 @@ let makeWave (ws: WaveSimModel) (fastSim: FastSimulation) (wi: WaveIndexT) : Wav
-/// Get all simulatable waves from CanvasState. Includes top-level Input and Output ports.
-/// Waves contain info which will be used later to create the SVGs for those waves actually
-/// selected. Init value of these from this function is None.
-let getWaves (ws: WaveSimModel) (fs: FastSimulation) : Map =
- let start = TimeHelpers.getTimeMs ()
- //printfn $"{fs.WaveIndex.Length} possible waves"
- fs.WaveIndex
- |> TimeHelpers.instrumentInterval "getAllPorts" start
- |> Array.map (fun wi -> wi, makeWave ws fs wi)
- //|> fun x -> printfn $"Made waves!";x
- |> Map.ofArray
- |> TimeHelpers.instrumentInterval "makeWavePipeline" start
-
-
/// Sets all waves as selected or not selected depending on value of selected
@@ -775,24 +761,4 @@ let selectRamModal (wsModel: WaveSimModel) (dispatch: Msg -> unit) : ReactElemen
]
-//-------------------------------------Popup menu for multiplier---------------------------------------------//
-
-let multiplierMenuButton(wsModel: WaveSimModel) (dispatch: Msg -> unit) =
- /// key = 0 .. n-1 where there are n possible multipliers
- let mulTable = Constants.multipliers
- let menuItem (key) =
- let itemLegend = str (match key with | 0 -> "Every Cycle" | _ -> $"Every {mulTable[key]} cycles")
- Menu.Item.li
- [ Menu.Item.IsActive (Constants.multipliers[key] = wsModel.CycleMultiplier)
- Menu.Item.OnClick (fun _ ->
- dispatch <| ChangeWaveSimMultiplier (key);
- dispatch ClosePopup)
- ]
- [ itemLegend ]
- let menu = Menu.menu [] [ Menu.list [] (List.map menuItem [0 .. mulTable.Length - 1 ]) ]
-
- let buttonClick = Button.OnClick (fun _ ->
- printfn $"Mul={wsModel.CycleMultiplier}"
- dispatch <| ShowStaticInfoPopup("Multiplier",menu,dispatch ))
- Button.button ( buttonClick :: topHalfButtonProps IColor.IsDanger "ZoomButton" false) [str $"zoom X{wsModel.CycleMultiplier}"]
diff --git a/src/Renderer/UI/WaveSim/WaveSimStyle.fs b/src/Renderer/UI/WaveSim/WaveSimStyle.fs
index a92547c80..4efa62526 100644
--- a/src/Renderer/UI/WaveSim/WaveSimStyle.fs
+++ b/src/Renderer/UI/WaveSim/WaveSimStyle.fs
@@ -1,12 +1,14 @@
module WaveSimStyle
-open ModelType
-open ModelHelpers
-open WaveSimHelpers
+
+
open Fulma
open Fable.React
open Fable.React.Props
+open CommonTypes
+open ModelType
+open ModelHelpers
module Constants =
// Width of names column - replaced by calcNamesColWidth function
@@ -67,8 +69,92 @@ module Constants =
/// height of the top half of the wave sim window (including tabs) when waveforms are displayed
let topHalfHeight = 260.
+ // helpers constants
+ /// initial time running simulation without spinner to check speed (in ms)
+ let initSimulationTime = 100.
+ /// max estimated time to run simulation and not need a spinner (in ms)
+ let maxSimulationTimeWithoutSpinner = 200.
+ /// The horizontal length of a transition cross-hatch for non-binary waveforms
+ let nonBinaryTransLen : float = 2.
+
+ /// The height of the viewbox used for a wave's SVG. This is the same as the height
+ /// of a label in the name and value columns.
+ /// TODO: Combine this with WaveSimStyle.Constants.rowHeight?
+ let viewBoxHeight : float = 30.0
+
+ /// Height of a waveform
+ let waveHeight : float = 0.8 * viewBoxHeight
+ /// Vertical padding between top and bottom of each wave and the row it is in.
+ let spacing : float = (viewBoxHeight - waveHeight) / 2.
+
+ /// y-coordinate of the top of a waveform
+ let yTop = spacing
+ /// y-coordiante of the bottom of a waveform
+ let yBot = waveHeight + spacing
+
+ /// minium number of cycles on screen when zooming in
+ let minVisibleCycles = 3
+
+ /// Minimum number of visible clock cycles.
+ let minCycleWidth = 5
+
+ let zoomChangeFactor = 1.5
+
+ /// If the width of a non-binary waveform is less than this value, display a cross-hatch
+ /// to indicate a non-binary wave is rapidly changing value.
+ let clkCycleNarrowThreshold = 20
+
+ /// number of extra steps simulated beyond that used in simulation. Is this needed?
+ let extraSimulatedSteps = 5
+
+ let infoMessage =
+ "Find ports by any part of their name. '.' = show all. '*' = show selected. '-' = collapse all"
+
+ let outOfDateMessage = "Use refresh button to update waveforms. 'End' and then 'Start' to simulate a different sheet"
+
+ let infoSignUnicode = "\U0001F6C8"
+
+ let waveLegendMaxChars = 35
+ let valueColumnMaxChars = 35
+ let multipliers = [1;2;5;10;20;50]
+
+// maybe these should be defined earlier in compile order? Or added as list functions?
+
+let listMaxWithDef defaultValue lst =
+ defaultValue :: lst
+ |> List.max
+
+let listCollectSomes mapFn lst =
+ lst
+ |> List.collect (fun x -> match mapFn x with | Some r -> [r] | None -> [])
+
+/// List of selected waves (of type Wave)
+let selectedWaves (wsModel: WaveSimModel) : Wave list =
+ wsModel.SelectedWaves
+ |> List.map (fun wi -> Map.tryFind wi wsModel.AllWaves |> Option.toList)
+ |> List.concat
+
+/// Convert XYPos list to string
+let pointsToString (points: XYPos array) : string =
+ Array.fold (fun str (point: XYPos) ->
+ $"{str} %.1f{point.X},%.1f{point.Y} "
+ ) "" points
+
let screenHeight() = Browser.Dom.document.defaultView.innerHeight
+
+/// Width of one clock cycle.
+let singleWaveWidth m = max 5.0 (float m.WaveformColumnWidth / float m.ShownCycles)
+
+/// Left-most coordinate of the SVG viewbox.
+let viewBoxMinX m = string (float m.StartCycle * singleWaveWidth m)
+
+/// Total width of the SVG viewbox.
+let viewBoxWidth m = string (max 5.0 (m.WaveformColumnWidth))
+
+/// Right-most visible clock cycle.
+let endCycle wsModel = wsModel.StartCycle + (wsModel.ShownCycles) - 1
+
/// Style for top row in wave viewer.
let topRowStyle = Style [
Height Constants.rowHeight
@@ -417,7 +503,7 @@ let valueLabelStyle =
let nameRowLevelLeftProps (visibility: string): IHTMLProp list = [
Style [
Position PositionOptions.Sticky
- Left 0
+ CSSProp.Left 0
Visibility visibility
]
]
@@ -712,6 +798,54 @@ let wavePolyfillStyle points : IProp list = [
Points (pointsToString points)
]
+/// Style for top half of waveform simulator (instructions and buttons)
+let topHalfStyle = Style [
+ Position PositionOptions.Sticky
+ CSSProp.Top 0
+ BackgroundColor "white"
+ ZIndex 10000
+]
+
+//---------------------------Code for selector details state----------------------------------//
+
+// It would be better to do this with one subfunction and Optics!
+
+/// Sets or clears a subset of ShowSheetDetail
+let setWaveSheetSelectionOpen (wsModel: WaveSimModel) (subSheets: string list list) (show: bool) =
+ let setChange = Set.ofList subSheets
+ let newSelect =
+ match show with
+ | false -> Set.difference wsModel.ShowSheetDetail setChange
+ | true -> Set.union setChange wsModel.ShowSheetDetail
+ {wsModel with ShowSheetDetail = newSelect}
+
+/// Sets or clears a subset of ShowComponentDetail
+let setWaveComponentSelectionOpen (wsModel: WaveSimModel) (fIds: FComponentId list) (show: bool) =
+ let fIdSet = Set.ofList fIds
+ let newSelect =
+ match show with
+ | true -> Set.union fIdSet wsModel.ShowComponentDetail
+ | false -> Set.difference wsModel.ShowComponentDetail fIdSet
+ {wsModel with ShowComponentDetail = newSelect}
+
+
+/// Sets or clears a subset of ShowGroupDetail
+let setWaveGroupSelectionOpen (wsModel: WaveSimModel) (grps :(ComponentGroup*string list) list) (show: bool) =
+ let grpSet = Set.ofList grps
+ let newSelect =
+ match show with
+ | true -> Set.union grpSet wsModel.ShowGroupDetail
+ | false -> Set.difference wsModel.ShowGroupDetail grpSet
+ {wsModel with ShowGroupDetail = newSelect}
+
+let setSelectionOpen (wsModel: WaveSimModel) (cBox: CheckBoxStyle) (show:bool) =
+ match cBox with
+ | PortItem _ -> failwithf "What? setselectionopen cannot be called from a Port"
+ | ComponentItem fc -> setWaveComponentSelectionOpen wsModel [fc.fId] show
+ | GroupItem (grp,subSheet) -> setWaveGroupSelectionOpen wsModel [grp,subSheet] show
+ | SheetItem subSheet -> setWaveSheetSelectionOpen wsModel [subSheet] show
+
+
/// Props for HTML Summary element
let summaryProps (isSummary:bool) cBox (ws: WaveSimModel) (dispatch: Msg -> Unit): IHTMLProp list = [
@@ -749,61 +883,6 @@ let detailsProps showDetails cBox (ws: WaveSimModel) (dispatch: Msg -> Unit): IH
Open (show || showDetails)
]
-/// Style for top half of waveform simulator (instructions and buttons)
-let topHalfStyle = Style [
- Position PositionOptions.Sticky
- Top 0
- BackgroundColor "white"
- ZIndex 10000
-]
-
-
-
-
-
-
-let inline updateViewerWidthInWaveSim w (model:Model) =
- printfn "updateviewerWidthInWaveSim" // ***>
- let wsModel = getWSModel model
- //dispatch <| SetViewerWidth w
- let namesColWidth = calcNamesColWidth wsModel
-
- /// The extra is probably because of some unnacounted for padding etc (there is a weird 2px spacer to right of the divider)
- /// It also allows space for a scroll bar (about 6 px)
- let otherDivWidths = Constants.leftMargin + Constants.rightMargin + DiagramStyle.Constants.dividerBarWidth + Constants.scrollBarWidth + 8
-
- /// This is what the overall waveform width must be
- let valuesColumnWidth,_ = valuesColumnSize wsModel
- let waveColWidth = w - otherDivWidths - namesColWidth - valuesColumnWidth
-
- /// Require at least one visible clock cycle: otherwise choose number to get close to correct width of 1 cycle
- let wholeCycles = max 1 (int (float waveColWidth / singleWaveWidth wsModel))
- let singleCycleWidth = float waveColWidth / float wholeCycles
- let finalWavesColWidth = singleCycleWidth * float wholeCycles
-
- /// Estimated length of scrollbar, adding three components together: names col, waveform port, and values col.
- let scrollbarWidth = (float namesColWidth) + finalWavesColWidth + (float valuesColumnWidth)
-
- // printfn "DEBUG:updateViewerWidthInWaveSim: Names Column Width = %Apx" (float namesColWidth)
- // printfn "DEBUG:updateViewerWidthInWaveSim: Waves Column Width = %Apx" finalWavesColWidth
- // printfn "DEBUG:updateViewerWidthInWaveSim: Values Column Width = %Apx" (float valuesColumnWidth)
- // printfn "DEBUG:updateViewerWidthInWaveSim: Calculated Scrollbar Width = %Apx" scrollbarWidth
-
- let updateFn wsModel =
- {
- wsModel with
- ShownCycles = wholeCycles
- StartCycle = min wsModel.StartCycle (Constants.maxLastClk - wholeCycles + 1)
- CurrClkCycle = min wsModel.CurrClkCycle Constants.maxLastClk
- WaveformColumnWidth = finalWavesColWidth
- ScrollbarBkgWidth = scrollbarWidth
- }
-
- {model with WaveSimViewerWidth = w}
- |> ModelHelpers.updateWSModel updateFn
-let inline setViewerWidthInWaveSim w dispatch =
- dispatch <| UpdateModel (updateViewerWidthInWaveSim w)
- dispatch <| GenerateCurrentWaveforms
diff --git a/src/Renderer/UI/WaveSim/WaveSimWaveforms.fs b/src/Renderer/UI/WaveSim/WaveSimWaveforms.fs
new file mode 100644
index 000000000..8a9b1e867
--- /dev/null
+++ b/src/Renderer/UI/WaveSim/WaveSimWaveforms.fs
@@ -0,0 +1,280 @@
+module WaveSimWaveforms
+
+open Fulma
+open Fable.React
+open Fable.React.Props
+
+open CommonTypes
+open ModelType
+open ModelHelpers
+open WaveSimStyle
+open WaveSimHelpers
+open TopMenuView
+open SimulatorTypes
+open NumberHelpers
+open DrawModelType
+open WaveSimNavigation
+open WaveSimSelect
+open DiagramStyle
+
+/// ReactElement of the tabs for changing displayed radix
+let radixButtons (wsModel: WaveSimModel) (dispatch: Msg -> unit) : ReactElement =
+ let radixString = [
+ Bin, "Bin"
+ Hex, "Hex"
+ Dec, "uDec"
+ SDec, "sDec"
+ ]
+
+ let radixTab (radix, radixStr) =
+ Tabs.tab [
+ Tabs.Tab.IsActive(wsModel.Radix = radix)
+ Tabs.Tab.Props radixTabProps
+ ] [ a [
+ radixTabAStyle
+ OnClick(fun _ -> dispatch <| GenerateWaveforms {wsModel with Radix = radix})
+ ] [ str radixStr ]
+ ]
+
+ Tabs.tabs [
+ Tabs.IsToggle
+ Tabs.Props [ radixTabsStyle ]
+ ] (List.map (radixTab) radixString)
+
+
+let highlightCircuit fs comps wave (dispatch: Msg -> Unit) =
+ dispatch <| Sheet (SheetT.Msg.Wire (BusWireT.Msg.Symbol (SymbolT.SelectSymbols comps)))
+ // Filter out any non-existent wires
+ let conns = connsOfWave fs wave
+ dispatch <| Sheet (SheetT.Msg.SelectWires conns)
+
+/// Create label of waveform name for each selected wave.
+/// Note that this is generated after calling selectedWaves. Any changes to this function
+/// must also be made to valueRows and waveRows, as the order of the waves matters here.
+/// This is because the wave viewer is comprised of three columns of many rows, rather
+/// than many rows of three columns.
+let nameRows (model: Model) (wsModel: WaveSimModel) dispatch: ReactElement list =
+ selectedWaves wsModel
+ |> List.map (fun wave ->
+ let visibility =
+ if wsModel.HoveredLabel = Some wave.WaveId then
+ "visible"
+ else "hidden"
+
+ Level.level [
+ Level.Level.Option.Props [
+ nameRowLevelStyle (wsModel.HoveredLabel = Some wave.WaveId)
+ let execWithModel (f: Model -> Unit) = ExecFuncInMessage((fun model _ -> f model), dispatch)
+ OnMouseOver (fun _ -> dispatch <| execWithModel (fun model ->
+ if wsModel.DraggedIndex = None then
+ dispatch <| SetWSModel {wsModel with HoveredLabel = Some wave.WaveId}
+ // Check if symbol exists on Canvas
+ let symbols = model.Sheet.Wire.Symbol.Symbols
+ match Map.tryFind (fst wave.WaveId.Id) symbols with
+ | Some {Component={Type=IOLabel;Label=lab}} ->
+ let labelComps =
+ symbols
+ |> Map.toList
+ |> List.map (fun (_,sym) -> sym.Component)
+ |> List.filter (function | {Type=IOLabel;Label = lab'} when lab' = lab -> true |_ -> false)
+ |> List.map (fun comp -> ComponentId comp.Id)
+ highlightCircuit wsModel.FastSim labelComps wave dispatch
+ | Some sym ->
+ highlightCircuit wsModel.FastSim [fst wave.WaveId.Id] wave dispatch
+ | None -> ())
+
+ )
+ OnMouseOut (fun _ ->
+ dispatch <| SetWSModel {wsModel with HoveredLabel = None}
+ dispatch <| Sheet (SheetT.Msg.Wire (BusWireT.Msg.Symbol (SymbolT.SelectSymbols [])))
+ dispatch <| Sheet (SheetT.Msg.UpdateSelectedWires (connsOfWave wsModel.FastSim wave, false))
+ )
+
+ Draggable true
+
+ OnDragStart (fun ev ->
+ ev.dataTransfer.effectAllowed <- "move"
+ ev.dataTransfer.dropEffect <- "move"
+ dispatch <| SetWSModel {
+ wsModel with
+ DraggedIndex = Some wave.WaveId
+ PrevSelectedWaves = Some wsModel.SelectedWaves
+ }
+ )
+
+ OnDrag (fun ev ->
+ ev.dataTransfer.dropEffect <- "move"
+ let nameColEl = Browser.Dom.document.getElementById "namesColumn"
+ let bcr = nameColEl.getBoundingClientRect ()
+
+ // If the user drags the label outside the bounds of the wave name column
+ if ev.clientX < bcr.left || ev.clientX > bcr.right ||
+ ev.clientY < bcr.top || ev.clientY > bcr.bottom
+ then
+ dispatch <| SetWSModel {
+ wsModel with
+ HoveredLabel = Some wave.WaveId
+ // Use wsModel.SelectedValues if somehow PrevSelectedWaves not set
+ SelectedWaves = Option.defaultValue wsModel.SelectedWaves wsModel.PrevSelectedWaves
+ }
+ )
+
+ OnDragOver (fun ev -> ev.preventDefault ())
+
+ OnDragEnter (fun ev ->
+ ev.preventDefault ()
+ ev.dataTransfer.dropEffect <- "move"
+ let nameColEl = Browser.Dom.document.getElementById "namesColumn"
+ let bcr = nameColEl.getBoundingClientRect ()
+ let index = int (ev.clientY - bcr.top) / Constants.rowHeight - 1
+ let draggedWave =
+ match wsModel.DraggedIndex with
+ | Some waveId -> [waveId]
+ | None -> []
+
+ let selectedWaves =
+ wsModel.SelectedWaves
+ |> List.except draggedWave
+ |> List.insertManyAt index draggedWave
+
+ dispatch <| SetWSModel {wsModel with SelectedWaves = selectedWaves}
+ )
+
+ OnDragEnd (fun _ ->
+ dispatch <| SetWSModel {
+ wsModel with
+ DraggedIndex = None
+ PrevSelectedWaves = None
+ }
+ )
+ ]
+ ] [ Level.left
+ [ Props (nameRowLevelLeftProps visibility) ]
+ [ Delete.delete [
+ Delete.Option.Size IsSmall
+ Delete.Option.Props [
+ OnClick (fun _ ->
+ let selectedWaves = List.except [wave.WaveId] wsModel.SelectedWaves
+ dispatch <| SetWSModel {wsModel with SelectedWaves = selectedWaves}
+ )
+ ]
+ ] []
+ ]
+ Level.right
+ [ Props [ Style [ PaddingRight Constants.labelPadding ] ] ]
+ [ label [ nameLabelStyle (wsModel.HoveredLabel = Some wave.WaveId) ] [ wave.ViewerDisplayName|> str ] ]
+ ]
+ )
+
+/// Create column of waveform names
+let namesColumn model wsModel dispatch : ReactElement =
+ let start = TimeHelpers.getTimeMs ()
+ let rows =
+ nameRows model wsModel dispatch
+ div (namesColumnProps wsModel)
+ (List.concat [ topRow []; rows ])
+ |> TimeHelpers.instrumentInterval "namesColumn" start
+
+
+/// Create label of waveform value for each selected wave at a given clk cycle.
+/// Note that this is generated after calling selectedWaves.
+/// Any changes to this function must also be made to nameRows
+/// and waveRows, as the order of the waves matters here. This is
+/// because the wave viewer is comprised of three columns of many
+/// rows, rather than many rows of three columns.
+/// Return required width of values column in pixels, and list of cloumn react elements.
+let valueRows (wsModel: WaveSimModel) =
+ let valueColWidth, valueColNumChars =
+ valuesColumnSize wsModel
+ selectedWaves wsModel
+ |> List.map (fun wave -> getWaveValue wsModel.CurrClkCycleDetail wave wave.Width)
+ |> List.map (fun fd ->
+ match fd.Width, fd.Dat with
+ | 1, Word b -> $" {b}"
+ | _ -> fastDataToPaddedString valueColNumChars wsModel.Radix fd)
+ |> List.map (fun value -> label [ valueLabelStyle ] [ str value ])
+ |> (fun rows -> valueColWidth, rows)
+
+
+/// Generate a row of numbers in the waveforms column.
+/// Numbers correspond to clock cycles multiplied by the current multiplier
+let clkCycleNumberRow (wsModel: WaveSimModel) =
+ let makeClkCycleLabel i =
+ let n = i * wsModel.CycleMultiplier
+ match singleWaveWidth wsModel with
+ | width when width < float Constants.clkCycleNarrowThreshold && i % 5 <> 0 ->
+ []
+ | width when n >= 1000 && width < (float Constants.clkCycleNarrowThreshold * 4. / 3.) && i % 10 <> 0 ->
+ []
+ | _ ->
+ [ text (clkCycleText wsModel i) [str (string n)] ]
+
+
+ [ wsModel.StartCycle .. endCycle wsModel]
+ |> List.collect makeClkCycleLabel
+ |> svg (clkCycleNumberRowProps wsModel)
+
+/// Create column of waveform values
+let private valuesColumn wsModel : ReactElement =
+ let start = TimeHelpers.getTimeMs ()
+ let width, rows = valueRows wsModel
+ let cursorClkNum = wsModel.CurrClkCycleDetail
+ let topRowNumber = [ text [Style [FontWeight "bold"; PaddingLeft "2pt"]] [str (string <| cursorClkNum)] ]
+
+ div [ HTMLAttr.Id "ValuesCol" ; valuesColumnStyle width]
+ (List.concat [ topRow topRowNumber ; rows ])
+ |> TimeHelpers.instrumentInterval "valuesColumn" start
+
+/// Generate a column of waveforms corresponding to selected waves.
+let waveformColumn (wsModel: WaveSimModel) dispatch : ReactElement =
+ let start = TimeHelpers.getTimeMs ()
+ /// Note that this is generated after calling selectedWaves.
+ /// Any changes to this function must also be made to nameRows
+ /// and valueRows, as the order of the waves matters here. This is
+ /// because the wave viewer is comprised of three columns of many
+ /// rows, rather than many rows of three columns.
+ let waves = selectedWaves wsModel
+ if List.exists (fun wave -> wave.SVG = None) waves then
+ dispatch <| GenerateCurrentWaveforms
+ let waveRows : ReactElement list =
+ waves
+ |> List.map (fun wave ->
+ match wave.SVG with
+ | Some waveform ->
+ waveform
+ | None ->
+ div [] [] // the GenerateCurrentWaveforms message will soon update this
+ )
+
+ div [ waveformColumnStyle ]
+ [
+ clkCycleHighlightSVG wsModel dispatch
+ div [ waveRowsStyle <| wsModel.WaveformColumnWidth]
+ ([ clkCycleNumberRow wsModel ] @
+ waveRows
+ )
+ ]
+ |> TimeHelpers.instrumentInterval "waveformColumn" start
+
+/// Display the names, waveforms, and values of selected waveforms
+let showWaveforms (model: Model) (wsModel: WaveSimModel) (dispatch: Msg -> unit) : ReactElement =
+ if List.isEmpty wsModel.SelectedWaves then
+ div [] [] // no waveforms
+ else
+ let wHeight = calcWaveformHeight wsModel
+ let fixedHeight = Constants.softScrollBarWidth + Constants.topHalfHeight
+ let cssHeight =
+ if wsModel.SelectedRams.Count > 0 then
+ $"min( calc(50vh - (0.5 * {fixedHeight}px)) , {wHeight}px)"
+ else
+ $"min( calc(100vh - {fixedHeight}px) , {wHeight}px)"
+
+ div [ HTMLAttr.Id "Scroller"; Style [ Height cssHeight; Width "100%"; CSSProp.Custom("overflow", "auto")]] [
+ div [ HTMLAttr.Id "WaveCols" ;showWaveformsStyle ]
+ [
+ namesColumn model wsModel dispatch
+ waveformColumn wsModel dispatch
+ valuesColumn wsModel
+ ]
+ ]
+
diff --git a/src/Renderer/UI/WaveSim/WaveSimWaves.fs b/src/Renderer/UI/WaveSim/WaveSimWaves.fs
new file mode 100644
index 000000000..77b2aeff9
--- /dev/null
+++ b/src/Renderer/UI/WaveSim/WaveSimWaves.fs
@@ -0,0 +1,329 @@
+module WaveSimWaves
+
+open Fulma
+open Fable.React
+open Fable.React.Props
+
+open CommonTypes
+open ModelType
+open ModelHelpers
+open WaveSimStyle
+open WaveSimHelpers
+open TopMenuView
+open SimulatorTypes
+open NumberHelpers
+open DrawModelType
+open WaveSimNavigation
+open WaveSimSelect
+open DiagramStyle
+
+
+module Constants =
+ /// Config variable to choose whether to generate the full 1000 cycles of SVG.
+ let generateVisibleOnly = true
+ /// Config variable to choose whether to print performance analysis info to console.
+ let showPerfLogs = false
+ let inlineNoWrap = WhiteSpace WhiteSpaceOptions.Nowrap
+
+open Constants
+
+/// Get all simulatable waves from CanvasState. Includes top-level Input and Output ports.
+/// Waves contain info which will be used later to create the SVGs for those waves actually
+/// selected. Init value of these from this function is None.
+let getWaves (ws: WaveSimModel) (fs: FastSimulation) : Map =
+ let start = TimeHelpers.getTimeMs ()
+ //printfn $"{fs.WaveIndex.Length} possible waves"
+ fs.WaveIndex
+ |> TimeHelpers.instrumentInterval "getAllPorts" start
+ |> Array.map (fun wi -> wi, makeWave ws fs wi)
+ //|> fun x -> printfn $"Made waves!";x
+ |> Map.ofArray
+ |> TimeHelpers.instrumentInterval "makeWavePipeline" start
+
+
+
+
+
+/// Generates SVG to display non-binary values on waveforms.
+/// Should be refactored together with displayBigIntOnWave.
+let displayUInt32OnWave
+ (wsModel: WaveSimModel)
+ (width: int)
+ (waveValues: array)
+ (transitions: array)
+ : list =
+ // find all clock cycles where there is a NonBinaryTransition.Change
+ let changeTransitions =
+ transitions
+ |> Array.indexed
+ |> Array.filter (fun (_, x) -> x = Change)
+ |> Array.map (fun (i, _) -> i)
+
+ // find start and length of each gap between a Change transition
+ let gaps: array =
+ if Constants.generateVisibleOnly
+ then
+ // add dummy change at visible end, but need account for difference in changes:
+ // e.g. if we are showing 3 cycles, a wave with a change in each would be 0, 1, 2, 3 and would be fine when
+ // 4 is added; however, a wave with no change at all would be 0, and would produce an errorneous gap length
+ // of 4 when 4 is added - we therefore add 3
+ if changeTransitions[Array.length changeTransitions-1] <> wsModel.ShownCycles
+ then Array.append changeTransitions [|wsModel.ShownCycles|]
+ else Array.append changeTransitions [|wsModel.ShownCycles+1|]
+ |> Array.map (fun loc -> loc+wsModel.StartCycle) // shift cycle to start cycle
+ else
+ Array.append changeTransitions [|wsModel.StartCycle+transitions.Length-1|] // add dummry change length end
+ |> Array.pairwise
+ |> Array.map (fun (i1, i2) -> {Start = i1; Length = i2-i1}) // get start and length of gap
+
+ // utility functions for SVG generation
+ /// Function to make polygon fill for a gap.
+ /// Array of polyline points to fill.
+ let makePolyfill (points: array) =
+ let points = points |> Array.distinct
+ polyline (wavePolyfillStyle points) []
+
+ /// Function to make text element for a gap.
+ /// Starting X location of element.
+ let makeTextElement (start: float) (waveValue: string) =
+ text (singleValueOnWaveProps start) [ str waveValue ]
+
+ // create text element for every gap
+ gaps
+ |> Array.map (fun gap ->
+ // generate string
+ let waveValue = UInt32ToPaddedString Constants.waveLegendMaxChars wsModel.Radix width waveValues[gap.Start]
+
+ // calculate display widths
+ let cycleWidth = singleWaveWidth wsModel
+ let gapWidth = (float gap.Length * cycleWidth) - 2. * Constants.nonBinaryTransLen
+ let singleWidth = 1.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
+ let doubleWidth = 2. * singleWidth + Constants.valueOnWavePadding
+
+ match gapWidth with
+ | w when (w < singleWidth) -> // display filled polygon
+ let fillPoints = nonBinaryFillPoints cycleWidth gap
+ let fill = makePolyfill fillPoints
+ [ fill ]
+ | w when (singleWidth <= w && w < doubleWidth) -> // diplay 1 copy at centre
+ let gapCenterPadWidth = (float gap.Length * cycleWidth - singleWidth) / 2.
+ let singleText = makeTextElement (float gap.Start * cycleWidth + gapCenterPadWidth) waveValue
+ [ singleText ]
+ | w when (doubleWidth <= w) -> // display 2 copies at end of gaps
+ let singleCycleCenterPadWidth = // if a single cycle gap can include 2 copies, set arbitrary padding
+ if cycleWidth < doubleWidth
+ then (cycleWidth - singleWidth) / 2.
+ else Constants.valueOnWaveEdgePadding
+ let startPadWidth =
+ if singleCycleCenterPadWidth < 0.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
+ then 0.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
+ else singleCycleCenterPadWidth
+ let endPadWidth = (float gap.Length * cycleWidth - startPadWidth - singleWidth)
+ let startText = makeTextElement (float gap.Start * cycleWidth + startPadWidth) waveValue
+ let endText = makeTextElement (float gap.Start * cycleWidth + endPadWidth) waveValue
+ [ startText; endText ]
+ | _ -> // catch-all
+ failwithf "displayUInt32OnWave: impossible case"
+ )
+ |> List.concat
+
+/// Generates SVG to display bigint values on waveforms.
+/// Should be refactored together with displayUInt32OnWave.
+let displayBigIntOnWave
+ (wsModel: WaveSimModel)
+ (width: int)
+ (waveValues: array)
+ (transitions: array)
+ : list =
+ // find all clock cycles where there is a NonBinaryTransition.Change
+ let changeTransitions =
+ transitions
+ |> Array.indexed
+ |> Array.filter (fun (_, x) -> x = Change)
+ |> Array.map (fun (i, _) -> i)
+
+ // find start and length of each gap between a Change transition
+ let gaps: array =
+ if Constants.generateVisibleOnly
+ then
+ // add dummy change at visible end, but need account for difference in changes:
+ // e.g. if we are showing 3 cycles, a wave with a change in each would be 0, 1, 2, 3 and would be fine when
+ // 4 is added; however, a wave with no change at all would be 0, and would produce an errorneous gap length
+ // of 4 when 4 is added - we therefore add 3
+ if changeTransitions[Array.length changeTransitions-1] <> wsModel.ShownCycles
+ then Array.append changeTransitions [|wsModel.ShownCycles|]
+ else Array.append changeTransitions [|wsModel.ShownCycles+1|]
+ |> Array.map (fun loc -> loc+wsModel.StartCycle) // shift cycle to start cycle
+ else
+ Array.append changeTransitions [|wsModel.StartCycle+transitions.Length-1|] // add dummry change length end
+ |> Array.pairwise
+ |> Array.map (fun (i1, i2) -> {Start = i1; Length = i2-i1}) // get start and length of gap
+
+ // utility functions for SVG generation
+ /// Function to make polygon fill for a gap.
+ /// Array of polyline points to fill.
+ let makePolyfill (points: array) =
+ let points = points |> Array.distinct
+ polyline (wavePolyfillStyle points) []
+
+ /// Function to make text element for a gap.
+ /// Starting X location of element.
+ let makeTextElement (start: float) (waveValue: string) =
+ text (singleValueOnWaveProps start) [ str waveValue ]
+
+ // create text element for every gap
+ gaps
+ |> Array.map (fun gap ->
+ // generate string
+ let waveValue = BigIntToPaddedString Constants.waveLegendMaxChars wsModel.Radix width waveValues[gap.Start]
+
+ // calculate display widths
+ let cycleWidth = singleWaveWidth wsModel
+ let gapWidth = (float gap.Length * cycleWidth) - 2. * Constants.nonBinaryTransLen
+ let singleWidth = 1.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
+ let doubleWidth = 2. * singleWidth + Constants.valueOnWavePadding
+
+ match gapWidth with
+ | w when (w < singleWidth) -> // display filled polygon
+ let fillPoints = nonBinaryFillPoints cycleWidth gap
+ let fill = makePolyfill fillPoints
+ [ fill ]
+ | w when (singleWidth <= w && w < doubleWidth) -> // diplay 1 copy at centre
+ let gapCenterPadWidth = (float gap.Length * cycleWidth - singleWidth) / 2.
+ let singleText = makeTextElement (float gap.Start * cycleWidth + gapCenterPadWidth) waveValue
+ [ singleText ]
+ | w when (doubleWidth <= w) -> // display 2 copies at end of gaps
+ let singleCycleCenterPadWidth = // if a single cycle gap can include 2 copies, set arbitrary padding
+ if cycleWidth < doubleWidth
+ then (cycleWidth - singleWidth) / 2.
+ else Constants.valueOnWaveEdgePadding
+ let startPadWidth =
+ if singleCycleCenterPadWidth < 0.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
+ then 0.1 * DrawHelpers.getTextWidthInPixels Constants.valueOnWaveText waveValue
+ else singleCycleCenterPadWidth
+ let endPadWidth = (float gap.Length * cycleWidth - startPadWidth - singleWidth)
+ let startText = makeTextElement (float gap.Start * cycleWidth + startPadWidth) waveValue
+ let endText = makeTextElement (float gap.Start * cycleWidth + endPadWidth) waveValue
+ [ startText; endText ]
+ | _ -> // catch-all
+ failwithf "displayUInt32OnWave: impossible case"
+ )
+ |> List.concat
+
+
+
+/// Check if generated SVG is correct, based on existence, position,
+/// and zoom. Fast simulation data is assumed unchanged. Used to determine if
+/// generateWaveform is run.
+let waveformIsUptodate (ws: WaveSimModel) (wave: Wave): bool =
+ wave.SVG <> None &&
+ wave.ShownCycles = ws.ShownCycles &&
+ wave.StartCycle = ws.StartCycle &&
+ wave.CycleWidth = singleWaveWidth ws &&
+ wave.Radix = ws.Radix &&
+ wave.Multiplier = ws.CycleMultiplier
+
+/// Called when InitiateWaveSimulation message is dispatched and when wave
+/// simulator is refreshed. Generates or updates the SVG for a specific waveform
+/// whether needed or not. The SVG depends on cycle width as well as start/stop
+/// clocks and design. Assumes that the fast simulation data has not changed and
+/// has enough cycles.
+let generateWaveform (ws: WaveSimModel) (index: WaveIndexT) (wave: Wave): Wave =
+ let makePolyline points =
+ let points = points |> Array.concat |> Array.distinct
+ polyline (wavePolylineStyle points) []
+
+ let waveform =
+ match wave.Width with
+ | 0 ->
+ failwithf "Cannot have wave of width 0"
+
+ | 1 -> // binary waveform
+ let transitions = calculateBinaryTransitionsUInt32 wave.WaveValues.UInt32Step ws.StartCycle ws.ShownCycles ws.CycleMultiplier
+
+ let wavePoints =
+ let waveWidth = singleWaveWidth ws
+ let startCycle = if Constants.generateVisibleOnly then ws.StartCycle else 0
+ Array.mapi (binaryWavePoints waveWidth startCycle) transitions
+ |> Array.concat
+ |> Array.distinct
+
+ svg (waveRowProps ws) [ polyline (wavePolylineStyle wavePoints) [] ]
+
+ | w when w <= 32 -> // non-binary waveform
+ let transitions = calculateNonBinaryTransitions wave.WaveValues.UInt32Step ws.StartCycle ws.ShownCycles ws.CycleMultiplier
+ let fstPoints, sndPoints =
+ let waveWidth = singleWaveWidth ws
+ let startCycle = if Constants.generateVisibleOnly then ws.StartCycle else 0
+ Array.mapi (nonBinaryWavePoints waveWidth startCycle) transitions |> Array.unzip
+
+ let valuesSVG = displayUInt32OnWave ws wave.Width wave.WaveValues.UInt32Step transitions
+ let polyLines = [makePolyline fstPoints; makePolyline sndPoints]
+
+ svg (waveRowProps ws) (List.append polyLines valuesSVG)
+
+ | _ -> // non-binary waveform with width greather than 32
+ let transitions = calculateNonBinaryTransitions wave.WaveValues.UInt32Step ws.StartCycle ws.ShownCycles ws.CycleMultiplier
+
+ let fstPoints, sndPoints =
+ Array.mapi (nonBinaryWavePoints (singleWaveWidth ws) 0) transitions |> Array.unzip
+
+ let valuesSVG = displayBigIntOnWave ws wave.Width wave.WaveValues.BigIntStep transitions
+
+ svg (waveRowProps ws) (List.append [makePolyline fstPoints; makePolyline sndPoints] valuesSVG)
+ {wave with
+ Radix = ws.Radix
+ ShownCycles = ws.ShownCycles
+ StartCycle = ws.StartCycle
+ Multiplier = ws.CycleMultiplier
+ CycleWidth = singleWaveWidth ws
+ SVG = Some waveform}
+
+
+/// This function regenerates all the waveforms listed on wavesToBeMade .
+/// Generation is subject to timeout, so may not complete.
+/// This function have been augmented with performance monitoring function, turn Constants.showPerfLogs
+/// to print performance information to console.
+/// A tuple with the following information:
+/// a) allWaves (with new waveforms),
+/// b) numberDone (no of waveforms made), and
+/// c) timeToDo (Some timeTaken when greater than timeOut or None
+/// if completed with no time out).
+let makeWaveformsWithTimeOut
+ (timeOut: option)
+ (ws: WaveSimModel)
+ (allWaves: Map)
+ (wavesToBeMade: list)
+ : Map * int * option =
+ let start = TimeHelpers.getTimeMs()
+ let allWaves, numberDone, timeToDo =
+ ((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)
+
+ allWaves, numberDone, timeToDo
+
+
+
+
+
+
+
+