mirror of
https://github.com/Stone-Red-Code/StoneRed.LogicSimulator.git
synced 2026-09-04 09:06:29 +02:00
Add new cycle and event based simulator for better performance and associated unit tests
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
namespace StoneRed.LogicSimulator.Simulation;
|
||||
|
||||
public sealed class CircuitDefinition
|
||||
{
|
||||
private readonly List<GateKind> gateKinds = [];
|
||||
private readonly List<(int FromGate, int ToGate, byte ToInputBit)> connections = [];
|
||||
private readonly List<int> inputPins = [];
|
||||
private readonly List<int> outputPins = [];
|
||||
private readonly List<MacroInstanceDef> macroInstances = [];
|
||||
|
||||
public IReadOnlyList<GateKind> GateKinds => gateKinds;
|
||||
public IReadOnlyList<(int FromGate, int ToGate, byte ToInputBit)> Connections => connections;
|
||||
public IReadOnlyList<int> InputPins => inputPins;
|
||||
public IReadOnlyList<int> OutputPins => outputPins;
|
||||
public IReadOnlyList<MacroInstanceDef> MacroInstances => macroInstances;
|
||||
|
||||
public sealed record MacroInstanceDef(string Name, int[] Inputs, int[] Outputs);
|
||||
|
||||
public int AddGate(GateKind kind)
|
||||
{
|
||||
int id = gateKinds.Count;
|
||||
gateKinds.Add(kind);
|
||||
return id;
|
||||
}
|
||||
|
||||
public int AddInputPin()
|
||||
{
|
||||
int id = AddGate(GateKind.Source);
|
||||
inputPins.Add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
public int AddOutputPin()
|
||||
{
|
||||
int id = AddGate(GateKind.Sink);
|
||||
outputPins.Add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
public MacroInstanceDef AddMacroInstance(string name, int inputCount, int outputCount)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("Macro name must not be empty.", nameof(name));
|
||||
}
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(inputCount);
|
||||
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(outputCount);
|
||||
|
||||
int[] inputs = new int[inputCount];
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
inputs[i] = AddGate(GateKind.Buffer);
|
||||
}
|
||||
|
||||
int[] outputs = new int[outputCount];
|
||||
for (int i = 0; i < outputCount; i++)
|
||||
{
|
||||
outputs[i] = AddGate(GateKind.Sink);
|
||||
}
|
||||
|
||||
MacroInstanceDef instance = new MacroInstanceDef(name, inputs, outputs);
|
||||
macroInstances.Add(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void Connect(int fromGate, int toGate, int toInputBit)
|
||||
{
|
||||
if ((uint)fromGate >= (uint)gateKinds.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(fromGate));
|
||||
}
|
||||
|
||||
if ((uint)toGate >= (uint)gateKinds.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(toGate));
|
||||
}
|
||||
|
||||
if ((uint)toInputBit >= 32u)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(toInputBit));
|
||||
}
|
||||
|
||||
connections.Add((fromGate, toGate, (byte)toInputBit));
|
||||
}
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
bool[] hasIncoming = new bool[gateKinds.Count];
|
||||
for (int i = 0; i < connections.Count; i++)
|
||||
{
|
||||
(_, int to, _) = connections[i];
|
||||
hasIncoming[to] = true;
|
||||
}
|
||||
|
||||
HashSet<int> inputPinSet = [.. inputPins];
|
||||
for (int i = 0; i < gateKinds.Count; i++)
|
||||
{
|
||||
if (gateKinds[i] == GateKind.Source && !inputPinSet.Contains(i))
|
||||
{
|
||||
throw new InvalidOperationException("Only Source gates marked as InputPins are allowed inside a circuit definition.");
|
||||
}
|
||||
|
||||
if (gateKinds[i] == GateKind.Lut)
|
||||
{
|
||||
throw new InvalidOperationException("Circuit definitions must not contain LUT gates.");
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<int> macroPinGates = [];
|
||||
foreach (MacroInstanceDef instance in macroInstances)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(instance.Name))
|
||||
{
|
||||
throw new InvalidOperationException("Macro instance name must not be empty.");
|
||||
}
|
||||
|
||||
foreach (int pin in instance.Inputs)
|
||||
{
|
||||
if ((uint)pin >= (uint)gateKinds.Count)
|
||||
{
|
||||
throw new InvalidOperationException("Macro instance input pin is out of range.");
|
||||
}
|
||||
|
||||
if (gateKinds[pin] != GateKind.Buffer)
|
||||
{
|
||||
throw new InvalidOperationException("Macro instance inputs must be Buffer gates.");
|
||||
}
|
||||
|
||||
if (!macroPinGates.Add(pin))
|
||||
{
|
||||
throw new InvalidOperationException("Macro instance pin is used more than once.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (int pin in instance.Outputs)
|
||||
{
|
||||
if ((uint)pin >= (uint)gateKinds.Count)
|
||||
{
|
||||
throw new InvalidOperationException("Macro instance output pin is out of range.");
|
||||
}
|
||||
|
||||
if (gateKinds[pin] != GateKind.Sink)
|
||||
{
|
||||
throw new InvalidOperationException("Macro instance outputs must be Sink gates.");
|
||||
}
|
||||
|
||||
if (!macroPinGates.Add(pin))
|
||||
{
|
||||
throw new InvalidOperationException("Macro instance pin is used more than once.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (int pin in macroPinGates)
|
||||
{
|
||||
if (inputPinSet.Contains(pin) || outputPins.Contains(pin))
|
||||
{
|
||||
throw new InvalidOperationException("Macro instance pins must not be listed as InputPins/OutputPins.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (int input in inputPins)
|
||||
{
|
||||
if (gateKinds[input] != GateKind.Source)
|
||||
{
|
||||
throw new InvalidOperationException("Input pins must be Source gates.");
|
||||
}
|
||||
|
||||
if (hasIncoming[input])
|
||||
{
|
||||
throw new InvalidOperationException("Input pins must not have incoming connections.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (int output in outputPins)
|
||||
{
|
||||
if (gateKinds[output] != GateKind.Sink)
|
||||
{
|
||||
throw new InvalidOperationException("Output pins must be Sink gates.");
|
||||
}
|
||||
}
|
||||
|
||||
if (inputPins.Distinct().Count() != inputPins.Count)
|
||||
{
|
||||
throw new InvalidOperationException("Input pins contain duplicates.");
|
||||
}
|
||||
|
||||
if (outputPins.Distinct().Count() != outputPins.Count)
|
||||
{
|
||||
throw new InvalidOperationException("Output pins contain duplicates.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace StoneRed.LogicSimulator.Simulation;
|
||||
|
||||
public sealed class CycleCircuitSimulator : SimulatorBase
|
||||
{
|
||||
private int[] nextInputMasks = [];
|
||||
private Action<int[], int[], int[]> computeOutputs = (_, _, _) => { };
|
||||
|
||||
protected override void EnsureStorage()
|
||||
{
|
||||
base.EnsureStorage();
|
||||
if (nextInputMasks.Length != gateKinds.Count)
|
||||
{
|
||||
nextInputMasks = new int[gateKinds.Count];
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
Array.Clear(nextInputMasks);
|
||||
}
|
||||
|
||||
protected override void OnSourceChanged(int gateId) { }
|
||||
|
||||
public override void Step()
|
||||
{
|
||||
EnsureCompiled();
|
||||
if (!initialized)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
computeOutputs(inputMasks, outputMasks, sourceStates);
|
||||
PropagateAndSwap();
|
||||
NotifyAllWatchers();
|
||||
}
|
||||
|
||||
private void PropagateAndSwap()
|
||||
{
|
||||
Array.Clear(nextInputMasks);
|
||||
for (int fromGate = 0; fromGate < gateKinds.Count; fromGate++)
|
||||
{
|
||||
if ((outputMasks[fromGate] & 1) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int start = edgeStart[fromGate];
|
||||
int end = edgeStart[fromGate + 1];
|
||||
for (int e = start; e < end; e++)
|
||||
{
|
||||
nextInputMasks[edgeToGate[e]] |= 1 << edgeToInputBit[e];
|
||||
}
|
||||
}
|
||||
(inputMasks, nextInputMasks) = (nextInputMasks, inputMasks);
|
||||
}
|
||||
|
||||
private bool PropagateAndSwapDetectChange()
|
||||
{
|
||||
Array.Clear(nextInputMasks);
|
||||
for (int fromGate = 0; fromGate < gateKinds.Count; fromGate++)
|
||||
{
|
||||
if ((outputMasks[fromGate] & 1) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int start = edgeStart[fromGate];
|
||||
int end = edgeStart[fromGate + 1];
|
||||
for (int e = start; e < end; e++)
|
||||
{
|
||||
nextInputMasks[edgeToGate[e]] |= 1 << edgeToInputBit[e];
|
||||
}
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
for (int i = 0; i < inputMasks.Length; i++)
|
||||
{
|
||||
if (inputMasks[i] != nextInputMasks[i]) { changed = true; break; }
|
||||
}
|
||||
|
||||
(inputMasks, nextInputMasks) = (nextInputMasks, inputMasks);
|
||||
return changed;
|
||||
}
|
||||
|
||||
public override bool TryRunUntilStable(int maxSteps, out int steps)
|
||||
{
|
||||
steps = 0;
|
||||
if (maxSteps <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureCompiled();
|
||||
if (!initialized)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
bool changed = true;
|
||||
while (changed && steps < maxSteps)
|
||||
{
|
||||
steps++;
|
||||
computeOutputs(inputMasks, outputMasks, sourceStates);
|
||||
changed = PropagateAndSwapDetectChange();
|
||||
NotifyAllWatchers();
|
||||
}
|
||||
return !changed;
|
||||
}
|
||||
|
||||
protected override void CompileEngine()
|
||||
{
|
||||
ParameterExpression inputsParam = Expression.Parameter(typeof(int[]), "inputs");
|
||||
ParameterExpression outputsParam = Expression.Parameter(typeof(int[]), "outputs");
|
||||
ParameterExpression sourcesParam = Expression.Parameter(typeof(int[]), "sources");
|
||||
ConstantExpression lutDataConst = Expression.Constant(lutData);
|
||||
|
||||
List<Expression> body = [];
|
||||
for (int i = 0; i < gateKinds.Count; i++)
|
||||
{
|
||||
ConstantExpression indexExpr = Expression.Constant(i);
|
||||
BinaryExpression inMask = Expression.ArrayIndex(inputsParam, indexExpr);
|
||||
Expression logicExpr = GenerateGateLogic(i, inMask, sourcesParam, indexExpr, lutDataConst);
|
||||
|
||||
body.Add(Expression.Assign(Expression.ArrayAccess(outputsParam, indexExpr), logicExpr));
|
||||
}
|
||||
|
||||
computeOutputs = Expression.Lambda<Action<int[], int[], int[]>>(Expression.Block(body), inputsParam, outputsParam, sourcesParam).Compile();
|
||||
}
|
||||
|
||||
protected override SimulatorBase CreateInternalSimulator()
|
||||
{
|
||||
return new CycleCircuitSimulator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace StoneRed.LogicSimulator.Simulation;
|
||||
|
||||
public sealed class EventCircuitSimulator : SimulatorBase
|
||||
{
|
||||
private Action<int[], int[], int[], int>[] gateEvaluators = [];
|
||||
private readonly Queue<int> activeQueue = new();
|
||||
private bool[] inQueue = [];
|
||||
|
||||
protected override void OnGateAdded(int gateId)
|
||||
{
|
||||
if (inQueue.Length != gateKinds.Count)
|
||||
{
|
||||
Array.Resize(ref inQueue, gateKinds.Count);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
activeQueue.Clear();
|
||||
Array.Clear(inQueue);
|
||||
for (int i = 0; i < gateKinds.Count; i++)
|
||||
{
|
||||
Enqueue(i);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSourceChanged(int gateId)
|
||||
{
|
||||
Enqueue(gateId);
|
||||
}
|
||||
|
||||
private void Enqueue(int gateId)
|
||||
{
|
||||
if (!inQueue[gateId])
|
||||
{
|
||||
inQueue[gateId] = true;
|
||||
activeQueue.Enqueue(gateId);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Step()
|
||||
{
|
||||
EnsureCompiled();
|
||||
if (!initialized)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
while (activeQueue.Count > 0)
|
||||
{
|
||||
int gateId = activeQueue.Dequeue();
|
||||
inQueue[gateId] = false;
|
||||
|
||||
int oldOutput = outputMasks[gateId];
|
||||
gateEvaluators[gateId](inputMasks, outputMasks, sourceStates, gateId);
|
||||
|
||||
if (outputMasks[gateId] != oldOutput)
|
||||
{
|
||||
Propagate(gateId);
|
||||
NotifyGateWatchers(gateId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Propagate(int fromGate)
|
||||
{
|
||||
int start = edgeStart[fromGate];
|
||||
int end = edgeStart[fromGate + 1];
|
||||
int outVal = outputMasks[fromGate] & 1;
|
||||
|
||||
for (int e = start; e < end; e++)
|
||||
{
|
||||
int toGate = edgeToGate[e];
|
||||
int bit = edgeToInputBit[e];
|
||||
int oldBit = (inputMasks[toGate] >> bit) & 1;
|
||||
if (oldBit != outVal)
|
||||
{
|
||||
inputMasks[toGate] ^= 1 << bit;
|
||||
Enqueue(toGate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool TryRunUntilStable(int maxSteps, out int steps)
|
||||
{
|
||||
steps = 0;
|
||||
if (maxSteps <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureCompiled();
|
||||
if (!initialized)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
while (activeQueue.Count > 0 && steps < maxSteps)
|
||||
{
|
||||
steps++;
|
||||
Step();
|
||||
}
|
||||
return activeQueue.Count == 0;
|
||||
}
|
||||
|
||||
protected override void CompileEngine()
|
||||
{
|
||||
gateEvaluators = new Action<int[], int[], int[], int>[gateKinds.Count];
|
||||
ParameterExpression inputsParam = Expression.Parameter(typeof(int[]), "inputs");
|
||||
ParameterExpression outputsParam = Expression.Parameter(typeof(int[]), "outputs");
|
||||
ParameterExpression sourcesParam = Expression.Parameter(typeof(int[]), "sources");
|
||||
ParameterExpression gateIdParam = Expression.Parameter(typeof(int), "gateId");
|
||||
ConstantExpression lutDataConst = Expression.Constant(lutData);
|
||||
|
||||
for (int i = 0; i < gateKinds.Count; i++)
|
||||
{
|
||||
BinaryExpression inMask = Expression.ArrayIndex(inputsParam, gateIdParam);
|
||||
Expression logicExpr = GenerateGateLogic(i, inMask, sourcesParam, gateIdParam, lutDataConst);
|
||||
|
||||
gateEvaluators[i] = Expression.Lambda<Action<int[], int[], int[], int>>(
|
||||
Expression.Assign(Expression.ArrayAccess(outputsParam, gateIdParam), logicExpr),
|
||||
inputsParam, outputsParam, sourcesParam, gateIdParam).Compile();
|
||||
}
|
||||
}
|
||||
|
||||
protected override SimulatorBase CreateInternalSimulator()
|
||||
{
|
||||
return new EventCircuitSimulator();
|
||||
}
|
||||
|
||||
protected override void EnsureStorage()
|
||||
{
|
||||
base.EnsureStorage();
|
||||
if (inQueue.Length != gateKinds.Count)
|
||||
{
|
||||
Array.Resize(ref inQueue, gateKinds.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace StoneRed.LogicSimulator.Simulation;
|
||||
|
||||
public enum GateKind : byte
|
||||
{
|
||||
Source,
|
||||
Not,
|
||||
And2,
|
||||
Or2,
|
||||
Buffer,
|
||||
Sink,
|
||||
Lut,
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace StoneRed.LogicSimulator.Simulation;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a digital logic simulator.
|
||||
/// </summary>
|
||||
public interface ICircuitSimulator
|
||||
{
|
||||
int GateCount { get; }
|
||||
int AddGate(GateKind kind);
|
||||
int AddLutGate(int inputCount, int[] table);
|
||||
void ConnectGates(int fromGate, int toGate, int toInputBit);
|
||||
void RegisterMacroGate(string name, CircuitDefinition definition);
|
||||
bool ComputeLut(string name, int maxSteps = 4096);
|
||||
MacroInstance AddMacroGate(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the circuit to its initial state (all signals at 0) and
|
||||
/// kickstarts the simulation logic (evaluating gates like NOT).
|
||||
/// This is automatically called on the first Step if not called manually.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
void SetSource(int gateId, bool value);
|
||||
bool GetOutput(int gateId);
|
||||
void Step();
|
||||
int RunUntilStable(int maxSteps = 1024);
|
||||
bool TryRunUntilStable(int maxSteps, out int steps);
|
||||
IDisposable WatchGate(int gateId, Action<int, int> callback);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace StoneRed.LogicSimulator.Simulation;
|
||||
|
||||
public sealed record MacroInstance(string Name, int[] Inputs, int[] Outputs);
|
||||
@@ -0,0 +1,500 @@
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace StoneRed.LogicSimulator.Simulation;
|
||||
|
||||
public abstract class SimulatorBase : ICircuitSimulator
|
||||
{
|
||||
protected readonly List<GateKind> gateKinds = [];
|
||||
protected readonly List<(int FromGate, int ToGate, byte ToInputBit)> connections = [];
|
||||
protected readonly List<int[]?> lutTableByGate = [];
|
||||
protected readonly Dictionary<string, MacroInfo> macroGates = new(StringComparer.Ordinal);
|
||||
|
||||
protected int[] inputMasks = [];
|
||||
protected int[] outputMasks = [];
|
||||
protected int[] sourceStates = [];
|
||||
protected bool[] sourceInitialized = [];
|
||||
|
||||
protected int[] edgeStart = [];
|
||||
protected int[] edgeToGate = [];
|
||||
protected byte[] edgeToInputBit = [];
|
||||
|
||||
protected int[] lutOffsets = [];
|
||||
protected int[] lutMasks = [];
|
||||
protected int[] lutData = [];
|
||||
|
||||
protected bool compiled;
|
||||
protected bool initialized;
|
||||
|
||||
private readonly List<GateWatcherEntry> allWatchers = [];
|
||||
private Action<int, int>[][] watcherCache = [];
|
||||
private int[] gatesWithWatchers = [];
|
||||
protected int nextWatcherId;
|
||||
|
||||
protected sealed record GateWatcherEntry(int Id, int GateId, Action<int, int> Callback);
|
||||
protected sealed record MacroLut(int InputCount, int OutputCount, int[][] OutputTables);
|
||||
protected sealed record MacroInfo(CircuitDefinition Definition, MacroLut? Lut);
|
||||
|
||||
public int GateCount => gateKinds.Count;
|
||||
|
||||
public int AddGate(GateKind kind)
|
||||
{
|
||||
if (kind == GateKind.Lut)
|
||||
{
|
||||
throw new InvalidOperationException("Use AddLutGate() to create LUT gates.");
|
||||
}
|
||||
|
||||
int id = gateKinds.Count;
|
||||
gateKinds.Add(kind);
|
||||
lutTableByGate.Add(null);
|
||||
OnGateAdded(id);
|
||||
compiled = false;
|
||||
initialized = false;
|
||||
return id;
|
||||
}
|
||||
|
||||
protected virtual void OnGateAdded(int gateId) { }
|
||||
|
||||
public int AddLutGate(int inputCount, int[] table)
|
||||
{
|
||||
if (inputCount is < 0 or > 30)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(inputCount));
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(table);
|
||||
if (table.Length != (1 << inputCount))
|
||||
{
|
||||
throw new ArgumentException("Invalid table length.");
|
||||
}
|
||||
|
||||
int id = gateKinds.Count;
|
||||
gateKinds.Add(GateKind.Lut);
|
||||
lutTableByGate.Add(table);
|
||||
OnGateAdded(id);
|
||||
compiled = false;
|
||||
initialized = false;
|
||||
return id;
|
||||
}
|
||||
|
||||
public void ConnectGates(int fromGate, int toGate, int toInputBit)
|
||||
{
|
||||
if ((uint)fromGate >= (uint)gateKinds.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(fromGate));
|
||||
}
|
||||
|
||||
if ((uint)toGate >= (uint)gateKinds.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(toGate));
|
||||
}
|
||||
|
||||
if ((uint)toInputBit >= 32u)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(toInputBit));
|
||||
}
|
||||
|
||||
connections.Add((fromGate, toGate, (byte)toInputBit));
|
||||
compiled = false;
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
public void RegisterMacroGate(string name, CircuitDefinition definition)
|
||||
{
|
||||
definition.Validate();
|
||||
macroGates[name] = new MacroInfo(definition, Lut: null);
|
||||
compiled = false;
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
EnsureStorage();
|
||||
Array.Clear(inputMasks);
|
||||
Array.Clear(outputMasks);
|
||||
Array.Clear(sourceInitialized);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public abstract void Step();
|
||||
public abstract bool TryRunUntilStable(int maxSteps, out int steps);
|
||||
|
||||
public int RunUntilStable(int maxSteps = 1024)
|
||||
{
|
||||
if (!TryRunUntilStable(maxSteps, out int steps))
|
||||
{
|
||||
throw new InvalidOperationException($"Circuit did not stabilize within {maxSteps} steps.");
|
||||
}
|
||||
return steps;
|
||||
}
|
||||
|
||||
public virtual void SetSource(int gateId, bool value)
|
||||
{
|
||||
EnsureStorage();
|
||||
if ((uint)gateId >= (uint)gateKinds.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(gateId));
|
||||
}
|
||||
|
||||
if (gateKinds[gateId] != GateKind.Source)
|
||||
{
|
||||
throw new InvalidOperationException("Gate is not a source.");
|
||||
}
|
||||
|
||||
int bit = value ? 1 : 0;
|
||||
if (sourceStates[gateId] != bit || !sourceInitialized[gateId])
|
||||
{
|
||||
sourceStates[gateId] = bit;
|
||||
sourceInitialized[gateId] = true;
|
||||
OnSourceChanged(gateId);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnSourceChanged(int gateId) { }
|
||||
|
||||
public bool GetOutput(int gateId)
|
||||
{
|
||||
EnsureStorage();
|
||||
return (outputMasks[gateId] & 1) != 0;
|
||||
}
|
||||
|
||||
public IDisposable WatchGate(int gateId, Action<int, int> callback)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(callback);
|
||||
if ((uint)gateId >= (uint)gateKinds.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(gateId));
|
||||
}
|
||||
|
||||
int id = nextWatcherId++;
|
||||
GateWatcherEntry entry = new GateWatcherEntry(id, gateId, callback);
|
||||
allWatchers.Add(entry);
|
||||
RebuildWatcherCache();
|
||||
return new GateWatcherSubscription(this, id);
|
||||
}
|
||||
|
||||
private void RemoveWatcher(int id)
|
||||
{
|
||||
_ = allWatchers.RemoveAll(w => w.Id == id);
|
||||
RebuildWatcherCache();
|
||||
}
|
||||
|
||||
private void RebuildWatcherCache()
|
||||
{
|
||||
int n = gateKinds.Count;
|
||||
watcherCache = new Action<int, int>[n][];
|
||||
|
||||
IEnumerable<IGrouping<int, GateWatcherEntry>> groups = allWatchers.GroupBy(w => w.GateId);
|
||||
List<int> activeGates = [];
|
||||
|
||||
foreach (IGrouping<int, GateWatcherEntry> group in groups)
|
||||
{
|
||||
watcherCache[group.Key] = [.. group.Select(w => w.Callback)];
|
||||
activeGates.Add(group.Key);
|
||||
}
|
||||
|
||||
gatesWithWatchers = [.. activeGates];
|
||||
}
|
||||
|
||||
protected void NotifyAllWatchers()
|
||||
{
|
||||
for (int i = 0; i < gatesWithWatchers.Length; i++)
|
||||
{
|
||||
int gateId = gatesWithWatchers[i];
|
||||
Action<int, int>[] callbacks = watcherCache[gateId];
|
||||
int val = outputMasks[gateId];
|
||||
for (int j = 0; j < callbacks.Length; j++)
|
||||
{
|
||||
callbacks[j](gateId, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void NotifyGateWatchers(int gateId)
|
||||
{
|
||||
if (gateId >= watcherCache.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Action<int, int>[] callbacks = watcherCache[gateId];
|
||||
if (callbacks == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int val = outputMasks[gateId];
|
||||
for (int i = 0; i < callbacks.Length; i++)
|
||||
{
|
||||
callbacks[i](gateId, val);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void EnsureStorage()
|
||||
{
|
||||
int n = gateKinds.Count;
|
||||
if (inputMasks.Length == n)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
inputMasks = new int[n];
|
||||
outputMasks = new int[n];
|
||||
sourceStates = new int[n];
|
||||
sourceInitialized = new bool[n];
|
||||
}
|
||||
|
||||
protected void EnsureCompiled()
|
||||
{
|
||||
EnsureStorage();
|
||||
if (compiled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CompileNetlist();
|
||||
CompileLuts();
|
||||
CompileEngine();
|
||||
compiled = true;
|
||||
}
|
||||
|
||||
protected abstract void CompileEngine();
|
||||
|
||||
protected Expression GenerateGateLogic(
|
||||
int gateId,
|
||||
Expression inMask,
|
||||
Expression sourcesParam,
|
||||
Expression indexExpr,
|
||||
Expression lutDataConst)
|
||||
{
|
||||
return gateKinds[gateId] switch
|
||||
{
|
||||
GateKind.Source => Expression.And(Expression.ArrayIndex(sourcesParam, indexExpr), Expression.Constant(1)),
|
||||
GateKind.Not => Expression.Condition(Expression.Equal(Expression.And(inMask, Expression.Constant(1)), Expression.Constant(0)), Expression.Constant(1), Expression.Constant(0)),
|
||||
GateKind.And2 => Expression.Condition(Expression.Equal(Expression.And(inMask, Expression.Constant(0b11)), Expression.Constant(0b11)), Expression.Constant(1), Expression.Constant(0)),
|
||||
GateKind.Or2 => Expression.Condition(Expression.NotEqual(Expression.And(inMask, Expression.Constant(0b11)), Expression.Constant(0)), Expression.Constant(1), Expression.Constant(0)),
|
||||
GateKind.Buffer => Expression.Condition(Expression.NotEqual(Expression.And(inMask, Expression.Constant(1)), Expression.Constant(0)), Expression.Constant(1), Expression.Constant(0)),
|
||||
GateKind.Sink => Expression.Condition(Expression.NotEqual(Expression.And(inMask, Expression.Constant(1)), Expression.Constant(0)), Expression.Constant(1), Expression.Constant(0)),
|
||||
GateKind.Lut => Expression.ArrayIndex(lutDataConst, Expression.Add(Expression.Constant(lutOffsets[gateId]), Expression.And(inMask, Expression.Constant(lutMasks[gateId])))),
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
|
||||
private void CompileNetlist()
|
||||
{
|
||||
int n = gateKinds.Count;
|
||||
edgeStart = new int[n + 1];
|
||||
foreach ((int FromGate, int _, byte _) in connections)
|
||||
{
|
||||
edgeStart[FromGate + 1]++;
|
||||
}
|
||||
|
||||
for (int i = 1; i < edgeStart.Length; i++)
|
||||
{
|
||||
edgeStart[i] += edgeStart[i - 1];
|
||||
}
|
||||
|
||||
edgeToGate = new int[connections.Count];
|
||||
edgeToInputBit = new byte[connections.Count];
|
||||
int[] cursor = (int[])edgeStart.Clone();
|
||||
foreach ((int FromGate, int ToGate, byte ToInputBit) in connections)
|
||||
{
|
||||
int at = cursor[FromGate]++;
|
||||
edgeToGate[at] = ToGate;
|
||||
edgeToInputBit[at] = ToInputBit;
|
||||
}
|
||||
}
|
||||
|
||||
private void CompileLuts()
|
||||
{
|
||||
int n = gateKinds.Count;
|
||||
lutOffsets = new int[n];
|
||||
lutMasks = new int[n];
|
||||
int total = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
if (gateKinds[i] != GateKind.Lut)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] table = lutTableByGate[i]!;
|
||||
lutOffsets[i] = total;
|
||||
lutMasks[i] = table.Length - 1;
|
||||
total += table.Length;
|
||||
}
|
||||
lutData = new int[total];
|
||||
int cursor = 0;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
if (gateKinds[i] != GateKind.Lut)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int[] table = lutTableByGate[i]!;
|
||||
Array.Copy(table, 0, lutData, cursor, table.Length);
|
||||
cursor += table.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ComputeLut(string name, int maxSteps = 4096)
|
||||
{
|
||||
if (!macroGates.TryGetValue(name, out MacroInfo? macro))
|
||||
{
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
|
||||
MacroLut? lut = TryBuildMacroLut(macro.Definition, maxSteps);
|
||||
macroGates[name] = macro with { Lut = lut };
|
||||
compiled = false;
|
||||
initialized = false;
|
||||
return lut is not null;
|
||||
}
|
||||
|
||||
protected abstract SimulatorBase CreateInternalSimulator();
|
||||
|
||||
private MacroLut? TryBuildMacroLut(CircuitDefinition definition, int maxSteps)
|
||||
{
|
||||
int inputCount = definition.InputPins.Count;
|
||||
int outputCount = definition.OutputPins.Count;
|
||||
if (inputCount < 0 || outputCount <= 0 || inputCount > 30)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int patterns = 1 << inputCount;
|
||||
SimulatorBase sim = CreateInternalSimulator();
|
||||
foreach (KeyValuePair<string, MacroInfo> pair in macroGates)
|
||||
{
|
||||
sim.macroGates[pair.Key] = pair.Value;
|
||||
}
|
||||
|
||||
int[] map = CopyDefinitionGatesAndConnections(sim, definition, static (_, kind) => kind);
|
||||
int[] inGates = MapPins(definition.InputPins, map);
|
||||
int[] outGates = MapPins(definition.OutputPins, map);
|
||||
|
||||
int[][] outputTables = new int[outputCount][];
|
||||
for (int o = 0; o < outputCount; o++)
|
||||
{
|
||||
outputTables[o] = new int[patterns];
|
||||
}
|
||||
|
||||
for (int pattern = 0; pattern < patterns; pattern++)
|
||||
{
|
||||
sim.Reset();
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
sim.SetSource(inGates[i], ((pattern >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
if (!sim.TryRunUntilStable(maxSteps, out _))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int o = 0; o < outputCount; o++)
|
||||
{
|
||||
outputTables[o][pattern] = sim.GetOutput(outGates[o]) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
return new MacroLut(inputCount, outputCount, outputTables);
|
||||
}
|
||||
|
||||
public MacroInstance AddMacroGate(string name)
|
||||
{
|
||||
if (!macroGates.TryGetValue(name, out MacroInfo? macro))
|
||||
{
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
|
||||
if (macro.Lut is not null)
|
||||
{
|
||||
return AddMacroGateFromLut(name, macro.Lut);
|
||||
}
|
||||
|
||||
int[] map = CopyDefinitionGatesAndConnections(this, macro.Definition, (gateId, kind) =>
|
||||
kind == GateKind.Source ? (macro.Definition.InputPins.Contains(gateId) ? GateKind.Buffer : throw new InvalidOperationException()) : kind);
|
||||
|
||||
return new MacroInstance(name, MapPins(macro.Definition.InputPins, map), MapPins(macro.Definition.OutputPins, map));
|
||||
}
|
||||
|
||||
private MacroInstance AddMacroGateFromLut(string name, MacroLut lut)
|
||||
{
|
||||
int[] inputs = new int[lut.InputCount];
|
||||
for (int i = 0; i < inputs.Length; i++)
|
||||
{
|
||||
inputs[i] = AddGate(GateKind.Buffer);
|
||||
}
|
||||
|
||||
int[] outputs = new int[lut.OutputCount];
|
||||
for (int o = 0; o < outputs.Length; o++)
|
||||
{
|
||||
int lutGate = AddLutGate(lut.InputCount, lut.OutputTables[o]);
|
||||
for (int i = 0; i < inputs.Length; i++)
|
||||
{
|
||||
ConnectGates(inputs[i], lutGate, i);
|
||||
}
|
||||
|
||||
int sink = AddGate(GateKind.Sink);
|
||||
ConnectGates(lutGate, sink, 0);
|
||||
outputs[o] = sink;
|
||||
}
|
||||
return new MacroInstance(name, inputs, outputs);
|
||||
}
|
||||
|
||||
protected static int[] CopyDefinitionGatesAndConnections(SimulatorBase destination, CircuitDefinition definition, Func<int, GateKind, GateKind> mapKind)
|
||||
{
|
||||
int gateCount = definition.GateKinds.Count;
|
||||
int[] map = new int[gateCount];
|
||||
Array.Fill(map, -1);
|
||||
|
||||
for (int i = 0; i < definition.MacroInstances.Count; i++)
|
||||
{
|
||||
CircuitDefinition.MacroInstanceDef instanceDef = definition.MacroInstances[i];
|
||||
MacroInstance instance = destination.AddMacroGate(instanceDef.Name);
|
||||
for (int p = 0; p < instanceDef.Inputs.Length; p++)
|
||||
{
|
||||
map[instanceDef.Inputs[p]] = instance.Inputs[p];
|
||||
}
|
||||
|
||||
for (int p = 0; p < instanceDef.Outputs.Length; p++)
|
||||
{
|
||||
map[instanceDef.Outputs[p]] = instance.Outputs[p];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < gateCount; i++)
|
||||
{
|
||||
if (map[i] == -1)
|
||||
{
|
||||
map[i] = destination.AddGate(mapKind(i, definition.GateKinds[i]));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ((int FromGate, int ToGate, byte ToInputBit) in definition.Connections)
|
||||
{
|
||||
destination.ConnectGates(map[FromGate], map[ToGate], ToInputBit);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
protected static int[] MapPins(IReadOnlyList<int> pins, int[] map)
|
||||
{
|
||||
int[] result = new int[pins.Count];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
result[i] = map[pins[i]];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private sealed class GateWatcherSubscription(SimulatorBase simulator, int id) : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
simulator.RemoveWatcher(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user