sc_neurocore_engine 3.15.19

High-performance SIMD backend for SC-NeuroCore stochastic neuromorphic computing
Documentation
// SPDX-License-Identifier: AGPL-3.0-or-later
// Commercial license available
// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
// © Code 2020–2026 Miroslav Šotek. All rights reserved.
// ORCID: 0009-0009-3560-0851
// Contact: www.anulum.li | protoscience@anulum.li
// SC-NeuroCore — MLIR CIRCT emitter for SC IR graphs

//! MLIR CIRCT emitter for SC IR graphs.
//!
//! Produces CIRCT-compatible MLIR text using hw/comb/seq dialects.

use crate::ir::graph::*;

/// Emit CIRCT-compatible MLIR from an SC graph.
pub fn emit(graph: &ScGraph) -> Result<String, String> {
    let mut mlir = String::new();

    mlir.push_str(&format!(
        "// Auto-generated by SC-NeuroCore MLIR Emitter v3.11\n\
         // Source graph: {}\n\n",
        graph.name
    ));

    // Module header with ports
    let mut ports = Vec::new();
    ports.push("in %clk: i1".to_string());
    ports.push("in %rst_n: i1".to_string());

    for op in &graph.ops {
        match op {
            ScOp::Input { name, .. } => {
                ports.push(format!("in %{name}: i1"));
            }
            ScOp::Output { name, .. } => {
                ports.push(format!("out {name}: i1"));
            }
            _ => {}
        }
    }

    mlir.push_str(&format!(
        "hw.module @{}({}) {{\n",
        graph.name,
        ports.join(", ")
    ));

    let mut last_output = String::from("%clk");

    for op in &graph.ops {
        match op {
            ScOp::Input { .. } => {}
            ScOp::Output { name, source, .. } => {
                let src = value_wire(graph, *source);
                mlir.push_str(&format!("  hw.output {src} : i1\n"));
                last_output = format!("%{name}");
            }
            ScOp::Encode {
                id, prob, seed: _, ..
            } => {
                let prob_wire = value_wire(graph, *prob);
                mlir.push_str(&format!(
                    "  %v{} = hw.instance \"enc_{}\" @sc_bitstream_encoder(\
                     clk: %clk: i1, rst_n: %rst_n: i1, x_value: {}: i1) -> (bit_out: i1)\n",
                    id.0, id.0, prob_wire
                ));
                last_output = format!("%v{}", id.0);
            }
            ScOp::BitwiseAnd { id, lhs, rhs } => {
                let l = value_wire(graph, *lhs);
                let r = value_wire(graph, *rhs);
                mlir.push_str(&format!("  %v{} = comb.and {l}, {r} : i1\n", id.0));
                last_output = format!("%v{}", id.0);
            }
            ScOp::BitwiseXor { id, lhs, rhs } => {
                let l = value_wire(graph, *lhs);
                let r = value_wire(graph, *rhs);
                mlir.push_str(&format!("  %v{} = comb.xor {l}, {r} : i1\n", id.0));
                last_output = format!("%v{}", id.0);
            }
            ScOp::Constant { id, value, .. } => {
                let val = match value {
                    ScConst::I64(v) => format!("{v}"),
                    ScConst::U64(v) => format!("{v}"),
                    ScConst::F64(v) => format!("{}", (*v * 256.0) as i64),
                    _ => "0".to_string(),
                };
                mlir.push_str(&format!("  %c{} = hw.constant {} : i16\n", id.0, val));
            }
            ScOp::LifStep {
                id,
                current,
                params,
                ..
            } => {
                let cur = value_wire(graph, *current);
                mlir.push_str(&format!(
                    "  %v{id}_spike, %v{id}_v = hw.instance \"lif_{id}\" \
                     @sc_lif_neuron<DATA_WIDTH: i32 = {dw}, V_THRESHOLD: i32 = {vt}>(\
                     clk: %clk: i1, rst_n: %rst_n: i1, I_t: {cur}: i1) -> \
                     (spike_out: i1, v_out: i{dw})\n",
                    id = id.0,
                    dw = params.data_width,
                    vt = params.v_threshold,
                    cur = cur,
                ));
                last_output = format!("%v{}_spike", id.0);
            }
            ScOp::DenseForward { id, params, .. } => {
                mlir.push_str(&format!(
                    "  %v{} = hw.instance \"dense_{}\" @sc_dense_layer_core<\
                     N_INPUTS: i32 = {}, N_NEURONS: i32 = {}>(\
                     clk: %clk: i1, rst_n: %rst_n: i1) -> (spikes: i{})\n",
                    id.0, id.0, params.n_inputs, params.n_neurons, params.n_neurons
                ));
                last_output = format!("%v{}", id.0);
            }
            ScOp::DclsLayer { id, params, .. } => {
                mlir.push_str(&format!(
                    "  %v{} = hw.instance \"dcls_{}\" @sc_dcls_layer_core<\
                     N_TAPS: i32 = {}, DATA_WIDTH: i32 = {}, FRACTION: i32 = {}>(\
                     clk: %clk: i1, rst_n: %rst_n: i1) -> (weighted_sum_q88: i{})\n",
                    id.0,
                    id.0,
                    params.n_taps,
                    params.data_width,
                    params.fraction,
                    params.data_width
                ));
                last_output = format!("%v{}", id.0);
            }
            ScOp::Popcount { id, input } => {
                let inp = value_wire(graph, *input);
                mlir.push_str(&format!("  %v{} = comb.popcount {inp} : i64\n", id.0));
            }
            ScOp::GraphForward { id, features, .. } => {
                let inp = value_wire(graph, *features);
                mlir.push_str(&format!(
                    "  // GraphForward: SC AND-popcount aggregation\n  %v{} = {inp} : i64\n",
                    id.0
                ));
            }
            ScOp::SoftmaxAttention { id, v, .. } => {
                let inp = value_wire(graph, *v);
                mlir.push_str(&format!(
                    "  // SoftmaxAttention: SC bitstream attention\n  %v{} = {inp} : i64\n",
                    id.0
                ));
            }
            ScOp::KuramotoStep { id, phases, .. } => {
                let inp = value_wire(graph, *phases);
                mlir.push_str(&format!(
                    "  // KuramotoStep: phase accumulator with coupling\n  %v{} = {inp} : i64\n",
                    id.0
                ));
            }
            ScOp::Scale { id, input, factor } => {
                let inp = value_wire(graph, *input);
                let scale_int = (*factor * 256.0) as i64;
                mlir.push_str(&format!(
                    "  %v{} = comb.mul {inp}, %c_scale_{id} : i16\n",
                    id.0,
                    inp = inp,
                    id = id.0,
                ));
                let _ = scale_int; // used in constant emission
            }
            ScOp::Offset { id, input, offset } => {
                let inp = value_wire(graph, *input);
                let _ = offset;
                mlir.push_str(&format!(
                    "  %v{} = comb.add {inp}, %c_off_{id} : i16\n",
                    id.0,
                    inp = inp,
                    id = id.0,
                ));
            }
            ScOp::DivConst { id, input, divisor } => {
                let inp = value_wire(graph, *input);
                let _ = divisor;
                mlir.push_str(&format!(
                    "  %v{} = comb.divu {inp}, %c_div_{id} : i16\n",
                    id.0,
                    inp = inp,
                    id = id.0,
                ));
            }
            ScOp::Reduce { id, input, mode } => {
                let inp = value_wire(graph, *input);
                let op_name = match mode {
                    ReduceMode::Sum => "add",
                    ReduceMode::Max => "max",
                };
                mlir.push_str(&format!("  %v{} = comb.{op_name} {inp} : i64\n", id.0,));
            }
        }
    }

    // Close module (output already emitted in the Output arm)
    mlir.push_str("}\n");
    let _ = last_output;
    Ok(mlir)
}

