-
Notifications
You must be signed in to change notification settings - Fork 25
Implements 'Keep' Shifting Config #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev-0.4.10
Are you sure you want to change the base?
Conversation
|
@francof2a Do you have any thoughts? |
|
Hello @Jonah-Foley I do some previous comments before review your PR implementation:
I will be reviewing your implementation these days! |
|
I have:
I understand the testing could be more rigorous however I believe the current test framework doesn't really lend itself to that. I understand it is a goal of yours to change this in the future. Let me know what you think. Still open to modifications. let me know what you think @francof2a |
|
I see two issues with your solution:
Thinking about the problem you found, I think a good solution would be shift the value and keep the The def __lshift__(self, n):
if self.config.shifting == 'expand':
n_word = max(self.n_word, int(np.max(np.ceil(np.log2(np.abs(self.val)+0.5)))) + self.signed + n)
new_value = self.val << np.array(n, dtype=self.val.dtype)
elif self.config.shifting == "keep":
n_word = self.n_word
@utils.array_support
def _raw_lshift(val, nshift, nbits, signed):
# shift and keep only nbits after shifting
val = (val << np.array(nshift, dtype=val.dtype)) % (1 << nbits)
# if most significant bit is 1 and signed take two's complement
if signed and (int(val) & (1 << (nbits - 1))) != 0:
val = val - (1 << nbits)
return val
new_value = _raw_lshift(self.val, nshift=n, nbits=n_word, signed=self.signed)
else:
n_word = self.n_word
new_value = self.val << np.array(n, dtype=self.val.dtype)
y = Fxp(None, signed=self.signed, n_word=n_word, n_frac=self.n_frac)
y.set_val(new_value, raw=True, vdtype=self.vdtype) # set raw val shifted
return y
Note that I used I added extra tests in order to check the N-D support and for -1 value left-shifted in signed # keep shift
## left unsigned
x = Fxp(1, False, 8, 0, shifting="keep")
prev = x.get_val()
for i in range(x.n_word):
assert (x << i)() == prev
prev = 2 * prev
assert (x << 8)() == 0
## right unsigned
x.set_val(128)
prev = 128
for i in range(x.n_word):
assert (x >> i)() == prev
prev = prev // 2
assert (x >> 8)() == 0
## left signed
x = Fxp(1, True, 8, 0, shifting="keep")
prev = 1
for i in range(x.n_word - 1):
assert (x << i)() == prev, f"{i}, {prev}"
prev = 2 * prev
assert (x << x.n_word - 1)() == -128
assert (x << x.n_word)() == 0
x.set_val(113)
for i in range(x.n_word):
assert (x << i).bin() == x.bin()[i:] + (i * "0")
x.set_val(-1)
for i in range(x.n_word):
assert (x << i).bin() == (x.n_word - i) * '1' + (i * "0")
## right signed
x.set_val(-128)
for i in range(x.n_word):
assert (x >> i).bin() == (i + 1) * "1" + (x.n_word - (i + 1)
assert (x >> 8).bin() == x.n_word * "1"
## None type
x = Fxp(None, False, 8, 0, shifting="keep")
for i in range(10):
assert x << i == 0
## arrays
x = Fxp([1, 2, 4, 8], False, 8, 0, shifting="keep")
assert np.all(x << 1 == x*2)
x.set_val([128])
assert np.all(x << 1 == 0)
x = Fxp([[1,2],[4,8]], False, 8, 0, shifting="keep")
assert np.all(x << 1 == x*2)Please let me know if it's right for you. |
|
Awesome, thank you for your implementation. I shall review it in the coming few days. |
What This PR Achieves
After reviewing the fxpmath source, I saw that there was no current implementation for the 'Keep' shifting configuration, despite it being a valid option when constructing an
Fxpobject. This is important to me for the purpose of hardware modelling as i'd like a fixed-point type which:Implementation Details
This PR implements that. The basic approach is to shift the raw value (as in the case of the 'expand' approach) however then bitmask the result with a mask of the form
01...1where the number of 1's is equal to the input word size. We then create a newFxpobject from the masked value and return.Note, when we construct the new value, we have to explicitly do so from a binary string with the formatting
This is necessary for two cases:
0b1(i.e.1 << 0), this must be treated as0b00..01when callingset_val(), otherwise it will be treated asFxp('0b1', True, N, 0) = 0b11..110b0100 << 1 == 0b1000 == -8. Without this formatting0b0100 << 1is treated as 8, which would not fit into a signed 4-bit value, and hence passing it toset_val()would cause the value to saturate and the resulting object would take on the value0b0111 == 7Tests
I have written some tests in
test_operators.pyto demonstrate the functionality. I anticipate that there may be a better way to achieve this behaviour without use of the string formatting, so I am very open to suggestions before merging.