Skip to content

Commit 91e69e9

Browse files
authored
[dsl][bugfix][enhancement][#432] Implement constant folding for select1hot (#433)
* [milestone][docs][test.unit]: Milestone 1 for issue #432 .gitignore: Add .milestones/* to exclude milestone tracking files python/assassyn/ir/expr/expr.md: Document constant folding for Select1Hot python/assassyn/ir/value.md: Document constant folding behavior for select1hot method python/unit-tests/test_select1hot_constant_folding.py: Add comprehensive test cases This milestone creates documentation and test infrastructure for implementing constant folding in select1hot operations. The implementation will detect when both the selector and all values are constants, then compute the result at compile time instead of generating IR nodes. Test status: 0/1 tests passed (implementation not yet complete). Related to issue #432. * [dsl][bugfix][enhancement]: Implement constant folding for select1hot python/assassyn/ir/value.py: Add constant folding logic in select1hot method python/unit-tests/test_select1hot_constant_folding.py: Fix test values to be in valid range python/assassyn/codegen/verilog/elaborate.py: Fix pylint issues (line length and complexity) When the selector of a select1hot operation is a constant, the implementation now evaluates the selection at compile time and returns the selected value directly, avoiding unnecessary IR node creation. This follows the same pattern as existing constant folding in Const.concat() and Const.__getitem__(). The implementation verifies the one-hot property (exactly one bit set) and selects the value at the corresponding index. This fixes code generation issues when using select1hot with all constant operands. Resolves #432.
1 parent 83205b1 commit 91e69e9

File tree

6 files changed

+130
-3
lines changed

6 files changed

+130
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ Cargo.lock
2929
# Agentic Plans and Reports
3030
todos/*
3131
dones/*
32+
.milestones/*

python/assassyn/codegen/verilog/elaborate.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ def _sv_literal_for_initializer(value: int, width: int) -> str:
158158
return f"{width}'h{value:0{hex_digits}x}"
159159

160160

161+
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
161162
def generate_register_file_sv_files(sys: SysBuilder, path: Path) -> List[str]:
162163
"""Generate synthesizable multi-port register-file SV modules for non-payload arrays.
163164
@@ -226,7 +227,8 @@ def generate_register_file_sv_files(sys: SysBuilder, path: Path) -> List[str]:
226227
else:
227228
if len(initializer) != depth:
228229
raise ValueError(
229-
f"Initializer length {len(initializer)} does not match depth {depth} for {module_name}"
230+
f"Initializer length {len(initializer)} does not match "
231+
f"depth {depth} for {module_name}"
230232
)
231233
for idx, value in enumerate(initializer):
232234
lines.append(

python/assassyn/ir/expr/expr.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ A specialized multiplexer controlled by a one-hot encoded signal.
147147
- `values` - Get the list of possible values (property)
148148
- `dtype` - Get the data type of this operation (property)
149149

150+
**Note**: When constructed through the `Value.select1hot()` method, constant folding is automatically applied. If both the selector and all values are constants, the operation is evaluated at compile time and this node is not created. Direct construction of `Select1Hot` bypasses constant folding.
151+
150152
#### `class Log(Expr)`
151153

152154
A non-synthesizable node that functions as a print statement for debugging during simulation.

python/assassyn/ir/value.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,11 +370,25 @@ def select1hot(self, *args):
370370
Creates one-hot selection operation.
371371
372372
@param args Variable number of values to select from
373-
@return Select1Hot node for one-hot selection
373+
@return Select1Hot node for one-hot selection, or Const if folded
374374
'''
375375
```
376376

377-
**Explanation**: Performs one-hot selection, creating a `Select1Hot` node. `self` is a one-hot encoded selector, and `args` are the values to select from.
377+
**Explanation**: Performs one-hot selection. When both the selector (`self`) and all values in `args` are constants, the operation is **automatically constant-folded** at compile time, returning the selected `Const` directly without creating IR nodes. Otherwise, creates a `Select1Hot` node. The i-th value is selected if bit i of `self` is set.
378+
379+
**Constant Folding**: This method follows the same constant folding pattern used by `Const.concat()` and `Const.__getitem__()`. When all operands are compile-time constants, the result is computed immediately, eliminating code generation overhead and simplifying the IR.
380+
381+
**Example:**
382+
```python
383+
# Runtime select1hot (generates hardware)
384+
selector = some_signal.zext(Bits(4))
385+
result = selector.select1hot(val0, val1, val2, val3)
386+
387+
# Compile-time folded select1hot (no hardware generated)
388+
selector = Bits(4)(1 << 2) # Constant: bit 2 is set
389+
result = selector.select1hot(Bits(8)(10), Bits(8)(20), Bits(8)(30), Bits(8)(40))
390+
# result is Const(30), no Select1Hot node created
391+
```
378392

379393
#### `valid`
380394

python/assassyn/ir/value.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,25 @@ def case(self, cases: dict['Value', 'Value']):
166166
def select1hot(self, *args):
167167
'''The frontend API to create a select1hot operation'''
168168
from .expr import Select1Hot
169+
from .const import Const
170+
171+
# Check if condition is constant - if so, we can fold the selection
172+
if isinstance(self, Const):
173+
cond_value = self.value
174+
175+
# Verify one-hot property: exactly one bit should be set
176+
bit_count = bin(cond_value).count('1')
177+
assert bit_count == 1, (
178+
f"select1hot requires one-hot selector, "
179+
f"got {bit_count} bits set in {cond_value}"
180+
)
181+
182+
# Find which bit is set and return that value directly
183+
for i, arg in enumerate(args):
184+
if (cond_value >> i) & 1:
185+
return arg
186+
187+
# Fallback to creating the Select1Hot node
169188
return Select1Hot(Select1Hot.SELECT_1HOT, self, args)
170189

171190
@ir_builder
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""Test constant folding for select1hot operations.
2+
3+
This test validates that select1hot operations with all constant operands
4+
are properly folded at compile time, avoiding unnecessary code generation.
5+
"""
6+
7+
from assassyn.frontend import *
8+
from assassyn.test import run_test
9+
10+
class TestConstantFolding(Module):
11+
"""Test module for select1hot constant folding."""
12+
13+
def __init__(self):
14+
super().__init__(ports={}, no_arbiter=True)
15+
16+
@module.combinational
17+
def build(self):
18+
"""Test various constant folding scenarios."""
19+
# Test case from the bug report - bit 10 set, selecting from 32 values
20+
select = [Bits(5)(i) for i in range(32)]
21+
one_hot = Bits(32)(1 << 10)
22+
select_one_hot = one_hot.select1hot(*select)
23+
log('Select one hot bit 10: {}', select_one_hot)
24+
25+
# Test bit 0 selection
26+
one_hot_0 = Bits(4)(1 << 0)
27+
result_0 = one_hot_0.select1hot(Bits(8)(100), Bits(8)(200), Bits(8)(50), Bits(8)(150))
28+
log('Test bit 0: {}', result_0)
29+
30+
# Test bit 3 selection (last bit in 4-bit selector)
31+
one_hot_3 = Bits(4)(1 << 3)
32+
result_3 = one_hot_3.select1hot(Bits(8)(100), Bits(8)(200), Bits(8)(50), Bits(8)(150))
33+
log('Test bit 3: {}', result_3)
34+
35+
# Test bit 1 selection
36+
one_hot_1 = Bits(4)(1 << 1)
37+
result_1 = one_hot_1.select1hot(Bits(8)(10), Bits(8)(20), Bits(8)(30), Bits(8)(40))
38+
log('Test bit 1: {}', result_1)
39+
40+
# Test with different data types
41+
one_hot_2 = Bits(8)(1 << 2)
42+
result_2 = one_hot_2.select1hot(
43+
Bits(16)(0x1234),
44+
Bits(16)(0x5678),
45+
Bits(16)(0xABCD),
46+
Bits(16)(0xEF01),
47+
Bits(16)(0x2345),
48+
Bits(16)(0x6789),
49+
Bits(16)(0xBCDE),
50+
Bits(16)(0xF012)
51+
)
52+
log('Test bit 2 with 16-bit values: 0x{:x}', result_2)
53+
54+
55+
def top():
56+
"""Top-level test function."""
57+
test = TestConstantFolding()
58+
test.build()
59+
60+
61+
def check(raw: str):
62+
"""Validate test output."""
63+
lines = raw.splitlines()
64+
for line in lines:
65+
if 'Select one hot bit 10:' in line:
66+
result = int(line.split()[-1])
67+
assert result == 10, f"Expected 10, got {result}"
68+
elif 'Test bit 0:' in line:
69+
result = int(line.split()[-1])
70+
assert result == 100, f"Expected 100, got {result}"
71+
elif 'Test bit 3:' in line:
72+
result = int(line.split()[-1])
73+
assert result == 150, f"Expected 150, got {result}"
74+
elif 'Test bit 1:' in line:
75+
result = int(line.split()[-1])
76+
assert result == 20, f"Expected 20, got {result}"
77+
elif 'Test bit 2 with 16-bit values:' in line:
78+
result_hex = line.split()[-1]
79+
result = int(result_hex, 16)
80+
assert result == 0xABCD, f"Expected 0xABCD, got {result_hex}"
81+
82+
83+
def test_select1hot_constant_folding():
84+
"""Run the select1hot constant folding test."""
85+
run_test('select1hot_constant_folding', top, check)
86+
87+
88+
if __name__ == '__main__':
89+
test_select1hot_constant_folding()

0 commit comments

Comments
 (0)