calyx_backend/
verilog.rs

1//! SystemVerilog backend for the Calyx compiler.
2//!
3//! Transforms an [`ir::Context`](crate::ir::Context) into a formatted string that represents a
4//! valid SystemVerilog program.
5
6use crate::traits::Backend;
7use calyx_ir::{self as ir, Control, FlatGuard, Group, Guard, GuardRef, RRC};
8use calyx_utils::{CalyxResult, Error, OutputFile};
9use ir::Nothing;
10use itertools::Itertools;
11use std::io;
12use std::{collections::HashMap, rc::Rc};
13use std::{fs::File, time::Instant};
14use vast::v17::ast as v;
15
16/// Implements a simple Verilog backend. The backend only accepts Calyx programs with no control
17/// and no groups.
18#[derive(Default)]
19pub struct VerilogBackend;
20
21// input string should be the cell type name of a memory cell. In other words one
22// of "seq/comb_mem_d_1/2/3/4". Becase we define seq_mem_d2/3/4 in terms of seq_mem_d1
23// we need another layer of memory access to get the actual memory array in verilog
24// for these mem types.
25// In other words, for memories not defined in terms of another memory, we can just use
26// "mem" to access them. But for memories defined in terms of another memory,
27// which are seq_mem_d2/3/4, we need "mem.mem" to access them.
28fn get_mem_str(mem_type: &str) -> &str {
29    if mem_type.contains("d1") || mem_type.contains("comb_mem") {
30        "mem"
31    } else {
32        "mem.mem"
33    }
34}
35
36/// Checks to make sure that there are no holes being
37/// used in a guard.
38fn validate_guard(guard: &ir::Guard<Nothing>) -> bool {
39    match guard {
40        Guard::Or(left, right) | Guard::And(left, right) => {
41            validate_guard(left) && validate_guard(right)
42        }
43        Guard::CompOp(_, left, right) => {
44            !left.borrow().is_hole() && !right.borrow().is_hole()
45        }
46        Guard::Not(inner) => validate_guard(inner),
47        Guard::Port(port) => !port.borrow().is_hole(),
48        Guard::True => true,
49        Guard::Info(_) => true,
50    }
51}
52
53/// Returns `Ok` if there are no groups defined.
54fn validate_structure<'a, I>(groups: I) -> CalyxResult<()>
55where
56    I: Iterator<Item = &'a RRC<Group>>,
57{
58    for group in groups {
59        for asgn in &group.borrow().assignments {
60            let port = asgn.dst.borrow();
61            // check if port is a hole
62            if port.is_hole() {
63                return Err(Error::malformed_structure(
64                    "Groups / Holes can not be turned into Verilog".to_string(),
65                )
66                .with_pos(&port.attributes));
67            }
68
69            // validate guard
70            if !validate_guard(&asgn.guard) {
71                return Err(Error::malformed_structure(
72                    "Groups / Holes can not be turned into Verilog".to_string(),
73                )
74                .with_pos(&port.attributes));
75            };
76        }
77    }
78    Ok(())
79}
80
81/// Returns `Ok` if the control for `comp` is either a single `enable`
82/// or `empty`.
83fn validate_control(ctrl: &ir::Control) -> CalyxResult<()> {
84    match ctrl {
85        Control::Empty(_) => Ok(()),
86        c => Err(Error::malformed_structure(
87            "Control must be empty".to_string(),
88        )
89        .with_pos(c)),
90    }
91}
92
93impl Backend for VerilogBackend {
94    fn name(&self) -> &'static str {
95        "verilog"
96    }
97
98    fn validate(ctx: &ir::Context) -> CalyxResult<()> {
99        for component in &ctx.components {
100            validate_structure(component.get_groups().iter())?;
101            validate_control(&component.control.borrow())?;
102        }
103        Ok(())
104    }
105
106    /// Generate a "fat" library by copy-pasting all of the extern files.
107    /// A possible alternative in the future is to use SystemVerilog `include`
108    /// statement.
109    fn link_externs(
110        ctx: &ir::Context,
111        file: &mut OutputFile,
112    ) -> CalyxResult<()> {
113        let fw = &mut file.get_write();
114        for extern_path in &ctx.lib.extern_paths() {
115            // The extern file is guaranteed to exist by the frontend.
116            let mut ext = File::open(extern_path).unwrap();
117            io::copy(&mut ext, fw).map_err(|err| {
118                let std::io::Error { .. } = err;
119                Error::write_error(format!(
120                    "File not found: {}",
121                    file.as_path_string()
122                ))
123            })?;
124            // Add a newline after appending a library file
125            writeln!(fw)?;
126        }
127        for (prim, _) in ctx.lib.prim_inlines() {
128            emit_prim_inline(prim, fw)?;
129        }
130        Ok(())
131    }
132
133    fn emit(ctx: &ir::Context, file: &mut OutputFile) -> CalyxResult<()> {
134        let out = &mut file.get_write();
135        let comps = ctx.components.iter().try_for_each(|comp| {
136            // Time the generation of the component.
137            let time = Instant::now();
138            let out = emit_component(
139                comp,
140                ctx.bc.synthesis_mode,
141                ctx.bc.enable_verification,
142                ctx.bc.flat_assign,
143                out,
144            );
145            log::info!("Generated `{}` in {:?}", comp.name, time.elapsed());
146            out
147        });
148        comps.map_err(|err| {
149            let std::io::Error { .. } = err;
150            Error::write_error(format!(
151                "File not found: {}",
152                file.as_path_string()
153            ))
154        })
155    }
156}
157
158// takes an inlined primitive and emits the corresponding verilog
159// note that this means that prim *must* have Some body
160fn emit_prim_inline<F: io::Write>(
161    prim: &ir::Primitive,
162    f: &mut F,
163) -> CalyxResult<()> {
164    write!(f, "module {}", prim.name)?;
165    if !prim.params.is_empty() {
166        writeln!(f, " #(")?;
167        for (idx, param) in prim.params.iter().enumerate() {
168            write!(f, "    parameter {} = 32", param)?;
169            if idx != prim.params.len() - 1 {
170                writeln!(f, ",")?;
171            } else {
172                writeln!(f)?;
173            }
174        }
175        write!(f, ")")?;
176    }
177    writeln!(f, " (")?;
178    for (idx, port) in prim.signature.iter().enumerate() {
179        // NOTE: The signature port definitions are reversed inside the component.
180        match port.direction {
181            ir::Direction::Input => {
182                write!(f, "   input")?;
183            }
184            ir::Direction::Output => {
185                write!(f, "   output")?;
186            }
187            ir::Direction::Inout => {
188                panic!("Unexpected Inout port on Component: {}", port.name())
189            }
190        }
191        match port.width {
192            ir::Width::Const { value } => {
193                if value == 1 {
194                    write!(f, " logic {}", port.name())?;
195                } else {
196                    write!(f, " logic [{}:0] {}", value - 1, port.name())?;
197                }
198            }
199            ir::Width::Param { value } => {
200                write!(f, " logic [{}-1:0] {}", value, port.name())?;
201            }
202        }
203        if idx == prim.signature.len() - 1 {
204            writeln!(f)?;
205        } else {
206            writeln!(f, ",")?;
207        }
208    }
209    writeln!(f, ");")?;
210
211    writeln!(
212        f,
213        "{}",
214        prim.body.as_ref().unwrap_or_else(|| panic!(
215            "expected primitive {} to have a body",
216            prim.name
217        ))
218    )?;
219
220    writeln!(f, "endmodule")?;
221    writeln!(f)?;
222
223    Ok(())
224}
225
226fn emit_component<F: io::Write>(
227    comp: &ir::Component,
228    synthesis_mode: bool,
229    enable_verification: bool,
230    flat_assign: bool,
231    f: &mut F,
232) -> io::Result<()> {
233    writeln!(f, "module {}(", comp.name)?;
234
235    let sig = comp.signature.borrow();
236    for (idx, port_ref) in sig.ports.iter().enumerate() {
237        let port = port_ref.borrow();
238        // NOTE: The signature port definitions are reversed inside the component.
239        match port.direction {
240            ir::Direction::Input => {
241                write!(f, "  output")?;
242            }
243            ir::Direction::Output => {
244                write!(f, "  input")?;
245            }
246            ir::Direction::Inout => {
247                panic!("Unexpected Inout port on Component: {}", port.name)
248            }
249        }
250        if port.width == 1 {
251            write!(f, " logic {}", port.name)?;
252        } else {
253            write!(f, " logic [{}:0] {}", port.width - 1, port.name)?;
254        }
255        if idx == sig.ports.len() - 1 {
256            writeln!(f)?;
257        } else {
258            writeln!(f, ",")?;
259        }
260    }
261    writeln!(f, ");")?;
262
263    // Add a COMPONENT START: <name> anchor before any code in the component
264    writeln!(f, "// COMPONENT START: {}", comp.name)?;
265
266    // Add memory initial and final blocks
267    if !synthesis_mode {
268        memory_read_write(comp)
269            .into_iter()
270            .try_for_each(|stmt| writeln!(f, "{}", stmt))?;
271    }
272
273    let cells = comp
274        .cells
275        .iter()
276        .flat_map(|cell| wire_decls(&cell.borrow()))
277        .collect_vec();
278    // structure wire declarations
279    cells.iter().try_for_each(|(name, width, _)| {
280        let decl = v::Decl::new_logic(name, *width);
281        writeln!(f, "{};", decl)
282    })?;
283
284    // cell instances
285    comp.cells
286        .iter()
287        .filter_map(|cell| cell_instance(&cell.borrow()))
288        .try_for_each(|instance| writeln!(f, "{instance}"))?;
289
290    // gather assignments keyed by destination
291    let mut map: HashMap<_, (RRC<ir::Port>, Vec<_>)> = HashMap::new();
292    for asgn in &comp.continuous_assignments {
293        map.entry(asgn.dst.borrow().canonical())
294            .and_modify(|(_, v)| v.push(asgn))
295            .or_insert((Rc::clone(&asgn.dst), vec![asgn]));
296    }
297
298    // Flatten all the guard expressions.
299    let mut pool = ir::GuardPool::new();
300    let grouped_asgns: Vec<_> = map
301        .values()
302        .sorted_by_key(|(port, _)| port.borrow().canonical())
303        .map(|(dst, asgns)| {
304            let flat_asgns: Vec<_> = asgns
305                .iter()
306                .map(|asgn| {
307                    let guard = pool.flatten(&asgn.guard);
308                    (asgn.src.clone(), guard)
309                })
310                .collect();
311            (dst, flat_asgns)
312        })
313        .collect();
314
315    if flat_assign {
316        // Emit "flattened" assignments as ANF statements.
317        // Emit Verilog for the flattened guards.
318        for (idx, guard) in pool.iter() {
319            write!(f, "wire {} = ", VerilogGuardRef(idx))?;
320            emit_guard(guard, f)?;
321            writeln!(f, ";")?;
322        }
323
324        // Emit assignments using these guards.
325        for (dst, asgns) in &grouped_asgns {
326            emit_assignment_flat(dst, asgns, f)?;
327
328            if enable_verification {
329                if let Some(check) =
330                    emit_guard_disjoint_check(dst, asgns, &pool, true)
331                {
332                    writeln!(f, "always_comb begin")?;
333                    writeln!(f, "  {check}")?;
334                    writeln!(f, "end")?;
335                }
336            }
337        }
338    } else {
339        // Build a top-level always block to contain verilator checks for assignments
340        let mut checks = v::ParallelProcess::new_always_comb();
341
342        // Emit nested assignments.
343        for (dst, asgns) in grouped_asgns {
344            let stmt =
345                v::Stmt::new_parallel(emit_assignment(dst, &asgns, &pool));
346            writeln!(f, "{stmt}")?;
347
348            if enable_verification {
349                if let Some(check) =
350                    emit_guard_disjoint_check(dst, &asgns, &pool, false)
351                {
352                    checks.add_seq(check);
353                }
354            }
355        }
356
357        if !synthesis_mode {
358            writeln!(f, "{checks}")?;
359        }
360    }
361
362    // Add COMPONENT END: <name> anchor
363    writeln!(f, "// COMPONENT END: {}\nendmodule", comp.name)?;
364    Ok(())
365}
366
367fn wire_decls(cell: &ir::Cell) -> Vec<(String, u64, ir::Direction)> {
368    cell.ports
369        .iter()
370        .filter_map(|port| match &port.borrow().parent {
371            ir::PortParent::Cell(cell) => {
372                let parent_ref = cell.upgrade();
373                let parent = parent_ref.borrow();
374                match parent.prototype {
375                    ir::CellType::Component { .. }
376                    | ir::CellType::Primitive { .. } => Some((
377                        format!(
378                            "{}_{}",
379                            parent.name().as_ref(),
380                            port.borrow().name.as_ref()
381                        ),
382                        port.borrow().width,
383                        port.borrow().direction.clone(),
384                    )),
385                    _ => None,
386                }
387            }
388            ir::PortParent::Group(_) => unreachable!(),
389            ir::PortParent::StaticGroup(_) => unreachable!(),
390        })
391        .collect()
392}
393
394fn cell_instance(cell: &ir::Cell) -> Option<v::Instance> {
395    match cell.type_name() {
396        Some(ty_name) => {
397            let mut inst =
398                v::Instance::new(cell.name().as_ref(), ty_name.as_ref());
399
400            if let ir::CellType::Primitive {
401                name,
402                param_binding,
403                ..
404            } = &cell.prototype
405            {
406                if name == "std_const" {
407                    let (wn, width) = &param_binding[0];
408                    let (vn, value) = &param_binding[1];
409                    inst.add_param(
410                        wn.id.as_str(),
411                        v::Expr::new_int(*width as i32),
412                    );
413                    inst.add_param(
414                        vn.id.as_str(),
415                        v::Expr::new_ulit_dec(
416                            *width as u32,
417                            &value.to_string(),
418                        ),
419                    );
420                } else {
421                    param_binding.iter().for_each(|(name, value)| {
422                    if *value > (std::i32::MAX as u64) {
423                        panic!(
424                            "Parameter value {} for `{}` cannot be represented using 32 bits",
425                            value,
426                            name
427                        )
428                    }
429                    inst.add_param(
430                        name.as_ref(),
431                        v::Expr::new_int(*value as i32),
432                    )
433                })
434                }
435            }
436
437            for port in &cell.ports {
438                inst.connect(port.borrow().name.as_ref(), port_to_ref(port));
439            }
440            Some(inst)
441        }
442        None => None,
443    }
444}
445
446/// Generates an always block that checks of the guards are disjoint when the
447/// length of assignments is greater than 1:
448/// ```verilog
449/// always_ff @(posedge clk) begin
450///   if (!$onehot0({fsm_out < 1'd1 & go, fsm_out < 1'd1 & go})) begin
451///     $error("Multiple assignments to r_in");
452///   end
453/// end
454/// ```
455fn emit_guard_disjoint_check(
456    dst: &RRC<ir::Port>,
457    assignments: &[(RRC<ir::Port>, GuardRef)],
458    pool: &ir::GuardPool,
459    flat: bool,
460) -> Option<v::Sequential> {
461    if assignments.len() < 2 {
462        return None;
463    }
464    // Construct concat with all guards.
465    let mut concat = v::ExprConcat::default();
466    assignments.iter().for_each(|(_, gr)| {
467        let expr = if flat {
468            v::Expr::new_ref(VerilogGuardRef(*gr).to_string())
469        } else {
470            let guard = pool.get(*gr);
471            guard_to_expr(guard, pool)
472        };
473        concat.add_expr(expr);
474    });
475
476    let onehot0 = v::Expr::new_call("$onehot0", vec![v::Expr::Concat(concat)]);
477    let not_onehot0 = v::Expr::new_not(onehot0);
478    let mut check = v::SequentialIfElse::new(not_onehot0);
479
480    // Generated error message
481    let ir::Canonical { cell, port } = dst.borrow().canonical();
482    let msg = format!("Multiple assignment to port `{}.{}'.", cell, port);
483    let err = v::Sequential::new_seqexpr(v::Expr::new_call(
484        "$fatal",
485        vec![v::Expr::new_int(2), v::Expr::Str(msg)],
486    ));
487    check.add_seq(err);
488    Some(v::Sequential::If(check))
489}
490
491/// Checks if:
492/// 1. The port is marked with `@data`
493/// 2. The port's cell parent is marked with `@data`
494fn is_data_port(pr: &RRC<ir::Port>) -> bool {
495    let port = pr.borrow();
496    if !port.attributes.has(ir::BoolAttr::Data) {
497        return false;
498    }
499    if let ir::PortParent::Cell(cwr) = &port.parent {
500        let cr = cwr.upgrade();
501        let cell = cr.borrow();
502        if cell.attributes.has(ir::BoolAttr::Data) {
503            return true;
504        }
505    }
506    false
507}
508
509/// Generates an assign statement that uses ternaries to select the correct
510/// assignment to enable and adds a default assignment to 0 when none of the
511/// guards are active.
512///
513/// Example:
514/// ```
515/// // Input Calyx code
516/// a.in = foo ? 2'd0;
517/// a.in = bar ? 2'd1;
518/// ```
519/// Into:
520/// ```
521/// assign a_in = foo ? 2'd0 : bar ? 2d'1 : 2'd0;
522/// ```
523fn emit_assignment(
524    dst: &RRC<ir::Port>,
525    assignments: &[(RRC<ir::Port>, GuardRef)],
526    pool: &ir::GuardPool,
527) -> v::Parallel {
528    // Mux over the assignment with the given default value.
529    let fold_assigns = |init: v::Expr| -> v::Expr {
530        assignments.iter().rfold(init, |acc, (src, gr)| {
531            let guard = pool.get(*gr);
532            let asgn = port_to_ref(src);
533            v::Expr::new_mux(guard_to_expr(guard, pool), asgn, acc)
534        })
535    };
536
537    // If this is a data port
538    let rhs: v::Expr = if is_data_port(dst) {
539        if assignments.len() == 1 {
540            // If there is exactly one guard, generate a continuous assignment.
541            // This encodes the rewrite:
542            // in = g ? out : 'x => in = out;
543            // This is valid because 'x can be replaced with any value
544            let (dst, _) = &assignments[0];
545            port_to_ref(dst)
546        } else {
547            // Produce an assignment with 'x as the default case.
548            fold_assigns(v::Expr::X)
549        }
550    } else {
551        let init =
552            v::Expr::new_ulit_dec(dst.borrow().width as u32, &0.to_string());
553
554        // Flatten the mux expression if there is exactly one assignment with a true guard.
555        if assignments.len() == 1 {
556            let (src, gr) = &assignments[0];
557            if gr.is_true() {
558                port_to_ref(src)
559            } else if src.borrow().is_constant(1, 1) {
560                let guard = pool.get(*gr);
561                guard_to_expr(guard, pool)
562            } else {
563                let guard = pool.get(*gr);
564                v::Expr::new_mux(
565                    guard_to_expr(guard, pool),
566                    port_to_ref(src),
567                    init,
568                )
569            }
570        } else {
571            fold_assigns(init)
572        }
573    };
574    v::Parallel::ParAssign(port_to_ref(dst), rhs)
575}
576
577fn emit_assignment_flat<F: io::Write>(
578    dst: &RRC<ir::Port>,
579    assignments: &[(RRC<ir::Port>, GuardRef)],
580    f: &mut F,
581) -> io::Result<()> {
582    let data = is_data_port(dst);
583
584    // Simple optimizations for 1-guard cases.
585    if assignments.len() == 1 {
586        let (src, guard) = &assignments[0];
587        if data {
588            // For data ports (for whom unassigned values are undefined), we can drop the guard
589            // entirely and assume it is always true (because it would be UB if it were ever false).
590            return writeln!(
591                f,
592                "assign {} = {};",
593                VerilogPortRef(dst),
594                VerilogPortRef(src)
595            );
596        } else {
597            // For non-data ("control") ports, we have special cases for true guards and constant-1 RHSes.
598            if guard.is_true() {
599                return writeln!(
600                    f,
601                    "assign {} = {};",
602                    VerilogPortRef(dst),
603                    VerilogPortRef(src)
604                );
605            } else if src.borrow().is_constant(1, 1) {
606                return writeln!(
607                    f,
608                    "assign {} = {};",
609                    VerilogPortRef(dst),
610                    VerilogGuardRef(*guard)
611                );
612            }
613        }
614    }
615
616    // Use a cascade of ternary expressions to assign the right RHS to dst.
617    writeln!(f, "assign {} =", VerilogPortRef(dst))?;
618    for (src, guard) in assignments {
619        writeln!(
620            f,
621            "  {} ? {} :",
622            VerilogGuardRef(*guard),
623            VerilogPortRef(src)
624        )?;
625    }
626
627    // The default value depends on whether we are assigning to a data or control port.
628    if data {
629        writeln!(f, "  'x;")
630    } else {
631        writeln!(f, "  {}'d0;", dst.borrow().width)
632    }
633}
634
635fn port_to_ref(port_ref: &RRC<ir::Port>) -> v::Expr {
636    let port = port_ref.borrow();
637    match &port.parent {
638        ir::PortParent::Cell(cell) => {
639            let parent_ref = cell.upgrade();
640            let parent = parent_ref.borrow();
641            match parent.prototype {
642                ir::CellType::Constant { val, width } => {
643                    v::Expr::new_ulit_dec(width as u32, &val.to_string())
644                }
645                ir::CellType::ThisComponent => v::Expr::new_ref(port.name),
646                _ => v::Expr::Ref(format!(
647                    "{}_{}",
648                    parent.name().as_ref(),
649                    port.name.as_ref()
650                )),
651            }
652        }
653        ir::PortParent::Group(_) => unreachable!(),
654        ir::PortParent::StaticGroup(_) => unreachable!(),
655    }
656}
657
658fn guard_to_expr(guard: &ir::FlatGuard, pool: &ir::GuardPool) -> v::Expr {
659    let op = |g: &ir::FlatGuard| match g {
660        FlatGuard::Or(..) => v::Expr::new_bit_or,
661        FlatGuard::And(..) => v::Expr::new_bit_and,
662        FlatGuard::CompOp(op, ..) => match op {
663            ir::PortComp::Eq => v::Expr::new_eq,
664            ir::PortComp::Neq => v::Expr::new_neq,
665            ir::PortComp::Gt => v::Expr::new_gt,
666            ir::PortComp::Lt => v::Expr::new_lt,
667            ir::PortComp::Geq => v::Expr::new_geq,
668            ir::PortComp::Leq => v::Expr::new_leq,
669        },
670        FlatGuard::Not(..) | FlatGuard::Port(..) | FlatGuard::True => {
671            unreachable!()
672        }
673    };
674
675    match guard {
676        FlatGuard::And(l, r) | FlatGuard::Or(l, r) => {
677            let lg = pool.get(*l);
678            let rg = pool.get(*r);
679            op(guard)(guard_to_expr(lg, pool), guard_to_expr(rg, pool))
680        }
681        FlatGuard::CompOp(_, l, r) => op(guard)(port_to_ref(l), port_to_ref(r)),
682        FlatGuard::Not(r) => {
683            let g = pool.get(*r);
684            v::Expr::new_not(guard_to_expr(g, pool))
685        }
686        FlatGuard::Port(p) => port_to_ref(p),
687        FlatGuard::True => v::Expr::new_ulit_bin(1, &1.to_string()),
688    }
689}
690
691/// A little newtype wrapper for GuardRefs that makes it easy to format them as Verilog variables.
692struct VerilogGuardRef(GuardRef);
693
694impl std::fmt::Display for VerilogGuardRef {
695    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
696        write!(f, "_guard{}", self.0.index())
697    }
698}
699
700/// Similarly, a little wrapper for PortRefs that makes it easy to format them as Verilog variables.
701struct VerilogPortRef<'a>(&'a RRC<ir::Port>);
702
703impl<'a> std::fmt::Display for VerilogPortRef<'a> {
704    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
705        let port = self.0.borrow();
706        match &port.parent {
707            ir::PortParent::Cell(cell) => {
708                let parent_ref = cell.upgrade();
709                let parent = parent_ref.borrow();
710                match parent.prototype {
711                    ir::CellType::Constant { val, width } => {
712                        write!(f, "{width}'d{val}")
713                    }
714                    ir::CellType::ThisComponent => {
715                        write!(f, "{}", port.name)
716                    }
717                    _ => {
718                        write!(
719                            f,
720                            "{}_{}",
721                            parent.name().as_ref(),
722                            port.name.as_ref()
723                        )
724                    }
725                }
726            }
727            ir::PortParent::Group(_) => unreachable!(),
728            ir::PortParent::StaticGroup(_) => unreachable!(),
729        }
730    }
731}
732
733fn emit_guard<F: std::io::Write>(
734    guard: &ir::FlatGuard,
735    f: &mut F,
736) -> io::Result<()> {
737    let gr = VerilogGuardRef;
738    match guard {
739        FlatGuard::Or(l, r) => write!(f, "{} | {}", gr(*l), gr(*r)),
740        FlatGuard::And(l, r) => write!(f, "{} & {}", gr(*l), gr(*r)),
741        FlatGuard::CompOp(op, l, r) => {
742            let op = match op {
743                ir::PortComp::Eq => "==",
744                ir::PortComp::Neq => "!=",
745                ir::PortComp::Gt => ">",
746                ir::PortComp::Lt => "<",
747                ir::PortComp::Geq => ">=",
748                ir::PortComp::Leq => "<=",
749            };
750            write!(f, "{} {} {}", VerilogPortRef(l), op, VerilogPortRef(r))
751        }
752        FlatGuard::Not(g) => write!(f, "~{}", gr(*g)),
753        FlatGuard::True => write!(f, "1"),
754        FlatGuard::Port(p) => write!(f, "{}", VerilogPortRef(p)),
755    }
756}
757
758//==========================================
759//        Memory input and output
760//==========================================
761/// Generates code of the form:
762/// ```
763/// string DATA;
764/// int CODE;
765/// initial begin
766///   CODE = $value$plusargs("DATA=%s", DATA);
767///   $display("DATA: %s", DATA);
768///   $readmemh({DATA, "/<mem_name>.dat"}, <mem_name>.mem);
769///   ...
770/// end
771/// final begin
772///   $writememh({DATA, "/<mem_name>.out"}, <mem_name>.mem);
773/// end
774/// ```
775fn memory_read_write(comp: &ir::Component) -> Vec<v::Stmt> {
776    // Find all memories marked as @external
777    let memories = comp
778        .cells
779        .iter()
780        .filter_map(|cell| {
781            let is_external = cell.borrow().get_attribute(ir::BoolAttr::External).is_some();
782            if is_external
783                && cell
784                    .borrow()
785                    .type_name()
786                    // HACK: Check if the name of the primitive contains the string "mem"
787                    .map(|proto| proto.id.as_str().contains("mem"))
788                    .unwrap_or_default()
789            {
790                Some((
791                    cell.borrow().name().id,
792                    cell.borrow().type_name().unwrap_or_else(|| unreachable!("tried to add a memory cell but there was no type name")),
793                ))
794            } else {
795                None
796            }
797        })
798        .collect_vec();
799
800    if memories.is_empty() {
801        return vec![];
802    }
803
804    // Import futil helper library.
805    let data_decl = v::Stmt::new_rawstr("string DATA;".to_string());
806    let code_decl = v::Stmt::new_rawstr("int CODE;".to_string());
807
808    let plus_args = v::Sequential::new_blk_assign(
809        v::Expr::Ref("CODE".to_string()),
810        v::Expr::new_call(
811            "$value$plusargs",
812            vec![v::Expr::new_str("DATA=%s"), v::Expr::new_ref("DATA")],
813        ),
814    );
815
816    let mut initial_block = v::ParallelProcess::new_initial();
817    initial_block
818        // get the data
819        .add_seq(plus_args)
820        // log the path to the data
821        .add_seq(v::Sequential::new_seqexpr(v::Expr::new_call(
822            "$display",
823            vec![
824                v::Expr::new_str("DATA (path to meminit files): %s"),
825                v::Expr::new_ref("DATA"),
826            ],
827        )));
828
829    memories.iter().for_each(|(name, mem_type)| {
830        let mem_access_str = get_mem_str(mem_type.id.as_str());
831        initial_block.add_seq(v::Sequential::new_seqexpr(v::Expr::new_call(
832            "$readmemh",
833            vec![
834                v::Expr::Concat(v::ExprConcat {
835                    exprs: vec![
836                        v::Expr::new_str(&format!("/{}.dat", name)),
837                        v::Expr::new_ref("DATA"),
838                    ],
839                }),
840                v::Expr::new_ipath(&format!("{}.{}", name, mem_access_str)),
841            ],
842        )));
843    });
844
845    let mut final_block = v::ParallelProcess::new_final();
846    memories.iter().for_each(|(name, mem_type)| {
847        let mem_access_str = get_mem_str(mem_type.id.as_str());
848
849        final_block.add_seq(v::Sequential::new_seqexpr(v::Expr::new_call(
850            "$writememh",
851            vec![
852                v::Expr::Concat(v::ExprConcat {
853                    exprs: vec![
854                        v::Expr::new_str(&format!("/{}.out", name)),
855                        v::Expr::new_ref("DATA"),
856                    ],
857                }),
858                v::Expr::new_ipath(&format!("{}.{}", name, mem_access_str)),
859            ],
860        )));
861    });
862
863    vec![
864        data_decl,
865        code_decl,
866        v::Stmt::new_parallel(v::Parallel::new_process(initial_block)),
867        v::Stmt::new_parallel(v::Parallel::new_process(final_block)),
868    ]
869}