1use crate::ir::graph::*;
14
15pub fn emit(graph: &ScGraph) -> Result<String, String> {
17 let mut mlir = String::new();
18
19 mlir.push_str(&format!(
20 "// Auto-generated by SC-NeuroCore MLIR Emitter v3.11\n\
21 // Source graph: {}\n\n",
22 graph.name
23 ));
24
25 let mut ports = Vec::new();
27 ports.push("in %clk: i1".to_string());
28 ports.push("in %rst_n: i1".to_string());
29
30 for op in &graph.ops {
31 match op {
32 ScOp::Input { name, .. } => {
33 ports.push(format!("in %{name}: i1"));
34 }
35 ScOp::Output { name, .. } => {
36 ports.push(format!("out {name}: i1"));
37 }
38 _ => {}
39 }
40 }
41
42 mlir.push_str(&format!(
43 "hw.module @{}({}) {{\n",
44 graph.name,
45 ports.join(", ")
46 ));
47
48 let mut last_output = String::from("%clk");
49
50 for op in &graph.ops {
51 match op {
52 ScOp::Input { .. } => {}
53 ScOp::Output { name, source, .. } => {
54 let src = value_wire(graph, *source);
55 mlir.push_str(&format!(" hw.output {src} : i1\n"));
56 last_output = format!("%{name}");
57 }
58 ScOp::Encode {
59 id, prob, seed: _, ..
60 } => {
61 let prob_wire = value_wire(graph, *prob);
62 mlir.push_str(&format!(
63 " %v{} = hw.instance \"enc_{}\" @sc_bitstream_encoder(\
64 clk: %clk: i1, rst_n: %rst_n: i1, x_value: {}: i1) -> (bit_out: i1)\n",
65 id.0, id.0, prob_wire
66 ));
67 last_output = format!("%v{}", id.0);
68 }
69 ScOp::BitwiseAnd { id, lhs, rhs } => {
70 let l = value_wire(graph, *lhs);
71 let r = value_wire(graph, *rhs);
72 mlir.push_str(&format!(" %v{} = comb.and {l}, {r} : i1\n", id.0));
73 last_output = format!("%v{}", id.0);
74 }
75 ScOp::BitwiseXor { id, lhs, rhs } => {
76 let l = value_wire(graph, *lhs);
77 let r = value_wire(graph, *rhs);
78 mlir.push_str(&format!(" %v{} = comb.xor {l}, {r} : i1\n", id.0));
79 last_output = format!("%v{}", id.0);
80 }
81 ScOp::Constant { id, value, .. } => {
82 let val = match value {
83 ScConst::I64(v) => format!("{v}"),
84 ScConst::U64(v) => format!("{v}"),
85 ScConst::F64(v) => format!("{}", (*v * 256.0) as i64),
86 _ => "0".to_string(),
87 };
88 mlir.push_str(&format!(" %c{} = hw.constant {} : i16\n", id.0, val));
89 }
90 ScOp::LifStep {
91 id,
92 current,
93 params,
94 ..
95 } => {
96 let cur = value_wire(graph, *current);
97 mlir.push_str(&format!(
98 " %v{id}_spike, %v{id}_v = hw.instance \"lif_{id}\" \
99 @sc_lif_neuron<DATA_WIDTH: i32 = {dw}, V_THRESHOLD: i32 = {vt}>(\
100 clk: %clk: i1, rst_n: %rst_n: i1, I_t: {cur}: i1) -> \
101 (spike_out: i1, v_out: i{dw})\n",
102 id = id.0,
103 dw = params.data_width,
104 vt = params.v_threshold,
105 cur = cur,
106 ));
107 last_output = format!("%v{}_spike", id.0);
108 }
109 ScOp::DenseForward { id, params, .. } => {
110 mlir.push_str(&format!(
111 " %v{} = hw.instance \"dense_{}\" @sc_dense_layer_core<\
112 N_INPUTS: i32 = {}, N_NEURONS: i32 = {}>(\
113 clk: %clk: i1, rst_n: %rst_n: i1) -> (spikes: i{})\n",
114 id.0, id.0, params.n_inputs, params.n_neurons, params.n_neurons
115 ));
116 last_output = format!("%v{}", id.0);
117 }
118 ScOp::DclsLayer { id, params, .. } => {
119 mlir.push_str(&format!(
120 " %v{} = hw.instance \"dcls_{}\" @sc_dcls_layer_core<\
121 N_TAPS: i32 = {}, DATA_WIDTH: i32 = {}, FRACTION: i32 = {}>(\
122 clk: %clk: i1, rst_n: %rst_n: i1) -> (weighted_sum_q88: i{})\n",
123 id.0,
124 id.0,
125 params.n_taps,
126 params.data_width,
127 params.fraction,
128 params.data_width
129 ));
130 last_output = format!("%v{}", id.0);
131 }
132 ScOp::Popcount { id, input } => {
133 let inp = value_wire(graph, *input);
134 mlir.push_str(&format!(" %v{} = comb.popcount {inp} : i64\n", id.0));
135 }
136 ScOp::GraphForward { id, features, .. } => {
137 let inp = value_wire(graph, *features);
138 mlir.push_str(&format!(
139 " // GraphForward: SC AND-popcount aggregation\n %v{} = {inp} : i64\n",
140 id.0
141 ));
142 }
143 ScOp::SoftmaxAttention { id, v, .. } => {
144 let inp = value_wire(graph, *v);
145 mlir.push_str(&format!(
146 " // SoftmaxAttention: SC bitstream attention\n %v{} = {inp} : i64\n",
147 id.0
148 ));
149 }
150 ScOp::KuramotoStep { id, phases, .. } => {
151 let inp = value_wire(graph, *phases);
152 mlir.push_str(&format!(
153 " // KuramotoStep: phase accumulator with coupling\n %v{} = {inp} : i64\n",
154 id.0
155 ));
156 }
157 ScOp::Scale { id, input, factor } => {
158 let inp = value_wire(graph, *input);
159 let scale_int = (*factor * 256.0) as i64;
160 mlir.push_str(&format!(
161 " %v{} = comb.mul {inp}, %c_scale_{id} : i16\n",
162 id.0,
163 inp = inp,
164 id = id.0,
165 ));
166 let _ = scale_int; }
168 ScOp::Offset { id, input, offset } => {
169 let inp = value_wire(graph, *input);
170 let _ = offset;
171 mlir.push_str(&format!(
172 " %v{} = comb.add {inp}, %c_off_{id} : i16\n",
173 id.0,
174 inp = inp,
175 id = id.0,
176 ));
177 }
178 ScOp::DivConst { id, input, divisor } => {
179 let inp = value_wire(graph, *input);
180 let _ = divisor;
181 mlir.push_str(&format!(
182 " %v{} = comb.divu {inp}, %c_div_{id} : i16\n",
183 id.0,
184 inp = inp,
185 id = id.0,
186 ));
187 }
188 ScOp::Reduce { id, input, mode } => {
189 let inp = value_wire(graph, *input);
190 let op_name = match mode {
191 ReduceMode::Sum => "add",
192 ReduceMode::Max => "max",
193 };
194 mlir.push_str(&format!(" %v{} = comb.{op_name} {inp} : i64\n", id.0,));
195 }
196 }
197 }
198
199 mlir.push_str("}\n");
201 let _ = last_output;
202 Ok(mlir)
203}
204
205fn value_wire(graph: &ScGraph, id: ValueId) -> String {
206 for op in &graph.ops {
207 if op.result_id() == id {
208 return match op {
209 ScOp::Input { name, .. } => format!("%{name}"),
210 ScOp::Constant { id, .. } => format!("%c{}", id.0),
211 ScOp::LifStep { id, .. } => format!("%v{}_spike", id.0),
212 ScOp::DenseForward { id, .. } => format!("%v{}", id.0),
213 _ => format!("%v{}", id.0),
214 };
215 }
216 }
217 format!("%v{}", id.0)
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::ir::builder::ScGraphBuilder;
224
225 #[test]
226 fn basic_and_gate_emits_mlir() {
227 let mut b = ScGraphBuilder::new("test_and");
228 let a = b.input("a", ScType::Bool);
229 let bv = b.input("b", ScType::Bool);
230 let c = b.bitwise_and(a, bv);
231 b.output("out", c);
232 let graph = b.build();
233
234 let mlir = emit(&graph).unwrap();
235 assert!(mlir.contains("hw.module @test_and"));
236 assert!(mlir.contains("comb.and"));
237 assert!(mlir.contains("hw.output"));
238 }
239
240 #[test]
241 fn module_wrapping() {
242 let mut b = ScGraphBuilder::new("wrapper");
243 let x = b.input("x", ScType::Bool);
244 b.output("y", x);
245 let graph = b.build();
246
247 let mlir = emit(&graph).unwrap();
248 assert!(mlir.starts_with("// Auto-generated"));
249 assert!(mlir.contains("hw.module @wrapper"));
250 assert!(mlir.ends_with("}\n"));
251 }
252
253 #[test]
254 fn xor_gate() {
255 let mut b = ScGraphBuilder::new("test_xor");
256 let a = b.input("a", ScType::Bool);
257 let bv = b.input("b", ScType::Bool);
258 let c = b.bitwise_xor(a, bv);
259 b.output("out", c);
260 let graph = b.build();
261
262 let mlir = emit(&graph).unwrap();
263 assert!(mlir.contains("comb.xor"));
264 }
265}