Add XML docs

This commit is contained in:
Stone_Red
2026-03-26 13:34:05 +01:00
parent c40195d0a3
commit 057802c43d
7 changed files with 413 additions and 1 deletions
@@ -1,5 +1,9 @@
namespace StoneRed.LogicSimulator.Simulation; namespace StoneRed.LogicSimulator.Simulation;
/// <summary>
/// Defines a reusable circuit component that can be registered as a macro gate.
/// Circuit definitions specify gates, connections, input pins, output pins, and nested macro instances.
/// </summary>
public sealed class CircuitDefinition public sealed class CircuitDefinition
{ {
private readonly List<GateKind> gateKinds = []; private readonly List<GateKind> gateKinds = [];
@@ -8,14 +12,44 @@ public sealed class CircuitDefinition
private readonly List<int> outputPins = []; private readonly List<int> outputPins = [];
private readonly List<MacroInstanceDef> macroInstances = []; private readonly List<MacroInstanceDef> macroInstances = [];
/// <summary>
/// Gets the read-only list of gate types in this circuit definition.
/// </summary>
public IReadOnlyList<GateKind> GateKinds => gateKinds; public IReadOnlyList<GateKind> GateKinds => gateKinds;
/// <summary>
/// Gets the read-only list of connections between gates.
/// </summary>
public IReadOnlyList<(int FromGate, int ToGate, byte ToInputBit)> Connections => connections; public IReadOnlyList<(int FromGate, int ToGate, byte ToInputBit)> Connections => connections;
/// <summary>
/// Gets the read-only list of gate IDs designated as input pins.
/// </summary>
public IReadOnlyList<int> InputPins => inputPins; public IReadOnlyList<int> InputPins => inputPins;
/// <summary>
/// Gets the read-only list of gate IDs designated as output pins.
/// </summary>
public IReadOnlyList<int> OutputPins => outputPins; public IReadOnlyList<int> OutputPins => outputPins;
/// <summary>
/// Gets the read-only list of nested macro instances within this definition.
/// </summary>
public IReadOnlyList<MacroInstanceDef> MacroInstances => macroInstances; public IReadOnlyList<MacroInstanceDef> MacroInstances => macroInstances;
/// <summary>
/// Represents a nested macro gate instance within a circuit definition.
/// </summary>
/// <param name="Name">The name of the macro gate to instantiate.</param>
/// <param name="Inputs">Array of gate IDs representing the input connections.</param>
/// <param name="Outputs">Array of gate IDs representing the output connections.</param>
public sealed record MacroInstanceDef(string Name, int[] Inputs, int[] Outputs); public sealed record MacroInstanceDef(string Name, int[] Inputs, int[] Outputs);
/// <summary>
/// Adds a logic gate to the circuit definition.
/// </summary>
/// <param name="kind">The type of gate to add.</param>
/// <returns>The gate ID assigned to the newly created gate.</returns>
public int AddGate(GateKind kind) public int AddGate(GateKind kind)
{ {
int id = gateKinds.Count; int id = gateKinds.Count;
@@ -23,6 +57,10 @@ public sealed class CircuitDefinition
return id; return id;
} }
/// <summary>
/// Adds a Source gate and marks it as an input pin of this circuit.
/// </summary>
/// <returns>The gate ID of the newly created input pin.</returns>
public int AddInputPin() public int AddInputPin()
{ {
int id = AddGate(GateKind.Source); int id = AddGate(GateKind.Source);
@@ -30,6 +68,10 @@ public sealed class CircuitDefinition
return id; return id;
} }
/// <summary>
/// Adds a Sink gate and marks it as an output pin of this circuit.
/// </summary>
/// <returns>The gate ID of the newly created output pin.</returns>
public int AddOutputPin() public int AddOutputPin()
{ {
int id = AddGate(GateKind.Sink); int id = AddGate(GateKind.Sink);
@@ -37,6 +79,16 @@ public sealed class CircuitDefinition
return id; return id;
} }
/// <summary>
/// Adds a nested macro gate instance to the circuit definition.
/// Creates Buffer gates for inputs and Sink gates for outputs.
/// </summary>
/// <param name="name">The name of the macro gate to instantiate.</param>
/// <param name="inputCount">The number of input pins the macro requires.</param>
/// <param name="outputCount">The number of output pins the macro provides.</param>
/// <returns>A <see cref="MacroInstanceDef"/> containing the gate IDs for the instance's pins.</returns>
/// <exception cref="ArgumentException">Thrown when name is null or whitespace.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when inputCount or outputCount is negative.</exception>
public MacroInstanceDef AddMacroInstance(string name, int inputCount, int outputCount) public MacroInstanceDef AddMacroInstance(string name, int inputCount, int outputCount)
{ {
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(name))
@@ -65,6 +117,13 @@ public sealed class CircuitDefinition
return instance; return instance;
} }
/// <summary>
/// Connects the output of one gate to the input of another gate.
/// </summary>
/// <param name="fromGate">The gate ID whose output will be connected.</param>
/// <param name="toGate">The gate ID that will receive the signal.</param>
/// <param name="toInputBit">The input bit position (0-31) on the destination gate.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when gate IDs are invalid or toInputBit is not between 0 and 31.</exception>
public void Connect(int fromGate, int toGate, int toInputBit) public void Connect(int fromGate, int toGate, int toInputBit)
{ {
if ((uint)fromGate >= (uint)gateKinds.Count) if ((uint)fromGate >= (uint)gateKinds.Count)
@@ -85,6 +144,11 @@ public sealed class CircuitDefinition
connections.Add((fromGate, toGate, (byte)toInputBit)); connections.Add((fromGate, toGate, (byte)toInputBit));
} }
/// <summary>
/// Validates the circuit definition for correctness.
/// Ensures proper gate types, valid connections, and no structural errors.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when the circuit definition contains invalid structure or gate usage.</exception>
public void Validate() public void Validate()
{ {
bool[] hasIncoming = new bool[gateKinds.Count]; bool[] hasIncoming = new bool[gateKinds.Count];
@@ -2,12 +2,25 @@ using System.Linq.Expressions;
namespace StoneRed.LogicSimulator.Simulation; namespace StoneRed.LogicSimulator.Simulation;
/// <summary>
/// A synchronous cycle-based circuit simulator that evaluates all gates every cycle.
/// Provides deterministic timing and predictable evaluation order (gate 0 to N).
/// </summary>
/// <remarks>
/// This simulator evaluates all gates synchronously each step, making it simpler and more
/// predictable than <see cref="EventCircuitSimulator"/>. It's ideal for synchronous digital
/// designs where deterministic timing is important. Less efficient for sparse circuits
/// but has uniform performance characteristics.
/// </remarks>
public sealed class CycleCircuitSimulator : SimulatorBase public sealed class CycleCircuitSimulator : SimulatorBase
{ {
private int[] nextInputMasks = []; private int[] nextInputMasks = [];
private Action<int[], int[], int[]> computeOutputs = (_, _, _) => { }; private Action<int[], int[], int[]> computeOutputs = (_, _, _) => { };
private int[] previousOutputMasks = []; private int[] previousOutputMasks = [];
/// <summary>
/// Ensures internal storage arrays are properly sized for the current gate count.
/// </summary>
protected override void EnsureStorage() protected override void EnsureStorage()
{ {
base.EnsureStorage(); base.EnsureStorage();
@@ -18,6 +31,9 @@ public sealed class CycleCircuitSimulator : SimulatorBase
} }
} }
/// <summary>
/// Resets the circuit to initial state, clearing all input and output buffers.
/// </summary>
public override void Reset() public override void Reset()
{ {
base.Reset(); base.Reset();
@@ -25,6 +41,10 @@ public sealed class CycleCircuitSimulator : SimulatorBase
Array.Clear(previousOutputMasks); Array.Clear(previousOutputMasks);
} }
/// <summary>
/// Executes one simulation cycle by evaluating all gates synchronously,
/// then propagating outputs to inputs for the next cycle.
/// </summary>
public override void Step() public override void Step()
{ {
EnsureCompiled(); EnsureCompiled();
@@ -90,6 +110,12 @@ public sealed class CycleCircuitSimulator : SimulatorBase
return changed; return changed;
} }
/// <summary>
/// Runs the simulation until inputs stabilize (no changes between cycles) or maxSteps is reached.
/// </summary>
/// <param name="maxSteps">Maximum number of cycles to execute.</param>
/// <param name="steps">Output parameter containing the number of cycles executed.</param>
/// <returns>True if the circuit stabilized; false if maxSteps was exceeded.</returns>
public override bool TryRunUntilStable(int maxSteps, out int steps) public override bool TryRunUntilStable(int maxSteps, out int steps)
{ {
steps = 0; steps = 0;
@@ -119,6 +145,10 @@ public sealed class CycleCircuitSimulator : SimulatorBase
return !changed; return !changed;
} }
/// <summary>
/// Compiles a single evaluator that computes outputs for all gates in one call.
/// All gate logic is combined into a single compiled lambda expression.
/// </summary>
protected override void CompileEngine() protected override void CompileEngine()
{ {
ParameterExpression inputsParam = Expression.Parameter(typeof(int[]), "inputs"); ParameterExpression inputsParam = Expression.Parameter(typeof(int[]), "inputs");
@@ -139,6 +169,10 @@ public sealed class CycleCircuitSimulator : SimulatorBase
computeOutputs = Expression.Lambda<Action<int[], int[], int[]>>(Expression.Block(body), inputsParam, outputsParam, sourcesParam).Compile(); computeOutputs = Expression.Lambda<Action<int[], int[], int[]>>(Expression.Block(body), inputsParam, outputsParam, sourcesParam).Compile();
} }
/// <summary>
/// Creates a new instance of CycleCircuitSimulator for internal use (e.g., LUT computation).
/// </summary>
/// <returns>A new CycleCircuitSimulator instance.</returns>
protected override SimulatorBase CreateInternalSimulator() protected override SimulatorBase CreateInternalSimulator()
{ {
return new CycleCircuitSimulator(); return new CycleCircuitSimulator();
@@ -2,12 +2,25 @@ using System.Linq.Expressions;
namespace StoneRed.LogicSimulator.Simulation; namespace StoneRed.LogicSimulator.Simulation;
/// <summary>
/// An event-driven circuit simulator that uses queue-based change propagation.
/// Only gates with changed inputs are evaluated, making it efficient for circuits with localized activity.
/// </summary>
/// <remarks>
/// This simulator uses a dirty-marking propagation queue. When a gate's output changes,
/// only the gates connected to it are queued for re-evaluation. This is more efficient
/// than <see cref="CycleCircuitSimulator"/> for sparse circuits where most gates remain stable.
/// </remarks>
public sealed class EventCircuitSimulator : SimulatorBase public sealed class EventCircuitSimulator : SimulatorBase
{ {
private Action<int[], int[], int[], int>[] gateEvaluators = []; private Action<int[], int[], int[], int>[] gateEvaluators = [];
private readonly Queue<int> activeQueue = new(); private readonly Queue<int> activeQueue = new();
private bool[] inQueue = []; private bool[] inQueue = [];
/// <summary>
/// Called when a gate is added to ensure internal arrays are properly sized.
/// </summary>
/// <param name="gateId">The ID of the newly added gate.</param>
protected override void OnGateAdded(int gateId) protected override void OnGateAdded(int gateId)
{ {
if (inQueue.Length != gateKinds.Count) if (inQueue.Length != gateKinds.Count)
@@ -16,6 +29,9 @@ public sealed class EventCircuitSimulator : SimulatorBase
} }
} }
/// <summary>
/// Resets the circuit to initial state and queues all gates for initial evaluation.
/// </summary>
public override void Reset() public override void Reset()
{ {
base.Reset(); base.Reset();
@@ -27,6 +43,10 @@ public sealed class EventCircuitSimulator : SimulatorBase
} }
} }
/// <summary>
/// Called when a source gate's value changes. Queues the source for propagation.
/// </summary>
/// <param name="gateId">The ID of the source gate that changed.</param>
protected override void OnSourceChanged(int gateId) protected override void OnSourceChanged(int gateId)
{ {
Enqueue(gateId); Enqueue(gateId);
@@ -41,6 +61,10 @@ public sealed class EventCircuitSimulator : SimulatorBase
} }
} }
/// <summary>
/// Executes one simulation step by processing all pending changes in the queue.
/// Continues until the queue is empty (all changes have propagated).
/// </summary>
public override void Step() public override void Step()
{ {
EnsureCompiled(); EnsureCompiled();
@@ -84,6 +108,12 @@ public sealed class EventCircuitSimulator : SimulatorBase
} }
} }
/// <summary>
/// Runs the simulation until the queue is empty (circuit is stable) or maxSteps is reached.
/// </summary>
/// <param name="maxSteps">Maximum number of steps to execute.</param>
/// <param name="steps">Output parameter containing the number of steps executed.</param>
/// <returns>True if the circuit stabilized (queue is empty); false if maxSteps was exceeded.</returns>
public override bool TryRunUntilStable(int maxSteps, out int steps) public override bool TryRunUntilStable(int maxSteps, out int steps)
{ {
steps = 0; steps = 0;
@@ -106,6 +136,10 @@ public sealed class EventCircuitSimulator : SimulatorBase
return activeQueue.Count == 0; return activeQueue.Count == 0;
} }
/// <summary>
/// Compiles per-gate evaluators as separate compiled lambda expressions.
/// Each gate has its own evaluator function for efficient event-driven execution.
/// </summary>
protected override void CompileEngine() protected override void CompileEngine()
{ {
gateEvaluators = new Action<int[], int[], int[], int>[gateKinds.Count]; gateEvaluators = new Action<int[], int[], int[], int>[gateKinds.Count];
@@ -126,11 +160,18 @@ public sealed class EventCircuitSimulator : SimulatorBase
} }
} }
/// <summary>
/// Creates a new instance of EventCircuitSimulator for internal use (e.g., LUT computation).
/// </summary>
/// <returns>A new EventCircuitSimulator instance.</returns>
protected override SimulatorBase CreateInternalSimulator() protected override SimulatorBase CreateInternalSimulator()
{ {
return new EventCircuitSimulator(); return new EventCircuitSimulator();
} }
/// <summary>
/// Ensures internal storage arrays are properly sized for the current gate count.
/// </summary>
protected override void EnsureStorage() protected override void EnsureStorage()
{ {
base.EnsureStorage(); base.EnsureStorage();
@@ -1,12 +1,46 @@
namespace StoneRed.LogicSimulator.Simulation; namespace StoneRed.LogicSimulator.Simulation;
/// <summary>
/// Defines the types of logic gates available in the circuit simulator.
/// </summary>
public enum GateKind : byte public enum GateKind : byte
{ {
/// <summary>
/// An input gate that can have its value set externally via <see cref="ICircuitSimulator.SetSource"/>.
/// Used as circuit inputs or constant values.
/// </summary>
Source, Source,
/// <summary>
/// A NOT gate that inverts its input (0 → 1, 1 → 0).
/// </summary>
Not, Not,
/// <summary>
/// A 2-input AND gate. Output is 1 only when both inputs are 1.
/// </summary>
And2, And2,
/// <summary>
/// A 2-input OR gate. Output is 1 when at least one input is 1.
/// </summary>
Or2, Or2,
/// <summary>
/// A buffer gate that passes its input directly to the output.
/// Used for signal routing or as macro gate input pins.
/// </summary>
Buffer, Buffer,
/// <summary>
/// A sink gate that receives input but produces no useful output.
/// Used as circuit outputs or macro gate output pins.
/// </summary>
Sink, Sink,
/// <summary>
/// A Look-Up Table gate implementing arbitrary combinational logic via a truth table.
/// Created using <see cref="ICircuitSimulator.AddLutGate"/> instead of <see cref="ICircuitSimulator.AddGate"/>.
/// </summary>
Lut, Lut,
} }
@@ -1,16 +1,69 @@
namespace StoneRed.LogicSimulator.Simulation; namespace StoneRed.LogicSimulator.Simulation;
/// <summary> /// <summary>
/// Represents a digital logic simulator. /// Represents a digital logic circuit simulator that supports combinational logic gates,
/// look-up tables (LUTs), and hierarchical macro gates.
/// </summary> /// </summary>
public interface ICircuitSimulator public interface ICircuitSimulator
{ {
/// <summary>
/// Gets the total number of gates in the circuit.
/// </summary>
int GateCount { get; } int GateCount { get; }
/// <summary>
/// Adds a logic gate to the circuit.
/// </summary>
/// <param name="kind">The type of gate to add (NOT, AND, OR, etc.).</param>
/// <returns>The unique identifier of the newly created gate.</returns>
/// <exception cref="InvalidOperationException">Thrown when attempting to add a LUT gate using this method. Use <see cref="AddLutGate"/> instead.</exception>
int AddGate(GateKind kind); int AddGate(GateKind kind);
/// <summary>
/// Adds a Look-Up Table (LUT) gate that implements arbitrary combinational logic.
/// </summary>
/// <param name="inputCount">The number of inputs (0-30).</param>
/// <param name="table">The truth table array. Length must be 2^inputCount. Each element is the output (0 or 1) for the corresponding input pattern.</param>
/// <returns>The unique identifier of the newly created LUT gate.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when inputCount is not between 0 and 30.</exception>
/// <exception cref="ArgumentNullException">Thrown when table is null.</exception>
/// <exception cref="ArgumentException">Thrown when table length doesn't match 2^inputCount.</exception>
int AddLutGate(int inputCount, int[] table); int AddLutGate(int inputCount, int[] table);
/// <summary>
/// Connects the output of one gate to the input of another gate.
/// A single output can be connected to multiple inputs (fan-out).
/// </summary>
/// <param name="fromGate">The gate ID whose output will be connected.</param>
/// <param name="toGate">The gate ID that will receive the signal.</param>
/// <param name="toInputBit">The input bit position (0-31) on the destination gate.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when gate IDs are invalid or toInputBit is not between 0 and 31.</exception>
void ConnectGates(int fromGate, int toGate, int toInputBit); void ConnectGates(int fromGate, int toGate, int toInputBit);
/// <summary>
/// Registers a reusable circuit definition as a macro gate that can be instantiated multiple times.
/// </summary>
/// <param name="name">The unique name for this macro gate.</param>
/// <param name="definition">The circuit definition containing gates and connections.</param>
void RegisterMacroGate(string name, CircuitDefinition definition); void RegisterMacroGate(string name, CircuitDefinition definition);
/// <summary>
/// Computes and caches a Look-Up Table representation of a registered macro gate for optimization.
/// This converts the macro's combinational logic into a truth table for faster simulation.
/// </summary>
/// <param name="name">The name of the registered macro gate.</param>
/// <param name="maxSteps">Maximum simulation steps allowed to compute each output pattern. Default is 4096.</param>
/// <returns>True if the LUT was successfully computed; false if the circuit didn't stabilize within maxSteps.</returns>
/// <exception cref="KeyNotFoundException">Thrown when the macro name is not registered.</exception>
bool ComputeLut(string name, int maxSteps = 4096); bool ComputeLut(string name, int maxSteps = 4096);
/// <summary>
/// Adds an instance of a registered macro gate to the circuit.
/// If a LUT has been computed for this macro, the optimized version is used.
/// </summary>
/// <param name="name">The name of the registered macro gate.</param>
/// <returns>A <see cref="MacroInstance"/> containing the gate IDs of the instance's inputs and outputs.</returns>
/// <exception cref="KeyNotFoundException">Thrown when the macro name is not registered.</exception>
MacroInstance AddMacroGate(string name); MacroInstance AddMacroGate(string name);
/// <summary> /// <summary>
@@ -20,10 +73,54 @@ public interface ICircuitSimulator
/// </summary> /// </summary>
void Reset(); void Reset();
/// <summary>
/// Sets the input signal value on a Source gate.
/// Only gates of type <see cref="GateKind.Source"/> can have their values set.
/// </summary>
/// <param name="gateId">The gate ID of the source gate.</param>
/// <param name="value">The boolean value to set (true = 1, false = 0).</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when gateId is invalid.</exception>
/// <exception cref="InvalidOperationException">Thrown when the gate is not a Source gate.</exception>
void SetSource(int gateId, bool value); void SetSource(int gateId, bool value);
/// <summary>
/// Reads the current output signal of a gate.
/// </summary>
/// <param name="gateId">The gate ID to read from.</param>
/// <returns>True if the output is 1, false if 0.</returns>
bool GetOutput(int gateId); bool GetOutput(int gateId);
/// <summary>
/// Executes one simulation step. The behavior depends on the implementation:
/// - EventCircuitSimulator: Processes all pending changes in the propagation queue.
/// - CycleCircuitSimulator: Evaluates all gates once synchronously.
/// </summary>
void Step(); void Step();
/// <summary>
/// Runs the simulation until all signals stabilize (no more changes occur).
/// </summary>
/// <param name="maxSteps">Maximum number of steps to execute before timing out. Default is 1024.</param>
/// <returns>The number of steps executed before stabilization.</returns>
/// <exception cref="InvalidOperationException">Thrown when the circuit does not stabilize within maxSteps.</exception>
int RunUntilStable(int maxSteps = 1024); int RunUntilStable(int maxSteps = 1024);
/// <summary>
/// Attempts to run the simulation until all signals stabilize.
/// </summary>
/// <param name="maxSteps">Maximum number of steps to execute.</param>
/// <param name="steps">Output parameter containing the number of steps executed.</param>
/// <returns>True if the circuit stabilized; false if maxSteps was exceeded.</returns>
bool TryRunUntilStable(int maxSteps, out int steps); bool TryRunUntilStable(int maxSteps, out int steps);
/// <summary>
/// Subscribes to output changes on a specific gate.
/// The callback is invoked whenever the gate's output value changes.
/// </summary>
/// <param name="gateId">The gate ID to watch.</param>
/// <param name="callback">Action to invoke on change. Parameters are (gateId, newOutputMask).</param>
/// <returns>An <see cref="IDisposable"/> that unsubscribes the watcher when disposed.</returns>
/// <exception cref="ArgumentNullException">Thrown when callback is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown when gateId is invalid.</exception>
IDisposable WatchGate(int gateId, Action<int, int> callback); IDisposable WatchGate(int gateId, Action<int, int> callback);
} }
@@ -1,3 +1,10 @@
namespace StoneRed.LogicSimulator.Simulation; namespace StoneRed.LogicSimulator.Simulation;
/// <summary>
/// Represents an instance of a macro gate added to the circuit.
/// Contains the gate IDs of the instance's input and output pins.
/// </summary>
/// <param name="Name">The name of the macro gate definition.</param>
/// <param name="Inputs">Array of gate IDs representing the input pins of this instance.</param>
/// <param name="Outputs">Array of gate IDs representing the output pins of this instance.</param>
public sealed record MacroInstance(string Name, int[] Inputs, int[] Outputs); public sealed record MacroInstance(string Name, int[] Inputs, int[] Outputs);
@@ -2,6 +2,10 @@ using System.Linq.Expressions;
namespace StoneRed.LogicSimulator.Simulation; namespace StoneRed.LogicSimulator.Simulation;
/// <summary>
/// Abstract base class providing common functionality for circuit simulator implementations.
/// Handles gate storage, connections, macro gates, LUT compilation, and gate watching.
/// </summary>
public abstract class SimulatorBase : ICircuitSimulator public abstract class SimulatorBase : ICircuitSimulator
{ {
protected readonly List<GateKind> gateKinds = []; protected readonly List<GateKind> gateKinds = [];
@@ -31,12 +35,33 @@ public abstract class SimulatorBase : ICircuitSimulator
protected bool hasAnyWatchers; protected bool hasAnyWatchers;
protected int nextWatcherId; protected int nextWatcherId;
/// <summary>
/// Internal record representing a gate watcher subscription.
/// </summary>
/// <param name="Id">Unique identifier for this watcher.</param>
/// <param name="GateId">The gate being watched.</param>
/// <param name="Callback">The callback to invoke on changes.</param>
protected sealed record GateWatcherEntry(int Id, int GateId, Action<int, int> Callback); protected sealed record GateWatcherEntry(int Id, int GateId, Action<int, int> Callback);
/// <summary>
/// Internal record representing a compiled LUT for a macro gate.
/// </summary>
/// <param name="InputCount">Number of input pins.</param>
/// <param name="OutputCount">Number of output pins.</param>
/// <param name="OutputTables">Truth tables for each output (indexed by input pattern).</param>
protected sealed record MacroLut(int InputCount, int OutputCount, int[][] OutputTables); protected sealed record MacroLut(int InputCount, int OutputCount, int[][] OutputTables);
/// <summary>
/// Internal record storing macro gate definition and optional compiled LUT.
/// </summary>
/// <param name="Definition">The circuit definition of the macro.</param>
/// <param name="Lut">Optional compiled LUT representation for optimization.</param>
protected sealed record MacroInfo(CircuitDefinition Definition, MacroLut? Lut); protected sealed record MacroInfo(CircuitDefinition Definition, MacroLut? Lut);
/// <inheritdoc/>
public int GateCount => gateKinds.Count; public int GateCount => gateKinds.Count;
/// <inheritdoc/>
public int AddGate(GateKind kind) public int AddGate(GateKind kind)
{ {
if (kind == GateKind.Lut) if (kind == GateKind.Lut)
@@ -53,8 +78,14 @@ public abstract class SimulatorBase : ICircuitSimulator
return id; return id;
} }
/// <summary>
/// Called when a gate is added. Override to perform implementation-specific initialization.
/// </summary>
/// <param name="gateId">The ID of the newly added gate.</param>
protected virtual void OnGateAdded(int gateId) { } protected virtual void OnGateAdded(int gateId) { }
/// <inheritdoc/>
/// <inheritdoc/>
public int AddLutGate(int inputCount, int[] table) public int AddLutGate(int inputCount, int[] table)
{ {
if (inputCount is < 0 or > 30) if (inputCount is < 0 or > 30)
@@ -77,6 +108,7 @@ public abstract class SimulatorBase : ICircuitSimulator
return id; return id;
} }
/// <inheritdoc/>
public void ConnectGates(int fromGate, int toGate, int toInputBit) public void ConnectGates(int fromGate, int toGate, int toInputBit)
{ {
if ((uint)fromGate >= (uint)gateKinds.Count) if ((uint)fromGate >= (uint)gateKinds.Count)
@@ -99,6 +131,7 @@ public abstract class SimulatorBase : ICircuitSimulator
initialized = false; initialized = false;
} }
/// <inheritdoc/>
public void RegisterMacroGate(string name, CircuitDefinition definition) public void RegisterMacroGate(string name, CircuitDefinition definition)
{ {
definition.Validate(); definition.Validate();
@@ -107,6 +140,7 @@ public abstract class SimulatorBase : ICircuitSimulator
initialized = false; initialized = false;
} }
/// <inheritdoc/>
public virtual void Reset() public virtual void Reset()
{ {
EnsureStorage(); EnsureStorage();
@@ -116,9 +150,13 @@ public abstract class SimulatorBase : ICircuitSimulator
initialized = true; initialized = true;
} }
/// <inheritdoc/>
public abstract void Step(); public abstract void Step();
/// <inheritdoc/>
public abstract bool TryRunUntilStable(int maxSteps, out int steps); public abstract bool TryRunUntilStable(int maxSteps, out int steps);
/// <inheritdoc/>
public int RunUntilStable(int maxSteps = 1024) public int RunUntilStable(int maxSteps = 1024)
{ {
if (!TryRunUntilStable(maxSteps, out int steps)) if (!TryRunUntilStable(maxSteps, out int steps))
@@ -128,6 +166,7 @@ public abstract class SimulatorBase : ICircuitSimulator
return steps; return steps;
} }
/// <inheritdoc/>
public virtual void SetSource(int gateId, bool value) public virtual void SetSource(int gateId, bool value)
{ {
EnsureStorage(); EnsureStorage();
@@ -150,14 +189,21 @@ public abstract class SimulatorBase : ICircuitSimulator
} }
} }
/// <summary>
/// Called when a source gate's value changes. Override to perform implementation-specific handling.
/// </summary>
/// <param name="gateId">The ID of the source gate that changed.</param>
protected virtual void OnSourceChanged(int gateId) { } protected virtual void OnSourceChanged(int gateId) { }
/// <inheritdoc/>
public bool GetOutput(int gateId) public bool GetOutput(int gateId)
{ {
EnsureStorage(); EnsureStorage();
return (outputMasks[gateId] & 1) != 0; return (outputMasks[gateId] & 1) != 0;
} }
/// <inheritdoc/>
/// <inheritdoc/>
public IDisposable WatchGate(int gateId, Action<int, int> callback) public IDisposable WatchGate(int gateId, Action<int, int> callback)
{ {
ArgumentNullException.ThrowIfNull(callback); ArgumentNullException.ThrowIfNull(callback);
@@ -199,6 +245,10 @@ public abstract class SimulatorBase : ICircuitSimulator
hasAnyWatchers = allWatchers.Count > 0; hasAnyWatchers = allWatchers.Count > 0;
} }
/// <summary>
/// Notifies all registered watchers of gates that changed between the previous and current output states.
/// </summary>
/// <param name="previousOutputMasks">The output states from before the change.</param>
protected void NotifyAllWatchers(int[] previousOutputMasks) protected void NotifyAllWatchers(int[] previousOutputMasks)
{ {
for (int i = 0; i < gatesWithWatchers.Length; i++) for (int i = 0; i < gatesWithWatchers.Length; i++)
@@ -216,6 +266,10 @@ public abstract class SimulatorBase : ICircuitSimulator
} }
} }
/// <summary>
/// Notifies watchers of a specific gate that its output has changed.
/// </summary>
/// <param name="gateId">The ID of the gate that changed.</param>
protected void NotifyGateWatchers(int gateId) protected void NotifyGateWatchers(int gateId)
{ {
if (gateId >= watcherCache.Length) if (gateId >= watcherCache.Length)
@@ -236,6 +290,9 @@ public abstract class SimulatorBase : ICircuitSimulator
} }
} }
/// <summary>
/// Ensures that internal storage arrays are allocated and sized correctly for the current gate count.
/// </summary>
protected virtual void EnsureStorage() protected virtual void EnsureStorage()
{ {
int n = gateKinds.Count; int n = gateKinds.Count;
@@ -250,6 +307,10 @@ public abstract class SimulatorBase : ICircuitSimulator
sourceInitialized = new bool[n]; sourceInitialized = new bool[n];
} }
/// <summary>
/// Ensures the simulator is compiled (netlist, LUTs, and engine are ready).
/// Triggers compilation if not already done.
/// </summary>
protected void EnsureCompiled() protected void EnsureCompiled()
{ {
EnsureStorage(); EnsureStorage();
@@ -264,8 +325,31 @@ public abstract class SimulatorBase : ICircuitSimulator
compiled = true; compiled = true;
} }
/// <summary>
/// Compiles the simulation engine. Implemented by derived classes to build their specific evaluation logic.
/// </summary>
protected abstract void CompileEngine(); protected abstract void CompileEngine();
/// <summary>
/// Generates the expression tree for evaluating a single gate's logic.
/// Used during compilation to build gate evaluators.
/// </summary>
/// <param name="gateId">The ID of the gate to generate logic for.</param>
/// <param name="inMask">Expression representing the gate's input mask.</param>
/// <param name="sourcesParam">Expression representing the source states array.</param>
/// <param name="indexExpr">Expression representing the gate index.</param>
/// <param name="lutDataConst">Expression representing the LUT data array.</param>
/// <returns>An expression that evaluates to the gate's output value.</returns>
/// <summary>
/// Generates the expression tree for evaluating a single gate's logic.
/// Used during compilation to build gate evaluators.
/// </summary>
/// <param name="gateId">The ID of the gate to generate logic for.</param>
/// <param name="inMask">Expression representing the gate's input mask.</param>
/// <param name="sourcesParam">Expression representing the source states array.</param>
/// <param name="indexExpr">Expression representing the gate index.</param>
/// <param name="lutDataConst">Expression representing the LUT data array.</param>
/// <returns>An expression that evaluates to the gate's output value.</returns>
protected Expression GenerateGateLogic( protected Expression GenerateGateLogic(
int gateId, int gateId,
Expression inMask, Expression inMask,
@@ -286,6 +370,10 @@ public abstract class SimulatorBase : ICircuitSimulator
}; };
} }
/// <summary>
/// Compiles the connection netlist into optimized adjacency list structures for fast propagation.
/// Creates edgeStart, edgeToGate, and edgeToInputBit arrays.
/// </summary>
private void CompileNetlist() private void CompileNetlist()
{ {
int n = gateKinds.Count; int n = gateKinds.Count;
@@ -311,6 +399,10 @@ public abstract class SimulatorBase : ICircuitSimulator
} }
} }
/// <summary>
/// Compiles LUT gate data into flat arrays for efficient lookup during simulation.
/// Creates lutOffsets, lutMasks, and lutData arrays.
/// </summary>
private void CompileLuts() private void CompileLuts()
{ {
int n = gateKinds.Count; int n = gateKinds.Count;
@@ -344,6 +436,7 @@ public abstract class SimulatorBase : ICircuitSimulator
} }
} }
/// <inheritdoc/>
public bool ComputeLut(string name, int maxSteps = 4096) public bool ComputeLut(string name, int maxSteps = 4096)
{ {
if (!macroGates.TryGetValue(name, out MacroInfo? macro)) if (!macroGates.TryGetValue(name, out MacroInfo? macro))
@@ -358,8 +451,25 @@ public abstract class SimulatorBase : ICircuitSimulator
return lut is not null; return lut is not null;
} }
/// <summary>
/// Creates an internal simulator instance for LUT computation or other internal operations.
/// Implemented by derived classes to return the appropriate simulator type.
/// </summary>
/// <returns>A new simulator instance of the same type as the current implementation.</returns>
protected abstract SimulatorBase CreateInternalSimulator(); protected abstract SimulatorBase CreateInternalSimulator();
/// <summary>
/// Attempts to build a LUT representation of a macro gate by simulating all input patterns.
/// </summary>
/// <param name="definition">The circuit definition to convert to LUT.</param>
/// <param name="maxSteps">Maximum steps per pattern simulation.</param>
/// <returns>A MacroLut if successful; null if the circuit didn't stabilize for any pattern.</returns>
/// <summary>
/// Attempts to build a LUT representation of a macro gate by simulating all input patterns.
/// </summary>
/// <param name="definition">The circuit definition to convert to LUT.</param>
/// <param name="maxSteps">Maximum steps per pattern simulation.</param>
/// <returns>A MacroLut if successful; null if the circuit didn't stabilize for any pattern.</returns>
private MacroLut? TryBuildMacroLut(CircuitDefinition definition, int maxSteps) private MacroLut? TryBuildMacroLut(CircuitDefinition definition, int maxSteps)
{ {
int inputCount = definition.InputPins.Count; int inputCount = definition.InputPins.Count;
@@ -407,6 +517,7 @@ public abstract class SimulatorBase : ICircuitSimulator
return new MacroLut(inputCount, outputCount, outputTables); return new MacroLut(inputCount, outputCount, outputTables);
} }
/// <inheritdoc/>
public MacroInstance AddMacroGate(string name) public MacroInstance AddMacroGate(string name)
{ {
if (!macroGates.TryGetValue(name, out MacroInfo? macro)) if (!macroGates.TryGetValue(name, out MacroInfo? macro))
@@ -425,6 +536,13 @@ public abstract class SimulatorBase : ICircuitSimulator
return new MacroInstance(name, MapPins(macro.Definition.InputPins, map), MapPins(macro.Definition.OutputPins, map)); return new MacroInstance(name, MapPins(macro.Definition.InputPins, map), MapPins(macro.Definition.OutputPins, map));
} }
/// <summary>
/// Adds a macro gate instance using its pre-computed LUT representation.
/// Creates buffer gates for inputs and LUT gates for each output.
/// </summary>
/// <param name="name">The name of the macro gate.</param>
/// <param name="lut">The compiled LUT data.</param>
/// <returns>A MacroInstance with the input and output gate IDs.</returns>
private MacroInstance AddMacroGateFromLut(string name, MacroLut lut) private MacroInstance AddMacroGateFromLut(string name, MacroLut lut)
{ {
int[] inputs = new int[lut.InputCount]; int[] inputs = new int[lut.InputCount];
@@ -449,6 +567,14 @@ public abstract class SimulatorBase : ICircuitSimulator
return new MacroInstance(name, inputs, outputs); return new MacroInstance(name, inputs, outputs);
} }
/// <summary>
/// Copies gates and connections from a circuit definition to a simulator instance.
/// Handles macro instances recursively and maps gate IDs appropriately.
/// </summary>
/// <param name="destination">The simulator to copy gates and connections to.</param>
/// <param name="definition">The circuit definition to copy from.</param>
/// <param name="mapKind">Function to transform gate kinds during copying (e.g., Source to Buffer).</param>
/// <returns>An array mapping original gate IDs to new gate IDs in the destination.</returns>
protected static int[] CopyDefinitionGatesAndConnections(SimulatorBase destination, CircuitDefinition definition, Func<int, GateKind, GateKind> mapKind) protected static int[] CopyDefinitionGatesAndConnections(SimulatorBase destination, CircuitDefinition definition, Func<int, GateKind, GateKind> mapKind)
{ {
int gateCount = definition.GateKinds.Count; int gateCount = definition.GateKinds.Count;
@@ -486,6 +612,12 @@ public abstract class SimulatorBase : ICircuitSimulator
return map; return map;
} }
/// <summary>
/// Maps a list of pin IDs from one gate ID space to another using a mapping array.
/// </summary>
/// <param name="pins">The original pin IDs.</param>
/// <param name="map">The ID mapping array.</param>
/// <returns>An array of mapped pin IDs.</returns>
protected static int[] MapPins(IReadOnlyList<int> pins, int[] map) protected static int[] MapPins(IReadOnlyList<int> pins, int[] map)
{ {
int[] result = new int[pins.Count]; int[] result = new int[pins.Count];
@@ -497,6 +629,9 @@ public abstract class SimulatorBase : ICircuitSimulator
return result; return result;
} }
/// <summary>
/// Internal class implementing IDisposable for gate watcher unsubscription.
/// </summary>
private sealed class GateWatcherSubscription(SimulatorBase simulator, int id) : IDisposable private sealed class GateWatcherSubscription(SimulatorBase simulator, int id) : IDisposable
{ {
public void Dispose() public void Dispose()