2 * yosys -- Yosys Open SYnthesis Suite
4 * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 #include "kernel/yosys.h"
21 #include "kernel/sigtools.h"
22 #include "kernel/modtools.h"
23 #include "kernel/ffinit.h"
24 #include "kernel/qcsat.h"
25 #include "kernel/mem.h"
26 #include "kernel/ff.h"
27 #include "kernel/ffmerge.h"
30 PRIVATE_NAMESPACE_BEGIN
37 std::vector
<SigSpec
> sig_other
;
42 std::vector
<bool> uncollidable_mask
;
43 std::vector
<bool> transparency_mask
;
44 std::vector
<bool> collision_x_mask
;
45 bool final_transparency
;
46 bool final_collision_x
;
49 // A helper with some caching for transparency-related SAT queries.
50 // Bound to a single memory read port in the process of being converted
51 // from async to sync..
57 // The port, still async at this point.
59 // The virtual FF that will end up merged into this port.
61 // An ezSAT variable that is true when we actually care about the data
62 // read from memory (ie. the FF has enable on and is not in reset).
65 dict
<std::pair
<int, SigBit
>, bool> cache_can_collide_rdwr
;
66 dict
<std::tuple
<int, int, SigBit
, SigBit
>, bool> cache_can_collide_together
;
67 dict
<std::tuple
<int, SigBit
, SigBit
, bool>, bool> cache_is_w2rbyp
;
68 dict
<std::tuple
<SigBit
, bool>, bool> cache_impossible_with_ren
;
70 MemQueryCache(QuickConeSat
&qcsat
, Mem
&mem
, MemRd
&port
, FfData
&ff
) : qcsat(qcsat
), mem(mem
), port(port
), ff(ff
) {
71 // port_ren is an upper bound on when we care about the value fetched
72 // from memory this cycle.
73 int ren
= ezSAT::CONST_TRUE
;
75 ren
= qcsat
.importSigBit(ff
.sig_ce
);
77 ren
= qcsat
.ez
->NOT(ren
);
80 int nrst
= qcsat
.importSigBit(ff
.sig_srst
);
82 nrst
= qcsat
.ez
->NOT(nrst
);
83 ren
= qcsat
.ez
->AND(ren
, nrst
);
88 // Returns ezSAT variable that is true iff the two addresses are the same.
89 int addr_eq(SigSpec raddr
, SigSpec waddr
) {
90 int abits
= std::max(GetSize(raddr
), GetSize(waddr
));
91 raddr
.extend_u0(abits
);
92 waddr
.extend_u0(abits
);
93 return qcsat
.ez
->vec_eq(qcsat
.importSig(raddr
), qcsat
.importSig(waddr
));
96 // Returns true if a given write port bit can be active at the same time
97 // as this read port and at the same address.
98 bool can_collide_rdwr(int widx
, SigBit wen
) {
99 std::pair
<int, SigBit
> key(widx
, wen
);
100 auto it
= cache_can_collide_rdwr
.find(key
);
101 if (it
!= cache_can_collide_rdwr
.end())
103 auto &wport
= mem
.wr_ports
[widx
];
104 int aeq
= addr_eq(port
.addr
, wport
.addr
);
105 int wen_sat
= qcsat
.importSigBit(wen
);
107 bool res
= qcsat
.ez
->solve(aeq
, wen_sat
, port_ren
);
108 cache_can_collide_rdwr
[key
] = res
;
112 // Returns true if both given write port bits can be active at the same
113 // time as this read port and at the same address (three-way collision).
114 bool can_collide_together(int widx1
, int widx2
, int bitidx
) {
115 auto &wport1
= mem
.wr_ports
[widx1
];
116 auto &wport2
= mem
.wr_ports
[widx2
];
117 SigBit wen1
= wport1
.en
[bitidx
];
118 SigBit wen2
= wport2
.en
[bitidx
];
119 std::tuple
<int, int, SigBit
, SigBit
> key(widx1
, widx2
, wen1
, wen2
);
120 auto it
= cache_can_collide_together
.find(key
);
121 if (it
!= cache_can_collide_together
.end())
123 int aeq1
= addr_eq(port
.addr
, wport1
.addr
);
124 int aeq2
= addr_eq(port
.addr
, wport2
.addr
);
125 int wen1_sat
= qcsat
.importSigBit(wen1
);
126 int wen2_sat
= qcsat
.importSigBit(wen2
);
128 bool res
= qcsat
.ez
->solve(wen1_sat
, wen2_sat
, aeq1
, aeq2
, port_ren
);
129 cache_can_collide_together
[key
] = res
;
133 // Returns true if the given mux selection signal is a valid data-bypass
134 // signal in soft transparency logic for a given write port bit.
135 bool is_w2rbyp(int widx
, SigBit wen
, SigBit sel
, bool neg_sel
) {
136 std::tuple
<int, SigBit
, SigBit
, bool> key(widx
, wen
, sel
, neg_sel
);
137 auto it
= cache_is_w2rbyp
.find(key
);
138 if (it
!= cache_is_w2rbyp
.end())
140 auto &wport
= mem
.wr_ports
[widx
];
141 int aeq
= addr_eq(port
.addr
, wport
.addr
);
142 int wen_sat
= qcsat
.importSigBit(wen
);
143 int sel_expected
= qcsat
.ez
->AND(aeq
, wen_sat
);
144 int sel_sat
= qcsat
.importSigBit(sel
);
146 sel_sat
= qcsat
.ez
->NOT(sel_sat
);
148 bool res
= !qcsat
.ez
->solve(port_ren
, qcsat
.ez
->XOR(sel_expected
, sel_sat
));
149 cache_is_w2rbyp
[key
] = res
;
153 // Returns true if the given mux selection signal can never be true
154 // when this port is active.
155 bool impossible_with_ren(SigBit sel
, bool neg_sel
) {
156 std::tuple
<SigBit
, bool> key(sel
, neg_sel
);
157 auto it
= cache_impossible_with_ren
.find(key
);
158 if (it
!= cache_impossible_with_ren
.end())
160 int sel_sat
= qcsat
.importSigBit(sel
);
162 sel_sat
= qcsat
.ez
->NOT(sel_sat
);
164 bool res
= !qcsat
.ez
->solve(port_ren
, sel_sat
);
165 cache_impossible_with_ren
[key
] = res
;
169 // Helper for data_eq: walks up a multiplexer when the value of its
170 // sel signal is constant under the assumption that this read port
171 // is active and a given other mux sel signal is true.
172 bool walk_up_mux_cond(SigBit sel
, bool neg_sel
, SigBit
&bit
) {
173 auto &drivers
= qcsat
.modwalker
.signal_drivers
[qcsat
.modwalker
.sigmap(bit
)];
174 if (GetSize(drivers
) != 1)
176 auto driver
= *drivers
.begin();
177 if (!driver
.cell
->type
.in(ID($mux
), ID($pmux
)))
179 log_assert(driver
.port
== ID::Y
);
180 SigSpec sig_s
= driver
.cell
->getPort(ID::S
);
181 int sel_sat
= qcsat
.importSigBit(sel
);
183 sel_sat
= qcsat
.ez
->NOT(sel_sat
);
185 int width
= driver
.cell
->parameters
.at(ID::WIDTH
).as_int();
186 for (int i
= 0; i
< GetSize(sig_s
); i
++) {
187 int sbit
= qcsat
.importSigBit(sig_s
[i
]);
189 if (!qcsat
.ez
->solve(port_ren
, sel_sat
, qcsat
.ez
->NOT(sbit
))) {
190 bit
= driver
.cell
->getPort(ID::B
)[i
* width
+ driver
.offset
];
193 if (qcsat
.ez
->solve(port_ren
, sel_sat
, sbit
))
197 bit
= driver
.cell
->getPort(ID::A
)[driver
.offset
];
203 // Returns true if a given data signal is equivalent to another, under
204 // the assumption that this read port is active and a given mux sel signal
205 // is true. Used to match transparency logic data with write port data.
206 // The walk_up_mux_cond part is necessary because write ports in yosys
207 // tend to be connected to things like (wen ? wdata : 'x).
208 bool data_eq(SigBit sel
, bool neg_sel
, SigBit dbit
, SigBit odbit
) {
209 if (qcsat
.modwalker
.sigmap(dbit
) == qcsat
.modwalker
.sigmap(odbit
))
211 while (walk_up_mux_cond(sel
, neg_sel
, dbit
));
212 while (walk_up_mux_cond(sel
, neg_sel
, odbit
));
213 return qcsat
.modwalker
.sigmap(dbit
) == qcsat
.modwalker
.sigmap(odbit
);
217 struct MemoryDffWorker
222 FfMergeHelper merger
;
224 MemoryDffWorker(Module
*module
) : module(module
), modwalker(module
->design
)
226 modwalker
.setup(module
);
227 initvals
.set(&modwalker
.sigmap
, module
);
228 merger
.set(&initvals
, module
);
231 // Starting from the output of an async read port, as long as the data
232 // signal's only user is a mux data signal, passes through the mux
233 // and remembers information about it. Conceptually works on every
234 // bit separately, but coalesces the result when possible.
235 SigSpec
walk_muxes(SigSpec data
, std::vector
<MuxData
> &res
) {
238 did_something
= false;
240 Cell
*prev_cell
= nullptr;
241 bool prev_is_b
= false;
242 for (int i
= 0; i
< GetSize(data
); i
++) {
243 SigBit bit
= modwalker
.sigmap(data
[i
]);
244 auto &consumers
= modwalker
.signal_consumers
[bit
];
245 if (GetSize(consumers
) != 1 || modwalker
.signal_outputs
.count(bit
))
247 auto consumer
= *consumers
.begin();
249 if (consumer
.cell
->type
== ID($mux
)) {
250 if (consumer
.port
== ID::A
) {
252 } else if (consumer
.port
== ID::B
) {
257 } else if (consumer
.cell
->type
== ID($pmux
)) {
258 if (consumer
.port
== ID::A
) {
266 SigSpec y
= consumer
.cell
->getPort(ID::Y
);
267 int mux_width
= GetSize(y
);
268 SigBit ybit
= y
.extract(consumer
.offset
);
269 if (prev_cell
!= consumer
.cell
|| prev_idx
+1 != i
|| prev_is_b
!= is_b
) {
274 md
.sig_s
= consumer
.cell
->getPort(ID::S
);
275 md
.sig_other
.resize(GetSize(md
.sig_s
));
276 prev_cell
= consumer
.cell
;
280 auto &md
= res
.back();
282 for (int j
= 0; j
< GetSize(md
.sig_s
); j
++) {
283 SigBit obit
= consumer
.cell
->getPort(is_b
? ID::A
: ID::B
).extract(j
* mux_width
+ consumer
.offset
);
284 md
.sig_other
[j
].append(obit
);
288 did_something
= true;
290 } while (did_something
);
294 // Merges FF and possibly soft transparency logic into an asynchronous
295 // read port, making it into a synchronous one.
297 // There are three moving parts involved here:
299 // - the async port, which we start from, whose data port is input to...
300 // - an arbitrary chain of $mux and $pmux cells implementing soft transparency
301 // logic (ie. bypassing write port's data iff the write port is active and
302 // writing to the same address as this read port), which in turn feeds...
305 // The async port and the mux chain are not allowed to have any users that
306 // are not part of the above.
310 // 1. Walk through the muxes.
311 // 2. Recognize the final FF.
312 // 3. Knowing the FF's clock and read enable, make a list of write ports
313 // that we'll run transparency analysis on.
314 // 4. For every mux bit, recognize it as one of:
315 // - a transparency bypass mux for some port
316 // - a bypass mux that feeds 'x instead (this will result in collision
317 // don't care behavior being recognized)
318 // - a mux that never selects the other value when read port is active,
319 // and can thus be skipped (this is necessary because this could
320 // be a transparency bypass mux for never-colliding port that other
321 // passes failed to optimize)
322 // - a mux whose other input is 'x, and can thus be skipped
323 // 5. When recognizing transparency bypasses, take care to preserve priority
324 // behavior — when two bypasses are sequential muxes on the chain, they
325 // effectively have priority over one another, and the transform can
326 // only be performed when either a) their corresponding write ports
327 // also have priority, or b) there can never be a three-way collision
328 // between the two write ports and the read port.
329 // 6. Check consistency of per-bit transparency masks, merge them into
330 // per-port transparency masks
331 // 7. If everything went fine in the previous steps, actually perform
333 void handle_rd_port(Mem
&mem
, QuickConeSat
&qcsat
, int idx
)
335 auto &port
= mem
.rd_ports
[idx
];
336 log("Checking read port `%s'[%d] in module `%s': ", mem
.memid
.c_str(), idx
, module
->name
.c_str());
338 std::vector
<MuxData
> muxdata
;
339 SigSpec data
= walk_muxes(port
.data
, muxdata
);
341 pool
<std::pair
<Cell
*, int>> bits
;
342 if (!merger
.find_output_ff(data
, ff
, bits
)) {
343 log("no output FF found.\n");
347 log("output latches are not supported.\n");
351 log("output FF has async load, not supported.\n");
355 // Latches and FFs with SR are not supported.
356 log("output FF has both set and reset, not supported.\n");
361 MemQueryCache
cache(qcsat
, mem
, port
, ff
);
363 // Prepare information structure about all ports, recognize port bits
364 // that can never collide at all and don't need to be checked.
365 std::vector
<PortData
> portdata
;
366 for (int i
= 0; i
< GetSize(mem
.wr_ports
); i
++) {
368 auto &wport
= mem
.wr_ports
[i
];
370 if (!wport
.clk_enable
)
372 if (wport
.clk
!= ff
.sig_clk
)
374 if (wport
.clk_polarity
!= ff
.pol_clk
)
376 // In theory, we *could* support mismatched width
377 // ports here. However, it's not worth it — wide
378 // ports are recognized *after* memory_dff in
380 if (wport
.wide_log2
!= port
.wide_log2
)
382 pd
.uncollidable_mask
.resize(GetSize(port
.data
));
383 pd
.transparency_mask
.resize(GetSize(port
.data
));
384 pd
.collision_x_mask
.resize(GetSize(port
.data
));
386 // If we got this far, this port is potentially
387 // transparent and/or has undefined collision
388 // behavior. Now, for every bit, check if it can
390 for (int j
= 0; j
< ff
.width
; j
++) {
391 if (!cache
.can_collide_rdwr(i
, wport
.en
[j
])) {
392 pd
.uncollidable_mask
[j
] = true;
393 pd
.collision_x_mask
[j
] = true;
397 portdata
.push_back(pd
);
400 // Now inspect the mux chain.
401 for (auto &md
: muxdata
) {
402 // We only mark transparent bits after processing a complete
403 // mux, so that the transparency priority validation check
404 // below sees transparency information as of previous mux.
405 std::vector
<std::pair
<PortData
&, int>> trans_queue
;
406 for (int sel_idx
= 0; sel_idx
< GetSize(md
.sig_s
); sel_idx
++) {
407 SigBit sbit
= md
.sig_s
[sel_idx
];
408 SigSpec
&odata
= md
.sig_other
[sel_idx
];
409 for (int bitidx
= md
.base_idx
; bitidx
< md
.base_idx
+md
.size
; bitidx
++) {
410 SigBit odbit
= odata
[bitidx
-md
.base_idx
];
411 bool recognized
= false;
412 for (int pi
= 0; pi
< GetSize(mem
.wr_ports
); pi
++) {
413 auto &pd
= portdata
[pi
];
414 auto &wport
= mem
.wr_ports
[pi
];
417 if (pd
.uncollidable_mask
[bitidx
])
419 bool match
= cache
.is_w2rbyp(pi
, wport
.en
[bitidx
], sbit
, md
.is_b
);
422 // If we got here, we recognized this mux sel
423 // as valid bypass sel for a given port bit.
424 if (odbit
== State::Sx
) {
425 // 'x, mark collision don't care.
426 pd
.collision_x_mask
[bitidx
] = true;
427 pd
.transparency_mask
[bitidx
] = false;
428 } else if (cache
.data_eq(sbit
, md
.is_b
, wport
.data
[bitidx
], odbit
)) {
429 // Correct data value, mark transparency,
430 // but only after verifying that priority
432 for (int k
= 0; k
< GetSize(mem
.wr_ports
); k
++) {
433 if (portdata
[k
].transparency_mask
[bitidx
]) {
434 if (wport
.priority_mask
[k
])
436 if (!cache
.can_collide_together(pi
, k
, bitidx
))
438 log("FF found, but transparency logic priority doesn't match write priority.\n");
443 trans_queue
.push_back({pd
, bitidx
});
446 log("FF found, but with a mux data input that doesn't seem to correspond to transparency logic.\n");
451 // If we haven't positively identified this as
452 // a bypass: it's still skippable if the
453 // data is 'x, or if the sel cannot actually be
455 if (odbit
== State::Sx
)
457 if (cache
.impossible_with_ren(sbit
, md
.is_b
))
459 log("FF found, but with a mux select that doesn't seem to correspond to transparency logic.\n");
464 // Done with this mux, now actually apply the transparencies.
465 for (auto it
: trans_queue
) {
466 it
.first
.transparency_mask
[it
.second
] = true;
467 it
.first
.collision_x_mask
[it
.second
] = false;
471 // Final merging and validation of per-bit masks.
472 for (int pi
= 0; pi
< GetSize(mem
.wr_ports
); pi
++) {
473 auto &pd
= portdata
[pi
];
477 bool non_trans
= false;
478 for (int i
= 0; i
< ff
.width
; i
++) {
479 if (pd
.collision_x_mask
[i
])
481 if (pd
.transparency_mask
[i
])
486 if (trans
&& non_trans
) {
487 log("FF found, but soft transparency logic is inconsistent for port %d.\n", pi
);
490 pd
.final_transparency
= trans
;
491 pd
.final_collision_x
= !trans
&& !non_trans
;
495 log("merging output FF to cell.\n");
497 merger
.remove_output_ff(bits
);
498 if (ff
.has_ce
&& !ff
.pol_ce
)
499 ff
.sig_ce
= module
->LogicNot(NEW_ID
, ff
.sig_ce
);
500 if (ff
.has_arst
&& !ff
.pol_arst
)
501 ff
.sig_arst
= module
->LogicNot(NEW_ID
, ff
.sig_arst
);
502 if (ff
.has_srst
&& !ff
.pol_srst
)
503 ff
.sig_srst
= module
->LogicNot(NEW_ID
, ff
.sig_srst
);
504 port
.clk
= ff
.sig_clk
;
505 port
.clk_enable
= true;
506 port
.clk_polarity
= ff
.pol_clk
;
512 port
.arst
= ff
.sig_arst
;
513 port
.arst_value
= ff
.val_arst
;
515 port
.arst
= State::S0
;
518 port
.srst
= ff
.sig_srst
;
519 port
.srst_value
= ff
.val_srst
;
520 port
.ce_over_srst
= ff
.ce_over_srst
;
522 port
.srst
= State::S0
;
524 port
.init_value
= ff
.val_init
;
525 port
.data
= ff
.sig_q
;
526 for (int pi
= 0; pi
< GetSize(mem
.wr_ports
); pi
++) {
527 auto &pd
= portdata
[pi
];
530 if (pd
.final_collision_x
) {
531 log(" Write port %d: don't care on collision.\n", pi
);
532 port
.collision_x_mask
[pi
] = true;
533 } else if (pd
.final_transparency
) {
534 log(" Write port %d: transparent.\n", pi
);
535 port
.transparency_mask
[pi
] = true;
537 log(" Write port %d: non-transparent.\n", pi
);
543 void handle_rd_port_addr(Mem
&mem
, int idx
)
545 auto &port
= mem
.rd_ports
[idx
];
546 log("Checking read port address `%s'[%d] in module `%s': ", mem
.memid
.c_str(), idx
, module
->name
.c_str());
549 pool
<std::pair
<Cell
*, int>> bits
;
550 if (!merger
.find_input_ff(port
.addr
, ff
, bits
)) {
551 log("no address FF found.\n");
555 log("address latches are not supported.\n");
559 log("address FF has async load, not supported.\n");
562 if (ff
.has_sr
|| ff
.has_arst
) {
563 log("address FF has async set and/or reset, not supported.\n");
566 // Trick part: this transform is invalid if the initial
567 // value of the FF is fully-defined. However, we
568 // cannot simply reject FFs with any defined init bit,
569 // as this is often the result of merging a const bit.
570 if (ff
.val_init
.is_fully_def()) {
571 log("address FF has fully-defined init value, not supported.\n");
574 for (int i
= 0; i
< GetSize(mem
.wr_ports
); i
++) {
575 auto &wport
= mem
.wr_ports
[i
];
576 if (!wport
.clk_enable
|| wport
.clk
!= ff
.sig_clk
|| wport
.clk_polarity
!= ff
.pol_clk
) {
577 log("address FF clock is not compatible with write clock.\n");
581 // Now we're commited to merge it.
582 merger
.mark_input_ff(bits
);
583 // If the address FF has enable and/or sync reset, unmap it.
585 port
.clk
= ff
.sig_clk
;
587 port
.addr
= ff
.sig_d
;
588 port
.clk_enable
= true;
589 port
.clk_polarity
= ff
.pol_clk
;
590 for (int i
= 0; i
< GetSize(mem
.wr_ports
); i
++)
591 port
.transparency_mask
[i
] = true;
593 log("merged address FF to cell.\n");
598 std::vector
<Mem
> memories
= Mem::get_selected_memories(module
);
599 for (auto &mem
: memories
) {
600 QuickConeSat
qcsat(modwalker
);
601 for (int i
= 0; i
< GetSize(mem
.rd_ports
); i
++) {
602 if (!mem
.rd_ports
[i
].clk_enable
)
603 handle_rd_port(mem
, qcsat
, i
);
606 for (auto &mem
: memories
) {
607 for (int i
= 0; i
< GetSize(mem
.rd_ports
); i
++) {
608 if (!mem
.rd_ports
[i
].clk_enable
)
609 handle_rd_port_addr(mem
, i
);
615 struct MemoryDffPass
: public Pass
{
616 MemoryDffPass() : Pass("memory_dff", "merge input/output DFFs into memory read ports") { }
619 // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
621 log(" memory_dff [options] [selection]\n");
623 log("This pass detects DFFs at memory read ports and merges them into the memory port.\n");
624 log("I.e. it consumes an asynchronous memory port and the flip-flops at its\n");
625 log("interface and yields a synchronous memory port.\n");
628 void execute(std::vector
<std::string
> args
, RTLIL::Design
*design
) override
630 log_header(design
, "Executing MEMORY_DFF pass (merging $dff cells to $memrd).\n");
633 for (argidx
= 1; argidx
< args
.size(); argidx
++) {
636 extra_args(args
, argidx
, design
);
638 for (auto mod
: design
->selected_modules()) {
639 MemoryDffWorker
worker(mod
);
645 PRIVATE_NAMESPACE_END