use crate::ir::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
));
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::Popcount { id, input } => {
let inp = value_wire(graph, *input);
mlir.push_str(&format!(" %v{} = comb.popcount {inp} : i64\n", id.0));
}
ScOp::GraphForward { .. }
| ScOp::SoftmaxAttention { .. }
| ScOp::KuramotoStep { .. } => {
return Err(format!("MLIR emission for {:?} requires HLS backend", op));
}
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; }
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,));
}
}
}
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"));
}
}