Sim Circuit is a Rust crate that provides a generic circuit model along with predefined structures to build circuits with custom execution over custom values.
The model views a circuit as a set of components, each with a custom execution, that operate over a linear set of slots defined as a memory.
Defines a memory structure with read and write operations.
pub trait Memory<T> {
type Error;
/// Reads a value from the memory at the specified index.
fn read(&self, index: usize) -> Result<T, Self::Error>;
/// Writes a value to the memory at the specified index.
fn write(&mut self, index: usize, value: T) -> Result<(), Self::Error>;
}
Defines a component structure with input and output nodes.
pub trait Component {
/// Returns the indices of the input nodes.
fn inputs(&self) -> &[usize];
/// Returns the indices of the output nodes.
fn outputs(&self) -> &[usize];
/// Sets the input nodes.
fn set_inputs(&mut self, inputs: Vec<usize>);
/// Sets the output nodes.
fn set_outputs(&mut self, outputs: Vec<usize>);
}
Defines the execution of a component over a custom memory.
pub trait Executable<T, U: Memory<T>>: Component {
type Error;
/// Executes the component using the provided memory.
fn execute(&self, memory: &mut U) -> Result<(), Self::Error>;
}
These are the provided structures to help build circuits with the model.
In the CircuitMemory
, T
is the memory value type. In the rest of the components, T
is the component type T
and U
the memory value type.
A storage structure to keep track of the values of all nodes in the circuit during execution.
pub struct CircuitMemory<T> {
wires: Vec<Option<T>>,
}
Used to create and configure a circuit by adding components and specifying input nodes.
pub struct CircuitBuilder<T, U> {
components: Vec<T>,
circuit_inputs: Vec<usize>,
component_inputs: HashSet<usize>,
component_outputs: HashSet<usize>,
index_map: HashMap<usize, usize>,
next_index: usize,
_phantom: PhantomData<U>,
}
Represents the entire circuit with a defined execution order. It also implements the Component
trait, allowing it to be part of another circuit.
pub struct GenericCircuit<T, U> {
components: Vec<T>,
inputs: Vec<usize>,
outputs: Vec<usize>,
memory_map: HashMap<usize, usize>,
_phantom: PhantomData<U>,
}
Used to execute the circuit over a memory with specific input values.
pub struct GenericCircuitExecutor<T, U> {
circuit: GenericCircuit<T, U>,
memory: CircuitMemory<U>,
}
Let's see how to use the provided structures.
First, you need to define your custom components by implementing the Component
and Executable
traits.
pub enum BinaryOperation {
AND,
XOR,
}
pub struct BinaryGate {
op: BinaryOperation,
inputs: Vec<usize>,
outputs: Vec<usize>,
}
impl Component for BinaryGate {
fn inputs(&self) -> &[usize] {
&self.inputs
}
fn outputs(&self) -> &[usize] {
&self.outputs
}
fn set_inputs(&mut self, inputs: Vec<usize>) {
self.inputs = inputs;
}
fn set_outputs(&mut self, outputs: Vec<usize>) {
self.outputs = outputs;
}
}
impl Executable<bool, CircuitMemory<bool>> for BinaryGate {
type Error = ();
fn execute(
&self,
memory: &mut CircuitMemory<bool>,
) -> Result<(), Self::Error> {
let a = memory.read(self.inputs[0]).unwrap();
let b = memory.read(self.inputs[1]).unwrap();
let result = match self.op {
BinaryOperation::AND => a && b,
BinaryOperation::XOR => a ^ b,
};
memory.write(self.outputs[0], result).unwrap();
Ok(())
}
}
Use CircuitBuilder
to create and configure your circuit by adding components and specifying input nodes.
let mut builder = CircuitBuilder::<BinaryGate, bool>::new();
builder.add_inputs(&[0, 1]);
Add your custom components to the circuit builder.
let and_gate = BinaryGate {
op: BinaryOperation::AND,
inputs: vec![0, 1],
outputs: vec![2],
};
builder.add_component(and_gate).unwrap();
Build the circuit using the circuit builder.
let circuit = builder.build().unwrap();
Use GenericCircuitExecutor
to execute the circuit over a memory with specific input values.
let mut executor = GenericCircuitExecutor::new(circuit);
Execute the circuit with specific input values.
let input_values = HashMap::from([(0, true), (1, false)]);
let output = executor.run(&input_values).unwrap();