prjunnamed_yosys_json/
export.rs

1use jzon::JsonValue;
2use std::{cell::RefCell, collections::BTreeMap, io::Write};
3
4use crate::yosys::{self, CellDetails, MetadataValue, NetDetails, PortDetails};
5use prjunnamed_netlist::{
6    Cell, Const, ControlNet, Design, IoNet, IoValue, MemoryPortRelation, MetaItem, MetaItemRef, Net, Trit, Value,
7};
8
9struct Counter(usize);
10
11impl Counter {
12    fn advance(&mut self) -> usize {
13        let index = self.0;
14        self.0 += 1;
15        index
16    }
17}
18
19struct NetlistIndexerState {
20    map: BTreeMap<Net, usize>,
21    io_map: BTreeMap<IoNet, usize>,
22    next: Counter,
23}
24
25struct NetlistIndexer(RefCell<NetlistIndexerState>);
26
27impl NetlistIndexer {
28    fn new() -> NetlistIndexer {
29        NetlistIndexer(RefCell::new(NetlistIndexerState {
30            map: BTreeMap::new(),
31            io_map: BTreeMap::new(),
32            next: Counter(2), // "to avoid confusion", as write_json claims
33        }))
34    }
35
36    fn net(&self, net: Net) -> yosys::Bit {
37        match net.as_const() {
38            Some(Trit::Undef) => yosys::Bit::Undef,
39            Some(Trit::Zero) => yosys::Bit::Zero,
40            Some(Trit::One) => yosys::Bit::One,
41            None => {
42                let state = &mut *self.0.borrow_mut();
43                yosys::Bit::Net(*state.map.entry(net).or_insert_with(|| state.next.advance()))
44            }
45        }
46    }
47
48    fn synthetic_net(&self) -> yosys::Bit {
49        yosys::Bit::Net(self.0.borrow_mut().next.advance())
50    }
51
52    fn value(&self, value: &Value) -> yosys::BitVector {
53        yosys::BitVector(value.iter().map(|n| self.net(n)).collect::<Vec<_>>())
54    }
55
56    fn synthetic_value(&self, size: usize) -> yosys::BitVector {
57        yosys::BitVector(std::iter::repeat_with(|| self.synthetic_net()).take(size).collect::<Vec<_>>())
58    }
59
60    fn io_net(&self, net: IoNet) -> yosys::Bit {
61        let state = &mut *self.0.borrow_mut();
62        yosys::Bit::Net(*state.io_map.entry(net).or_insert_with(|| state.next.advance()))
63    }
64
65    fn io_value(&self, value: &IoValue) -> yosys::BitVector {
66        yosys::BitVector(value.iter().map(|n| self.io_net(n)).collect::<Vec<_>>())
67    }
68}
69
70fn map_metadata(metadata: MetaItemRef) -> Vec<(String, yosys::MetadataValue)> {
71    let mut ys_attrs: Vec<(String, MetadataValue)> = Vec::new();
72    let mut ys_srcs = Vec::new();
73    for item in metadata.iter() {
74        match item.get() {
75            MetaItem::Source { file, start, end } => {
76                ys_srcs.push(format!(
77                    "{}:{}.{}-{}.{}",
78                    file.get(),
79                    start.line + 1,
80                    start.column + 1,
81                    end.line + 1,
82                    end.column + 1
83                ));
84            }
85            MetaItem::Attr { name, value } => {
86                ys_attrs.push((name.get().to_owned(), value.into()));
87            }
88            _ => (),
89        }
90    }
91    if !ys_srcs.is_empty() {
92        ys_attrs.push(("src".to_owned(), ys_srcs.join("|").into()));
93    }
94    ys_attrs
95}
96
97fn export_module(mut design: Design) -> yosys::Module {
98    let indexer = NetlistIndexer::new();
99    let mut ys_module = yosys::Module::new();
100
101    // Yosys IR cannot express DFFs with both asynchronous and synchronous reset.
102    for cell_ref in design.iter_cells() {
103        if let Cell::Dff(flip_flop) = &*cell_ref.get() {
104            if flip_flop.has_clear() && flip_flop.has_reset() {
105                let mut flip_flop = flip_flop.clone();
106                flip_flop.unmap_reset(&design);
107                cell_ref.replace(Cell::Dff(flip_flop));
108            }
109        }
110    }
111    design.apply();
112
113    for (name, io_value) in design.iter_ios() {
114        ys_module.ports.add(name, PortDetails::new(yosys::PortDirection::Inout, indexer.io_value(&io_value)))
115    }
116
117    for cell_ref in design.iter_cells() {
118        let cell_index = cell_ref.debug_index();
119        let output = cell_ref.output();
120
121        let ys_cell_name = format!("${}", cell_index);
122
123        let ys_cell_unary = |module: &mut yosys::Module, ty: &str, a: &Value| {
124            CellDetails::new(ty)
125                .param("A_SIGNED", 0)
126                .param("A_WIDTH", a.len())
127                .param("Y_WIDTH", output.len())
128                .input("A", indexer.value(a))
129                .output("Y", indexer.value(&output))
130                .attrs(map_metadata(cell_ref.metadata()))
131                .add_to(&format!("${}", cell_index), module)
132        };
133
134        let ys_cell_binary = |module: &mut yosys::Module, ty: &str, a: &Value, b: &Value, signed: bool| {
135            CellDetails::new(ty)
136                .param("A_SIGNED", if signed { 1 } else { 0 })
137                .param("A_WIDTH", a.len())
138                .param("B_SIGNED", if signed { 1 } else { 0 })
139                .param("B_WIDTH", b.len())
140                .param("Y_WIDTH", output.len())
141                .input("A", indexer.value(a))
142                .input("B", indexer.value(b))
143                .output("Y", indexer.value(&output))
144                .attrs(map_metadata(cell_ref.metadata()))
145                .add_to(&format!("${}", cell_index), module)
146        };
147
148        let ys_shift_count = |module: &mut yosys::Module, a: &Value, stride: u32| -> yosys::BitVector {
149            if stride == 1 {
150                indexer.value(a)
151            } else if stride == 0 {
152                indexer.value(&Value::zero(1))
153            } else {
154                let stride_bits = stride.ilog2() + 1;
155                let stride = Const::from_uint(stride.into(), stride_bits as usize);
156                let result = indexer.synthetic_value(a.len() + stride.len());
157                CellDetails::new("$mul")
158                    .param("A_SIGNED", 0)
159                    .param("A_WIDTH", a.len())
160                    .param("B_SIGNED", 0)
161                    .param("B_WIDTH", stride.len())
162                    .param("Y_WIDTH", result.len())
163                    .input("A", indexer.value(a))
164                    .input("B", indexer.value(&Value::from(stride)))
165                    .output("Y", result.clone())
166                    .attrs(map_metadata(cell_ref.metadata()))
167                    .add_to(&format!("${}$stride", cell_index), module);
168                result
169            }
170        };
171
172        let ys_cell_shift = |module: &mut yosys::Module, ty: &str, a: &Value, b: &Value, stride: u32, signed: bool| {
173            let b = ys_shift_count(module, b, stride);
174            CellDetails::new(ty)
175                .param("A_SIGNED", if signed { 1 } else { 0 })
176                .param("A_WIDTH", a.len())
177                .param("B_SIGNED", 0)
178                .param("B_WIDTH", b.len())
179                .param("Y_WIDTH", output.len())
180                .input("A", indexer.value(a))
181                .input("B", b)
182                .output("Y", indexer.value(&output))
183                .attrs(map_metadata(cell_ref.metadata()))
184                .add_to(&format!("${}", cell_index), module)
185        };
186
187        let ys_control_net_pos = |module: &mut yosys::Module, not_name: &str, cnet: ControlNet| -> yosys::Bit {
188            match cnet {
189                ControlNet::Pos(net) => indexer.net(net),
190                ControlNet::Neg(net) => {
191                    let result = indexer.synthetic_net();
192                    CellDetails::new("$not")
193                        .param("A_SIGNED", 0)
194                        .param("A_WIDTH", 1)
195                        .param("Y_WIDTH", output.len())
196                        .input("A", indexer.value(&net.into()))
197                        .output("Y", result)
198                        .attrs(map_metadata(cell_ref.metadata()))
199                        .add_to(not_name, module);
200                    result
201                }
202            }
203        };
204
205        match &*cell_ref.get() {
206            Cell::Buf(arg) => ys_cell_unary(&mut ys_module, "$pos", arg),
207            Cell::Not(arg) => ys_cell_unary(&mut ys_module, "$not", arg),
208            Cell::And(arg1, arg2) => ys_cell_binary(&mut ys_module, "$and", arg1, arg2, false),
209            Cell::Or(arg1, arg2) => ys_cell_binary(&mut ys_module, "$or", arg1, arg2, false),
210            Cell::Xor(arg1, arg2) => ys_cell_binary(&mut ys_module, "$xor", arg1, arg2, false),
211            Cell::Mux(arg1, arg2, arg3) => CellDetails::new("$mux")
212                .param("WIDTH", output.len())
213                .input("A", indexer.value(arg3))
214                .input("B", indexer.value(arg2))
215                .input("S", indexer.net(*arg1))
216                .output("Y", indexer.value(&output))
217                .attrs(map_metadata(cell_ref.metadata()))
218                .add_to(&format!("${}", cell_index), &mut ys_module),
219            Cell::Adc(arg1, arg2, arg3) => {
220                // The $alu cell isn't supported by `write_verilog`, so we have to pattern-match here.
221                match arg3.as_const() {
222                    Some(Trit::Zero) => {
223                        // no carry-in
224                        CellDetails::new("$add")
225                            .param("A_SIGNED", 0)
226                            .param("A_WIDTH", arg1.len())
227                            .param("B_SIGNED", 0)
228                            .param("B_WIDTH", arg2.len())
229                            .param("Y_WIDTH", output.len())
230                            .input("A", indexer.value(arg1))
231                            .input("B", indexer.value(arg2))
232                            .output("Y", indexer.value(&output))
233                            .attrs(map_metadata(cell_ref.metadata()))
234                            .add_to(&format!("${}", cell_index), &mut ys_module);
235                    }
236                    _ => {
237                        // generic
238                        let ys_a = Value::from(arg3).concat(arg1);
239                        let ys_b = Value::from(Net::ONE).concat(arg2);
240                        let ys_y = indexer.synthetic_value(1).concat(&indexer.value(&output));
241                        CellDetails::new("$add")
242                            .param("A_SIGNED", 0)
243                            .param("A_WIDTH", 1 + arg1.len())
244                            .param("B_SIGNED", 0)
245                            .param("B_WIDTH", 1 + arg2.len())
246                            .param("Y_WIDTH", 1 + output.len())
247                            .input("A", indexer.value(&ys_a))
248                            .input("B", indexer.value(&ys_b))
249                            .output("Y", ys_y)
250                            .attrs(map_metadata(cell_ref.metadata()))
251                            .add_to(&format!("${}", cell_index), &mut ys_module);
252                    }
253                }
254            }
255            Cell::Aig(arg1, arg2) => {
256                let arg1 = ys_control_net_pos(&mut ys_module, &format!("${}$not1", cell_index), *arg1);
257                let arg2 = ys_control_net_pos(&mut ys_module, &format!("${}$not2", cell_index), *arg2);
258                CellDetails::new("$and")
259                    .param("A_SIGNED", 0)
260                    .param("A_WIDTH", 1)
261                    .param("B_SIGNED", 0)
262                    .param("B_WIDTH", 1)
263                    .param("Y_WIDTH", 1)
264                    .input("A", arg1)
265                    .input("B", arg2)
266                    .output("Y", indexer.value(&output))
267                    .attrs(map_metadata(cell_ref.metadata()))
268                    .add_to(&format!("${}", cell_index), &mut ys_module)
269            }
270
271            Cell::Eq(arg1, arg2) => ys_cell_binary(&mut ys_module, "$eq", arg1, arg2, false),
272            Cell::ULt(arg1, arg2) => ys_cell_binary(&mut ys_module, "$lt", arg1, arg2, false),
273            Cell::SLt(arg1, arg2) => ys_cell_binary(&mut ys_module, "$lt", arg1, arg2, true),
274
275            Cell::Shl(arg1, arg2, stride) => ys_cell_shift(&mut ys_module, "$shl", arg1, arg2, *stride, false),
276            Cell::UShr(arg1, arg2, stride) => ys_cell_shift(&mut ys_module, "$shr", arg1, arg2, *stride, false),
277            Cell::SShr(arg1, arg2, stride) => ys_cell_shift(&mut ys_module, "$sshr", arg1, arg2, *stride, true),
278            Cell::XShr(arg1, arg2, stride) => ys_cell_shift(&mut ys_module, "$shiftx", arg1, arg2, *stride, false),
279
280            Cell::Mul(arg1, arg2) => ys_cell_binary(&mut ys_module, "$mul", arg1, arg2, false),
281            Cell::UDiv(arg1, arg2) => ys_cell_binary(&mut ys_module, "$div", arg1, arg2, false),
282            Cell::UMod(arg1, arg2) => ys_cell_binary(&mut ys_module, "$mod", arg1, arg2, false),
283            Cell::SDivTrunc(arg1, arg2) => ys_cell_binary(&mut ys_module, "$div", arg1, arg2, true),
284            Cell::SDivFloor(arg1, arg2) => ys_cell_binary(&mut ys_module, "$divfloor", arg1, arg2, true),
285            Cell::SModTrunc(arg1, arg2) => ys_cell_binary(&mut ys_module, "$mod", arg1, arg2, true),
286            Cell::SModFloor(arg1, arg2) => ys_cell_binary(&mut ys_module, "$modfloor", arg1, arg2, true),
287
288            Cell::Match { .. } => unimplemented!("match cells must be lowered first for Yosys JSON export"),
289            Cell::Assign { .. } => unimplemented!("assign cells must be lowered first for Yosys JSON export"),
290
291            Cell::Dff(flip_flop) => {
292                let ys_cell_type = match (
293                    flip_flop.has_clear(),
294                    flip_flop.has_reset(),
295                    flip_flop.has_enable(),
296                    flip_flop.reset_over_enable,
297                ) {
298                    (true, true, _, _) => unreachable!(),
299                    (true, false, false, _) => "$adff",
300                    (true, false, true, _) => "$adffe",
301                    (false, true, false, _) => "$sdff",
302                    (false, true, true, false) => "$sdffce",
303                    (false, true, true, true) => "$sdffe",
304                    (false, false, false, _) => "$dff",
305                    (false, false, true, _) => "$dffe",
306                };
307                let mut ys_cell = CellDetails::new(ys_cell_type);
308                if flip_flop.has_clear() {
309                    ys_cell = ys_cell
310                        .param("ARST_POLARITY", flip_flop.clear.is_positive())
311                        .param("ARST_VALUE", flip_flop.clear_value.clone())
312                        .input("ARST", indexer.net(flip_flop.clear.net()));
313                }
314                if flip_flop.has_reset() {
315                    ys_cell = ys_cell
316                        .param("SRST_POLARITY", flip_flop.reset.is_positive())
317                        .param("SRST_VALUE", flip_flop.reset_value.clone())
318                        .input("SRST", indexer.net(flip_flop.reset.net()));
319                }
320                if flip_flop.has_enable() {
321                    ys_cell = ys_cell
322                        .param("EN_POLARITY", flip_flop.enable.is_positive())
323                        .input("EN", indexer.net(flip_flop.enable.net()));
324                }
325                ys_cell
326                    .param("CLK_POLARITY", flip_flop.clock.is_positive())
327                    .input("CLK", indexer.net(flip_flop.clock.net()))
328                    .param("WIDTH", output.len())
329                    .input("D", indexer.value(&flip_flop.data))
330                    .output("Q", indexer.value(&output))
331                    .attrs(map_metadata(cell_ref.metadata()))
332                    .add_to(&ys_cell_name, &mut ys_module);
333                NetDetails::new(indexer.value(&output))
334                    .attr("init", flip_flop.init_value.clone())
335                    .add_to(&format!("{}$ff", ys_cell_name), &mut ys_module);
336                continue; // skip default $out wire (init-less) creation
337            }
338
339            Cell::Memory(memory) => {
340                let abits = memory
341                    .write_ports
342                    .iter()
343                    .map(|port| port.addr.len() + port.wide_log2(memory))
344                    .max()
345                    .unwrap_or(0)
346                    .max(
347                        memory
348                            .read_ports
349                            .iter()
350                            .map(|port| port.addr.len() + port.wide_log2(memory))
351                            .max()
352                            .unwrap_or(0),
353                    );
354
355                let mut wr_ports = 0;
356                let mut wr_clk_polarity = Const::new();
357                let mut wr_wide_continuation = Const::new();
358                let mut wr_addr = Value::new();
359                let mut wr_data = Value::new();
360                let mut wr_clk = Value::new();
361                let mut wr_en = Value::new();
362                let mut write_port_indices = vec![];
363                for (port_index, port) in memory.write_ports.iter().enumerate() {
364                    let wide_log2 = port.wide_log2(memory);
365                    for index in 0..(1 << wide_log2) {
366                        let addr = Value::from(Const::from_uint(index, wide_log2)).concat(&port.addr).zext(abits);
367                        wr_clk.extend([port.clock.net()]);
368                        wr_addr.extend(&addr);
369                        wr_clk_polarity.push(port.clock.is_positive());
370                        wr_wide_continuation.push(index != 0);
371                        write_port_indices.push(port_index);
372                    }
373                    wr_data.extend(&port.data);
374                    wr_en.extend(&port.mask);
375                    wr_ports += 1 << wide_log2;
376                }
377                if wr_ports == 0 {
378                    wr_clk_polarity.push(false);
379                    wr_wide_continuation.push(false);
380                }
381
382                let mut rd_ports = 0;
383                let mut rd_clk_enable = Const::new();
384                let mut rd_clk_polarity = Const::new();
385                let mut rd_transparency_mask = Const::new();
386                let mut rd_collision_x_mask = Const::new();
387                let mut rd_wide_continuation = Const::new();
388                let mut rd_ce_over_srst = Const::new();
389                let mut rd_arst_value = Const::new();
390                let mut rd_srst_value = Const::new();
391                let mut rd_init_value = Const::new();
392                let mut rd_clk = Value::new();
393                let mut rd_en = yosys::BitVector(vec![]);
394                let mut rd_arst = yosys::BitVector(vec![]);
395                let mut rd_srst = yosys::BitVector(vec![]);
396                let mut rd_addr = Value::new();
397                for (port_index, port) in memory.read_ports.iter().enumerate() {
398                    let wide_log2 = port.wide_log2(memory);
399                    for index in 0..(1 << wide_log2) {
400                        let addr = Value::from(Const::from_uint(index, wide_log2)).concat(&port.addr).zext(abits);
401                        rd_addr.extend(&addr);
402                        if let Some(ref flip_flop) = port.flip_flop {
403                            rd_clk.extend([flip_flop.clock.net()]);
404                            rd_clk_enable.push(true);
405                            rd_clk_polarity.push(flip_flop.clock.is_positive());
406                            rd_ce_over_srst.push(!flip_flop.reset_over_enable);
407                            for &write_port_index in &write_port_indices {
408                                let (trans, col_x) = if flip_flop.clock == memory.write_ports[write_port_index].clock {
409                                    match flip_flop.relations[write_port_index] {
410                                        MemoryPortRelation::Undefined => (false, true),
411                                        MemoryPortRelation::ReadBeforeWrite => (false, false),
412                                        MemoryPortRelation::Transparent => (true, false),
413                                    }
414                                } else {
415                                    (false, false)
416                                };
417                                rd_transparency_mask.push(trans);
418                                rd_collision_x_mask.push(col_x);
419                            }
420                        } else {
421                            rd_clk.extend([Net::UNDEF]);
422                            rd_clk_enable.push(false);
423                            rd_clk_polarity.push(Trit::Undef);
424                            rd_ce_over_srst.push(Trit::Undef);
425                            rd_transparency_mask.extend(Const::undef(wr_ports));
426                            rd_collision_x_mask.extend(Const::undef(wr_ports));
427                        }
428                        rd_wide_continuation.push(index != 0);
429                    }
430                    if let Some(ref flip_flop) = port.flip_flop {
431                        rd_arst_value.extend(&flip_flop.clear_value);
432                        rd_srst_value.extend(&flip_flop.reset_value);
433                        rd_init_value.extend(&flip_flop.init_value);
434                        let enable = ys_control_net_pos(
435                            &mut ys_module,
436                            &format!("${cell_index}$rd{port_index}$en$not"),
437                            flip_flop.enable,
438                        );
439                        let clear = ys_control_net_pos(
440                            &mut ys_module,
441                            &format!("${cell_index}$rd{port_index}$arst$not"),
442                            flip_flop.clear,
443                        );
444                        let reset = ys_control_net_pos(
445                            &mut ys_module,
446                            &format!("${cell_index}$rd{port_index}$srst$not"),
447                            flip_flop.reset,
448                        );
449                        rd_en = rd_en.concat(&yosys::BitVector(vec![enable; 1 << wide_log2]));
450                        rd_srst = rd_srst.concat(&yosys::BitVector(vec![reset; 1 << wide_log2]));
451                        rd_arst = rd_arst.concat(&yosys::BitVector(vec![clear; 1 << wide_log2]));
452                    } else {
453                        rd_arst_value.extend(Const::undef(port.data_len));
454                        rd_srst_value.extend(Const::undef(port.data_len));
455                        rd_init_value.extend(Const::undef(port.data_len));
456                        rd_en = rd_en.concat(&yosys::BitVector(vec![yosys::Bit::One; 1 << wide_log2]));
457                        rd_srst = rd_srst.concat(&yosys::BitVector(vec![yosys::Bit::Zero; 1 << wide_log2]));
458                        rd_arst = rd_arst.concat(&yosys::BitVector(vec![yosys::Bit::Zero; 1 << wide_log2]));
459                    }
460                    rd_ports += 1 << wide_log2;
461                }
462                if rd_ports == 0 {
463                    rd_clk_enable.push(false);
464                    rd_clk_polarity.push(false);
465                    rd_wide_continuation.push(false);
466                    rd_ce_over_srst.push(false);
467                    rd_arst_value.push(false);
468                    rd_srst_value.push(false);
469                    rd_init_value.push(false);
470                }
471                if rd_ports == 0 || wr_ports == 0 {
472                    rd_transparency_mask.push(false);
473                    rd_collision_x_mask.push(false);
474                }
475
476                CellDetails::new("$mem_v2")
477                    .param("MEMID", format!("${}$mem", cell_index))
478                    .param("OFFSET", 0)
479                    .param("SIZE", memory.depth)
480                    .param("WIDTH", memory.width)
481                    .param("ABITS", abits)
482                    .param("INIT", memory.init_value.clone())
483                    .param("WR_PORTS", wr_ports)
484                    .param("WR_CLK_ENABLE", Const::ones(wr_ports.max(1)))
485                    .param("WR_CLK_POLARITY", wr_clk_polarity)
486                    .param("WR_PRIORITY_MASK", Const::zero(wr_ports.max(1)))
487                    .param("WR_WIDE_CONTINUATION", wr_wide_continuation)
488                    .input("WR_ADDR", indexer.value(&wr_addr))
489                    .input("WR_DATA", indexer.value(&wr_data))
490                    .input("WR_CLK", indexer.value(&wr_clk))
491                    .input("WR_EN", indexer.value(&wr_en))
492                    .param("RD_PORTS", rd_ports)
493                    .param("RD_CLK_ENABLE", rd_clk_enable)
494                    .param("RD_CLK_POLARITY", rd_clk_polarity)
495                    .param("RD_TRANSPARENCY_MASK", rd_transparency_mask)
496                    .param("RD_COLLISION_X_MASK", rd_collision_x_mask)
497                    .param("RD_WIDE_CONTINUATION", rd_wide_continuation)
498                    .param("RD_CE_OVER_SRST", rd_ce_over_srst)
499                    .param("RD_ARST_VALUE", rd_arst_value)
500                    .param("RD_SRST_VALUE", rd_srst_value)
501                    .param("RD_INIT_VALUE", rd_init_value)
502                    .input("RD_CLK", indexer.value(&rd_clk))
503                    .input("RD_EN", rd_en)
504                    .input("RD_ARST", rd_arst)
505                    .input("RD_SRST", rd_srst)
506                    .input("RD_ADDR", indexer.value(&rd_addr))
507                    .output("RD_DATA", indexer.value(&output))
508                    .add_to(&format!("${}", cell_index), &mut ys_module);
509            }
510
511            Cell::IoBuf(io_buffer) => {
512                let ys_enable =
513                    ys_control_net_pos(&mut ys_module, &format!("${}$en$not", cell_index), io_buffer.enable);
514                let ys_attrs = map_metadata(cell_ref.metadata());
515                CellDetails::new("$tribuf")
516                    .param("WIDTH", output.len())
517                    .input("A", indexer.value(&io_buffer.output))
518                    .input("EN", ys_enable)
519                    .output("Y", indexer.io_value(&io_buffer.io))
520                    .attrs(ys_attrs.clone())
521                    .add_to(&format!("${}", cell_index), &mut ys_module);
522                CellDetails::new("$pos")
523                    .param("A_SIGNED", 0)
524                    .param("A_WIDTH", io_buffer.io.len())
525                    .param("Y_WIDTH", output.len())
526                    .input("A", indexer.io_value(&io_buffer.io))
527                    .output("Y", indexer.value(&output))
528                    .attrs(ys_attrs)
529                    .add_to(&format!("${}$pos", cell_index), &mut ys_module);
530            }
531
532            Cell::Other(instance) => {
533                let mut ys_cell = CellDetails::new(&instance.kind);
534                for (name, value) in instance.params.iter() {
535                    ys_cell = ys_cell.param(name, value);
536                }
537                for (name, value) in instance.inputs.iter() {
538                    ys_cell = ys_cell.input(name, indexer.value(value));
539                }
540                for (name, value_range) in instance.outputs.iter() {
541                    ys_cell = ys_cell.output(name, indexer.value(&Value::from(&output[value_range.clone()])));
542                }
543                for (name, io_value) in instance.ios.iter() {
544                    ys_cell = ys_cell.inout(name, indexer.io_value(io_value));
545                }
546                ys_cell.attrs(map_metadata(cell_ref.metadata())).add_to(&ys_cell_name, &mut ys_module);
547            }
548            Cell::Target(_target_cell) => {
549                unimplemented!("target cells must be converted to instances first for Yosys JSON export")
550            }
551
552            Cell::Input(port_name, _size) => {
553                ys_module.ports.add(port_name, PortDetails::new(yosys::PortDirection::Input, indexer.value(&output)))
554            }
555            Cell::Output(port_name, value) => {
556                ys_module.ports.add(port_name, PortDetails::new(yosys::PortDirection::Output, indexer.value(value)))
557            }
558            Cell::Name(name, value) | Cell::Debug(name, value) => ys_module.netnames.add(
559                &name.replace(" ", "."),
560                NetDetails::new(indexer.value(value)).attrs(map_metadata(cell_ref.metadata())).attr("hdlname", name),
561            ),
562        };
563
564        if output.len() > 0 {
565            // Duplicating the cell metadata on the cell output net is the only way to get source locations
566            // to show up in nextpnr's timing reports.
567            NetDetails::new(indexer.value(&output))
568                .attrs(map_metadata(cell_ref.metadata()))
569                .add_to(&format!("{}$out", ys_cell_name), &mut ys_module);
570        }
571    }
572
573    ys_module
574}
575
576// Exporting a design to Yosys JSON can require modifying it if it has target cells that must be mapped to instances.
577pub fn export(writer: &mut impl Write, designs: BTreeMap<String, Design>) -> std::io::Result<()> {
578    let mut ys_modules = BTreeMap::new();
579    for (name, design) in designs {
580        ys_modules.insert(name, export_module(design));
581    }
582    let ys_design = yosys::Design { creator: "prjunnamed".into(), modules: ys_modules.into() };
583
584    let json = JsonValue::from(ys_design);
585    json.write_pretty(writer, /*spaces=*/ 4)
586}