diff --git a/Assets/Scripts/Description/Types/ChipDescription.cs b/Assets/Scripts/Description/Types/ChipDescription.cs index b5b0e67f..06dc950a 100644 --- a/Assets/Scripts/Description/Types/ChipDescription.cs +++ b/Assets/Scripts/Description/Types/ChipDescription.cs @@ -14,6 +14,7 @@ public class ChipDescription public string Name; public NameDisplayLocation NameLocation; public ChipType ChipType; + public bool ShouldBeCached; public Vector2 Size; public Color Colour; public PinDescription[] InputPins; diff --git a/Assets/Scripts/Game/Project/DevChipInstance.cs b/Assets/Scripts/Game/Project/DevChipInstance.cs index c25d9584..2c3d7391 100644 --- a/Assets/Scripts/Game/Project/DevChipInstance.cs +++ b/Assets/Scripts/Game/Project/DevChipInstance.cs @@ -212,6 +212,9 @@ public void NotifySaved(ChipDescription savedDescription) LastSavedDescription = savedDescription; RegenerateParentChipNamesHash(); + + Simulator.combinationalChipCaches.Remove(savedDescription.Name); + Simulator.chipsKnowToNotBeCombinational.Remove(savedDescription.Name); } public void AddNewSubChip(SubChipInstance subChip, bool isLoading) @@ -228,7 +231,7 @@ public void AddNewDevPin(DevPinInstance pin, bool isLoadingFromFile) AddElement(pin); if (!isLoadingFromFile) { - Simulator.AddPin(SimChip, pin.ID, pin.IsInputPin); + Simulator.AddPin(SimChip, pin.ID, pin.IsInputPin, pin.BitCount); } } diff --git a/Assets/Scripts/Game/Project/Project.cs b/Assets/Scripts/Game/Project/Project.cs index e2a5be6e..0566a711 100644 --- a/Assets/Scripts/Game/Project/Project.cs +++ b/Assets/Scripts/Game/Project/Project.cs @@ -122,6 +122,9 @@ public void EnterViewMode(SubChipInstance subchip) { if (chipLibrary.TryGetChipDescription(subchip.Description.Name, out ChipDescription description)) { + Simulator.useCaching = false; // Disable caching while viewing so subchips actually show what they are doing + Simulator.isCreatingACache = false; // Cancel any cache creation that might be going on + SimChip simChipToView = ViewedChip.SimChip.GetSubChipFromID(subchip.ID); DevChipInstance viewChip = DevChipInstance.LoadFromDescriptionTest(description, chipLibrary).devChip; @@ -140,6 +143,8 @@ public void ReturnToPreviousViewedChip() chipViewStack.Pop(); controller.CancelEverything(); UpdateViewedChipsString(); + + if (chipViewStack.Count == 1) Simulator.useCaching = true; // Left View mode, so turn caching back on } } diff --git a/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs b/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs index 932230d8..f7af4852 100644 --- a/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs +++ b/Assets/Scripts/Graphics/UI/Menus/BottomBarUI.cs @@ -13,7 +13,7 @@ public static class BottomBarUI { public const float barHeight = 3; const float padY = 0.3f; - const float buttonSpacing = 0.25f; + public const float buttonSpacing = 0.25f; const float buttonHeight = barHeight - padY * 2; const string shortcutTextCol = ""; diff --git a/Assets/Scripts/Graphics/UI/Menus/ChipCustomizationMenu.cs b/Assets/Scripts/Graphics/UI/Menus/ChipCustomizationMenu.cs index 959e0c26..87ec7061 100644 --- a/Assets/Scripts/Graphics/UI/Menus/ChipCustomizationMenu.cs +++ b/Assets/Scripts/Graphics/UI/Menus/ChipCustomizationMenu.cs @@ -3,6 +3,7 @@ using System.Linq; using DLS.Description; using DLS.Game; +using DLS.Simulation; using Seb.Helpers; using Seb.Vis; using Seb.Vis.UI; @@ -19,6 +20,7 @@ public static class ChipCustomizationMenu "Name: Hidden" }; + static readonly string[] cachingOptions = { "Caching: Off", "Caching: On" }; // ---- State ---- static SubChipInstance[] subChipsWithDisplays; @@ -29,6 +31,7 @@ public static class ChipCustomizationMenu static readonly UIHandle ID_ColourPicker = new("CustomizeMenu_ChipCol"); static readonly UIHandle ID_ColourHexInput = new("CustomizeMenu_ChipColHexInput"); static readonly UIHandle ID_NameDisplayOptions = new("CustomizeMenu_NameDisplayOptions"); + static readonly UIHandle ID_CachingOptions = new("CustomizeMenu_CachingOptions"); static readonly UI.ScrollViewDrawElementFunc drawDisplayScrollEntry = DrawDisplayScroll; static readonly Func hexStringInputValidator = ValidateHexStringInput; @@ -78,6 +81,40 @@ public static void DrawMenu() UpdateChipColFromHexString(hexColInput.text); } + // ---- Chip caching UI ---- + UI.DrawText("Chip Caching:", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeDefault, NextPos(1), Anchor.TopLeft, Color.white); + SimChip chip = Project.ActiveProject.ViewedChip.SimChip; + if (chip.IsCombinational()) + { + int numberOfInputBits = chip.CalculateNumberOfInputBits(); + if (numberOfInputBits <= Simulator.MAX_NUM_INPUT_BITS_WHEN_AUTO_CACHING) + { + UI.DrawText("This chip is being cached.", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white); + } + else if (numberOfInputBits <= Simulator.MAX_NUM_INPUT_BITS_WHEN_USER_CACHING) + { + int shouldBeCachedNum = UI.WheelSelector(ID_CachingOptions, cachingOptions, NextPos(), new Vector2(pw, DrawSettings.ButtonHeight), theme.OptionsWheel, Anchor.TopLeft); + bool shouldBeCached = false; + if (shouldBeCachedNum == 1) shouldBeCached = true; + ChipSaveMenu.ActiveCustomizeDescription.ShouldBeCached = shouldBeCached; + UI.DrawText("WARNING: Caching chips with many", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white); + UI.DrawText("input bits significantly", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white); + UI.DrawText("increases the time required to", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white); + UI.DrawText("create the cache and may also", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white); + UI.DrawText("increase memory consumption!", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white); + } + else + { + UI.DrawText("This chip has too many input", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white); + UI.DrawText("bits to be cached.", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white); + } + } + else + { + UI.DrawText("Non-combinational chips", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white); + UI.DrawText("can not be cached.", UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeSmall, NextPos(), Anchor.TopLeft, Color.white); + } + // ---- Displays UI ---- Color labelCol = ColHelper.Darken(theme.MenuPanelCol, 0.01f); Vector2 labelPos = NextPos(1); @@ -151,6 +188,13 @@ static void InitUIFromChipDescription() // Init name display mode WheelSelectorState nameDisplayWheelState = UI.GetWheelSelectorState(ID_NameDisplayOptions); nameDisplayWheelState.index = (int)ChipSaveMenu.ActiveCustomizeDescription.NameLocation; + + // Init cache setting + WheelSelectorState cacheSettingWheelState = UI.GetWheelSelectorState(ID_CachingOptions); + bool cacheBool = ChipSaveMenu.ActiveCustomizeDescription.ShouldBeCached; + int cacheInt = 0; + if (cacheBool) cacheInt = 1; + cacheSettingWheelState.index = cacheInt; } static void UpdateCustomizeDescription() diff --git a/Assets/Scripts/Graphics/UI/Menus/CreateCacheUI.cs b/Assets/Scripts/Graphics/UI/Menus/CreateCacheUI.cs new file mode 100644 index 00000000..5b5c2558 --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/CreateCacheUI.cs @@ -0,0 +1,20 @@ +using DLS.Simulation; +using Seb.Helpers; +using Seb.Vis; +using Seb.Vis.UI; +using UnityEngine; + +namespace DLS.Graphics +{ + public static class CreateCacheUI + { + public static void DrawCreatingCacheInfo() + { + string chipName = Simulator.nameOfChipWhoseCacheIsBeingCreated; + int percentage = (int)(Simulator.cacheCreatingProgress * 100); + string text = $"Creating Cache ({percentage}%): {chipName}"; + Vector2 textSize = UI.CalculateTextSize(text, UIThemeLibrary.FontSizeDefault, UIThemeLibrary.DefaultFont); + UI.TextWithBackground(new Vector2(BottomBarUI.buttonSpacing, BottomBarUI.barHeight + BottomBarUI.buttonSpacing), new Vector2(textSize.x + 1, textSize.y + 1), Anchor.BottomLeft, text, UIThemeLibrary.DefaultFont, UIThemeLibrary.FontSizeDefault, Color.yellow, ColHelper.MakeCol255(40)); + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/Menus/CreateCacheUI.cs.meta b/Assets/Scripts/Graphics/UI/Menus/CreateCacheUI.cs.meta new file mode 100644 index 00000000..0bc6fbe5 --- /dev/null +++ b/Assets/Scripts/Graphics/UI/Menus/CreateCacheUI.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1f1b8d5b37edf744dbb027b01aaba678 \ No newline at end of file diff --git a/Assets/Scripts/Graphics/UI/UIDrawer.cs b/Assets/Scripts/Graphics/UI/UIDrawer.cs index 091248e4..6e02d59e 100644 --- a/Assets/Scripts/Graphics/UI/UIDrawer.cs +++ b/Assets/Scripts/Graphics/UI/UIDrawer.cs @@ -1,4 +1,5 @@ using DLS.Game; +using DLS.Simulation; using Seb.Vis.UI; namespace DLS.Graphics @@ -57,6 +58,7 @@ static void DrawProjectMenus(Project project) if (menuToDraw != MenuType.ChipCustomization) BottomBarUI.DrawUI(project); + bool aMenuIsOpen = true; if (menuToDraw == MenuType.ChipSave) ChipSaveMenu.DrawMenu(); else if (menuToDraw == MenuType.ChipLibrary) ChipLibraryMenu.DrawMenu(); else if (menuToDraw == MenuType.ChipCustomization) ChipCustomizationMenu.DrawMenu(); @@ -73,8 +75,12 @@ static void DrawProjectMenus(Project project) bool showSimPausedBanner = project.simPaused; if (showSimPausedBanner) SimPausedUI.DrawPausedBanner(); if (project.chipViewStack.Count > 1) ViewedChipsBar.DrawViewedChipsBanner(project, showSimPausedBanner); + if (Simulator.isCreatingACache) CreateCacheUI.DrawCreatingCacheInfo(); + aMenuIsOpen = false; } + if (aMenuIsOpen) Simulator.isCreatingACache = false; // Cancel current caching process when a menu gets opened + ContextMenu.Update(); } diff --git a/Assets/Scripts/SaveSystem/DescriptionCreator.cs b/Assets/Scripts/SaveSystem/DescriptionCreator.cs index 196e0ea1..8765f71e 100644 --- a/Assets/Scripts/SaveSystem/DescriptionCreator.cs +++ b/Assets/Scripts/SaveSystem/DescriptionCreator.cs @@ -37,6 +37,7 @@ public static ChipDescription CreateChipDescription(DevChipInstance chip) NameLocation = hasSavedDesc ? descOld.NameLocation : NameDisplayLocation.Centre, Size = size, Colour = col, + ShouldBeCached = hasSavedDesc ? descOld.ShouldBeCached : false, SubChips = subchips, InputPins = inputPins, diff --git a/Assets/Scripts/SaveSystem/Loader.cs b/Assets/Scripts/SaveSystem/Loader.cs index 590a3af1..9e041514 100644 --- a/Assets/Scripts/SaveSystem/Loader.cs +++ b/Assets/Scripts/SaveSystem/Loader.cs @@ -4,6 +4,7 @@ using System.Linq; using DLS.Description; using DLS.Game; +using DLS.Simulation; namespace DLS.SaveSystem { @@ -24,6 +25,10 @@ public static Project LoadProject(string projectName) { ProjectDescription projectDescription = LoadProjectDescription(projectName); ChipLibrary chipLibrary = LoadChipLibrary(projectDescription); + + Simulator.combinationalChipCaches.Clear(); + Simulator.chipsKnowToNotBeCombinational.Clear(); + return new Project(projectDescription, chipLibrary); } diff --git a/Assets/Scripts/Simulation/SimChip.cs b/Assets/Scripts/Simulation/SimChip.cs index 5efec7c5..a5b62a85 100644 --- a/Assets/Scripts/Simulation/SimChip.cs +++ b/Assets/Scripts/Simulation/SimChip.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using DLS.Description; @@ -8,6 +9,7 @@ public class SimChip { public readonly ChipType ChipType; public readonly int ID; + public readonly string Name; // Some builtin chips, such as RAM, require an internal state for memory // (can also be used for other arbitrary chip-specific data) @@ -15,6 +17,7 @@ public class SimChip public readonly bool IsBuiltin; public SimPin[] InputPins = Array.Empty(); public int numConnectedInputs; + public bool shouldBeCached; // True, if the user specifically wanted this chip to be cached public int numInputsReady; public SimPin[] OutputPins = Array.Empty(); @@ -30,7 +33,9 @@ public SimChip(ChipDescription desc, int id, uint[] internalState, SimChip[] sub { SubChips = subChips; ID = id; + Name = desc.Name; ChipType = desc.ChipType; + shouldBeCached = desc.ShouldBeCached; IsBuiltin = ChipType != ChipType.Custom; // ---- Create pins (don't allocate unnecessarily as very many sim chips maybe created!) ---- @@ -85,6 +90,133 @@ public SimChip(ChipDescription desc, int id, uint[] internalState, SimChip[] sub } } + // Returns true, when this chip is purely combinational / stateless. This is the case, when the outputs of this chip depend entirely on the inputs and on nothing else. + public bool IsCombinational() + { + // Handle built in chips + switch (ChipType) + { + case ChipType.Nand: + case ChipType.TriStateBuffer: + case ChipType.Merge_1To4Bit: + case ChipType.Merge_1To8Bit: + case ChipType.Merge_4To8Bit: + case ChipType.Split_4To1Bit: + case ChipType.Split_8To4Bit: + case ChipType.Split_8To1Bit: + return true; + case ChipType.Clock: + case ChipType.Pulse: + case ChipType.dev_Ram_8Bit: + case ChipType.Rom_256x16: + case ChipType.SevenSegmentDisplay: + case ChipType.DisplayRGB: + case ChipType.DisplayDot: + case ChipType.DisplayLED: + case ChipType.Key: + case ChipType.Buzzer: + return false; + } + + // Chip isn't combinational, if any of the subChips inputPins has more than one connection + foreach (SimChip subChip in SubChips) + { + foreach (SimPin inputPin in subChip.InputPins) + { + if (inputPin.numInputConnections > 1) return false; + } + } + + // Can only be combinational if all subchips are combinational + foreach (SimChip subChip in SubChips) + { + // recursively make sure, that subchip is combinational + if (!subChip.IsCombinational()) return false; + } + + // Check for loops in wiring using topo sort + Dictionary> graph = new(); // chipID -> list of dependent chipIDs + Dictionary inDegree = new(); // chipID -> number of incoming edges + // Build Graph + foreach (SimChip chip in SubChips) + { + int chipID = chip.ID; + if (!graph.ContainsKey(chipID)) graph[chipID] = new List(); + if (!inDegree.ContainsKey(chipID)) inDegree[chipID] = 0; + + foreach (SimPin output in chip.OutputPins) + { + foreach (SimPin target in output.ConnectedTargetPins) + { + SimChip targetChip = target.parentChip; + if (targetChip == null || targetChip.ID == chipID) continue; + + // Add edge: chip -> targetChip + if (!graph[chipID].Contains(targetChip.ID)) + { + graph[chipID].Add(targetChip.ID); + + // Update in-degree for topological sort + if (!inDegree.ContainsKey(targetChip.ID)) inDegree[targetChip.ID] = 0; + + inDegree[targetChip.ID]++; + } + } + } + } + // Run topo sort + Queue zeroInDegree = new(); + foreach (var kvp in inDegree) + { + if (kvp.Value == 0) zeroInDegree.Enqueue(kvp.Key); + } + int visitedCount = 0; + while (zeroInDegree.Count > 0) + { + int chipID = zeroInDegree.Dequeue(); + visitedCount++; + + if (!graph.ContainsKey(chipID)) continue; + + foreach (int neighborID in graph[chipID]) + { + inDegree[neighborID]--; + if (inDegree[neighborID] == 0) zeroInDegree.Enqueue(neighborID); + } + } + + // If we couldn't visit all chips, a cycle exists + if (visitedCount != inDegree.Count) return false; + + return true; + } + + public int CalculateNumberOfInputBits() + { + int numberOfBits = 0; + foreach (SimPin pin in InputPins) + { + numberOfBits += (int)pin.numberOfBits; + } + return numberOfBits; + } + + public void ResetReceivedFlagsOnAllPins() + { + foreach(SimPin pin in InputPins) + { + pin.numInputsReceivedThisFrame = 0; + } + foreach (SimPin pin in OutputPins) + { + pin.numInputsReceivedThisFrame = 0; + } + foreach (SimChip subChip in SubChips) + { + subChip.ResetReceivedFlagsOnAllPins(); + } + } + public void UpdateInternalState(uint[] source) => Array.Copy(source, InternalState, InternalState.Length); @@ -218,7 +350,7 @@ public void AddPin(SimPin pin, bool isInput) } } - static SimPin CreateSimPinFromDescription(PinDescription desc, bool isInput, SimChip parent) => new(desc.ID, isInput, parent); + static SimPin CreateSimPinFromDescription(PinDescription desc, bool isInput, SimChip parent) => new(desc.ID, isInput, desc.BitCount, parent); public void RemovePin(int removePinID) { diff --git a/Assets/Scripts/Simulation/SimPin.cs b/Assets/Scripts/Simulation/SimPin.cs index 9528fe5e..20eceb7a 100644 --- a/Assets/Scripts/Simulation/SimPin.cs +++ b/Assets/Scripts/Simulation/SimPin.cs @@ -1,3 +1,4 @@ +using DLS.Description; using System; namespace DLS.Simulation @@ -7,6 +8,7 @@ public class SimPin public readonly int ID; public readonly SimChip parentChip; public readonly bool isInput; + public readonly PinBitCount numberOfBits; public uint State; public SimPin[] ConnectedTargetPins = Array.Empty(); @@ -23,10 +25,11 @@ public class SimPin public int numInputConnections; public int numInputsReceivedThisFrame; - public SimPin(int id, bool isInput, SimChip parentChip) + public SimPin(int id, bool isInput, PinBitCount numberOfBits, SimChip parentChip) { this.parentChip = parentChip; this.isInput = isInput; + this.numberOfBits = numberOfBits; ID = id; latestSourceID = -1; latestSourceParentChipID = -1; diff --git a/Assets/Scripts/Simulation/Simulator.cs b/Assets/Scripts/Simulation/Simulator.cs index 1d777628..3d458496 100644 --- a/Assets/Scripts/Simulation/Simulator.cs +++ b/Assets/Scripts/Simulation/Simulator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using DLS.Description; using DLS.Game; @@ -9,6 +10,22 @@ namespace DLS.Simulation { public static class Simulator { + // Constants, for when a chip should be cached. If a chip is purely combinational and has at most AUTO_CACHING number of input bits, it will always be cached. + // Otherwise, a user can specifie a chip to be cached, if the chip is combinational and has at most USER_CACHING number of input bits. + // If a chip has more than USER_CACHING input bits, it will never be cached. (This is, because memory requirements grow exponentially with number of input bits.) + public const int MAX_NUM_INPUT_BITS_WHEN_AUTO_CACHING = 12; + public const int MAX_NUM_INPUT_BITS_WHEN_USER_CACHING = 24; + + // Small, purely combinational chips use a LUT for fast calculations. These are stored here. Maps the name of a chip to its LUT. + public static readonly Dictionary combinationalChipCaches = new(); + public static readonly HashSet chipsKnowToNotBeCombinational = new(); + public static bool useCaching = true; + + // Variables for the creating cache info popup + public static bool isCreatingACache = false; + public static string nameOfChipWhoseCacheIsBeingCreated; + public static float cacheCreatingProgress; + public static readonly Random rng = new(); static readonly Stopwatch stopwatch = Stopwatch.StartNew(); public static int stepsPerClockTransition; @@ -75,7 +92,7 @@ public static void RunSimulationStep(SimChip rootSimChip, DevPinInstance[] input // Possible for sim to be temporarily out of sync since running on separate threads, so just ignore failure to find pin. } } - + // Process if (needsOrderPass) { @@ -133,8 +150,24 @@ static void StepChip(SimChip chip) } } - if (nextSubChip.IsBuiltin) ProcessBuiltinChip(nextSubChip); // We've reached a built-in chip, so process it directly - else StepChip(nextSubChip); // Recursively process custom chip + if (nextSubChip.IsBuiltin) + { + ProcessBuiltinChip(nextSubChip); // We've reached a built-in chip, so process it directly + } + else if (combinationalChipCaches.ContainsKey(nextSubChip.Name) && useCaching) + { + bool wasSuccessful = ProcessCachedChip(nextSubChip); // We found a cached chip, so use LUT to process it directly + if (!wasSuccessful) StepChip(nextSubChip); // Fallback to normal simulation, if lookup failed + } + else if (!chipsKnowToNotBeCombinational.Contains(nextSubChip.Name) && useCaching) + { + RecalculateCachedLUTs(nextSubChip); // We found a chip that isn't cached but might be cachable, so we try to cache it + StepChip(nextSubChip); + } + else + { + StepChip(nextSubChip); // Recursively process custom chip + } // Step 3) Forward the outputs of the processed subchip to connected pins nextSubChip.Sim_PropagateOutputs(); @@ -171,6 +204,79 @@ static void StepChipReorder(SimChip chip) } } + // Recalculates the caches of this chip and and all of its subChips. + static void RecalculateCachedLUTs(SimChip chip) + { + // Skip this chip, if its cache status is already known + if (combinationalChipCaches.ContainsKey(chip.Name) || chipsKnowToNotBeCombinational.Contains(chip.Name)) return; + + // Recalculate caches for the subChips of the passed chip recursively + foreach (SimChip subChip in chip.SubChips) + { + RecalculateCachedLUTs(subChip); + } + + // Don't cache this chip, if it isn't cachable + if (chip.ChipType != ChipType.Custom + || (!chip.shouldBeCached && chip.CalculateNumberOfInputBits() > MAX_NUM_INPUT_BITS_WHEN_AUTO_CACHING) + || !chip.IsCombinational()) + { + chipsKnowToNotBeCombinational.Add(chip.Name); + return; + } + + nameOfChipWhoseCacheIsBeingCreated = chip.Name; + cacheCreatingProgress = 0; + isCreatingACache = true; + + // Buffer current Input + uint[] bufferedInput = new uint[chip.InputPins.Length]; + for (int i = 0; i < bufferedInput.Length; i++) + { + bufferedInput[i] = chip.InputPins[i].State; + } + + // Cache this chip + int numberOfPossibleInputs = 1 << chip.CalculateNumberOfInputBits(); + uint[][] LUT = new uint[numberOfPossibleInputs][]; + for (int input = 0; input < numberOfPossibleInputs; input++) + { + chip.ResetReceivedFlagsOnAllPins(); // Make sure the chip only recieves our new input + // Set all inputPins to their part of the input + int tempInput = input; + for (int i = 0; i < chip.InputPins.Length; i++) + { + uint mask = ((uint)1 << (int)chip.InputPins[i].numberOfBits) - 1; + chip.InputPins[i].State = (uint)(tempInput & mask); + tempInput >>= (int)chip.InputPins[i].numberOfBits; + } + StepChip(chip); // Calculate Result + + // Store output into cache + int numberOfOutputPins = chip.OutputPins.Length; + uint[] outputs = new uint[numberOfOutputPins]; + for (int i = 0; i < numberOfOutputPins; i++) + { + outputs[i] = chip.OutputPins[i].State; + } + LUT[input] = outputs; + + cacheCreatingProgress = (float)input / numberOfPossibleInputs; + if (!isCreatingACache) return; // Cancel the caching, if something ordered the caching to be stopped + } + combinationalChipCaches[chip.Name] = LUT; + + // Reload buffered Input + chip.ResetReceivedFlagsOnAllPins(); // Make sure the chip only recieves our new input + for (int i = 0; i < bufferedInput.Length; i++) + { + chip.InputPins[i].State = bufferedInput[i]; + } + StepChip(chip); // make sure the outputs are also correct again + + isCreatingACache = false; + } + static int ChooseNextSubChip(SimChip[] subChips, int num) { bool noSubChipsReady = true; @@ -223,6 +329,25 @@ public static bool RandomBool() return result < uint.MaxValue / 2; } + // Sets the output pins by using an LUT. Returns true if successful, false otherwise. + static bool ProcessCachedChip(SimChip chip) + { + int input = 0; + for (int i = chip.InputPins.Length - 1; i >= 0; i--) + { + if (chip.InputPins[i].State >> 16 != 0) return false; // Fails if at least one input is in TriState (as these are not cached) + input <<= (int)chip.InputPins[i].numberOfBits; + input |= (int)chip.InputPins[i].State; + } + uint[][] LUT = combinationalChipCaches[chip.Name]; + uint[] outputs = LUT[input]; + for (int i = 0; i < outputs.Length; i++) + { + chip.OutputPins[i].State = outputs[i]; + } + return true; + } + static void ProcessBuiltinChip(SimChip chip) { switch (chip.ChipType) @@ -557,13 +682,13 @@ static SimChip BuildSimChipRecursive(ChipDescription chipDesc, ChipLibrary libra return simChip; } - public static void AddPin(SimChip simChip, int pinID, bool isInputPin) + public static void AddPin(SimChip simChip, int pinID, bool isInputPin, PinBitCount numberOfBits) { SimModifyCommand command = new() { type = SimModifyCommand.ModificationType.AddPin, modifyTarget = simChip, - simPinToAdd = new SimPin(pinID, isInputPin, simChip), + simPinToAdd = new SimPin(pinID, isInputPin, numberOfBits, simChip), pinIsInputPin = isInputPin }; modificationQueue.Enqueue(command);