right alignment behavior
I've been strugging with a communication system that expects the bitstream to be "right-aligned". Basically the idea is to perform zero-padding on the left instead of the right. I also make this alter the behavior of Text/Raw so that the struct bytes are aligned based on the rightmost values and left padded from there. The tests illustrate this.
Please let me know your thoughts.
What about just adding padding to the format string? For example p3u77 instead of u77, ..., align_right=True.
That’s basically what I’m doing on line 277, but when the raw/text arg width doesn’t exactly match the field width (r7 and b'\x7f' for example) then the bits are still grabbed from the left of the arg instead of the right. See line 868 of the test.
Can you give an example of when the new functionality is needed/useful?
So my particular use case may be niche, but there is a co-processor I am communicating with over a serial UART but the underlying packet structure has a dynamic bit-width. It expects those bits to be right-aligned, i.e. there is padding on the left of the most significant byte.
The reason I figured this might be worth a PR is just that the documentation said that the output is right-padded to the nearest byte. That’s a fine default behavior (and to your credit makes more sense regarding memory/disk alignment) but in the interest of symmetry, I thought there might be value in supporting left-padding with a simple flag instead of having the user calculate it into the format string. This is fairly common for communication with hardware over byte-aligned interfaces. And the reason I change the way text/raw operates in this mode is parallelism between the input and outputs.
Also, let me know if the name of the flag should be different, like maybe right_aligned or pad_on_left.
Can you give code examples of where the change is this PR is needed? Just want to understand.
Sure thing, let me see if I can make it legible
net_charge_width = charge_width(self._network)
opc_width = _count_to_bitwidth(len(self._Opcode))
inp_names = list()
inp_names.append("opcode")
inp_fmt_str = f"u{opc_width}"
match self._source_type:
case _IoType.DISPATCH:
# using stream example
pass
case _IoType.STREAM:
inp_names.extend(range(self._network.num_inputs()))
inp_fmt_str += "".join(
[f"s{net_charge_width}" for _ in range(self._network.num_inputs())]
)
case _:
raise ValueError()
self._inp_fmt = bs.compile(inp_fmt_str, inp_names)
out_names = list()
out_fmt_str = ""
match self._sink_type:
case _IoType.DISPATCH:
# using stream example
pass
case _IoType.STREAM:
out_names.extend(range(self._network.num_outputs()))
out_fmt_str += "b1" for _ in range(self._network.num_outputs())
case _:
raise ValueError()
self._out_fmt = bs.compile(out_fmt_str, out_names)
So if we take packing a stream input as an example, there is a 1-bit opcode and then num_input x charge_width-bit signed ints to send over serial. However, just because of the way the hardware works, it expects that all to be right-aligned. Same with the stream output, it has num_output x 1-bit fires/no-fires that will come right-aligned in the bytestream. I could manually change the fmt string after I create it the first time, sure, but it seems like there should be an option to left-pad formats that aren't an integer number of bytes. Just my take, but I can work around it.
I think we have to simplify the implementation and also implement it in the C extension. Most likely easiest to have an optional step that just shifts all data to the right after packed with today's right-padded design.
So the preference is to not treat the raw/text as right-aligned in that case? I would want to know what the decision on that is before implementing in C. I just think for symmetry we would want raw byte fields to populate from the right on input the same as they would on output in the align_right=True case.
Sorry, I'm too busy working on my other projects right now.