rust_hdl_fpga_support/toolchains/
ecp5.rs

1// Covers the ECP5 via nextpnr, not via Diamond
2use rust_hdl_core::prelude::*;
3use std::collections::HashMap;
4
5use super::map_signal_type_to_lattice_string;
6
7#[derive(Default)]
8struct LPFGenerator {
9    path: NamedPath,
10    namespace: NamedPath,
11    lpf: Vec<String>,
12    names: HashMap<usize, String>,
13}
14
15impl Probe for LPFGenerator {
16    fn visit_start_scope(&mut self, name: &str, _node: &dyn Block) {
17        let _top_level = self.path.to_string();
18        self.path.push(name);
19        self.namespace.reset();
20    }
21    fn visit_start_namespace(&mut self, name: &str, _node: &dyn Block) {
22        self.namespace.push(name);
23    }
24    fn visit_atom(&mut self, name: &str, signal: &dyn Atom) {
25        let namespace = self.namespace.flat("$");
26        let name = if namespace.is_empty() {
27            name.to_owned()
28        } else {
29            format!("{}${}", namespace, name)
30        };
31        for pin in &signal.constraints() {
32            self.names.insert(signal.id(), name.clone());
33            let prefix = if signal.bits() == 1 {
34                format!("{}", name)
35            } else {
36                format!("{}[{}]", name, pin.index)
37            };
38            match &pin.constraint {
39                Constraint::Location(l) => {
40                    self.lpf
41                        .push(format!("LOCATE COMP \"{}\" SITE \"{}\"", prefix, l));
42                }
43                Constraint::Kind(k) => {
44                    let name = map_signal_type_to_lattice_string(k);
45                    self.lpf
46                        .push(format!("IOBUF PORT \"{}\" IO_TYPE={}", prefix, name))
47                }
48                Constraint::Timing(t) => {
49                    let timing = match t {
50                        Timing::Periodic(p) => {
51                            format!(
52                                "FREQUENCY PORT \"{prefix}\" {freq} MHz",
53                                prefix = prefix,
54                                freq =
55                                    ((1000.0 / p.period_nanoseconds) * 10000.0).round() / 10000.0
56                            )
57                        }
58                        Timing::Custom(c) => c.to_string(),
59                        _ => unimplemented!("Unknown timing constraint for ECP5 generation"),
60                    };
61                    if !timing.is_empty() {
62                        self.lpf.push(timing);
63                    }
64                }
65                Constraint::Custom(s) => self.lpf.push(s.clone()),
66                Constraint::Slew(k) => {
67                    let tag = match k {
68                        SlewType::Fast => "FAST",
69                        SlewType::Normal => "SLOW",
70                    };
71                    self.lpf
72                        .push(format!("IOBUF PORT \"{}\" SLEWRATE={}", prefix, tag));
73                }
74            }
75        }
76    }
77    fn visit_end_namespace(&mut self, _name: &str, _node: &dyn Block) {
78        self.namespace.pop();
79    }
80    fn visit_end_scope(&mut self, _name: &str, _node: &dyn Block) {
81        self.path.pop();
82    }
83}
84
85pub fn generate_lpf<U: Block>(uut: &U) -> String {
86    let mut lpf = LPFGenerator::default();
87    uut.accept("top", &mut lpf);
88    let mut lpf_uniq = vec![];
89    lpf_uniq.push("BLOCK RESETPATHS".to_string());
90    lpf_uniq.push("BLOCK ASYNCPATHS".to_string());
91    for line in lpf.lpf {
92        if !lpf_uniq.contains(&line) {
93            lpf_uniq.push(line);
94        }
95    }
96    lpf_uniq.join(";\n") + ";\n"
97}