2 * Copyright (c) 1998-2005 Stephen Williams (steve@icarus.com)
4 * This source code is free software; you can redistribute it
5 * and/or modify it in source code form under the terms of the GNU
6 * General Public License as published by the Free Software
7 * Foundation; either version 2 of the License, or (at your option)
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
26 # include "compiler.h"
27 # include "ivl_assert.h"
32 * The cprop function below invokes constant propagation where
33 * possible. The elaboration generates NetConst objects. I can remove
34 * these and replace the gates connected to it with simpler ones. I
35 * may even be able to replace nets with a new constant.
38 struct cprop_functor
: public functor_t
{
42 virtual void signal(Design
*des
, NetNet
*obj
);
43 virtual void lpm_add_sub(Design
*des
, NetAddSub
*obj
);
44 virtual void lpm_compare(Design
*des
, NetCompare
*obj
);
45 virtual void lpm_compare_eq_(Design
*des
, NetCompare
*obj
);
46 virtual void lpm_ff(Design
*des
, NetFF
*obj
);
47 virtual void lpm_logic(Design
*des
, NetLogic
*obj
);
48 virtual void lpm_mux(Design
*des
, NetMux
*obj
);
51 void cprop_functor::signal(Design
*des
, NetNet
*obj
)
55 void cprop_functor::lpm_add_sub(Design
*des
, NetAddSub
*obj
)
59 void cprop_functor::lpm_compare(Design
*des
, NetCompare
*obj
)
61 if (obj
->pin_AEB().is_linked()) {
62 assert( ! obj
->pin_AGB().is_linked() );
63 assert( ! obj
->pin_AGEB().is_linked() );
64 assert( ! obj
->pin_ALB().is_linked() );
65 assert( ! obj
->pin_ALEB().is_linked() );
66 assert( ! obj
->pin_AGB().is_linked() );
67 assert( ! obj
->pin_ANEB().is_linked() );
68 lpm_compare_eq_(des
, obj
);
73 void cprop_functor::lpm_compare_eq_(Design
*des
, NetCompare
*obj
)
76 /* XXXX Need to reimplement this code to account for vectors. */
77 NetScope
*scope
= obj
->scope();
79 unsigned const_count
= 0;
80 bool unknown_flag
= false;
82 /* First, look for the case where constant bits on matching A
83 and B inputs are different. This this is so, the device can
84 be completely eliminated and replaced with a constant 0. */
86 for (unsigned idx
= 0 ; idx
< obj
->width() ; idx
+= 1) {
87 if (! obj
->pin_DataA(idx
).nexus()->drivers_constant())
89 if (! obj
->pin_DataB(idx
).nexus()->drivers_constant())
94 verinum::V abit
= obj
->pin_DataA(idx
).nexus()->driven_value();
95 verinum::V bbit
= obj
->pin_DataB(idx
).nexus()->driven_value();
97 if ((abit
== verinum::V0
) && (bbit
== verinum::V0
))
99 if ((abit
== verinum::V1
) && (bbit
== verinum::V1
))
103 if ((abit
== verinum::Vz
) || (abit
== verinum::Vx
))
105 if ((bbit
== verinum::Vz
) || (bbit
== verinum::Vx
))
108 NetConst
*zero
= new NetConst(scope
, obj
->name(), verinum::V0
);
109 connect(zero
->pin(0), obj
->pin_AEB());
116 /* If all the inputs are constant, then at this point the
117 result is either V1 or Vx. */
118 if (const_count
== obj
->width()) {
120 NetConst
*val
= new NetConst(scope
, obj
->name(),
124 connect(val
->pin(0), obj
->pin_AEB());
131 /* Still may need the gate. Run through the inputs again, and
132 look for pairs of constants. Those inputs can be removed. */
134 unsigned top
= obj
->width();
135 for (unsigned idx
= 0 ; idx
< top
; ) {
136 if (! obj
->pin_DataA(idx
).nexus()->drivers_constant()) {
140 if (! obj
->pin_DataB(idx
).nexus()->drivers_constant()) {
145 obj
->pin_DataA(idx
).unlink();
146 obj
->pin_DataB(idx
).unlink();
149 for (unsigned jj
= idx
; jj
< top
; jj
+= 1) {
150 connect(obj
->pin_DataA(jj
), obj
->pin_DataA(jj
+1));
151 connect(obj
->pin_DataB(jj
), obj
->pin_DataB(jj
+1));
152 obj
->pin_DataA(jj
+1).unlink();
153 obj
->pin_DataB(jj
+1).unlink();
157 /* If we wound up disconnecting all the inputs, then remove
158 the device and replace it with a constant. */
160 NetConst
*one
= new NetConst(scope
, obj
->name(), verinum::V1
);
161 connect(one
->pin(0), obj
->pin_AEB());
168 /* If there is only one bit left, then replace the comparator
169 with a simple XOR gate. */
171 NetLogic
*tmp
= new NetLogic(scope
, obj
->name(), 3,
173 connect(tmp
->pin(0), obj
->pin_AEB());
174 connect(tmp
->pin(1), obj
->pin_DataA(0));
175 connect(tmp
->pin(2), obj
->pin_DataB(0));
183 if (top
== obj
->width())
186 NetCompare
*tmp
= new NetCompare(scope
, obj
->name(), top
);
187 connect(tmp
->pin_AEB(), obj
->pin_AEB());
188 for (unsigned idx
= 0 ; idx
< top
; idx
+= 1) {
189 connect(tmp
->pin_DataA(idx
), obj
->pin_DataA(idx
));
190 connect(tmp
->pin_DataB(idx
), obj
->pin_DataB(idx
));
198 void cprop_functor::lpm_ff(Design
*des
, NetFF
*obj
)
200 // Look for and count unlinked FF outputs. Note that if the
201 // Data and Q pins are connected together, they can be removed
202 // from the circuit, since it doesn't do anything.
204 if (connected(obj
->pin_Data(), obj
->pin_Q())
205 && (! obj
->pin_Sclr().is_linked())
206 && (! obj
->pin_Sset().is_linked())
207 && (! obj
->pin_Aclr().is_linked())
208 && (! obj
->pin_Aset().is_linked())) {
209 obj
->pin_Data().unlink();
210 obj
->pin_Q().unlink();
215 void cprop_functor::lpm_logic(Design
*des
, NetLogic
*obj
)
218 NetScope
*scope
= obj
->scope();
221 switch (obj
->type()) {
223 /* XXXX This old code assumed that the individual bit
224 slices could be replaced with different gates. They
225 cannot when the device takes atomic vectors, so this
226 needs to be rewritten. XXXX */
228 case NetLogic::AND
: {
229 unsigned top
= obj
->pin_count();
233 /* Eliminate all the 1 inputs. They have no effect
234 on the output of an AND gate. */
237 if (! obj
->pin(idx
).nexus()->drivers_constant()) {
242 if (obj
->pin(idx
).nexus()->driven_value()==verinum::V1
) {
243 obj
->pin(idx
).unlink();
246 connect(obj
->pin(idx
), obj
->pin(top
));
247 obj
->pin(top
).unlink();
253 if (obj
->pin(idx
).nexus()->driven_value() != verinum::V0
) {
259 /* Oops! We just stumbled on a driven-0 input
260 to the AND gate. That means we can replace
261 the whole bloody thing with a constant
262 driver and exit now. */
264 switch (obj
->type()) {
266 tmp
= new NetConst(scope
, obj
->name(), verinum::V0
);
269 tmp
= new NetConst(scope
, obj
->name(), verinum::V1
);
275 tmp
->rise_time(obj
->rise_time());
276 tmp
->fall_time(obj
->fall_time());
277 tmp
->decay_time(obj
->decay_time());
280 tmp
->pin(0).drive0(obj
->pin(0).drive0());
281 tmp
->pin(0).drive1(obj
->pin(0).drive1());
282 connect(obj
->pin(0), tmp
->pin(0));
289 /* If all the inputs were eliminated, then replace
290 the gate with a constant 1 and I am done. */
293 switch (obj
->type()) {
295 tmp
= new NetConst(scope
, obj
->name(), verinum::V1
);
298 tmp
= new NetConst(scope
, obj
->name(), verinum::V0
);
304 tmp
->rise_time(obj
->rise_time());
305 tmp
->fall_time(obj
->fall_time());
306 tmp
->decay_time(obj
->decay_time());
309 tmp
->pin(0).drive0(obj
->pin(0).drive0());
310 tmp
->pin(0).drive1(obj
->pin(0).drive1());
311 connect(obj
->pin(0), tmp
->pin(0));
318 /* If all the inputs are unknowns, then replace the
322 tmp
= new NetConst(scope
, obj
->name(), verinum::Vx
);
324 tmp
->pin(0).drive0(obj
->pin(0).drive0());
325 tmp
->pin(0).drive1(obj
->pin(0).drive1());
326 connect(obj
->pin(0), tmp
->pin(0));
333 /* If we are down to only one input, then replace
334 the AND with a BUF and exit now. */
337 switch (obj
->type()) {
339 tmp
= new NetLogic(scope
,
344 tmp
= new NetLogic(scope
,
352 tmp
->rise_time(obj
->rise_time());
353 tmp
->fall_time(obj
->fall_time());
354 tmp
->decay_time(obj
->decay_time());
357 tmp
->pin(0).drive0(obj
->pin(0).drive0());
358 tmp
->pin(0).drive1(obj
->pin(0).drive1());
359 connect(obj
->pin(0), tmp
->pin(0));
360 connect(obj
->pin(1), tmp
->pin(1));
366 /* Finally, this cleans up the gate by creating a
367 new [N]AND gate that has the right number of
368 inputs, connected in the right place. */
369 if (top
< obj
->pin_count()) {
370 NetLogic
*tmp
= new NetLogic(scope
,
373 tmp
->rise_time(obj
->rise_time());
374 tmp
->fall_time(obj
->fall_time());
375 tmp
->decay_time(obj
->decay_time());
378 tmp
->pin(0).drive0(obj
->pin(0).drive0());
379 tmp
->pin(0).drive1(obj
->pin(0).drive1());
380 for (unsigned idx
= 0 ; idx
< top
; idx
+= 1)
381 connect(tmp
->pin(idx
), obj
->pin(idx
));
391 /* XXXX This old code assumed that the individual bit
392 slices could be replaced with different gates. They
393 cannot when the device takes atomic vectors, so this
394 needs to be rewritten. XXXX */
398 unsigned top
= obj
->pin_count();
402 /* Eliminate all the 0 inputs. They have no effect
403 on the output of an OR gate. */
406 if (! obj
->pin(idx
).nexus()->drivers_constant()) {
411 if (obj
->pin(idx
).nexus()->driven_value() == verinum::V0
) {
412 obj
->pin(idx
).unlink();
415 connect(obj
->pin(idx
), obj
->pin(top
));
416 obj
->pin(top
).unlink();
422 if (obj
->pin(idx
).nexus()->driven_value() != verinum::V1
) {
427 /* Oops! We just stumbled on a driven-1 input
428 to the OR gate. That means we can replace
429 the whole bloody thing with a constant
430 driver and exit now. */
432 switch (obj
->type()) {
434 tmp
= new NetConst(scope
, obj
->name(), verinum::V1
);
437 tmp
= new NetConst(scope
, obj
->name(), verinum::V0
);
443 tmp
->rise_time(obj
->rise_time());
444 tmp
->fall_time(obj
->fall_time());
445 tmp
->decay_time(obj
->decay_time());
448 tmp
->pin(0).drive0(obj
->pin(0).drive0());
449 tmp
->pin(0).drive1(obj
->pin(0).drive1());
450 connect(obj
->pin(0), tmp
->pin(0));
457 /* If all the inputs were eliminated, then replace
458 the gate with a constant 0 and I am done. */
461 switch (obj
->type()) {
463 tmp
= new NetConst(scope
, obj
->name(), verinum::V0
);
466 tmp
= new NetConst(scope
, obj
->name(), verinum::V1
);
472 tmp
->rise_time(obj
->rise_time());
473 tmp
->fall_time(obj
->fall_time());
474 tmp
->decay_time(obj
->decay_time());
477 tmp
->pin(0).drive0(obj
->pin(0).drive0());
478 tmp
->pin(0).drive1(obj
->pin(0).drive1());
479 connect(obj
->pin(0), tmp
->pin(0));
486 /* If we are down to only one input, then replace
487 the OR with a BUF and exit now. */
490 switch (obj
->type()) {
492 tmp
= new NetLogic(scope
,
497 tmp
= new NetLogic(scope
,
504 tmp
->rise_time(obj
->rise_time());
505 tmp
->fall_time(obj
->fall_time());
506 tmp
->decay_time(obj
->decay_time());
509 tmp
->pin(0).drive0(obj
->pin(0).drive0());
510 tmp
->pin(0).drive1(obj
->pin(0).drive1());
511 connect(obj
->pin(0), tmp
->pin(0));
512 connect(obj
->pin(1), tmp
->pin(1));
518 /* Finally, this cleans up the gate by creating a
519 new [N]OR gate that has the right number of
520 inputs, connected in the right place. */
521 if (top
< obj
->pin_count()) {
522 NetLogic
*tmp
= new NetLogic(scope
,
525 tmp
->rise_time(obj
->rise_time());
526 tmp
->fall_time(obj
->fall_time());
527 tmp
->decay_time(obj
->decay_time());
530 tmp
->pin(0).drive0(obj
->pin(0).drive0());
531 tmp
->pin(0).drive1(obj
->pin(0).drive1());
532 for (unsigned idx
= 0 ; idx
< top
; idx
+= 1)
533 connect(tmp
->pin(idx
), obj
->pin(idx
));
543 /* XXXX This old code assumed that the individual bit
544 slices could be replaced with different gates. They
545 cannot when the device takes atomic vectors, so this
546 needs to be rewritten. XXXX */
548 case NetLogic::XOR
: {
549 unsigned top
= obj
->pin_count();
552 /* Eliminate all the 0 inputs. They have no effect
553 on the output of an XOR gate. The eliminate works
554 by unlinking the current input and relinking the
555 last input to this position. It's like bubbling
556 all the 0 inputs to the end. */
558 if (! obj
->pin(idx
).nexus()->drivers_constant()) {
563 if (obj
->pin(idx
).nexus()->driven_value() == verinum::V0
) {
564 obj
->pin(idx
).unlink();
567 connect(obj
->pin(idx
), obj
->pin(top
));
568 obj
->pin(top
).unlink();
576 /* Look for pairs of constant 1 inputs. If I find a
577 pair, then eliminate both. Each iteration through
578 the loop, the `one' variable holds the index to
579 the previous V1, or 0 if there is none.
581 The `ones' variable counts the number of V1
582 inputs. After this loop completes, `ones' will be
585 unsigned one
= 0, ones
= 0;
588 if (! obj
->pin(idx
).nexus()->drivers_constant()) {
593 if (obj
->pin(idx
).nexus()->driven_value() == verinum::V1
) {
601 /* Here we found two constant V1
602 inputs. Unlink both. */
603 obj
->pin(idx
).unlink();
606 connect(obj
->pin(idx
), obj
->pin(top
));
607 obj
->pin(top
).unlink();
610 obj
->pin(one
).unlink();
613 connect(obj
->pin(one
), obj
->pin(top
));
614 obj
->pin(top
).unlink();
617 /* Reset ones counter and one index,
618 start looking for the next pair. */
628 /* If all the inputs were eliminated, then replace
629 the gate with a constant value and I am done. */
631 verinum::V out
= obj
->type()==NetLogic::XNOR
634 NetConst
*tmp
= new NetConst(scope
, obj
->name(), out
);
636 tmp
->rise_time(obj
->rise_time());
637 tmp
->fall_time(obj
->fall_time());
638 tmp
->decay_time(obj
->decay_time());
641 tmp
->pin(0).drive0(obj
->pin(0).drive0());
642 tmp
->pin(0).drive1(obj
->pin(0).drive1());
643 connect(obj
->pin(0), tmp
->pin(0));
650 /* If there is a stray V1 input and only one other
651 input, then replace the gate with an inverter and
654 if ((top
== 3) && (ones
== 1)) {
656 if (! obj
->pin(1).nexus()->drivers_constant())
658 else if (obj
->pin(1).nexus()->driven_value() != verinum::V1
)
665 if (obj
->type() == NetLogic::XOR
)
666 tmp
= new NetLogic(scope
,
670 tmp
= new NetLogic(scope
,
674 tmp
->rise_time(obj
->rise_time());
675 tmp
->fall_time(obj
->fall_time());
676 tmp
->decay_time(obj
->decay_time());
679 tmp
->pin(0).drive0(obj
->pin(0).drive0());
680 tmp
->pin(0).drive1(obj
->pin(0).drive1());
681 connect(obj
->pin(0), tmp
->pin(0));
682 connect(obj
->pin(save
), tmp
->pin(1));
689 /* If we are down to only one input, then replace
690 the XOR with a BUF and exit now. */
694 if (obj
->type() == NetLogic::XOR
)
695 tmp
= new NetLogic(scope
,
699 tmp
= new NetLogic(scope
,
703 tmp
->rise_time(obj
->rise_time());
704 tmp
->fall_time(obj
->fall_time());
705 tmp
->decay_time(obj
->decay_time());
708 tmp
->pin(0).drive0(obj
->pin(0).drive0());
709 tmp
->pin(0).drive1(obj
->pin(0).drive1());
710 connect(obj
->pin(0), tmp
->pin(0));
711 connect(obj
->pin(1), tmp
->pin(1));
717 /* Finally, this cleans up the gate by creating a
718 new XOR gate that has the right number of
719 inputs, connected in the right place. */
720 if (top
< obj
->pin_count()) {
721 NetLogic
*tmp
= new NetLogic(scope
,
725 tmp
->pin(0).drive0(obj
->pin(0).drive0());
726 tmp
->pin(0).drive1(obj
->pin(0).drive1());
727 for (unsigned idx
= 0 ; idx
< top
; idx
+= 1)
728 connect(tmp
->pin(idx
), obj
->pin(idx
));
742 static void replace_with_bufif(Design
*des
, NetMux
*obj
, NetLogic::TYPE type
)
744 NetScope
*scope
= obj
->scope();
745 NetLogic
*tmp
= new NetLogic(obj
->scope(),
746 scope
->local_symbol(),
747 3, type
, obj
->width());
751 connect(obj
->pin_Result(), tmp
->pin(0));
752 connect(obj
->pin_Data(type
==NetLogic::BUFIF0
? 0 : 1), tmp
->pin(1));
754 if (obj
->width() == 1) {
755 /* Special case that the expression is 1 bit
756 wide. Connect the select directly to the enable. */
757 connect(obj
->pin_Sel(), tmp
->pin(2));
760 /* General case that the expression is arbitrarily
761 wide. Replicate the enable signal (which we
762 assume is 1 bit wide) to match the expression,
763 and connect the enable vector to the enable
764 input of the gate. */
765 NetReplicate
*rtmp
= new NetReplicate(scope
,
766 scope
->local_symbol(),
771 connect(obj
->pin_Sel(), rtmp
->pin(1));
772 connect(tmp
->pin(2), rtmp
->pin(0));
774 NetNet
*rsig
= new NetNet(scope
, scope
->local_symbol(),
775 NetNet::WIRE
, obj
->width());
776 rsig
->local_flag(true);
777 rsig
->data_type(IVL_VT_LOGIC
);
778 connect(tmp
->pin(2), rsig
->pin(0));
785 * This detects the case where the mux selects between a value and
786 * Vz. In this case, replace the device with a bufif with the sel
787 * input used to enable the output.
789 void cprop_functor::lpm_mux(Design
*des
, NetMux
*obj
)
791 if (obj
->size() != 2)
793 if (obj
->sel_width() != 1)
796 /* If the first input is all constant Vz, then replace the
797 NetMux with an array of BUFIF1 devices, with the enable
798 connected to the select input. */
801 if (! obj
->pin_Data(0).nexus()->drivers_constant()) {
805 if (flag
&& obj
->pin_Data(0).nexus()->driven_value() != verinum::Vz
) {
810 replace_with_bufif(des
, obj
, NetLogic::BUFIF1
);
816 /* If instead the second input is all constant Vz, replace the
817 NetMux with an array of BUFIF0 devices. */
819 if (! obj
->pin_Data(1).nexus()->drivers_constant()) {
823 if (flag
&& obj
->pin_Data(1).nexus()->driven_value() != verinum::Vz
) {
828 replace_with_bufif(des
, obj
, NetLogic::BUFIF0
);
833 /* If the select input is constant, then replace with a BUFZ */
834 flag
= obj
->pin_Sel().nexus()->drivers_constant();
835 verinum::V sel_val
= flag
? obj
->pin_Sel().nexus()->driven_value() : verinum::Vx
;
836 if ((sel_val
!= verinum::Vz
) && (sel_val
!= verinum::Vx
)) {
837 NetBUFZ
*tmp
= new NetBUFZ(obj
->scope(), obj
->name(), obj
->width());
841 cerr
<< obj
->get_fileline() << ": debug: "
842 << "Replace binary MUX with constant select=" << sel_val
843 << " with a BUFZ to the selected input." << endl
;
845 tmp
->rise_time(obj
->rise_time());
846 tmp
->fall_time(obj
->fall_time());
847 tmp
->decay_time(obj
->decay_time());
849 connect(tmp
->pin(0), obj
->pin_Result());
850 if (sel_val
== verinum::V1
)
851 connect(tmp
->pin(1), obj
->pin_Data(1));
853 connect(tmp
->pin(1), obj
->pin_Data(0));
861 * This functor looks to see if the constant is connected to nothing
862 * but signals. If that is the case, delete the dangling constant and
863 * the now useless signals. This functor is applied after the regular
864 * functor to clean up dangling constants that might be left behind.
866 struct cprop_dc_functor
: public functor_t
{
868 virtual void lpm_const(Design
*des
, NetConst
*obj
);
871 void cprop_dc_functor::lpm_const(Design
*des
, NetConst
*obj
)
873 // 'bz constant values drive high impedance to whatever is
874 // connected to it. In other words, it is a noop. But that is
875 // only true if *all* the bits of the vectors.
877 ivl_assert(*obj
, obj
->pin_count()==1);
878 for (unsigned idx
= 0 ; idx
< obj
->width() ; idx
+= 1) {
879 if (obj
->value(idx
) == verinum::Vz
) {
884 if (tmp
== obj
->width()) {
890 // For each bit, if this is the only driver, then set the
891 // initial value of all the signals to this value.
892 for (unsigned idx
= 0 ; idx
< obj
->pin_count() ; idx
+= 1) {
893 if (count_outputs(obj
->pin(idx
)) > 1)
896 Nexus
*nex
= obj
->pin(idx
).nexus();
897 for (Link
*clnk
= nex
->first_nlink()
898 ; clnk
; clnk
= clnk
->next_nlink()) {
902 clnk
->cur_link(cur
, pin
);
904 NetNet
*tmp
= dynamic_cast<NetNet
*>(cur
);
908 tmp
->pin(pin
).set_init(obj
->value(idx
));
912 // If there are any links that take input, the constant is
913 // used structurally somewhere.
914 for (unsigned idx
= 0 ; idx
< obj
->pin_count() ; idx
+= 1)
915 if (count_inputs(obj
->pin(idx
)) > 0)
918 // Look for signals that have NetESignal nodes attached to
919 // them. If I find any, then this constant is used by a
920 // behavioral expression somewhere.
921 for (unsigned idx
= 0 ; idx
< obj
->pin_count() ; idx
+= 1) {
922 Nexus
*nex
= obj
->pin(idx
).nexus();
923 for (Link
*clnk
= nex
->first_nlink()
924 ; clnk
; clnk
= clnk
->next_nlink()) {
928 clnk
->cur_link(cur
, pin
);
930 NetNet
*tmp
= dynamic_cast<NetNet
*>(cur
);
934 assert(tmp
->scope());
936 // If the net is a signal name from the source,
937 // then users will probably want to see it in the
938 // waveform dump, so unhooking the constant will
939 // make it look wrong.
940 if (! tmp
->local_flag())
943 // If the net has an eref, then there is an
944 // expression somewhere that reads this signal. So
945 // the constant does get read.
946 if (tmp
->peek_eref() > 0)
949 // If the net is a port of the root module, then
950 // the constant may be driving something outside
951 // the design, so do not eliminate it.
952 if ((tmp
->port_type() != NetNet::NOT_A_PORT
)
953 && (tmp
->scope()->parent() == 0))
964 void cprop(Design
*des
)
966 // Continually propagate constants until a scan finds nothing
972 } while (prop
.count
> 0);