mirror of
https://github.com/Stone-Red-Code/StoneRed.LogicSimulator.git
synced 2026-09-07 07:55:58 +02:00
Add experimental new logic simulator based on compiled expression trees
This commit is contained in:
@@ -0,0 +1,177 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace StoneRed.LogicSimulator.Test;
|
||||||
|
|
||||||
|
public sealed class CircuitDefinition
|
||||||
|
{
|
||||||
|
private readonly List<GateKind> gateKinds = new();
|
||||||
|
private readonly List<(int FromGate, int ToGate, byte ToInputBit)> connections = new();
|
||||||
|
private readonly List<int> inputPins = new();
|
||||||
|
private readonly List<int> outputPins = new();
|
||||||
|
private readonly List<MacroInstanceDef> macroInstances = new();
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputCount < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(inputCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputCount < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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()
|
||||||
|
{
|
||||||
|
// Minimal validation to prevent ambiguous semantics.
|
||||||
|
// - 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++)
|
||||||
|
{
|
||||||
|
(_, int to, _) = connections[i];
|
||||||
|
hasIncoming[to] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputPinSet = new HashSet<int>(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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var macroPinGates = new HashSet<int>();
|
||||||
|
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,618 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bool TryRunUntilStable(int maxSteps, out int steps)
|
||||||
|
{
|
||||||
|
steps = 0;
|
||||||
|
if (maxSteps <= 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCompiled();
|
||||||
|
|
||||||
|
while (steps < maxSteps)
|
||||||
|
{
|
||||||
|
steps++;
|
||||||
|
computeOutputs!(inputMasks, outputMasks, sourceStates);
|
||||||
|
if (!PropagateAndSwapDetectChange())
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
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,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user