Merge pull request #3310 from robinsonb5-PRs/master
[yosys.git] / passes / memory / memory_dff.cc
1 /*
2 * yosys -- Yosys Open SYnthesis Suite
3 *
4 * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
5 *
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.
9 *
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.
17 *
18 */
19
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"
28
29 USING_YOSYS_NAMESPACE
30 PRIVATE_NAMESPACE_BEGIN
31
32 struct MuxData {
33 int base_idx;
34 int size;
35 bool is_b;
36 SigSpec sig_s;
37 std::vector<SigSpec> sig_other;
38 };
39
40 struct PortData {
41 bool relevant;
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;
47 };
48
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..
52 struct MemQueryCache
53 {
54 QuickConeSat &qcsat;
55 // The memory.
56 Mem &mem;
57 // The port, still async at this point.
58 MemRd &port;
59 // The virtual FF that will end up merged into this port.
60 FfData &ff;
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).
63 int port_ren;
64 // Some caches.
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;
69
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;
74 if (ff.has_ce) {
75 ren = qcsat.importSigBit(ff.sig_ce);
76 if (!ff.pol_ce)
77 ren = qcsat.ez->NOT(ren);
78 }
79 if (ff.has_srst) {
80 int nrst = qcsat.importSigBit(ff.sig_srst);
81 if (ff.pol_srst)
82 nrst = qcsat.ez->NOT(nrst);
83 ren = qcsat.ez->AND(ren, nrst);
84 }
85 port_ren = ren;
86 }
87
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));
94 }
95
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())
102 return it->second;
103 auto &wport = mem.wr_ports[widx];
104 int aeq = addr_eq(port.addr, wport.addr);
105 int wen_sat = qcsat.importSigBit(wen);
106 qcsat.prepare();
107 bool res = qcsat.ez->solve(aeq, wen_sat, port_ren);
108 cache_can_collide_rdwr[key] = res;
109 return res;
110 }
111
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())
122 return it->second;
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);
127 qcsat.prepare();
128 bool res = qcsat.ez->solve(wen1_sat, wen2_sat, aeq1, aeq2, port_ren);
129 cache_can_collide_together[key] = res;
130 return res;
131 }
132
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())
139 return it->second;
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);
145 if (neg_sel)
146 sel_sat = qcsat.ez->NOT(sel_sat);
147 qcsat.prepare();
148 bool res = !qcsat.ez->solve(port_ren, qcsat.ez->XOR(sel_expected, sel_sat));
149 cache_is_w2rbyp[key] = res;
150 return res;
151 }
152
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())
159 return it->second;
160 int sel_sat = qcsat.importSigBit(sel);
161 if (neg_sel)
162 sel_sat = qcsat.ez->NOT(sel_sat);
163 qcsat.prepare();
164 bool res = !qcsat.ez->solve(port_ren, sel_sat);
165 cache_impossible_with_ren[key] = res;
166 return res;
167 }
168
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)
175 return false;
176 auto driver = *drivers.begin();
177 if (!driver.cell->type.in(ID($mux), ID($pmux)))
178 return false;
179 log_assert(driver.port == ID::Y);
180 SigSpec sig_s = driver.cell->getPort(ID::S);
181 int sel_sat = qcsat.importSigBit(sel);
182 if (neg_sel)
183 sel_sat = qcsat.ez->NOT(sel_sat);
184 bool all_0 = true;
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]);
188 qcsat.prepare();
189 if (!qcsat.ez->solve(port_ren, sel_sat, qcsat.ez->NOT(sbit))) {
190 bit = driver.cell->getPort(ID::B)[i * width + driver.offset];
191 return true;
192 }
193 if (qcsat.ez->solve(port_ren, sel_sat, sbit))
194 all_0 = false;
195 }
196 if (all_0) {
197 bit = driver.cell->getPort(ID::A)[driver.offset];
198 return true;
199 }
200 return false;
201 }
202
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))
210 return true;
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);
214 }
215 };
216
217 struct MemoryDffWorker
218 {
219 Module *module;
220 ModWalker modwalker;
221 FfInitVals initvals;
222 FfMergeHelper merger;
223
224 MemoryDffWorker(Module *module) : module(module), modwalker(module->design)
225 {
226 modwalker.setup(module);
227 initvals.set(&modwalker.sigmap, module);
228 merger.set(&initvals, module);
229 }
230
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) {
236 bool did_something;
237 do {
238 did_something = false;
239 int prev_idx = -1;
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))
246 continue;
247 auto consumer = *consumers.begin();
248 bool is_b;
249 if (consumer.cell->type == ID($mux)) {
250 if (consumer.port == ID::A) {
251 is_b = false;
252 } else if (consumer.port == ID::B) {
253 is_b = true;
254 } else {
255 continue;
256 }
257 } else if (consumer.cell->type == ID($pmux)) {
258 if (consumer.port == ID::A) {
259 is_b = false;
260 } else {
261 continue;
262 }
263 } else {
264 continue;
265 }
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) {
270 MuxData md;
271 md.base_idx = i;
272 md.size = 0;
273 md.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;
277 prev_is_b = is_b;
278 res.push_back(md);
279 }
280 auto &md = res.back();
281 md.size++;
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);
285 }
286 prev_idx = i;
287 data[i] = ybit;
288 did_something = true;
289 }
290 } while (did_something);
291 return data;
292 }
293
294 // Merges FF and possibly soft transparency logic into an asynchronous
295 // read port, making it into a synchronous one.
296 //
297 // There are three moving parts involved here:
298 //
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...
303 // - a final FF
304 //
305 // The async port and the mux chain are not allowed to have any users that
306 // are not part of the above.
307 //
308 // The algorithm is:
309 //
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
332 // the merge.
333 void handle_rd_port(Mem &mem, QuickConeSat &qcsat, int idx)
334 {
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());
337
338 std::vector<MuxData> muxdata;
339 SigSpec data = walk_muxes(port.data, muxdata);
340 FfData ff;
341 pool<std::pair<Cell *, int>> bits;
342 if (!merger.find_output_ff(data, ff, bits)) {
343 log("no output FF found.\n");
344 return;
345 }
346 if (!ff.has_clk) {
347 log("output latches are not supported.\n");
348 return;
349 }
350 if (ff.has_aload) {
351 log("output FF has async load, not supported.\n");
352 return;
353 }
354 if (ff.has_sr) {
355 // Latches and FFs with SR are not supported.
356 log("output FF has both set and reset, not supported.\n");
357 return;
358 }
359
360 // Construct cache.
361 MemQueryCache cache(qcsat, mem, port, ff);
362
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++) {
367 PortData pd;
368 auto &wport = mem.wr_ports[i];
369 pd.relevant = true;
370 if (!wport.clk_enable)
371 pd.relevant = false;
372 if (wport.clk != ff.sig_clk)
373 pd.relevant = false;
374 if (wport.clk_polarity != ff.pol_clk)
375 pd.relevant = false;
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
379 // a normal flow.
380 if (wport.wide_log2 != port.wide_log2)
381 pd.relevant = false;
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));
385 if (pd.relevant) {
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
389 // ever collide.
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;
394 }
395 }
396 }
397 portdata.push_back(pd);
398 }
399
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];
415 if (!pd.relevant)
416 continue;
417 if (pd.uncollidable_mask[bitidx])
418 continue;
419 bool match = cache.is_w2rbyp(pi, wport.en[bitidx], sbit, md.is_b);
420 if (!match)
421 continue;
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
431 // is fine.
432 for (int k = 0; k < GetSize(mem.wr_ports); k++) {
433 if (portdata[k].transparency_mask[bitidx]) {
434 if (wport.priority_mask[k])
435 continue;
436 if (!cache.can_collide_together(pi, k, bitidx))
437 continue;
438 log("FF found, but transparency logic priority doesn't match write priority.\n");
439 return;
440 }
441 }
442 recognized = true;
443 trans_queue.push_back({pd, bitidx});
444 break;
445 } else {
446 log("FF found, but with a mux data input that doesn't seem to correspond to transparency logic.\n");
447 return;
448 }
449 }
450 if (!recognized) {
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
454 // active.
455 if (odbit == State::Sx)
456 continue;
457 if (cache.impossible_with_ren(sbit, md.is_b))
458 continue;
459 log("FF found, but with a mux select that doesn't seem to correspond to transparency logic.\n");
460 return;
461 }
462 }
463 }
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;
468 }
469 }
470
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];
474 if (!pd.relevant)
475 continue;
476 bool trans = false;
477 bool non_trans = false;
478 for (int i = 0; i < ff.width; i++) {
479 if (pd.collision_x_mask[i])
480 continue;
481 if (pd.transparency_mask[i])
482 trans = true;
483 else
484 non_trans = true;
485 }
486 if (trans && non_trans) {
487 log("FF found, but soft transparency logic is inconsistent for port %d.\n", pi);
488 return;
489 }
490 pd.final_transparency = trans;
491 pd.final_collision_x = !trans && !non_trans;
492 }
493
494 // OK, it worked.
495 log("merging output FF to cell.\n");
496
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;
507 if (ff.has_ce)
508 port.en = ff.sig_ce;
509 else
510 port.en = State::S1;
511 if (ff.has_arst) {
512 port.arst = ff.sig_arst;
513 port.arst_value = ff.val_arst;
514 } else {
515 port.arst = State::S0;
516 }
517 if (ff.has_srst) {
518 port.srst = ff.sig_srst;
519 port.srst_value = ff.val_srst;
520 port.ce_over_srst = ff.ce_over_srst;
521 } else {
522 port.srst = State::S0;
523 }
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];
528 if (!pd.relevant)
529 continue;
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;
536 } else {
537 log(" Write port %d: non-transparent.\n", pi);
538 }
539 }
540 mem.emit();
541 }
542
543 void handle_rd_port_addr(Mem &mem, int idx)
544 {
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());
547
548 FfData ff;
549 pool<std::pair<Cell *, int>> bits;
550 if (!merger.find_input_ff(port.addr, ff, bits)) {
551 log("no address FF found.\n");
552 return;
553 }
554 if (!ff.has_clk) {
555 log("address latches are not supported.\n");
556 return;
557 }
558 if (ff.has_aload) {
559 log("address FF has async load, not supported.\n");
560 return;
561 }
562 if (ff.has_sr || ff.has_arst) {
563 log("address FF has async set and/or reset, not supported.\n");
564 return;
565 }
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");
572 return;
573 }
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");
578 return;
579 }
580 }
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.
584 ff.unmap_ce_srst();
585 port.clk = ff.sig_clk;
586 port.en = State::S1;
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;
592 mem.emit();
593 log("merged address FF to cell.\n");
594 }
595
596 void run()
597 {
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);
604 }
605 }
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);
610 }
611 }
612 }
613 };
614
615 struct MemoryDffPass : public Pass {
616 MemoryDffPass() : Pass("memory_dff", "merge input/output DFFs into memory read ports") { }
617 void help() override
618 {
619 // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
620 log("\n");
621 log(" memory_dff [options] [selection]\n");
622 log("\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");
626 log("\n");
627 }
628 void execute(std::vector<std::string> args, RTLIL::Design *design) override
629 {
630 log_header(design, "Executing MEMORY_DFF pass (merging $dff cells to $memrd).\n");
631
632 size_t argidx;
633 for (argidx = 1; argidx < args.size(); argidx++) {
634 break;
635 }
636 extra_args(args, argidx, design);
637
638 for (auto mod : design->selected_modules()) {
639 MemoryDffWorker worker(mod);
640 worker.run();
641 }
642 }
643 } MemoryDffPass;
644
645 PRIVATE_NAMESPACE_END