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,5 @@
|
|||||||
|
using BenchmarkDotNet.Running;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Benchmarks;
|
||||||
|
|
||||||
|
BenchmarkRunner.Run<SimulatorBenchmarks>();
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using StoneRed.LogicSimulator.Simulation;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Benchmarks;
|
||||||
|
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class SimulatorBenchmarks
|
||||||
|
{
|
||||||
|
private CycleCircuitSimulator cycleSim = null!;
|
||||||
|
private EventCircuitSimulator eventSim = null!;
|
||||||
|
private int cycleSource;
|
||||||
|
private int eventSource;
|
||||||
|
private bool state;
|
||||||
|
|
||||||
|
[Params(0.01, 0.10, 1.00)]
|
||||||
|
public double ActivityPercentage;
|
||||||
|
|
||||||
|
[Params(true, false)]
|
||||||
|
public bool UseLut;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
cycleSim = new CycleCircuitSimulator();
|
||||||
|
eventSim = new EventCircuitSimulator();
|
||||||
|
|
||||||
|
SetupCircuit(cycleSim, out cycleSource);
|
||||||
|
SetupCircuit(eventSim, out eventSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupCircuit(ICircuitSimulator sim, out int source)
|
||||||
|
{
|
||||||
|
var chain10 = new CircuitDefinition();
|
||||||
|
int input = chain10.AddInputPin();
|
||||||
|
int last = input;
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
int not = chain10.AddGate(GateKind.Not);
|
||||||
|
chain10.Connect(last, not, 0);
|
||||||
|
last = not;
|
||||||
|
}
|
||||||
|
int output = chain10.AddOutputPin();
|
||||||
|
chain10.Connect(last, output, 0);
|
||||||
|
|
||||||
|
sim.RegisterMacroGate("CHAIN10", chain10);
|
||||||
|
if (UseLut)
|
||||||
|
{
|
||||||
|
sim.ComputeLut("CHAIN10");
|
||||||
|
}
|
||||||
|
|
||||||
|
const int macroCount = 100;
|
||||||
|
int activeMacroCount = Math.Max(1, (int)(macroCount * ActivityPercentage));
|
||||||
|
int idleMacroCount = macroCount - activeMacroCount;
|
||||||
|
|
||||||
|
source = sim.AddGate(GateKind.Source);
|
||||||
|
int constantSource = sim.AddGate(GateKind.Source);
|
||||||
|
int sink = sim.AddGate(GateKind.Sink);
|
||||||
|
|
||||||
|
for (int i = 0; i < activeMacroCount; i++)
|
||||||
|
{
|
||||||
|
var inst = sim.AddMacroGate("CHAIN10");
|
||||||
|
sim.ConnectGates(source, inst.Inputs[0], 0);
|
||||||
|
sim.ConnectGates(inst.Outputs[0], sink, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < idleMacroCount; i++)
|
||||||
|
{
|
||||||
|
var inst = sim.AddMacroGate("CHAIN10");
|
||||||
|
sim.ConnectGates(constantSource, inst.Inputs[0], 0);
|
||||||
|
sim.ConnectGates(inst.Outputs[0], sink, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
sim.Reset();
|
||||||
|
sim.SetSource(constantSource, false);
|
||||||
|
sim.RunUntilStable();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void CycleBased()
|
||||||
|
{
|
||||||
|
state = !state;
|
||||||
|
cycleSim.SetSource(cycleSource, state);
|
||||||
|
cycleSim.Step();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void EventDriven()
|
||||||
|
{
|
||||||
|
state = !state;
|
||||||
|
eventSim.SetSource(eventSource, state);
|
||||||
|
eventSim.Step();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\StoneRed.LogicSimulator.Simulation\StoneRed.LogicSimulator.Simulation.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
+54
-36
@@ -1,16 +1,12 @@
|
|||||||
using System;
|
namespace StoneRed.LogicSimulator.Simulation;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Test;
|
|
||||||
|
|
||||||
public sealed class CircuitDefinition
|
public sealed class CircuitDefinition
|
||||||
{
|
{
|
||||||
private readonly List<GateKind> gateKinds = new();
|
private readonly List<GateKind> gateKinds = [];
|
||||||
private readonly List<(int FromGate, int ToGate, byte ToInputBit)> connections = new();
|
private readonly List<(int FromGate, int ToGate, byte ToInputBit)> connections = [];
|
||||||
private readonly List<int> inputPins = new();
|
private readonly List<int> inputPins = [];
|
||||||
private readonly List<int> outputPins = new();
|
private readonly List<int> outputPins = [];
|
||||||
private readonly List<MacroInstanceDef> macroInstances = new();
|
private readonly List<MacroInstanceDef> macroInstances = [];
|
||||||
|
|
||||||
public IReadOnlyList<GateKind> GateKinds => gateKinds;
|
public IReadOnlyList<GateKind> GateKinds => gateKinds;
|
||||||
public IReadOnlyList<(int FromGate, int ToGate, byte ToInputBit)> Connections => connections;
|
public IReadOnlyList<(int FromGate, int ToGate, byte ToInputBit)> Connections => connections;
|
||||||
@@ -48,15 +44,9 @@ public sealed class CircuitDefinition
|
|||||||
throw new ArgumentException("Macro name must not be empty.", nameof(name));
|
throw new ArgumentException("Macro name must not be empty.", nameof(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputCount < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(inputCount);
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(inputCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outputCount < 0)
|
ArgumentOutOfRangeException.ThrowIfNegative(outputCount);
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(outputCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
int[] inputs = new int[inputCount];
|
int[] inputs = new int[inputCount];
|
||||||
for (int i = 0; i < inputCount; i++)
|
for (int i = 0; i < inputCount; i++)
|
||||||
@@ -70,35 +60,41 @@ public sealed class CircuitDefinition
|
|||||||
outputs[i] = AddGate(GateKind.Sink);
|
outputs[i] = AddGate(GateKind.Sink);
|
||||||
}
|
}
|
||||||
|
|
||||||
var instance = new MacroInstanceDef(name, inputs, outputs);
|
MacroInstanceDef instance = new MacroInstanceDef(name, inputs, outputs);
|
||||||
macroInstances.Add(instance);
|
macroInstances.Add(instance);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Connect(int fromGate, int toGate, int toInputBit)
|
public void Connect(int fromGate, int toGate, int toInputBit)
|
||||||
{
|
{
|
||||||
if ((uint)fromGate >= (uint)gateKinds.Count) throw new ArgumentOutOfRangeException(nameof(fromGate));
|
if ((uint)fromGate >= (uint)gateKinds.Count)
|
||||||
if ((uint)toGate >= (uint)gateKinds.Count) throw new ArgumentOutOfRangeException(nameof(toGate));
|
{
|
||||||
if ((uint)toInputBit >= 32u) throw new ArgumentOutOfRangeException(nameof(toInputBit));
|
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));
|
connections.Add((fromGate, toGate, (byte)toInputBit));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Validate()
|
public void Validate()
|
||||||
{
|
{
|
||||||
// Minimal validation to prevent ambiguous semantics.
|
bool[] hasIncoming = new bool[gateKinds.Count];
|
||||||
// - Input pins must be Source gates and must not have incoming connections.
|
|
||||||
// - Output pins must be Sink gates.
|
|
||||||
// - All Source gates must be listed as InputPins.
|
|
||||||
// - LUT gates are not allowed in definitions (they are generated by the simulator).
|
|
||||||
var hasIncoming = new bool[gateKinds.Count];
|
|
||||||
for (int i = 0; i < connections.Count; i++)
|
for (int i = 0; i < connections.Count; i++)
|
||||||
{
|
{
|
||||||
(_, int to, _) = connections[i];
|
(_, int to, _) = connections[i];
|
||||||
hasIncoming[to] = true;
|
hasIncoming[to] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var inputPinSet = new HashSet<int>(inputPins);
|
HashSet<int> inputPinSet = [.. inputPins];
|
||||||
for (int i = 0; i < gateKinds.Count; i++)
|
for (int i = 0; i < gateKinds.Count; i++)
|
||||||
{
|
{
|
||||||
if (gateKinds[i] == GateKind.Source && !inputPinSet.Contains(i))
|
if (gateKinds[i] == GateKind.Source && !inputPinSet.Contains(i))
|
||||||
@@ -112,7 +108,7 @@ public sealed class CircuitDefinition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var macroPinGates = new HashSet<int>();
|
HashSet<int> macroPinGates = [];
|
||||||
foreach (MacroInstanceDef instance in macroInstances)
|
foreach (MacroInstanceDef instance in macroInstances)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(instance.Name))
|
if (string.IsNullOrWhiteSpace(instance.Name))
|
||||||
@@ -122,16 +118,38 @@ public sealed class CircuitDefinition
|
|||||||
|
|
||||||
foreach (int pin in instance.Inputs)
|
foreach (int pin in instance.Inputs)
|
||||||
{
|
{
|
||||||
if ((uint)pin >= (uint)gateKinds.Count) throw new InvalidOperationException("Macro instance input pin is out of range.");
|
if ((uint)pin >= (uint)gateKinds.Count)
|
||||||
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.");
|
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)
|
foreach (int pin in instance.Outputs)
|
||||||
{
|
{
|
||||||
if ((uint)pin >= (uint)gateKinds.Count) throw new InvalidOperationException("Macro instance output pin is out of range.");
|
if ((uint)pin >= (uint)gateKinds.Count)
|
||||||
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.");
|
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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-7
@@ -1,13 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,688 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
|
|
||||||
namespace StoneRed.LogicSimulator.Test;
|
|
||||||
|
|
||||||
public enum GateKind : byte
|
|
||||||
{
|
|
||||||
Source,
|
|
||||||
Not,
|
|
||||||
And2,
|
|
||||||
Or2,
|
|
||||||
Buffer,
|
|
||||||
Sink,
|
|
||||||
Lut,
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ExprCircuitSimulator
|
|
||||||
{
|
|
||||||
private readonly List<GateKind> gateKinds = [];
|
|
||||||
private readonly List<(int FromGate, int ToGate, byte ToInputBit)> connections = [];
|
|
||||||
private readonly List<int[]?> lutTableByGate = [];
|
|
||||||
private readonly Dictionary<string, MacroInfo> macroGates = new(StringComparer.Ordinal);
|
|
||||||
|
|
||||||
private int[] inputMasks = Array.Empty<int>();
|
|
||||||
private int[] nextInputMasks = Array.Empty<int>();
|
|
||||||
private int[] outputMasks = Array.Empty<int>();
|
|
||||||
private int[] sourceStates = Array.Empty<int>();
|
|
||||||
|
|
||||||
private int[] edgeStart = Array.Empty<int>();
|
|
||||||
private int[] edgeToGate = Array.Empty<int>();
|
|
||||||
private byte[] edgeToInputBit = Array.Empty<byte>();
|
|
||||||
|
|
||||||
private int[] lutOffsets = Array.Empty<int>();
|
|
||||||
private int[] lutMasks = Array.Empty<int>();
|
|
||||||
private int[] lutData = Array.Empty<int>();
|
|
||||||
|
|
||||||
private Action<int[], int[], int[]>? computeOutputs;
|
|
||||||
private bool compiled;
|
|
||||||
private readonly List<GateWatcherEntry> gateWatchers = [];
|
|
||||||
private int nextWatcherId;
|
|
||||||
|
|
||||||
private sealed record GateWatcherEntry(int Id, int GateId, Action<int, int> Callback);
|
|
||||||
|
|
||||||
public int GateCount => gateKinds.Count;
|
|
||||||
|
|
||||||
public sealed record MacroInstance(string Name, int[] Inputs, int[] Outputs);
|
|
||||||
|
|
||||||
private sealed record MacroLut(int InputCount, int OutputCount, int[][] OutputTables);
|
|
||||||
|
|
||||||
private sealed record MacroInfo(CircuitDefinition Definition, MacroLut? Lut);
|
|
||||||
|
|
||||||
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);
|
|
||||||
compiled = false;
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int AddLutGate(int inputCount, int[] table)
|
|
||||||
{
|
|
||||||
if (inputCount is < 0 or > 30)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(inputCount), "LUT input count must be between 0 and 30.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (table is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(table));
|
|
||||||
}
|
|
||||||
|
|
||||||
int expected = 1 << inputCount;
|
|
||||||
if (table.Length != expected)
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"LUT table length must be {expected} for {inputCount} inputs.", nameof(table));
|
|
||||||
}
|
|
||||||
|
|
||||||
int id = gateKinds.Count;
|
|
||||||
gateKinds.Add(GateKind.Lut);
|
|
||||||
lutTableByGate.Add(table);
|
|
||||||
compiled = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterMacroGate(string name, CircuitDefinition definition)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Macro name must not be empty.", nameof(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
definition.Validate();
|
|
||||||
macroGates[name] = new MacroInfo(definition, Lut: null);
|
|
||||||
compiled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ComputeLut(string name, int maxSteps = 4096)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Macro name must not be empty.", nameof(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxSteps <= 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(maxSteps));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!macroGates.TryGetValue(name, out MacroInfo? macro))
|
|
||||||
{
|
|
||||||
throw new KeyNotFoundException($"Macro gate '{name}' is not registered.");
|
|
||||||
}
|
|
||||||
|
|
||||||
MacroLut? lut = TryBuildMacroLut(macro.Definition, maxSteps: maxSteps);
|
|
||||||
macroGates[name] = macro with { Lut = lut };
|
|
||||||
compiled = false;
|
|
||||||
return lut is not null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MacroInstance AddMacroGate(string name)
|
|
||||||
{
|
|
||||||
if (!macroGates.TryGetValue(name, out MacroInfo? macro))
|
|
||||||
{
|
|
||||||
throw new KeyNotFoundException($"Macro gate '{name}' is not registered.");
|
|
||||||
}
|
|
||||||
|
|
||||||
CircuitDefinition definition = macro.Definition;
|
|
||||||
|
|
||||||
if (macro.Lut is not null)
|
|
||||||
{
|
|
||||||
return AddMacroGateFromLut(name, macro.Lut);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inline (flatten) definition into this simulator by copying its gates and connections.
|
|
||||||
// Input pins are represented as Source gates in the definition, but Source gates cannot be driven by wires.
|
|
||||||
// For each input pin we substitute a Buffer gate, which can be driven externally and fans out internally.
|
|
||||||
int[] map = CopyDefinitionGatesAndConnections(
|
|
||||||
destination: this,
|
|
||||||
definition: definition,
|
|
||||||
mapKind: (gateId, kind) =>
|
|
||||||
kind == GateKind.Source
|
|
||||||
? (definition.InputPins.Contains(gateId) ? GateKind.Buffer : throw new InvalidOperationException("Only Source gates marked as InputPins are allowed inside a macro definition."))
|
|
||||||
: kind);
|
|
||||||
|
|
||||||
int[] inputs = MapPins(definition.InputPins, map);
|
|
||||||
int[] outputs = MapPins(definition.OutputPins, map);
|
|
||||||
|
|
||||||
return new MacroInstance(name, inputs, outputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearSignals()
|
|
||||||
{
|
|
||||||
EnsureStorage();
|
|
||||||
Array.Clear(inputMasks);
|
|
||||||
Array.Clear(nextInputMasks);
|
|
||||||
Array.Clear(outputMasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public 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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceStates[gateId] = value ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool GetOutput(int gateId)
|
|
||||||
{
|
|
||||||
EnsureStorage();
|
|
||||||
if ((uint)gateId >= (uint)gateKinds.Count)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(gateId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (outputMasks[gateId] & 1) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Step()
|
|
||||||
{
|
|
||||||
EnsureCompiled();
|
|
||||||
computeOutputs!(inputMasks, outputMasks, sourceStates);
|
|
||||||
Propagate();
|
|
||||||
NotifyWatchers();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int RunUntilStable(int maxSteps = 1024)
|
|
||||||
{
|
|
||||||
if (!TryRunUntilStable(maxSteps, out int steps))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Circuit did not stabilize within {maxSteps} steps.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Propagate()
|
|
||||||
{
|
|
||||||
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++)
|
|
||||||
{
|
|
||||||
int toGate = edgeToGate[e];
|
|
||||||
nextInputMasks[toGate] |= 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++)
|
|
||||||
{
|
|
||||||
int toGate = edgeToGate[e];
|
|
||||||
nextInputMasks[toGate] |= 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureStorage()
|
|
||||||
{
|
|
||||||
int n = gateKinds.Count;
|
|
||||||
if (inputMasks.Length == n)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputMasks = new int[n];
|
|
||||||
nextInputMasks = new int[n];
|
|
||||||
outputMasks = new int[n];
|
|
||||||
sourceStates = new int[n];
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureCompiled()
|
|
||||||
{
|
|
||||||
EnsureStorage();
|
|
||||||
|
|
||||||
if (compiled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompileNetlist();
|
|
||||||
CompileLuts();
|
|
||||||
computeOutputs = CompileComputeOutputsExpr();
|
|
||||||
compiled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CompileNetlist()
|
|
||||||
{
|
|
||||||
int n = gateKinds.Count;
|
|
||||||
edgeStart = new int[n + 1];
|
|
||||||
|
|
||||||
for (int i = 0; i < connections.Count; i++)
|
|
||||||
{
|
|
||||||
(int from, _, _) = connections[i];
|
|
||||||
edgeStart[from + 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();
|
|
||||||
for (int i = 0; i < connections.Count; i++)
|
|
||||||
{
|
|
||||||
(int from, int to, byte bit) = connections[i];
|
|
||||||
int at = cursor[from]++;
|
|
||||||
edgeToGate[at] = to;
|
|
||||||
edgeToInputBit[at] = bit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
if (table is null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("LUT gate is missing its truth table.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsPowerOfTwo(table.Length))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("LUT table length must be a power of two.");
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Action<int[], int[], int[]> CompileComputeOutputsExpr()
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
Expression[] block = new Expression[gateKinds.Count];
|
|
||||||
|
|
||||||
for (int i = 0; i < gateKinds.Count; i++)
|
|
||||||
{
|
|
||||||
ConstantExpression idx = Expression.Constant(i);
|
|
||||||
Expression inMask = Expression.ArrayIndex(inputsParam, idx);
|
|
||||||
|
|
||||||
Expression outExpr = gateKinds[i] switch
|
|
||||||
{
|
|
||||||
GateKind.Source => Expression.And(Expression.ArrayIndex(sourcesParam, idx), 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[i]),
|
|
||||||
Expression.And(inMask, Expression.Constant(lutMasks[i])))),
|
|
||||||
|
|
||||||
_ => throw new ArgumentOutOfRangeException(),
|
|
||||||
};
|
|
||||||
|
|
||||||
block[i] = Expression.Assign(Expression.ArrayAccess(outputsParam, idx), outExpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockExpression body = Expression.Block(block);
|
|
||||||
return Expression.Lambda<Action<int[], int[], int[]>>(body, inputsParam, outputsParam, sourcesParam).Compile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsPowerOfTwo(int value) => value > 0 && (value & (value - 1)) == 0;
|
|
||||||
|
|
||||||
private MacroLut? TryBuildMacroLut(CircuitDefinition definition, int maxSteps)
|
|
||||||
{
|
|
||||||
int inputCount = definition.InputPins.Count;
|
|
||||||
int outputCount = definition.OutputPins.Count;
|
|
||||||
|
|
||||||
if (inputCount < 0 || outputCount <= 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputCount > 30)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Cannot build a LUT for macros with more than 30 inputs (would overflow 32-bit indexing).");
|
|
||||||
}
|
|
||||||
|
|
||||||
int patterns = 1 << inputCount;
|
|
||||||
|
|
||||||
var sim = new ExprCircuitSimulator();
|
|
||||||
foreach ((string macroName, MacroInfo macroInfo) in macroGates)
|
|
||||||
{
|
|
||||||
sim.macroGates[macroName] = macroInfo;
|
|
||||||
}
|
|
||||||
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.ClearSignals();
|
|
||||||
|
|
||||||
for (int i = 0; i < inputCount; i++)
|
|
||||||
{
|
|
||||||
bool bit = ((pattern >> i) & 1) != 0;
|
|
||||||
sim.SetSource(inGates[i], bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sim.TryRunUntilStable(maxSteps: 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, toInputBit: i);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sink = AddGate(GateKind.Sink);
|
|
||||||
ConnectGates(lutGate, sink, toInputBit: 0);
|
|
||||||
outputs[o] = sink;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MacroInstance(name, inputs, outputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDisposable WatchGate(int gateId, Action<int, int> callback)
|
|
||||||
{
|
|
||||||
if (callback is null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(callback));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((uint)gateId >= (uint)gateKinds.Count)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(gateId));
|
|
||||||
}
|
|
||||||
|
|
||||||
int id = nextWatcherId++;
|
|
||||||
var entry = new GateWatcherEntry(id, gateId, callback);
|
|
||||||
gateWatchers.Add(entry);
|
|
||||||
return new GateWatcherSubscription(this, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveWatcher(int id)
|
|
||||||
{
|
|
||||||
gateWatchers.RemoveAll(w => w.Id == id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void NotifyWatchers()
|
|
||||||
{
|
|
||||||
if (gateWatchers.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GateWatcherEntry[] snapshot = gateWatchers.ToArray();
|
|
||||||
for (int i = 0; i < snapshot.Length; i++)
|
|
||||||
{
|
|
||||||
GateWatcherEntry entry = snapshot[i];
|
|
||||||
entry.Callback(entry.GateId, outputMasks[entry.GateId]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class GateWatcherSubscription : IDisposable
|
|
||||||
{
|
|
||||||
private readonly ExprCircuitSimulator simulator;
|
|
||||||
private readonly int id;
|
|
||||||
private bool disposed;
|
|
||||||
|
|
||||||
public GateWatcherSubscription(ExprCircuitSimulator simulator, int id)
|
|
||||||
{
|
|
||||||
this.simulator = simulator;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
disposed = true;
|
|
||||||
simulator.RemoveWatcher(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryRunUntilStable(int maxSteps, out int steps)
|
|
||||||
{
|
|
||||||
steps = 0;
|
|
||||||
if (maxSteps <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureCompiled();
|
|
||||||
|
|
||||||
while (steps < maxSteps)
|
|
||||||
{
|
|
||||||
steps++;
|
|
||||||
computeOutputs!(inputMasks, outputMasks, sourceStates);
|
|
||||||
bool changed = PropagateAndSwapDetectChange();
|
|
||||||
NotifyWatchers();
|
|
||||||
if (!changed)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int[] CopyDefinitionGatesAndConnections(
|
|
||||||
ExprCircuitSimulator destination,
|
|
||||||
CircuitDefinition definition,
|
|
||||||
Func<int, GateKind, GateKind> mapKind)
|
|
||||||
{
|
|
||||||
int gateCount = definition.GateKinds.Count;
|
|
||||||
int[] map = new int[gateCount];
|
|
||||||
Array.Fill(map, -1);
|
|
||||||
|
|
||||||
// Expand nested macro instances by mapping their placeholder pin gates directly to the instantiated sub-macro pins.
|
|
||||||
for (int i = 0; i < definition.MacroInstances.Count; i++)
|
|
||||||
{
|
|
||||||
CircuitDefinition.MacroInstanceDef instanceDef = definition.MacroInstances[i];
|
|
||||||
MacroInstance instance = destination.AddMacroGate(instanceDef.Name);
|
|
||||||
|
|
||||||
if (instance.Inputs.Length != instanceDef.Inputs.Length || instance.Outputs.Length != instanceDef.Outputs.Length)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Macro instance '{instanceDef.Name}' pin counts do not match the referenced macro definition.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int p = 0; p < instanceDef.Inputs.Length; p++)
|
|
||||||
{
|
|
||||||
int gateId = instanceDef.Inputs[p];
|
|
||||||
if (map[gateId] != -1) throw new InvalidOperationException("Macro pin gate was mapped more than once.");
|
|
||||||
map[gateId] = instance.Inputs[p];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int p = 0; p < instanceDef.Outputs.Length; p++)
|
|
||||||
{
|
|
||||||
int gateId = instanceDef.Outputs[p];
|
|
||||||
if (map[gateId] != -1) throw new InvalidOperationException("Macro pin gate was mapped more than once.");
|
|
||||||
map[gateId] = instance.Outputs[p];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < gateCount; i++)
|
|
||||||
{
|
|
||||||
if (map[i] != -1)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
map[i] = destination.AddGate(mapKind(i, definition.GateKinds[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < definition.Connections.Count; i++)
|
|
||||||
{
|
|
||||||
(int from, int to, byte bit) = definition.Connections[i];
|
|
||||||
destination.ConnectGates(map[from], map[to], bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
namespace StoneRed.LogicSimulator.Test;
|
|
||||||
|
|
||||||
internal static class Program
|
|
||||||
{
|
|
||||||
public static void Main()
|
|
||||||
{
|
|
||||||
var sim = new ExprCircuitSimulator();
|
|
||||||
|
|
||||||
var inverter = new CircuitDefinition();
|
|
||||||
int invIn = inverter.AddInputPin(); // Source inside definition
|
|
||||||
int invNot = inverter.AddGate(GateKind.Not);
|
|
||||||
int invOut = inverter.AddOutputPin(); // Sink inside definition
|
|
||||||
inverter.Connect(invIn, invNot, toInputBit: 0);
|
|
||||||
inverter.Connect(invNot, invOut, toInputBit: 0);
|
|
||||||
|
|
||||||
sim.RegisterMacroGate("INV", inverter);
|
|
||||||
sim.ComputeLut("INV");
|
|
||||||
|
|
||||||
var doubleInverter = new CircuitDefinition();
|
|
||||||
int inv2In = doubleInverter.AddInputPin();
|
|
||||||
CircuitDefinition.MacroInstanceDef invA = doubleInverter.AddMacroInstance("INV", inputCount: 1, outputCount: 1);
|
|
||||||
CircuitDefinition.MacroInstanceDef invB = doubleInverter.AddMacroInstance("INV", inputCount: 1, outputCount: 1);
|
|
||||||
int inv2Out = doubleInverter.AddOutputPin();
|
|
||||||
doubleInverter.Connect(inv2In, invA.Inputs[0], toInputBit: 0);
|
|
||||||
doubleInverter.Connect(invA.Outputs[0], invB.Inputs[0], toInputBit: 0);
|
|
||||||
doubleInverter.Connect(invB.Outputs[0], inv2Out, toInputBit: 0);
|
|
||||||
|
|
||||||
sim.RegisterMacroGate("INV2", doubleInverter);
|
|
||||||
sim.ComputeLut("INV2");
|
|
||||||
|
|
||||||
int a = sim.AddGate(GateKind.Source);
|
|
||||||
ExprCircuitSimulator.MacroInstance inv2 = sim.AddMacroGate("INV2");
|
|
||||||
int lamp = sim.AddGate(GateKind.Sink);
|
|
||||||
|
|
||||||
sim.ConnectGates(a, inv2.Inputs[0], toInputBit: 0);
|
|
||||||
sim.ConnectGates(inv2.Outputs[0], lamp, toInputBit: 0);
|
|
||||||
|
|
||||||
int httpSink = sim.AddGate(GateKind.Sink);
|
|
||||||
sim.ConnectGates(inv2.Outputs[0], httpSink, toInputBit: 0);
|
|
||||||
using var httpWatcher = sim.WatchGate(httpSink, (gateId, mask) =>
|
|
||||||
{
|
|
||||||
if ((mask & 1) != 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"HTTP gate {gateId} fired at mask={mask:X}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sim.SetSource(a, value: false);
|
|
||||||
sim.RunUntilStable();
|
|
||||||
Console.WriteLine($"A=0 => Lamp={sim.GetOutput(lamp)} (expected False)");
|
|
||||||
|
|
||||||
sim.SetSource(a, value: true);
|
|
||||||
sim.RunUntilStable();
|
|
||||||
Console.WriteLine($"A=1 => Lamp={sim.GetOutput(lamp)} (expected True)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Tests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class CycleAdvancedTests : AdvancedCircuitTestsBase
|
||||||
|
{
|
||||||
|
protected override ICircuitSimulator CreateSimulator()
|
||||||
|
{
|
||||||
|
return new CycleCircuitSimulator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class EventAdvancedTests : AdvancedCircuitTestsBase
|
||||||
|
{
|
||||||
|
protected override ICircuitSimulator CreateSimulator()
|
||||||
|
{
|
||||||
|
return new EventCircuitSimulator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class AdvancedCircuitTestsBase
|
||||||
|
{
|
||||||
|
protected abstract ICircuitSimulator CreateSimulator();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestSRLatch()
|
||||||
|
{
|
||||||
|
// SR Latch using NOR gates
|
||||||
|
// Q = NOR(R, Q')
|
||||||
|
// Q' = NOR(S, Q)
|
||||||
|
ICircuitSimulator sim = CreateSimulator();
|
||||||
|
|
||||||
|
int s = sim.AddGate(GateKind.Source);
|
||||||
|
int r = sim.AddGate(GateKind.Source);
|
||||||
|
|
||||||
|
int orQ = sim.AddGate(GateKind.Or2);
|
||||||
|
int norQ = sim.AddGate(GateKind.Not); // Q
|
||||||
|
|
||||||
|
int orQNot = sim.AddGate(GateKind.Or2);
|
||||||
|
int norQNot = sim.AddGate(GateKind.Not); // Q'
|
||||||
|
|
||||||
|
// Q = NOR(R, Q')
|
||||||
|
sim.ConnectGates(r, orQ, 0);
|
||||||
|
sim.ConnectGates(norQNot, orQ, 1);
|
||||||
|
sim.ConnectGates(orQ, norQ, 0);
|
||||||
|
|
||||||
|
// Q' = NOR(S, Q)
|
||||||
|
sim.ConnectGates(s, orQNot, 0);
|
||||||
|
sim.ConnectGates(norQ, orQNot, 1);
|
||||||
|
sim.ConnectGates(orQNot, norQNot, 0);
|
||||||
|
|
||||||
|
int qSink = sim.AddGate(GateKind.Sink);
|
||||||
|
int qNotSink = sim.AddGate(GateKind.Sink);
|
||||||
|
sim.ConnectGates(norQ, qSink, 0);
|
||||||
|
sim.ConnectGates(norQNot, qNotSink, 0);
|
||||||
|
|
||||||
|
// Reset state (R=1, S=0) -> Q=0, Q'=1
|
||||||
|
sim.SetSource(r, true);
|
||||||
|
sim.SetSource(s, false);
|
||||||
|
_ = sim.RunUntilStable(10000);
|
||||||
|
Assert.IsFalse(sim.GetOutput(qSink), "Q should be 0 after Reset (R=1)");
|
||||||
|
Assert.IsTrue(sim.GetOutput(qNotSink), "Q' should be 1 after Reset (R=1)");
|
||||||
|
|
||||||
|
// Hold (R=0, S=0) -> Q=0
|
||||||
|
sim.SetSource(r, false);
|
||||||
|
_ = sim.RunUntilStable(10000);
|
||||||
|
Assert.IsFalse(sim.GetOutput(qSink), "Q should stay 0");
|
||||||
|
|
||||||
|
// Set state (R=0, S=1) -> Q=1, Q'=0
|
||||||
|
sim.SetSource(s, true);
|
||||||
|
_ = sim.RunUntilStable(10000);
|
||||||
|
Assert.IsTrue(sim.GetOutput(qSink), "Q should be 1 after Set (S=1)");
|
||||||
|
Assert.IsFalse(sim.GetOutput(qNotSink), "Q' should be 0 after Set (S=1)");
|
||||||
|
|
||||||
|
// Hold (R=0, S=0) -> Q=1
|
||||||
|
sim.SetSource(s, false);
|
||||||
|
_ = sim.RunUntilStable(10000);
|
||||||
|
Assert.IsTrue(sim.GetOutput(qSink), "Q should stay 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestFullAdder()
|
||||||
|
{
|
||||||
|
ICircuitSimulator sim = CreateSimulator();
|
||||||
|
|
||||||
|
int a = sim.AddGate(GateKind.Source);
|
||||||
|
int b = sim.AddGate(GateKind.Source);
|
||||||
|
int cin = sim.AddGate(GateKind.Source);
|
||||||
|
|
||||||
|
int[] xorTable = { 0, 1, 1, 0 };
|
||||||
|
int xor1 = sim.AddLutGate(2, xorTable);
|
||||||
|
int xor2 = sim.AddLutGate(2, xorTable);
|
||||||
|
|
||||||
|
int and1 = sim.AddGate(GateKind.And2);
|
||||||
|
int and2 = sim.AddGate(GateKind.And2);
|
||||||
|
int or1 = sim.AddGate(GateKind.Or2);
|
||||||
|
|
||||||
|
sim.ConnectGates(a, xor1, 0);
|
||||||
|
sim.ConnectGates(b, xor1, 1);
|
||||||
|
|
||||||
|
sim.ConnectGates(xor1, xor2, 0);
|
||||||
|
sim.ConnectGates(cin, xor2, 1);
|
||||||
|
|
||||||
|
sim.ConnectGates(a, and1, 0);
|
||||||
|
sim.ConnectGates(b, and1, 1);
|
||||||
|
|
||||||
|
sim.ConnectGates(xor1, and2, 0);
|
||||||
|
sim.ConnectGates(cin, and2, 1);
|
||||||
|
|
||||||
|
sim.ConnectGates(and1, or1, 0);
|
||||||
|
sim.ConnectGates(and2, or1, 1);
|
||||||
|
|
||||||
|
int sumSink = sim.AddGate(GateKind.Sink);
|
||||||
|
int coutSink = sim.AddGate(GateKind.Sink);
|
||||||
|
sim.ConnectGates(xor2, sumSink, 0);
|
||||||
|
sim.ConnectGates(or1, coutSink, 0);
|
||||||
|
|
||||||
|
void Check(bool iA, bool iB, bool iC, bool expectedSum, bool expectedCout)
|
||||||
|
{
|
||||||
|
sim.SetSource(a, iA);
|
||||||
|
sim.SetSource(b, iB);
|
||||||
|
sim.SetSource(cin, iC);
|
||||||
|
_ = sim.RunUntilStable(10000);
|
||||||
|
Assert.AreEqual(expectedSum, sim.GetOutput(sumSink), $"Sum failed for {iA},{iB},{iC}");
|
||||||
|
Assert.AreEqual(expectedCout, sim.GetOutput(coutSink), $"Cout failed for {iA},{iB},{iC}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Check(false, false, false, false, false);
|
||||||
|
Check(true, false, false, true, false);
|
||||||
|
Check(false, true, false, true, false);
|
||||||
|
Check(true, true, false, false, true);
|
||||||
|
Check(false, false, true, true, false);
|
||||||
|
Check(true, false, true, false, true);
|
||||||
|
Check(false, true, true, false, true);
|
||||||
|
Check(true, true, true, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestDLatch()
|
||||||
|
{
|
||||||
|
ICircuitSimulator sim = CreateSimulator();
|
||||||
|
|
||||||
|
int dSource = sim.AddGate(GateKind.Source);
|
||||||
|
int enSource = sim.AddGate(GateKind.Source);
|
||||||
|
|
||||||
|
int notD = sim.AddGate(GateKind.Not);
|
||||||
|
sim.ConnectGates(dSource, notD, 0);
|
||||||
|
|
||||||
|
int sAnd = sim.AddGate(GateKind.And2);
|
||||||
|
sim.ConnectGates(dSource, sAnd, 0);
|
||||||
|
sim.ConnectGates(enSource, sAnd, 1);
|
||||||
|
|
||||||
|
int rAnd = sim.AddGate(GateKind.And2);
|
||||||
|
sim.ConnectGates(notD, rAnd, 0);
|
||||||
|
sim.ConnectGates(enSource, rAnd, 1);
|
||||||
|
|
||||||
|
int orQ = sim.AddGate(GateKind.Or2);
|
||||||
|
int norQ = sim.AddGate(GateKind.Not);
|
||||||
|
|
||||||
|
int orQNot = sim.AddGate(GateKind.Or2);
|
||||||
|
int norQNot = sim.AddGate(GateKind.Not);
|
||||||
|
|
||||||
|
sim.ConnectGates(rAnd, orQ, 0);
|
||||||
|
sim.ConnectGates(norQNot, orQ, 1);
|
||||||
|
sim.ConnectGates(orQ, norQ, 0);
|
||||||
|
|
||||||
|
sim.ConnectGates(sAnd, orQNot, 0);
|
||||||
|
sim.ConnectGates(norQ, orQNot, 1);
|
||||||
|
sim.ConnectGates(orQNot, norQNot, 0);
|
||||||
|
|
||||||
|
int qSink = sim.AddGate(GateKind.Sink);
|
||||||
|
sim.ConnectGates(norQ, qSink, 0);
|
||||||
|
|
||||||
|
// 1. Transparent mode (EN=1) -> Q follows D
|
||||||
|
sim.SetSource(enSource, true);
|
||||||
|
sim.SetSource(dSource, true);
|
||||||
|
_ = sim.RunUntilStable(10000);
|
||||||
|
Assert.IsTrue(sim.GetOutput(qSink), "Q should be 1 when D=1, EN=1");
|
||||||
|
|
||||||
|
sim.SetSource(dSource, false);
|
||||||
|
_ = sim.RunUntilStable(10000);
|
||||||
|
Assert.IsFalse(sim.GetOutput(qSink), "Q should be 0 when D=0, EN=1");
|
||||||
|
|
||||||
|
// 2. Latch mode (EN=0) -> Q stays same
|
||||||
|
sim.SetSource(dSource, true);
|
||||||
|
sim.SetSource(enSource, true);
|
||||||
|
_ = sim.RunUntilStable(10000);
|
||||||
|
Assert.IsTrue(sim.GetOutput(qSink));
|
||||||
|
|
||||||
|
sim.SetSource(enSource, false); // LATCH
|
||||||
|
_ = sim.RunUntilStable(10000);
|
||||||
|
|
||||||
|
sim.SetSource(dSource, false); // Change D while latched
|
||||||
|
_ = sim.RunUntilStable(10000);
|
||||||
|
Assert.IsTrue(sim.GetOutput(qSink), "Q should stay 1 even if D changes while EN=0");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
using StoneRed.LogicSimulator.Simulation;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Tests;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class CycleSimulatorTests : SimulatorTestsBase
|
||||||
|
{
|
||||||
|
protected override ICircuitSimulator CreateSimulator()
|
||||||
|
{
|
||||||
|
return new CycleCircuitSimulator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class EventSimulatorTests : SimulatorTestsBase
|
||||||
|
{
|
||||||
|
protected override ICircuitSimulator CreateSimulator()
|
||||||
|
{
|
||||||
|
return new EventCircuitSimulator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class SimulatorTestsBase
|
||||||
|
{
|
||||||
|
protected abstract ICircuitSimulator CreateSimulator();
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestAndGate()
|
||||||
|
{
|
||||||
|
ICircuitSimulator sim = CreateSimulator();
|
||||||
|
int s1 = sim.AddGate(GateKind.Source);
|
||||||
|
int s2 = sim.AddGate(GateKind.Source);
|
||||||
|
int and = sim.AddGate(GateKind.And2);
|
||||||
|
int sink = sim.AddGate(GateKind.Sink);
|
||||||
|
sim.ConnectGates(s1, and, 0);
|
||||||
|
sim.ConnectGates(s2, and, 1);
|
||||||
|
sim.ConnectGates(and, sink, 0);
|
||||||
|
|
||||||
|
void Check(bool i1, bool i2, bool expected)
|
||||||
|
{
|
||||||
|
sim.SetSource(s1, i1);
|
||||||
|
sim.SetSource(s2, i2);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.AreEqual(expected, sim.GetOutput(sink), $"AND2 failed for {i1} & {i2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
sim.Reset();
|
||||||
|
Check(false, false, false);
|
||||||
|
Check(false, true, false);
|
||||||
|
Check(true, false, false);
|
||||||
|
Check(true, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestOrGate()
|
||||||
|
{
|
||||||
|
ICircuitSimulator sim = CreateSimulator();
|
||||||
|
int s1 = sim.AddGate(GateKind.Source);
|
||||||
|
int s2 = sim.AddGate(GateKind.Source);
|
||||||
|
int or = sim.AddGate(GateKind.Or2);
|
||||||
|
int sink = sim.AddGate(GateKind.Sink);
|
||||||
|
sim.ConnectGates(s1, or, 0);
|
||||||
|
sim.ConnectGates(s2, or, 1);
|
||||||
|
sim.ConnectGates(or, sink, 0);
|
||||||
|
|
||||||
|
void Check(bool i1, bool i2, bool expected)
|
||||||
|
{
|
||||||
|
sim.SetSource(s1, i1);
|
||||||
|
sim.SetSource(s2, i2);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.AreEqual(expected, sim.GetOutput(sink), $"OR2 failed for {i1} | {i2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
sim.Reset();
|
||||||
|
Check(false, false, false);
|
||||||
|
Check(false, true, true);
|
||||||
|
Check(true, false, true);
|
||||||
|
Check(true, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestNotGate()
|
||||||
|
{
|
||||||
|
ICircuitSimulator sim = CreateSimulator();
|
||||||
|
int s1 = sim.AddGate(GateKind.Source);
|
||||||
|
int not = sim.AddGate(GateKind.Not);
|
||||||
|
int sink = sim.AddGate(GateKind.Sink);
|
||||||
|
sim.ConnectGates(s1, not, 0);
|
||||||
|
sim.ConnectGates(not, sink, 0);
|
||||||
|
|
||||||
|
sim.Reset();
|
||||||
|
sim.SetSource(s1, false);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.IsTrue(sim.GetOutput(sink), "NOT(0) should be 1");
|
||||||
|
|
||||||
|
sim.SetSource(s1, true);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.IsFalse(sim.GetOutput(sink), "NOT(1) should be 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestLutGate()
|
||||||
|
{
|
||||||
|
ICircuitSimulator sim = CreateSimulator();
|
||||||
|
int s1 = sim.AddGate(GateKind.Source);
|
||||||
|
int s2 = sim.AddGate(GateKind.Source);
|
||||||
|
int s3 = sim.AddGate(GateKind.Source);
|
||||||
|
|
||||||
|
// Majority function (2 or more high)
|
||||||
|
int[] table = { 0, 0, 0, 1, 0, 1, 1, 1 };
|
||||||
|
int lut = sim.AddLutGate(3, table);
|
||||||
|
int sink = sim.AddGate(GateKind.Sink);
|
||||||
|
|
||||||
|
sim.ConnectGates(s1, lut, 0);
|
||||||
|
sim.ConnectGates(s2, lut, 1);
|
||||||
|
sim.ConnectGates(s3, lut, 2);
|
||||||
|
sim.ConnectGates(lut, sink, 0);
|
||||||
|
|
||||||
|
sim.Reset();
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
|
||||||
|
sim.SetSource(s1, true); sim.SetSource(s2, true); sim.SetSource(s3, false);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.IsTrue(sim.GetOutput(sink), "Majority(1,1,0) should be 1");
|
||||||
|
|
||||||
|
sim.SetSource(s1, false); sim.SetSource(s2, true); sim.SetSource(s3, false);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.IsFalse(sim.GetOutput(sink), "Majority(0,1,0) should be 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestMacroCorrectness()
|
||||||
|
{
|
||||||
|
ICircuitSimulator sim = CreateSimulator();
|
||||||
|
CircuitDefinition def = new CircuitDefinition();
|
||||||
|
int inPin = def.AddInputPin();
|
||||||
|
int not = def.AddGate(GateKind.Not);
|
||||||
|
int outPin = def.AddOutputPin();
|
||||||
|
def.Connect(inPin, not, 0);
|
||||||
|
def.Connect(not, outPin, 0);
|
||||||
|
|
||||||
|
sim.RegisterMacroGate("NOT", def);
|
||||||
|
MacroInstance inst = sim.AddMacroGate("NOT");
|
||||||
|
|
||||||
|
int src = sim.AddGate(GateKind.Source);
|
||||||
|
int sink = sim.AddGate(GateKind.Sink);
|
||||||
|
sim.ConnectGates(src, inst.Inputs[0], 0);
|
||||||
|
sim.ConnectGates(inst.Outputs[0], sink, 0);
|
||||||
|
|
||||||
|
sim.Reset();
|
||||||
|
sim.SetSource(src, true);
|
||||||
|
_ = sim.RunUntilStable();
|
||||||
|
Assert.IsFalse(sim.GetOutput(sink), "Macro NOT(1) should be 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
|
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||||
|
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\StoneRed.LogicSimulator.Simulation\StoneRed.LogicSimulator.Simulation.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 18
|
# Visual Studio Version 18
|
||||||
VisualStudioVersion = 18.4.11605.240 stable
|
VisualStudioVersion = 18.4.11605.240
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StoneRed.LogicSimulator", "StoneRed.LogicSimulator\StoneRed.LogicSimulator.csproj", "{7B259CCA-2947-4DF8-8252-7CF75FE39B8E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StoneRed.LogicSimulator", "StoneRed.LogicSimulator\StoneRed.LogicSimulator.csproj", "{7B259CCA-2947-4DF8-8252-7CF75FE39B8E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Api", "StoneRed.LogicSimulator.Api\StoneRed.LogicSimulator.Api.csproj", "{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Api", "StoneRed.LogicSimulator.Api\StoneRed.LogicSimulator.Api.csproj", "{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Test", "StoneRed.LogicSimulator.Test\StoneRed.LogicSimulator.Test.csproj", "{996500F0-3CD5-912F-03D5-25011C2BD106}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Simulation", "StoneRed.LogicSimulator.Simulation\StoneRed.LogicSimulator.Simulation.csproj", "{08D6072E-CEC7-FA4B-00E7-C0C88E8C5A38}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Benchmarks", "StoneRed.LogicSimulator.Benchmarks\StoneRed.LogicSimulator.Benchmarks.csproj", "{7F65C796-E597-2D31-BA0C-EE9A672A68EB}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StoneRed.LogicSimulator.Tests", "StoneRed.LogicSimulator.Tests\StoneRed.LogicSimulator.Tests.csproj", "{82A09AEE-FA5E-7E76-29A2-4FB1B78E3CD5}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@@ -23,10 +27,18 @@ Global
|
|||||||
{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}.Release|Any CPU.Build.0 = Release|Any CPU
|
{CD9C3C4D-4AF1-4A31-BDED-34CB91503B4C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{996500F0-3CD5-912F-03D5-25011C2BD106}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{08D6072E-CEC7-FA4B-00E7-C0C88E8C5A38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{996500F0-3CD5-912F-03D5-25011C2BD106}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{08D6072E-CEC7-FA4B-00E7-C0C88E8C5A38}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{996500F0-3CD5-912F-03D5-25011C2BD106}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{08D6072E-CEC7-FA4B-00E7-C0C88E8C5A38}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{996500F0-3CD5-912F-03D5-25011C2BD106}.Release|Any CPU.Build.0 = Release|Any CPU
|
{08D6072E-CEC7-FA4B-00E7-C0C88E8C5A38}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7F65C796-E597-2D31-BA0C-EE9A672A68EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7F65C796-E597-2D31-BA0C-EE9A672A68EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7F65C796-E597-2D31-BA0C-EE9A672A68EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7F65C796-E597-2D31-BA0C-EE9A672A68EB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{82A09AEE-FA5E-7E76-29A2-4FB1B78E3CD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{82A09AEE-FA5E-7E76-29A2-4FB1B78E3CD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{82A09AEE-FA5E-7E76-29A2-4FB1B78E3CD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{82A09AEE-FA5E-7E76-29A2-4FB1B78E3CD5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Reference in New Issue
Block a user