calyx_opt/analysis/
control_ports.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
use super::AssignmentAnalysis;
use calyx_ir::{self as ir, RRC};
use itertools::Itertools;
use std::{
    collections::{HashMap, HashSet},
    rc::Rc,
};

type PortMap = HashMap<ir::Id, Vec<RRC<ir::Port>>>;
type Binding = (Vec<(ir::Id, RRC<ir::Cell>)>, Vec<(ir::Id, RRC<ir::Port>)>);
type InvokeMap = HashMap<ir::Id, Vec<Binding>>;

/// Contains a mapping from name of [ir::CombGroup] to the ports read by the control program
/// as well as the mapping from invoke statements to the port mappings.
/// The vector of ports is guaranteed to only contain unique ports.
pub struct ControlPorts<const INVOKE_MAP: bool> {
    // Map name of combinational group to the ports read by the control program.
    cg_to_port: PortMap,
    // Mapping from name of invoke instance to the ref cells and port bindings.
    invoke_map: InvokeMap,
}

impl<const INVOKE_MAP: bool> ControlPorts<INVOKE_MAP> {
    /// Return a reference to the port reads associated with the group.
    pub fn get(&self, group: &ir::Id) -> Option<&Vec<RRC<ir::Port>>> {
        self.cg_to_port.get(group)
    }

    /// Remove the port reads associated with the group.
    pub fn remove(&mut self, group: &ir::Id) -> Option<Vec<RRC<ir::Port>>> {
        self.cg_to_port.remove(group)
    }

    /// Get all bindings for an instance
    pub fn get_bindings(&self, instance: &ir::Id) -> Option<&Vec<Binding>> {
        if INVOKE_MAP {
            self.invoke_map.get(instance)
        } else {
            panic!("ControlPorts instance built without invoke_map")
        }
    }

    /// Return the entire invoke binding map.
    pub fn get_all_bindings(self) -> InvokeMap {
        if INVOKE_MAP {
            self.invoke_map
        } else {
            panic!("ControlPorts instance built without invoke_map")
        }
    }
}

impl<const INVOKE_MAP: bool> ControlPorts<INVOKE_MAP> {
    // handles the invoke pattern match in construct_static: meant to handle
    // inputs, outputs =  inputs,outputs of the invoke that we're analzing
    // comp = comp of invoke
    // comb group = comb group of invoke, if it exists
    // either dynamic or static invokes
    // updates self.cg_to_port to account for comb_group of the invoke
    // updates self.invoke_map to account for the invoke
    fn handle_invoke(
        &mut self,
        inputs: &[(ir::Id, ir::RRC<ir::Port>)],
        outputs: &[(ir::Id, ir::RRC<ir::Port>)],
        ref_cells: &[(ir::Id, ir::RRC<ir::Cell>)],
        comp: &ir::RRC<ir::Cell>,
        comb_group: &Option<ir::RRC<ir::CombGroup>>,
    ) {
        if let Some(c) = comb_group {
            let cells = c
                .borrow()
                .assignments
                .iter()
                .analysis()
                .cell_uses()
                .map(|cell| cell.borrow().name())
                .collect::<HashSet<_>>();

            // Only add ports that come from cells used in this comb group.
            let ports =
                inputs
                    .iter()
                    .map(|(_, port)| Rc::clone(port))
                    .filter(|port| {
                        cells.contains(&port.borrow().get_parent_name())
                    });
            self.cg_to_port
                .entry(c.borrow().name())
                .or_default()
                .extend(ports);
        }
        if INVOKE_MAP {
            let name = comp.borrow().name();
            let bindings =
                inputs.iter().chain(outputs.iter()).cloned().collect_vec();
            self.invoke_map
                .entry(name)
                .or_default()
                .push((ref_cells.to_vec(), bindings));
        }
    }

    // currently does nothing since there are no invokes nor comb groups in
    // static control. However, we might want to add them, so we are keeping this
    /// (currenlty pointless) function here
    fn construct_static(&mut self, scon: &ir::StaticControl) {
        match scon {
            ir::StaticControl::Empty(_) | ir::StaticControl::Enable(_) => (),
            ir::StaticControl::Repeat(ir::StaticRepeat { body, .. }) => {
                self.construct_static(body)
            }
            ir::StaticControl::Seq(ir::StaticSeq { stmts, .. })
            | ir::StaticControl::Par(ir::StaticPar { stmts, .. }) => {
                stmts.iter().for_each(|stmt| self.construct_static(stmt));
            }
            ir::StaticControl::If(ir::StaticIf {
                tbranch, fbranch, ..
            }) => {
                self.construct_static(tbranch);
                self.construct_static(fbranch);
            }
            ir::StaticControl::Invoke(ir::StaticInvoke {
                comp,
                inputs,
                outputs,
                ref_cells,
                ..
            }) => {
                self.handle_invoke(inputs, outputs, ref_cells, comp, &None);
            }
        }
    }

    fn construct(&mut self, con: &ir::Control) {
        match con {
            ir::Control::Enable(_) | ir::Control::Empty(_) => {}
            ir::Control::Invoke(ir::Invoke {
                comp,
                comb_group,
                inputs,
                outputs,
                ref_cells,
                ..
            }) => {
                self.handle_invoke(
                    inputs, outputs, ref_cells, comp, comb_group,
                );
            }
            ir::Control::If(ir::If {
                cond,
                port,
                tbranch,
                fbranch,
                ..
            }) => {
                if let Some(c) = cond {
                    self.cg_to_port
                        .entry(c.borrow().name())
                        .or_default()
                        .push(Rc::clone(port));
                }

                self.construct(tbranch);
                self.construct(fbranch);
            }
            ir::Control::While(ir::While {
                cond, port, body, ..
            }) => {
                if let Some(c) = cond {
                    self.cg_to_port
                        .entry(c.borrow().name())
                        .or_default()
                        .push(Rc::clone(port));
                }
                self.construct(body);
            }
            ir::Control::Repeat(ir::Repeat { body, .. }) => {
                self.construct(body);
            }
            ir::Control::Seq(ir::Seq { stmts, .. })
            | ir::Control::Par(ir::Par { stmts, .. }) => {
                stmts.iter().for_each(|con| self.construct(con));
            }
            ir::Control::Static(sc) => {
                // Static control currently has no comb groups. But we have a
                // (currently pointless) function here in case we want to add
                // comb groups to static control at some point.
                self.construct_static(sc)
            }
        }
    }
}

impl<const INVOKE_MAP: bool> From<&ir::Control> for ControlPorts<INVOKE_MAP> {
    fn from(con: &ir::Control) -> Self {
        let mut cp = ControlPorts {
            cg_to_port: HashMap::new(),
            invoke_map: HashMap::new(),
        };
        cp.construct(con);
        // Deduplicate all group port reads
        cp.cg_to_port.values_mut().for_each(|v| {
            *v = v.drain(..).unique_by(|p| p.borrow().canonical()).collect()
        });
        // Deduplicate all invoke bindings if map was constructed
        if INVOKE_MAP {
            cp.invoke_map.values_mut().for_each(|v| {
                *v = v
                    .drain(..)
                    .unique_by(|(cells, ports)| {
                        (
                            cells
                                .clone()
                                .into_iter()
                                .map(|(c, cell)| (c, cell.borrow().name()))
                                .collect_vec(),
                            ports
                                .clone()
                                .into_iter()
                                .map(|(p, v)| (p, v.borrow().canonical()))
                                .collect_vec(),
                        )
                    })
                    .collect()
            });
        }
        cp
    }
}