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 { id, prob, .. } => {
59 let prob_wire = value_wire(graph, *prob);
60 mlir.push_str(&format!(
61 " %v{} = hw.instance \"enc_{}\" @sc_bitstream_encoder(\
62 clk: %clk: i1, rst_n: %rst_n: i1, x_value: {}: i1) -> (bit_out: i1)\n",
63 id.0, id.0, prob_wire
64 ));
65 last_output = format!("%v{}", id.0);
66 }
67 ScOp::BitwiseAnd { id, lhs, rhs } => {
68 let l = value_wire(graph, *lhs);
69 let r = value_wire(graph, *rhs);
70 mlir.push_str(&format!(" %v{} = comb.and {l}, {r} : i1\n", id.0));
71 last_output = format!("%v{}", id.0);
72 }
73 ScOp::BitwiseXor { id, lhs, rhs } => {
74 let l = value_wire(graph, *lhs);
75 let r = value_wire(graph, *rhs);
76 mlir.push_str(&format!(" %v{} = comb.xor {l}, {r} : i1\n", id.0));
77 last_output = format!("%v{}", id.0);
78 }
79 ScOp::Constant { id, value, .. } => {
80 let val = match value {
81 ScConst::I64(v) => format!("{v}"),
82 ScConst::U64(v) => format!("{v}"),
83 ScConst::F64(v) => format!("{}", (*v * 256.0) as i64),
84 _ => "0".to_string(),
85 };
86 mlir.push_str(&format!(" %c{} = hw.constant {} : i16\n", id.0, val));
87 }
88 ScOp::LifStep {
89 id,
90 current,
91 params,
92 ..
93 } => {
94 let cur = value_wire(graph, *current);
95 mlir.push_str(&format!(
96 " %v{id}_spike, %v{id}_v = hw.instance \"lif_{id}\" \
97 @sc_lif_neuron<DATA_WIDTH: i32 = {dw}, V_THRESHOLD: i32 = {vt}>(\
98 clk: %clk: i1, rst_n: %rst_n: i1, I_t: {cur}: i1) -> \
99 (spike_out: i1, v_out: i{dw})\n",
100 id = id.0,
101 dw = params.data_width,
102 vt = params.v_threshold,
103 cur = cur,
104 ));
105 last_output = format!("%v{}_spike", id.0);
106 }
107 ScOp::DenseForward { id, params, .. } => {
108 mlir.push_str(&format!(
109 " %v{} = hw.instance \"dense_{}\" @sc_dense_layer_core<\
110 N_INPUTS: i32 = {}, N_NEURONS: i32 = {}>(\
111 clk: %clk: i1, rst_n: %rst_n: i1) -> (spikes: i{})\n",
112 id.0, id.0, params.n_inputs, params.n_neurons, params.n_neurons
113 ));
114 last_output = format!("%v{}", id.0);
115 }
116 ScOp::DclsLayer { id, params, .. } => {
117 mlir.push_str(&format!(
118 " %v{} = hw.instance \"dcls_{}\" @sc_dcls_layer_core<\
119 N_TAPS: i32 = {}, DATA_WIDTH: i32 = {}, FRACTION: i32 = {}>(\
120 clk: %clk: i1, rst_n: %rst_n: i1) -> (weighted_sum_q88: i{})\n",
121 id.0,
122 id.0,
123 params.n_taps,
124 params.data_width,
125 params.fraction,
126 params.data_width
127 ));
128 last_output = format!("%v{}", id.0);
129 }
130 ScOp::Popcount { id, input } => {
131 let inp = value_wire(graph, *input);
132 mlir.push_str(&format!(" %v{} = comb.popcount {inp} : i64\n", id.0));
133 }
134 ScOp::GraphForward {
135 id,
136 features,
137 adjacency,
138 n_nodes,
139 n_features,
140 } => {
141 let features_w = value_wire(graph, *features);
142 let adjacency_w = value_wire(graph, *adjacency);
143 mlir.push_str(&format!(
144 " %v{id} = hw.instance \"graph_{id}\" @sc_graph_forward<\
145 N_NODES: i32 = {n_nodes}, N_FEATURES: i32 = {n_features}>(\
146 features: {features_w}: i64, adjacency: {adjacency_w}: i64) \
147 -> (agg: i64)\n",
148 id = id.0,
149 ));
150 last_output = format!("%v{}", id.0);
151 }
152 ScOp::SoftmaxAttention { id, q, k, v, dim_k } => {
153 let q_w = value_wire(graph, *q);
154 let k_w = value_wire(graph, *k);
155 let v_w = value_wire(graph, *v);
156 mlir.push_str(&format!(
157 " %v{id} = hw.instance \"softmax_{id}\" @sc_softmax_attention<\
158 DIM_K: i32 = {dim_k}>(\
159 q_in: {q_w}: i64, k_in: {k_w}: i64, v_in: {v_w}: i64) \
160 -> (attn_out: i64)\n",
161 id = id.0,
162 ));
163 last_output = format!("%v{}", id.0);
164 }
165 ScOp::KuramotoStep {
166 id,
167 phases,
168 omega,
169 coupling,
170 dt,
171 } => {
172 let phases_w = value_wire(graph, *phases);
173 let omega_w = value_wire(graph, *omega);
174 let coupling_w = value_wire(graph, *coupling);
175 let dt_fixed = (*dt * 65536.0).round() as i64;
176 mlir.push_str(&format!(
177 " %v{id} = hw.instance \"kuramoto_{id}\" @sc_kuramoto_step<\
178 DT_FIXED: i32 = {dt}>(\
179 phases_in: {phases_w}: i64, omega: {omega_w}: i64, \
180 coupling: {coupling_w}: i64) -> (phases_out: i64)\n",
181 id = id.0,
182 dt = dt_fixed,
183 ));
184 last_output = format!("%v{}", id.0);
185 }
186 ScOp::Scale { id, input, factor } => {
187 let inp = value_wire(graph, *input);
188 let scale_int = (*factor * 256.0) as i64;
189 mlir.push_str(&format!(
190 " %v{} = comb.mul {inp}, %c_scale_{id} : i16\n",
191 id.0,
192 inp = inp,
193 id = id.0,
194 ));
195 let _ = scale_int; }
197 ScOp::Offset { id, input, offset } => {
198 let inp = value_wire(graph, *input);
199 let _ = offset;
200 mlir.push_str(&format!(
201 " %v{} = comb.add {inp}, %c_off_{id} : i16\n",
202 id.0,
203 inp = inp,
204 id = id.0,
205 ));
206 }
207 ScOp::DivConst { id, input, divisor } => {
208 let inp = value_wire(graph, *input);
209 let _ = divisor;
210 mlir.push_str(&format!(
211 " %v{} = comb.divu {inp}, %c_div_{id} : i16\n",
212 id.0,
213 inp = inp,
214 id = id.0,
215 ));
216 }
217 ScOp::Reduce { id, input, mode } => {
218 let inp = value_wire(graph, *input);
219 let op_name = match mode {
220 ReduceMode::Sum => "add",
221 ReduceMode::Max => "max",
222 };
223 mlir.push_str(&format!(" %v{} = comb.{op_name} {inp} : i64\n", id.0,));
224 }
225 }
226 }
227
228 mlir.push_str("}\n");
230 let _ = last_output;
231 Ok(mlir)
232}
233
234fn value_wire(graph: &ScGraph, id: ValueId) -> String {
235 for op in &graph.ops {
236 if op.result_id() == id {
237 return match op {
238 ScOp::Input { name, .. } => format!("%{name}"),
239 ScOp::Constant { id, .. } => format!("%c{}", id.0),
240 ScOp::LifStep { id, .. } => format!("%v{}_spike", id.0),
241 ScOp::DenseForward { id, .. } => format!("%v{}", id.0),
242 _ => format!("%v{}", id.0),
243 };
244 }
245 }
246 format!("%v{}", id.0)
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252 use crate::ir::builder::ScGraphBuilder;
253
254 #[test]
255 fn basic_and_gate_emits_mlir() {
256 let mut b = ScGraphBuilder::new("test_and");
257 let a = b.input("a", ScType::Bool);
258 let bv = b.input("b", ScType::Bool);
259 let c = b.bitwise_and(a, bv);
260 b.output("out", c);
261 let graph = b.build();
262
263 let mlir = emit(&graph).unwrap();
264 assert!(mlir.contains("hw.module @test_and"));
265 assert!(mlir.contains("comb.and"));
266 assert!(mlir.contains("hw.output"));
267 }
268
269 #[test]
270 fn module_wrapping() {
271 let mut b = ScGraphBuilder::new("wrapper");
272 let x = b.input("x", ScType::Bool);
273 b.output("y", x);
274 let graph = b.build();
275
276 let mlir = emit(&graph).unwrap();
277 assert!(mlir.starts_with("// Auto-generated"));
278 assert!(mlir.contains("hw.module @wrapper"));
279 assert!(mlir.ends_with("}\n"));
280 }
281
282 #[test]
283 fn xor_gate() {
284 let mut b = ScGraphBuilder::new("test_xor");
285 let a = b.input("a", ScType::Bool);
286 let bv = b.input("b", ScType::Bool);
287 let c = b.bitwise_xor(a, bv);
288 b.output("out", c);
289 let graph = b.build();
290
291 let mlir = emit(&graph).unwrap();
292 assert!(mlir.contains("comb.xor"));
293 }
294}