1use 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#[derive(Default)]
19pub struct VerilogBackend;
20
21fn 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
36fn 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
53fn 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 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 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
81fn 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 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 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 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 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
158fn 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 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 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 writeln!(f, "// COMPONENT START: {}", comp.name)?;
265
266 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 cells.iter().try_for_each(|(name, width, _)| {
280 let decl = v::Decl::new_logic(name, *width);
281 writeln!(f, "{};", decl)
282 })?;
283
284 comp.cells
286 .iter()
287 .filter_map(|cell| cell_instance(&cell.borrow()))
288 .try_for_each(|instance| writeln!(f, "{instance}"))?;
289
290 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 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 for (idx, guard) in pool.iter() {
319 write!(f, "wire {} = ", VerilogGuardRef(idx))?;
320 emit_guard(guard, f)?;
321 writeln!(f, ";")?;
322 }
323
324 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 let mut checks = v::ParallelProcess::new_always_comb();
341
342 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 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) = ¶m_binding[0];
408 let (vn, value) = ¶m_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
446fn 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 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 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
491fn 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
509fn emit_assignment(
524 dst: &RRC<ir::Port>,
525 assignments: &[(RRC<ir::Port>, GuardRef)],
526 pool: &ir::GuardPool,
527) -> v::Parallel {
528 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 let rhs: v::Expr = if is_data_port(dst) {
539 if assignments.len() == 1 {
540 let (dst, _) = &assignments[0];
545 port_to_ref(dst)
546 } else {
547 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 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 if assignments.len() == 1 {
586 let (src, guard) = &assignments[0];
587 if data {
588 return writeln!(
591 f,
592 "assign {} = {};",
593 VerilogPortRef(dst),
594 VerilogPortRef(src)
595 );
596 } else {
597 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 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 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
691struct 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
700struct 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
758fn memory_read_write(comp: &ir::Component) -> Vec<v::Stmt> {
776 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 .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 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 .add_seq(plus_args)
820 .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}