pulsar_backend/
calyx.rs

1// Copyright (C) 2024 Ethan Uppal. All rights reserved.
2
3use super::PulsarBackend;
4use crate::{build_assignments_2, finish_component, Output};
5use builder::{
6    CalyxAssignmentContainer, CalyxBuilder, CalyxCell, CalyxCellKind,
7    CalyxComponent, CalyxControl, Sequential
8};
9use calyx_backend::Backend;
10use pulsar_frontend::ty::Type;
11use pulsar_ir::{
12    basic_block::BasicBlockCell,
13    control_flow_graph::ControlFlowGraph,
14    generator::GeneratedTopLevel,
15    label::{Label, LabelName, MAIN_SYMBOL_PREFIX},
16    operand::Operand,
17    variable::Variable,
18    Ir
19};
20use std::{io::stderr, path::PathBuf};
21
22pub mod builder;
23
24// This file contains many examples of BAD software engineering.
25// All components are treated very much like functions. They have input ports
26// all of width 64 and one output port of width 64. However hardware is a lot
27// more flexible than this. See if you can figure out how to better use it.
28//
29// I realized that a contributing factor to this is that my IR has everything
30// has Int64. I should change that
31
32#[derive(Default)]
33struct FunctionContext {
34    ret_cell: Option<CalyxCell>,
35    param_env: usize
36}
37
38pub struct CalyxBackend {
39    builder: CalyxBuilder
40}
41
42impl CalyxBackend {
43    // TODO: support larger nested arrays
44    // but arbitrary pointer access needs to be restricted in static analyzer
45    // when targeting hardware
46    fn make_cell_for_array(
47        &self, component: &mut CalyxComponent<FunctionContext>, var: Variable,
48        cell_size: usize, length: usize
49    ) -> CalyxCell {
50        component.named_mem(var.to_string(), cell_size, length, 64)
51    }
52
53    /// Builds a constant if the operand is a constant and looks up a variable
54    /// otherwise.
55    fn find_operand_cell(
56        &self, component: &mut CalyxComponent<FunctionContext>,
57        operand: &Operand
58    ) -> CalyxCell {
59        match &operand {
60            Operand::Constant(value) => component.constant(*value, 64),
61            Operand::Variable(var) => component.find(var.to_string())
62        }
63    }
64
65    fn register_func(&mut self, label: &Label, args: &[Type], ret: &Type) {
66        let mut comp_ports = vec![];
67        for (i, arg) in args.iter().enumerate() {
68            let width = arg.size();
69            let name = format!("arg{}", i);
70            comp_ports.push(calyx_ir::PortDef::new(
71                name,
72                (width * 8) as u64,
73                calyx_ir::Direction::Input,
74                calyx_ir::Attributes::default()
75            ));
76        }
77        if *ret != Type::Unit {
78            comp_ports.push(calyx_ir::PortDef::new(
79                "ret",
80                (ret.size() * 8) as u64,
81                calyx_ir::Direction::Output,
82                calyx_ir::Attributes::default()
83            ));
84        }
85        self.builder
86            .register_component(label.name.mangle().clone(), comp_ports);
87
88        if label.name.mangle().starts_with(MAIN_SYMBOL_PREFIX) {
89            self.builder.set_entrypoint(label.name.mangle().clone());
90        }
91    }
92
93    /// A component for a call to `call` instantiated as a cell a single time in
94    /// the current component if `unique` and instantiated fresh otherwise.
95    fn cell_for_call(
96        &self, component: &mut CalyxComponent<FunctionContext>,
97        call: &LabelName, unique: bool
98    ) -> (String, CalyxCell) {
99        let callee_name = call.mangle().clone();
100        let cell_name = format!("call{}", callee_name);
101        component.component_cell(cell_name, callee_name, unique)
102    }
103
104    /// A unique cell that is only used for a single instruction and does not
105    /// need to be referenced elsewhere.
106    fn new_unnamed_reg(
107        &self, component: &mut CalyxComponent<FunctionContext>
108    ) -> CalyxCell {
109        component.new_unnamed_cell(CalyxCellKind::Register { size: 64 })
110    }
111
112    fn emit_ir(
113        &self, component: &mut CalyxComponent<FunctionContext>,
114        parent: &mut CalyxControl<Sequential>, ir: &Ir
115    ) {
116        let signal_out = component.signal_out();
117        match ir {
118            Ir::Add(result, lhs, rhs) => {
119                let lhs_cell = self.find_operand_cell(component, lhs);
120                let rhs_cell = self.find_operand_cell(component, rhs);
121                let result_cell = component.new_reg(result.to_string(), 64);
122                let adder = component.new_prim("adder", "std_add", vec![64]);
123                let add_group = component.add_group("add");
124                add_group.extend(build_assignments_2!(component;
125                    adder["left"] = ? lhs_cell["out"];
126                    adder["right"] = ? rhs_cell["out"];
127                    result_cell["in"] = ? adder["out"];
128                    result_cell["write_en"] = ? signal_out["out"];
129                    add_group["done"] = ? result_cell["done"];
130                ));
131                parent.enable_next(&add_group);
132            }
133            Ir::Mul(result, lhs, rhs) => {
134                let lhs_cell = self.find_operand_cell(component, lhs);
135                let rhs_cell = self.find_operand_cell(component, rhs);
136                let result_cell = component.new_reg(result.to_string(), 64);
137                let mult =
138                    component.new_prim("mult", "std_mult_pipe", vec![64]);
139                let mult_group = component.add_group("multiply");
140                mult_group.extend(build_assignments_2!(component;
141                    mult["left"] = ? lhs_cell["out"];
142                    mult["right"] = ? rhs_cell["out"];
143                    mult["go"] = ? signal_out["out"];
144                    result_cell["in"] = ? mult["out"];
145                    result_cell["write_en"] = ? mult["done"];
146                    mult_group["done"] = ? result_cell["done"];
147                ));
148                parent.enable_next(&mult_group);
149            }
150            Ir::Assign(result, value) => {
151                let value_cell = self.find_operand_cell(component, value);
152                if value_cell.kind.is_memory() {
153                    // "copy" pointer
154                    component.alias_cell(result.to_string(), value_cell);
155                    return;
156                }
157                let result_cell = component.new_reg(result.to_string(), 64);
158                let assign_group = component.add_group("assign");
159                assign_group.extend(build_assignments_2!(component;
160                    result_cell["in"] = ? value_cell["out"];
161                    result_cell["write_en"] = ? signal_out["out"];
162                    assign_group["done"] = ? result_cell["done"];
163                ));
164                parent.enable_next(&assign_group);
165            }
166            Ir::GetParam(result) => {
167                let func = component.signature();
168                // TODO: memory refs
169                let result_cell = component.new_reg(result.to_string(), 64);
170                let get_param_group = component.add_group("get_param");
171                let param_port =
172                    format!("arg{}", component.user_data_ref().param_env);
173                get_param_group.extend(build_assignments_2!(component;
174                    result_cell["in"] = ? func[param_port];
175                    result_cell["write_en"] = ? signal_out["out"];
176                    get_param_group["done"] = ? result_cell["done"];
177                ));
178                parent.enable_next(&get_param_group);
179                component.user_data_mut().param_env += 1;
180            }
181            Ir::Return(value_opt) => {
182                // TODO: handle generating if/else control to simulate early
183                // returns, this requires structured IR anyways so doesn't
184                // matter right now
185                if let Some(value) = value_opt {
186                    let return_group = component.add_group("return");
187                    let mut value_cell =
188                        self.find_operand_cell(component, value);
189
190                    // We need to use the done port (doesn't exist on constants)
191                    // so if it's a constant we need to make
192                    // a temporary port
193                    if let Operand::Constant(_) = value {
194                        let temp_cell = self.new_unnamed_reg(component);
195                        return_group.extend(build_assignments_2!(component;
196                            temp_cell["in"] = ? value_cell["out"];
197                        ));
198                        value_cell = temp_cell;
199                    }
200
201                    let ret_cell = component
202                        .user_data_ref()
203                        .ret_cell
204                        .as_ref()
205                        .cloned()
206                        .unwrap();
207                    return_group.extend(build_assignments_2!(component;
208                        ret_cell["in"] = ? value_cell["out"];
209                        ret_cell["write_en"] = ? signal_out["out"];
210                        return_group["done"] = ? ret_cell["done"];
211                    ));
212                    parent.enable_next(&return_group);
213                } else {
214                    // todo!("I haven't figured out return fully yet")
215                }
216            }
217            Ir::LocalAlloc(result, size, count) => {
218                self.make_cell_for_array(component, *result, *size * 8, *count);
219            }
220            Ir::Store {
221                result,
222                value,
223                index
224            } => {
225                let store_group = component.add_group("store");
226                let result_cell = component.find(result.to_string());
227                let value_cell = self.find_operand_cell(component, value);
228                let index_cell = self.find_operand_cell(component, index);
229                assert!(
230                    result_cell.kind.is_memory(),
231                    "Ir::Store should take a memory result cell"
232                );
233                store_group.extend(build_assignments_2!(component;
234                    result_cell["addr0"] = ? index_cell["out"];
235                    result_cell["write_data"] = ? value_cell["out"];
236                    result_cell["write_en"] = ? signal_out["out"];
237                    store_group["done"] = ? result_cell["done"];
238                ));
239                parent.enable_next(&store_group);
240            }
241            Ir::Load {
242                result,
243                value,
244                index
245            } => {
246                let load_group = component.add_group("load");
247                let result_cell = component.new_reg(result.to_string(), 64);
248                let value_cell = self.find_operand_cell(component, value);
249                assert!(
250                    value_cell.kind.is_memory(),
251                    "Ir::Load should take a memory result cell"
252                );
253                let index_cell = self.find_operand_cell(component, index);
254                load_group.extend(build_assignments_2!(component;
255                    value_cell["addr0"] = ? index_cell["out"];
256                    result_cell["in"] = ? value_cell["read_data"];
257                    result_cell["write_en"] = ? signal_out["out"];
258                    load_group["done"] = ? result_cell["done"];
259                ));
260                parent.enable_next(&load_group);
261            }
262            Ir::Map {
263                result,
264                parallel_factor,
265                f,
266                input,
267                length
268            } => {
269                assert!(length % parallel_factor == 0, "parallel_factor must divide length. figure out a better place to assert this, probably in the type checker fix");
270                let index_cell = self.new_unnamed_reg(component);
271
272                let init_group = component.add_group("init");
273                let zero = component.constant(0, 64);
274                init_group.extend(build_assignments_2!(component;
275                    index_cell["in"] = ? zero["out"];
276                    index_cell["write_en"] = ? signal_out["out"];
277                    init_group["done"] = ? index_cell["done"];
278                ));
279
280                let cond_group = component.add_comb_group("cond");
281                let array_size_cell = component.constant(*length as i64, 64);
282                let lt_cell = component.new_prim("lt", "std_lt", vec![64]);
283                cond_group.extend(build_assignments_2!(component;
284                    lt_cell["left"] = ? index_cell["out"];
285                    lt_cell["right"] = ? array_size_cell["out"];
286                ));
287
288                let read_group = component.add_group("read");
289                let write_group = component.add_group("write");
290
291                let input_cell = self.find_operand_cell(component, input); // also a memory
292                assert!(
293                    input_cell.kind.is_memory(),
294                    "Ir::Map should take a memory input cell"
295                );
296                let result_cell = component.find(result.to_string());
297                assert!(
298                    result_cell.kind.is_memory(),
299                    "Ir::Map should take a memory result cell"
300                );
301                let (_, call_cell) = self.cell_for_call(component, f, true);
302
303                read_group.extend(build_assignments_2!(component;
304                    input_cell["addr0"] = ? index_cell["out"];
305                    call_cell["arg0"] = ? input_cell["read_data"];
306                    call_cell["go"] = ? signal_out["out"];
307                    read_group["done"] = ? call_cell["done"];
308                ));
309
310                write_group.extend(build_assignments_2!(component;
311                    result_cell["addr0"] = ? index_cell["out"];
312                    result_cell["write_data"] = ? call_cell["ret"];
313                    result_cell["write_en"] = ? call_cell["done"];
314                    write_group["done"] = ? result_cell["done"];
315                ));
316
317                let incr_group = component.add_group("incr");
318                let adder = component.new_prim("adder", "std_add", vec![64]);
319                let one = component.constant(1, 64);
320                incr_group.extend(build_assignments_2!(component;
321                    adder["left"] = ? index_cell["out"];
322                    adder["right"] = ? one["out"];
323                    index_cell["in"] = ? adder["out"];
324                    index_cell["write_en"] = ? signal_out["out"];
325                    incr_group["done"] = ? index_cell["done"];
326                ));
327
328                parent.enable_next(&init_group);
329                parent.while_(lt_cell.get("out"), Some(cond_group), |s| {
330                    s.enable_next(&read_group);
331                    s.enable_next(&write_group);
332                    s.enable_next(&incr_group);
333                });
334            }
335            Ir::Call(result_opt, func_name, args) => {
336                let (_, call_cell) =
337                    self.cell_for_call(component, func_name, false);
338                let call_group = component.add_group("call");
339                for (i, arg) in args.iter().enumerate() {
340                    let arg_port =
341                        self.find_operand_cell(component, arg).get("out");
342                    call_group.add(component.with_calyx_builder(|b| {
343                        b.build_assignment(
344                            call_cell.get(&format!("arg{}", i)),
345                            arg_port,
346                            calyx_ir::Guard::True
347                        )
348                    }));
349                }
350                call_group.extend(build_assignments_2!(component;
351                    call_cell["go"] = ? signal_out["out"];
352                    call_group["done"] = ? call_cell["done"];
353                ));
354                parent.enable_next(&call_group);
355
356                if let Some(result) = result_opt {
357                    let use_call_group = component.add_group("use_call");
358                    let result_cell = component.new_reg(result.to_string(), 64);
359                    use_call_group.extend(build_assignments_2!(component;
360                        result_cell["in"] = ? call_cell["ret"];
361                        result_cell["write_en"] = ? signal_out["out"];
362                        use_call_group["done"] = ? result_cell["done"];
363                    ));
364                    parent.enable_next(&use_call_group);
365                }
366            }
367        }
368    }
369
370    fn emit_block(
371        &self, component: &mut CalyxComponent<FunctionContext>,
372        parent: &mut CalyxControl<Sequential>, block: BasicBlockCell
373    ) {
374        parent.seq(|s| {
375            for ir in block.as_ref().into_iter() {
376                self.emit_ir(component, s, ir);
377            }
378        });
379    }
380
381    fn emit_func(
382        &mut self, label: &Label, _args: &[Type], ret: &Type, _is_pure: bool,
383        cfg: &ControlFlowGraph
384    ) {
385        let mut component: CalyxComponent<FunctionContext> =
386            self.builder.start_component(label.name.mangle().clone());
387
388        if *ret != Type::Unit {
389            let func = component.signature();
390            let ret_cell =
391                component.new_unnamed_cell(builder::CalyxCellKind::Register {
392                    size: ret.size() * 8
393                });
394            component.user_data_mut().ret_cell = Some(ret_cell.clone());
395            let always = build_assignments_2!(component;
396                func["ret"] = ? ret_cell["out"];
397            )
398            .to_vec();
399            component.with_calyx_builder(|b| {
400                b.add_continuous_assignments(always);
401            });
402        }
403
404        // for block in cfg.blocks() {
405        //     self.emit_block(block);
406        // }
407        assert_eq!(1, cfg.size(), "CalyxBackend requires structured IR only in the entry block, but other blocks were found in the CFG");
408        let mut root_control = CalyxControl::default();
409        self.emit_block(&mut component, &mut root_control, cfg.entry());
410        *component.control() = root_control;
411
412        finish_component!(self.builder, component);
413    }
414}
415
416pub struct CalyxBackendInput {
417    pub lib_path: PathBuf
418}
419
420impl PulsarBackend for CalyxBackend {
421    type InitInput = CalyxBackendInput;
422    type Error = calyx_utils::Error;
423
424    fn new(input: Self::InitInput) -> Self {
425        let mut prelude_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
426        prelude_file_path.push("resources");
427        prelude_file_path.push("prelude.futil");
428
429        Self {
430            builder: CalyxBuilder::new(
431                Some(prelude_file_path),
432                input.lib_path,
433                None,
434                "_".into()
435            )
436        }
437    }
438
439    fn run(
440        mut self, code: Vec<GeneratedTopLevel>, output: Output
441    ) -> Result<(), Self::Error> {
442        // Create a calyx program from the IR
443        // - Step 1: load signatures
444        for generated_top_level in &code {
445            match generated_top_level {
446                GeneratedTopLevel::Function {
447                    label,
448                    args,
449                    ret,
450                    is_pure: _,
451                    cfg: _
452                } => {
453                    self.register_func(label, args, ret);
454                }
455            }
456        }
457        // - Step 2: emit generated IR
458        for generated_top_level in &code {
459            match generated_top_level {
460                GeneratedTopLevel::Function {
461                    label,
462                    args,
463                    ret,
464                    is_pure,
465                    cfg
466                } => self.emit_func(label, args, ret, *is_pure, cfg)
467            }
468        }
469
470        // Obtain the program context
471        let mut builder = CalyxBuilder::dummy();
472        std::mem::swap(&mut builder, &mut self.builder);
473        let mut calyx_ctx = builder.finalize();
474
475        // Debug print
476        calyx_ir::Printer::write_context(&calyx_ctx, false, &mut stderr())
477            .unwrap();
478
479        // Perform optimization passes
480        let pm = calyx_opt::pass_manager::PassManager::default_passes()?;
481        let backend_conf = calyx_ir::BackendConf {
482            synthesis_mode: false,
483            enable_verification: false,
484            flat_assign: true,
485            emit_primitive_extmodules: false
486        };
487        calyx_ctx.bc = backend_conf;
488        pm.execute_plan(
489            &mut calyx_ctx,
490            &["all".to_string()],
491            &["canonicalize".to_string()],
492            false
493        )?;
494
495        // Emit to Verilog
496        let backend = calyx_backend::VerilogBackend;
497        backend.run(calyx_ctx, output.into())?;
498        Ok(())
499    }
500}
501
502impl From<Output> for calyx_utils::OutputFile {
503    fn from(output: Output) -> Self {
504        match output {
505            Output::Stdout => calyx_utils::OutputFile::Stdout,
506            Output::Stderr => calyx_utils::OutputFile::Stderr,
507            Output::File(path) => calyx_utils::OutputFile::File(path)
508        }
509    }
510}