Skip to content

Commit b506f43

Browse files
committed
Move more STL modules around, and create std/bitwise.sus. Add FindFirst
1 parent ec179e2 commit b506f43

4 files changed

Lines changed: 180 additions & 123 deletions

File tree

src/compiler_top.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const STL_FILES: &[&str] = &[
3131
"conversion.sus",
3232
"control_flow.sus",
3333
"util.sus",
34+
"bitwise.sus",
3435
"memory.sus",
3536
];
3637
const STL_FILES_FEATURE_XPM: &[&str] = &[
@@ -40,6 +41,7 @@ const STL_FILES_FEATURE_XPM: &[&str] = &[
4041
"conversion.sus",
4142
"control_flow.sus",
4243
"util.sus",
44+
"bitwise.sus",
4345
"feature/xpm/xpm.sus",
4446
"feature/xpm/memory.sus",
4547
];

std/bitwise.sus

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
2+
/// Creates a One-Hot encoding of the given integer.
3+
///
4+
/// Examples:
5+
/// `ToOneHot#(SIZE: 5)(3) == [false, false, false, true, false]`
6+
/// `ToOneHot#(SIZE: 2)(0) == [true, false]`
7+
module ToOneHot #(int SIZE) {
8+
interface ToOneHot : int#(FROM: 0, TO: SIZE - 1) selection'0 -> bool[SIZE] bits'0
9+
10+
bits = RepeatGen#(T: type bool, V: false, SIZE)
11+
12+
bits[selection] = true
13+
}
14+
15+
/// This module returns the index of the first `true` bit in the given bitstring.
16+
/// If the whole bitstring is `false`, then `is_nonzero` is not fired.
17+
/// If there is at least one set ('1') bit, `is_nonzero` triggers with the index of the first '1' bit.
18+
///
19+
/// Usage:
20+
/// ´´´sus
21+
/// FindFirst find_first_bit
22+
/// find_first_bit.find([false, false, true, false, true])
23+
/// when find_first_bit.is_nonzero : int first_nonzero {
24+
/// // Fires with `2`
25+
/// }
26+
/// ´´´
27+
module FindFirst #(int SIZE) {
28+
trigger is_nonzero : int#(FROM: 0, TO: SIZE) first_nonzero'0
29+
30+
action find : bool[SIZE] bits'0 {
31+
gen int BASE_CASE_SIZE = 6
32+
33+
if SIZE <= BASE_CASE_SIZE {
34+
// Iterate from high downwards
35+
for int I in 0..SIZE {
36+
gen int BIT = SIZE - 1 - I
37+
38+
// Later (therefore lower-index) bits override this call, hence the first bit effect.
39+
when bits[BIT] {
40+
is_nonzero(BIT)
41+
}
42+
}
43+
} else {
44+
// Split recursively
45+
gen int LEFT_SIZE = SIZE / 2
46+
47+
FindFirst left_part
48+
FindFirst right_part
49+
50+
left_part.find(bits[LEFT_SIZE:])
51+
right_part.find(bits[:LEFT_SIZE])
52+
53+
when left_part.is_nonzero : int left_first_nonzero {
54+
is_nonzero(left_first_nonzero)
55+
} else when right_part.is_nonzero : int right_first_nonzero {
56+
is_nonzero(right_first_nonzero + LEFT_SIZE)
57+
}
58+
}
59+
}
60+
}
61+
62+
/// Counts the number of set ('1') bits in the bitstring.
63+
///
64+
/// Example:
65+
/// `PopCount([true, false, true, true, true]) == 4`
66+
/// `PopCount(8'b00111011) == 5`
67+
module PopCount #(int SIZE) {
68+
// Should be chosen based on what's most efficient for the target architecture
69+
gen int BASE_CASE_SIZE = 6
70+
71+
interface PopCount : bool[SIZE] bits'0 -> int#(FROM: 0, TO: SIZE+1) popcount
72+
73+
if SIZE == 0 {
74+
int zero'0 = 0
75+
popcount = zero
76+
} else if SIZE <= BASE_CASE_SIZE {
77+
int[SIZE] cvt
78+
for int I in 0..SIZE {
79+
when bits[I] {
80+
cvt[I] = 1
81+
} else {
82+
cvt[I] = 0
83+
}
84+
}
85+
if SIZE == 1 {
86+
popcount = cvt[0]
87+
} else if SIZE == 2 {
88+
popcount = cvt[0] + cvt[1]
89+
} else if SIZE == 3 {
90+
popcount = cvt[0] + cvt[1] + cvt[2]
91+
} else if SIZE == 4 {
92+
popcount = cvt[0] + cvt[1] + cvt[2] + cvt[3]
93+
} else if SIZE == 5 {
94+
popcount = cvt[0] + cvt[1] + cvt[2] + cvt[3] + cvt[4]
95+
} else {
96+
assert#(C: false)
97+
}
98+
} else {
99+
popcount = PopCount(bits[:SIZE / 2]) + PopCount(bits[SIZE / 2:])
100+
}
101+
}
102+
103+
104+
/// Splits the given integer. The upper [LOWER_BITS:] bits are returned as upper, the lower [:LOWER_BITS] bits are returned in lower
105+
/// See also [BitwiseIntConcat]
106+
module BitwiseIntSplit #(int TO, int LOWER_BITS) {
107+
gen int UPPER_TO = (TO-1) / pow2#(E: LOWER_BITS) + 1
108+
interface BitwiseIntSplit :
109+
int#(FROM: 0, TO) v'0 ->
110+
int#(FROM: 0, TO: UPPER_TO) upper'0,
111+
int#(FROM: 0, TO: pow2#(E: LOWER_BITS)) lower'0
112+
113+
bool[clog2#(V: TO)] v_bits = UIntToBits(v)
114+
115+
lower = BitsToUInt(v_bits[:LOWER_BITS])
116+
// we use FromBits rather than BitsToUInt, because we can then immediately adjust the range too
117+
upper = FromBits#(T: type int#(FROM: 0, TO: UPPER_TO))(v_bits[LOWER_BITS:])
118+
}
119+
120+
/// Recombines a lower and upper integer parts.
121+
/// See also [BitwiseIntSplit]
122+
module BitwiseIntConcat #(int UPPER_TO, int LOWER_BITS) {
123+
interface BitwiseIntConcat :
124+
int#(FROM: 0, TO: UPPER_TO) upper'0,
125+
int#(FROM: 0, TO: pow2#(E: LOWER_BITS)) lower'0 ->
126+
int#(FROM: 0, TO: UPPER_TO * pow2#(E: LOWER_BITS)) v'0
127+
128+
bool[clog2#(V: UPPER_TO) + LOWER_BITS] v_bits
129+
v_bits[:LOWER_BITS] = UIntToBits(lower)
130+
v_bits[LOWER_BITS:] = UIntToBits(upper)
131+
// we use FromBits rather than BitsToUInt, because we can then immediately adjust the range too
132+
v = FromBits#(T: type int#(FROM: 0, TO: UPPER_TO * pow2#(E: LOWER_BITS)))(v_bits)
133+
}
134+
135+
/// Sets the lower [:LOWER_BITS] bits to false, thus aligning the value
136+
/// Works for unsigned and signed integers alike
137+
module AlignToPow2 #(int FROM, int TO, int LOWER_BITS) {
138+
gen int ALIGNED_FROM = FROM - (FROM mod clog2#(V: LOWER_BITS))
139+
gen int ALIGNED_TO = TO - (TO + 1 mod clog2#(V: LOWER_BITS))
140+
interface AlignToPow2 :
141+
int#(FROM, TO) i'0 ->
142+
int#(FROM: ALIGNED_FROM, TO: ALIGNED_TO) o'0
143+
144+
bool[sizeof#(T: type int#(FROM, TO))] bits = ToBits(i)
145+
bool[sizeof#(T: type int#(FROM, TO))] aligned_bits
146+
aligned_bits[:LOWER_BITS] = Repeat(false)
147+
aligned_bits[LOWER_BITS:] = bits[LOWER_BITS:]
148+
// we use FromBits rather than BitsToInt, because we can then immediately adjust the range too
149+
o = FromBits#(T: type int#(FROM: ALIGNED_FROM, TO: ALIGNED_TO))(aligned_bits)
150+
}

std/math.sus

Lines changed: 28 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -34,52 +34,36 @@ __builtin__ const int min #(int A, int B) {}
3434
/// Compute the maximum of `A` and `B`
3535
__builtin__ const int max #(int A, int B) {}
3636

37-
/// Splits the given integer. The upper [LOWER_BITS:] bits are returned as upper, the lower [:LOWER_BITS] bits are returned in lower
38-
/// See also [BitwiseIntConcat]
39-
module BitwiseIntSplit #(int TO, int LOWER_BITS) {
40-
gen int UPPER_TO = (TO-1) / pow2#(E: LOWER_BITS) + 1
41-
interface BitwiseIntSplit :
42-
int#(FROM: 0, TO) v'0 ->
43-
int#(FROM: 0, TO: UPPER_TO) upper'0,
44-
int#(FROM: 0, TO: pow2#(E: LOWER_BITS)) lower'0
45-
46-
bool[clog2#(V: TO)] v_bits = UIntToBits(v)
47-
48-
lower = BitsToUInt(v_bits[:LOWER_BITS])
49-
// we use FromBits rather than BitsToUInt, because we can then immediately adjust the range too
50-
upper = FromBits#(T: type int#(FROM: 0, TO: UPPER_TO))(v_bits[LOWER_BITS:])
51-
}
52-
53-
/// Recombines a lower and upper integer parts.
54-
/// See also [BitwiseIntSplit]
55-
module BitwiseIntConcat #(int UPPER_TO, int LOWER_BITS) {
56-
interface BitwiseIntConcat :
57-
int#(FROM: 0, TO: UPPER_TO) upper'0,
58-
int#(FROM: 0, TO: pow2#(E: LOWER_BITS)) lower'0 ->
59-
int#(FROM: 0, TO: UPPER_TO * pow2#(E: LOWER_BITS)) v'0
60-
61-
bool[clog2#(V: UPPER_TO) + LOWER_BITS] v_bits
62-
v_bits[:LOWER_BITS] = UIntToBits(lower)
63-
v_bits[LOWER_BITS:] = UIntToBits(upper)
64-
// we use FromBits rather than BitsToUInt, because we can then immediately adjust the range too
65-
v = FromBits#(T: type int#(FROM: 0, TO: UPPER_TO * pow2#(E: LOWER_BITS)))(v_bits)
37+
module Abs #(int TO) {
38+
interface Abs : int #(FROM: -TO+1, TO) a -> int #(FROM: 0, TO) o
39+
40+
when a < 0 {
41+
o = IntNarrow#(FROM: 0, TO)(-a)
42+
} else {
43+
o = IntNarrow#(FROM: 0, TO)(a)
44+
}
6645
}
6746

68-
/// Sets the lower [:LOWER_BITS] bits to false, thus aligning the value
69-
/// Works for unsigned and signed integers alike
70-
module AlignToPow2 #(int FROM, int TO, int LOWER_BITS) {
71-
gen int ALIGNED_FROM = FROM - (FROM mod clog2#(V: LOWER_BITS))
72-
gen int ALIGNED_TO = TO - (TO + 1 mod clog2#(V: LOWER_BITS))
73-
interface AlignToPow2 :
74-
int#(FROM, TO) i'0 ->
75-
int#(FROM: ALIGNED_FROM, TO: ALIGNED_TO) o'0
76-
77-
bool[sizeof#(T: type int#(FROM, TO))] bits = ToBits(i)
78-
bool[sizeof#(T: type int#(FROM, TO))] aligned_bits
79-
aligned_bits[:LOWER_BITS] = Repeat(false)
80-
aligned_bits[LOWER_BITS:] = bits[LOWER_BITS:]
81-
// we use FromBits rather than BitsToInt, because we can then immediately adjust the range too
82-
o = FromBits#(T: type int#(FROM: ALIGNED_FROM, TO: ALIGNED_TO))(aligned_bits)
47+
// Recursive Tree Add module recurses smaller copies of itself.
48+
module TreeAdd #(int WIDTH, int FROM, int TO) {
49+
interface TreeAdd : int#(FROM, TO)[WIDTH] values'0 -> int#(FROM: FROM*WIDTH, TO: (TO - 1)*WIDTH + 1) total
50+
51+
if WIDTH == 0 {
52+
// Have to explicitly give zero a latency count.
53+
// Otherwise total's latency can't be determined.
54+
int zero'0 = 0
55+
total = zero
56+
} else if WIDTH == 1 {
57+
total = values[0]
58+
} else {
59+
// Or instantiate submodules inline
60+
int left_total = TreeAdd(values[:WIDTH / 2])
61+
int right_total = TreeAdd(values[WIDTH / 2:])
62+
63+
// Can add pipelining registers here too.
64+
// Latency Counting will figure it out.
65+
reg total = left_total + right_total
66+
}
8367
}
8468

8569
/// Unsafely shrinks the bounds of an integer.

std/util.sus

Lines changed: 0 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,4 @@
11

2-
module Abs #(int TO) {
3-
interface Abs : int #(FROM: -TO+1, TO) a -> int #(FROM: 0, TO) o
4-
5-
when a < 0 {
6-
o = IntNarrow#(FROM: 0, TO)(-a)
7-
} else {
8-
o = IntNarrow#(FROM: 0, TO)(a)
9-
}
10-
}
11-
12-
module ToOneHot #(int SIZE) {
13-
interface ToOneHot : int#(FROM: 0, TO: SIZE - 1) selection'0 -> bool[SIZE] bits'0
14-
15-
for int I in 0..SIZE {
16-
bits[I] = false
17-
}
18-
19-
bits[selection] = true
20-
}
21-
22-
module PopCount #(int WIDTH) {
23-
// Should be chosen based on what's most efficient for the target architecture
24-
gen int BASE_CASE_SIZE = 5
25-
26-
interface PopCount : bool[WIDTH] bits'0 -> int#(FROM: 0, TO: WIDTH+1) popcount
27-
28-
if WIDTH == 0 {
29-
int zero'0 = 0
30-
popcount = zero
31-
} else if WIDTH <= BASE_CASE_SIZE {
32-
int[WIDTH] cvt
33-
for int I in 0..WIDTH {
34-
when bits[I] {
35-
cvt[I] = 1
36-
} else {
37-
cvt[I] = 0
38-
}
39-
}
40-
if WIDTH == 1 {
41-
popcount = cvt[0]
42-
} else if WIDTH == 2 {
43-
popcount = cvt[0] + cvt[1]
44-
} else if WIDTH == 3 {
45-
popcount = cvt[0] + cvt[1] + cvt[2]
46-
} else if WIDTH == 4 {
47-
popcount = cvt[0] + cvt[1] + cvt[2] + cvt[3]
48-
} else if WIDTH == 5 {
49-
popcount = cvt[0] + cvt[1] + cvt[2] + cvt[3] + cvt[4]
50-
} else {
51-
assert#(C: false)
52-
}
53-
} else {
54-
reg reg popcount = PopCount(bits[:WIDTH / 2]) + PopCount(bits[WIDTH / 2:])
55-
}
56-
}
57-
58-
59-
// Recursive Tree Add module recurses smaller copies of itself.
60-
module TreeAdd #(int WIDTH, int FROM, int TO) {
61-
interface TreeAdd : int#(FROM, TO)[WIDTH] values'0 -> int#(FROM: FROM*WIDTH, TO: (TO - 1)*WIDTH + 1) total
62-
63-
if WIDTH == 0 {
64-
// Have to explicitly give zero a latency count.
65-
// Otherwise total's latency can't be determined.
66-
int zero'0 = 0
67-
total = zero
68-
} else if WIDTH == 1 {
69-
total = values[0]
70-
} else {
71-
// Or instantiate submodules inline
72-
int left_total = TreeAdd(values[:WIDTH / 2])
73-
int right_total = TreeAdd(values[WIDTH / 2:])
74-
75-
// Can add pipelining registers here too.
76-
// Latency Counting will figure it out.
77-
reg total = left_total + right_total
78-
}
79-
}
80-
812
/// Equivalent to the C/Verilog `cond ? a : b` ternary operator. When `cond` is `true` it returns `a`, otherwise it returns `b`
823
module Ternary #(T) {
834
interface Ternary : bool cond'0, T a'0, T b'0 -> T o'0

0 commit comments

Comments
 (0)