From 6dfd3a6de728807ab28b8a853d9bddb4f631e327 Mon Sep 17 00:00:00 2001 From: Jeremy Lau <30300826+fdxmw@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:02:33 -0700 Subject: [PATCH] Update `concat` to immediately concatenate `Consts`, returning a new `Const`. This removes many unnecessary `concats`, especially from the common `counter + 1` pattern. In this pattern, `as_wires` creates a 1-bit `Const` for the `1`, then `match_bitwidth` zero-extends the `Const` to `counter`'s bitwidth, which concatenates zeroes to the 1-bit `Const`. This `concat` is not useful, because the zeroes and the 1-bit `1` are all `Consts`. This commit removes these unnecessary `concats`. This significantly reduces the number of `concat` `LogicNets` in `pyrtlnet` (33%, 611 -> 412). --- pyrtl/corecircuits.py | 34 ++++++++--- tests/test_analysis.py | 26 ++++---- tests/test_importexport.py | 119 ++++++++++++++++++------------------ tests/test_passes.py | 10 +-- tests/test_visualization.py | 10 +-- 5 files changed, 107 insertions(+), 92 deletions(-) diff --git a/pyrtl/corecircuits.py b/pyrtl/corecircuits.py index cdb156e7..64e68c84 100644 --- a/pyrtl/corecircuits.py +++ b/pyrtl/corecircuits.py @@ -213,6 +213,15 @@ def concat(*args: WireVectorLike) -> WireVector: arg_wirevectors = tuple(as_wires(arg) for arg in args) final_width = sum(len(arg) for arg in arg_wirevectors) + + if all(isinstance(arg, Const) for arg in arg_wirevectors): + # If all args are `Const`, do the `concat` now and return the `Const` result. + result = 0 + for const in arg_wirevectors: + result = result << const.bitwidth + result = result | const.val + return Const(result, bitwidth=final_width) + outwire = WireVector(bitwidth=final_width) net = LogicNet(op="c", op_param=None, args=arg_wirevectors, dests=(outwire,)) working_block().add_net(net) @@ -714,22 +723,27 @@ def match_bitwidth(*args: WireVector, signed: bool = False) -> tuple[WireVector] Example with sign-extension:: - >>> a = pyrtl.Const(-1, name="a_short", signed=True, bitwidth=2) - >>> b = pyrtl.Const(-3, name="b", signed=True, bitwidth=4) + >>> a_short = pyrtl.Input(name="a_short", bitwidth=2) + >>> b_long = pyrtl.Input(name="b_long", bitwidth=4) - >>> a, b = pyrtl.match_bitwidth(a, b, signed=True) - >>> a.name = "a_long" - >>> a.bitwidth, b.bitwidth - (4, 4) + >>> a_matched = pyrtl.Output(name="a_matched", bitwidth=4) + >>> b_matched = pyrtl.Output(name="b_matched", bitwidth=4) + + >>> a, b = pyrtl.match_bitwidth(a_short, b_long, signed=True) + >>> a_matched <<= a + >>> b_matched <<= b >>> sim = pyrtl.Simulation() - >>> sim.step() - >>> bin(sim.inspect("b")) - '0b1101' + >>> sim.step({"a_short": -1, "b_long": -3}) + >>> bin(sim.inspect("a_short")) '0b11' - >>> bin(sim.inspect("a_long")) + >>> bin(sim.inspect("b_long")) + '0b1101' + >>> bin(sim.inspect("a_matched")) '0b1111' + >>> bin(sim.inspect("b_matched")) + '0b1101' :param args: :class:`WireVectors` of which to match :attr:`~WireVector.bitwidth` diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 484b5505..76a3085d 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -156,25 +156,25 @@ def setUp(self): From i To o Path 0 - tmp3/3W <-- - -- i/2I, tmp2/2W - tmp4/3W <-- | -- tmp1/3W, tmp3/3W - o/3O <-- w -- tmp4/3W + tmp2/3W <-- - -- i/2I, const_3_1/2C + tmp3/3W <-- | -- tmp1/3W, tmp2/3W + o/3O <-- w -- tmp3/3W Path 1 tmp0/3W <-- c -- const_0_0/1C, i/2I tmp1/3W <-- & -- tmp0/3W, j/3I - tmp4/3W <-- | -- tmp1/3W, tmp3/3W - o/3O <-- w -- tmp4/3W + tmp3/3W <-- | -- tmp1/3W, tmp2/3W + o/3O <-- w -- tmp3/3W To p Path 0 - tmp5/4W <-- c -- const_3_0/2C, i/2I - tmp6/5W <-- - -- k/4I, tmp5/4W - p/5O <-- w -- tmp6/5W + tmp4/4W <-- c -- const_4_0/2C, i/2I + tmp5/5W <-- - -- k/4I, tmp4/4W + p/5O <-- w -- tmp5/5W From j To o Path 0 tmp1/3W <-- & -- tmp0/3W, j/3I - tmp4/3W <-- | -- tmp1/3W, tmp3/3W - o/3O <-- w -- tmp4/3W + tmp3/3W <-- | -- tmp1/3W, tmp2/3W + o/3O <-- w -- tmp3/3W To p (No paths) From k @@ -182,8 +182,8 @@ def setUp(self): (No paths) To p Path 0 - tmp6/5W <-- - -- k/4I, tmp5/4W - p/5O <-- w -- tmp6/5W + tmp5/5W <-- - -- k/4I, tmp4/4W + p/5O <-- w -- tmp5/5W """ @@ -382,7 +382,7 @@ def test_pretty_print(self): output = io.StringIO() paths.print(file=output) self.maxDiff = 30000 - self.assertEqual(output.getvalue(), paths_print_output) + self.assertEqual(paths_print_output, output.getvalue()) class TestDistance(unittest.TestCase): diff --git a/tests/test_importexport.py b/tests/test_importexport.py index d67abe4e..edf2b3f8 100644 --- a/tests/test_importexport.py +++ b/tests/test_importexport.py @@ -781,15 +781,16 @@ def test_blif_nor_gate_correct(self): // As one initial test of synthesis, map to FPGA with: // yosys -p "synth_xilinx -top toplevel" thisfile.v -module toplevel(clk, o); +module toplevel(clk, j, o); input clk; + input[2:0] j; output[12:0] o; // Constants wire[5:0] k = 6'd38; // Combinational logic - assign o = {4'd12, 3'd3, k}; + assign o = {4'd12, j, k}; endmodule """ @@ -825,29 +826,29 @@ def test_blif_nor_gate_correct(self): reg[3:0] s; // Temporaries - wire[4:0] tmp13; - wire[3:0] tmp14; - wire[4:0] tmp15; - wire[5:0] tmp17; - wire[6:0] tmp19; - wire[4:0] tmp22; - wire tmp24; + wire[4:0] tmp12; + wire[3:0] tmp13; + wire[4:0] tmp14; + wire[5:0] tmp15; + wire[6:0] tmp17; + wire[4:0] tmp19; + wire tmp21; + wire[3:0] tmp23; + wire[10:0] tmp25; wire[3:0] tmp26; - wire[10:0] tmp28; - wire[3:0] tmp29; - wire[11:0] tmp31; + wire[11:0] tmp28; // Combinational logic - assign o = (tmp31[9:0]); - assign tmp13 = (r + {3'd0, 1'd1}); - assign tmp14 = (tmp13[3:0]); - assign tmp15 = (a + r); - assign tmp17 = (tmp15 + {4'd0, 1'd1}); - assign tmp19 = (tmp17 - {2'd0, s}); - assign tmp22 = (a - {3'd0, 1'd1}); - assign tmp24 = (tmp15[4]); - assign tmp28 = ({{5 {tmp24}}, tmp15} + {6'd0, tmp26}); - assign tmp31 = (tmp28 + {7'd0, tmp29}); + assign o = (tmp28[9:0]); + assign tmp12 = (r + 4'd1); + assign tmp13 = (tmp12[3:0]); + assign tmp14 = (a + r); + assign tmp15 = (tmp14 + 5'd1); + assign tmp17 = (tmp15 - {2'd0, s}); + assign tmp19 = (a - 4'd1); + assign tmp21 = (tmp14[4]); + assign tmp25 = ({{5 {tmp21}}, tmp14} + {6'd0, tmp23}); + assign tmp28 = (tmp25 + {7'd0, tmp26}); // Register logic always @(posedge clk) begin @@ -855,8 +856,8 @@ def test_blif_nor_gate_correct(self): r <= 4'd0; s <= 4'd13; end else begin - r <= (tmp19[3:0]); - s <= (tmp22[3:0]); + r <= (tmp17[3:0]); + s <= (tmp19[3:0]); end end @@ -868,75 +869,75 @@ def test_blif_nor_gate_correct(self): // MemBlock tmp0 logic always @(posedge clk) begin tmp0[2'd0] <= a; - tmp0[2'd1] <= tmp14; + tmp0[2'd1] <= tmp13; end - assign tmp26 = tmp0[2'd0]; + assign tmp23 = tmp0[2'd0]; // MemBlock tmp1 logic always @(posedge clk) begin tmp1[2'd0] <= a; - tmp1[2'd1] <= tmp14; + tmp1[2'd1] <= tmp13; end - assign tmp29 = tmp1[2'd0]; + assign tmp26 = tmp1[2'd0]; // MemBlock tmp2 logic always @(posedge clk) begin tmp2[2'd0] <= a; - tmp2[2'd1] <= tmp14; + tmp2[2'd1] <= tmp13; end // MemBlock tmp3 logic always @(posedge clk) begin tmp3[2'd0] <= a; - tmp3[2'd1] <= tmp14; + tmp3[2'd1] <= tmp13; end // MemBlock tmp4 logic always @(posedge clk) begin tmp4[2'd0] <= a; - tmp4[2'd1] <= tmp14; + tmp4[2'd1] <= tmp13; end // MemBlock tmp5 logic always @(posedge clk) begin tmp5[2'd0] <= a; - tmp5[2'd1] <= tmp14; + tmp5[2'd1] <= tmp13; end // MemBlock tmp6 logic always @(posedge clk) begin tmp6[2'd0] <= a; - tmp6[2'd1] <= tmp14; + tmp6[2'd1] <= tmp13; end // MemBlock tmp7 logic always @(posedge clk) begin tmp7[2'd0] <= a; - tmp7[2'd1] <= tmp14; + tmp7[2'd1] <= tmp13; end // MemBlock tmp8 logic always @(posedge clk) begin tmp8[2'd0] <= a; - tmp8[2'd1] <= tmp14; + tmp8[2'd1] <= tmp13; end // MemBlock tmp9 logic always @(posedge clk) begin tmp9[2'd0] <= a; - tmp9[2'd1] <= tmp14; + tmp9[2'd1] <= tmp13; end // MemBlock tmp10 logic always @(posedge clk) begin tmp10[2'd0] <= a; - tmp10[2'd1] <= tmp14; + tmp10[2'd1] <= tmp13; end // MemBlock tmp11 logic always @(posedge clk) begin tmp11[2'd0] <= a; - tmp11[2'd1] <= tmp14; + tmp11[2'd1] <= tmp13; end endmodule """ @@ -1000,18 +1001,18 @@ def test_blif_nor_gate_correct(self): reg[3:0] tmp0; // Temporaries - wire[4:0] tmp2; + wire[4:0] tmp1; // Combinational logic assign o = tmp0; - assign tmp2 = (tmp0 + {3'd0, 1'd1}); + assign tmp1 = (tmp0 + 4'd1); // Register logic always @(posedge clk) begin if (rst) begin tmp0 <= 4'd2; end else begin - tmp0 <= (tmp2[3:0]); + tmp0 <= (tmp1[3:0]); end end endmodule @@ -1032,18 +1033,18 @@ def test_blif_nor_gate_correct(self): reg[3:0] tmp0; // Temporaries - wire[4:0] tmp2; + wire[4:0] tmp1; // Combinational logic assign o = tmp0; - assign tmp2 = (tmp0 + {3'd0, 1'd1}); + assign tmp1 = (tmp0 + 4'd1); // Register logic always @(posedge clk or posedge rst) begin if (rst) begin tmp0 <= 4'd2; end else begin - tmp0 <= (tmp2[3:0]); + tmp0 <= (tmp1[3:0]); end end endmodule @@ -1063,15 +1064,15 @@ def test_blif_nor_gate_correct(self): reg[3:0] tmp0; // Temporaries - wire[4:0] tmp2; + wire[4:0] tmp1; // Combinational logic assign o = tmp0; - assign tmp2 = (tmp0 + {3'd0, 1'd1}); + assign tmp1 = (tmp0 + 4'd1); // Register logic always @(posedge clk) begin - tmp0 <= (tmp2[3:0]); + tmp0 <= (tmp1[3:0]); end endmodule """ @@ -1090,16 +1091,16 @@ def test_blif_nor_gate_correct(self): reg[3:0] r; // Temporaries + wire[4:0] tmp0; wire[4:0] tmp1; - wire[4:0] tmp3; // Combinational logic - assign tmp1 = (r + {3'd0, 1'd1}); - assign tmp3 = (rst ? {4'd0, 1'd0} : tmp1); + assign tmp0 = (r + 4'd1); + assign tmp1 = (rst ? 5'd0 : tmp0); // Register logic always @(posedge clk) begin - r <= (tmp3[3:0]); + r <= (tmp1[3:0]); end endmodule """ @@ -1165,7 +1166,7 @@ def test_romblock_does_not_throw_error(self): def test_textual_consistency_small(self): i = pyrtl.Const(12) - j = pyrtl.Const(3, bitwidth=3) + j = pyrtl.Input(name="j", bitwidth=3) k = pyrtl.Const(38, name="k") o = pyrtl.Output(bitwidth=13, name="o") o <<= pyrtl.concat(i, j, k) @@ -1405,8 +1406,8 @@ def test_import_counter(self): def test_import_small(self): pyrtl.input_from_verilog(verilog_output_small) sim = pyrtl.Simulation() - sim.step() - self.assertEqual(sim.tracer.trace["o"][0], 0b1100011100110) + sim.step({"j": 3}) + self.assertEqual(sim.tracer.trace["o"][0], 0b1100_011_100110) def test_import_counter_with_reset(self): pyrtl.input_from_verilog(verilog_output_counter_sync_reset) @@ -1714,18 +1715,18 @@ def test_custom_module_name_testbench(self): module Example : input clock : Clock input reset : UInt<1> + input j : UInt<3> output o : UInt<13> wire tmp0 : UInt<13> wire tmp1 : UInt<7> wire tmp2 : UInt<13> node const_0_12 = UInt<4>(12) - node const_1_3 = UInt<3>(3) - node const_2_38 = UInt<6>(38) + node const_1_38 = UInt<6>(38) o <= tmp0 tmp0 <= tmp2 - tmp1 <= cat(const_0_12, const_1_3) - tmp2 <= cat(tmp1, const_2_38) + tmp1 <= cat(const_0_12, j) + tmp2 <= cat(tmp1, const_1_38) """ firrtl_output_select_test = """\ @@ -1774,7 +1775,7 @@ def setUp(self): def test_textual_consistency_concats(self): i = pyrtl.Const(0b1100) - j = pyrtl.Const(0b011, bitwidth=3) + j = pyrtl.Input(name="j", bitwidth=3) k = pyrtl.Const(0b100110) o = pyrtl.Output(13, "o") o <<= pyrtl.concat(i, j, k) diff --git a/tests/test_passes.py b/tests/test_passes.py index eb363138..06c5f4fa 100644 --- a/tests/test_passes.py +++ b/tests/test_passes.py @@ -949,9 +949,9 @@ def setUp(self): pyrtl.reset_working_block() def test_two_way_concat(self): - i = pyrtl.Const(0b1100) - j = pyrtl.Const(0b011, bitwidth=3) - k = pyrtl.Const(0b100110) + i = pyrtl.Input(name="i", bitwidth=4) + j = pyrtl.Input(name="j", bitwidth=3) + k = pyrtl.Input(name="k", bitwidth=6) o = pyrtl.Output(13, "o") o <<= pyrtl.concat(i, j, k) @@ -971,8 +971,8 @@ def test_two_way_concat(self): self.assertEqual(lower_concat.args, (upper_concat.dests[0], k)) sim = pyrtl.Simulation() - sim.step() - self.assertEqual(sim.inspect("o"), 0b1100011100110) + sim.step({"i": 0b1100, "j": 0b11, "k": 0b100110}) + self.assertEqual(sim.inspect("o"), 0b1100_011_100110) def test_one_bit_selects(self): a = pyrtl.Const(0b101101001101) diff --git a/tests/test_visualization.py b/tests/test_visualization.py index 476921cc..3368c5f0 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -305,14 +305,14 @@ def test_netgraph_unused_wires(self): self.assertEqual(len(g), 0) def test_netgraph_same_wire_multiple_edges_to_same_net(self): - c = pyrtl.Const(1, 1) - w = pyrtl.concat(c, c, c) + i = pyrtl.Input(name="i", bitwidth=1) + w = pyrtl.concat(i, i, i) g = pyrtl.net_graph() - self.assertEqual(len(g[c]), 1) - edges = next(iter(g[c].values())) + self.assertEqual(len(g[i]), 1) + edges = next(iter(g[i].values())) self.assertEqual(len(edges), 3) for w in edges: - self.assertIs(w, c) + self.assertIs(w, i) class TestOutputIPynb(unittest.TestCase):