Skip to main content

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