fn value_wire(graph: &ScGraph, id: ValueId) -> String {
    for op in &graph.ops {
        if op.result_id() == id {
            return match op {
                ScOp::Input { name, .. } => format!("%{name}"),
                ScOp::Constant { id, .. } => format!("%c{}", id.0),
                ScOp::LifStep { id, .. } => format!("%v{}_spike", id.0),
                ScOp::DenseForward { id, .. } => format!("%v{}", id.0),
                _ => format!("%v{}", id.0),
            };
        }
    }
    format!("%v{}", id.0)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ir::builder::ScGraphBuilder;

    #[test]
    fn basic_and_gate_emits_mlir() {
        let mut b = ScGraphBuilder::new("test_and");
        let a = b.input("a", ScType::Bool);
        let bv = b.input("b", ScType::Bool);
        let c = b.bitwise_and(a, bv);
        b.output("out", c);
        let graph = b.build();

        let mlir = emit(&graph).unwrap();
        assert!(mlir.contains("hw.module @test_and"));
        assert!(mlir.contains("comb.and"));
        assert!(mlir.contains("hw.output"));
    }

    #[test]
    fn module_wrapping() {
        let mut b = ScGraphBuilder::new("wrapper");
        let x = b.input("x", ScType::Bool);
        b.output("y", x);
        let graph = b.build();

        let mlir = emit(&graph).unwrap();
        assert!(mlir.starts_with("// Auto-generated"));
        assert!(mlir.contains("hw.module @wrapper"));
        assert!(mlir.ends_with("}\n"));
    }

    #[test]
    fn xor_gate() {
        let mut b = ScGraphBuilder::new("test_xor");
        let a = b.input("a", ScType::Bool);
        let bv = b.input("b", ScType::Bool);
        let c = b.bitwise_xor(a, bv);
        b.output("out", c);
        let graph = b.build();

        let mlir = emit(&graph).unwrap();
        assert!(mlir.contains("comb.xor"));
    }
}