From 272b16d78357a62f9f33608f783d5bfcfd1fc818 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:28:10 +0000 Subject: [PATCH 1/3] Initial plan From 011831b7642c51aa874ffa1830e5d5d7c206094b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:44:46 +0000 Subject: [PATCH 2/3] Add bit_numbering attribute support for MSB-is-zero bit numbering on bits types Co-authored-by: AaronWebster <3766083+AaronWebster@users.noreply.github.com> --- compiler/back_end/cpp/header_generator.py | 84 ++++++++++-- compiler/front_end/attribute_checker.py | 4 + compiler/front_end/attributes.py | 1 + runtime/cpp/emboss_memory_util.h | 160 ++++++++++++++++++++++ testdata/bit_numbering.emb | 48 +++++++ 5 files changed, 283 insertions(+), 14 deletions(-) create mode 100644 testdata/bit_numbering.emb diff --git a/compiler/back_end/cpp/header_generator.py b/compiler/back_end/cpp/header_generator.py index 5860bc8..e4658c6 100644 --- a/compiler/back_end/cpp/header_generator.py +++ b/compiler/back_end/cpp/header_generator.py @@ -343,11 +343,16 @@ def _offset_storage_adapter(buffer_type, alignment, static_offset): ) -def _bytes_to_bits_convertor(buffer_type, byte_order, size): +def _bytes_to_bits_convertor(buffer_type, byte_order, size, bit_numbering="Lsb0"): assert byte_order, "byte_order should not be empty." - return "{}::BitBlock, {}>".format( - _SUPPORT_NAMESPACE, _SUPPORT_NAMESPACE, byte_order, buffer_type, size - ) + if bit_numbering == "Msb0": + return "{}::BitBlockMsb0, {}>".format( + _SUPPORT_NAMESPACE, _SUPPORT_NAMESPACE, byte_order, buffer_type, size + ) + else: + return "{}::BitBlock, {}>".format( + _SUPPORT_NAMESPACE, _SUPPORT_NAMESPACE, byte_order, buffer_type, size + ) def _get_fully_qualified_namespace(name, ir): @@ -365,7 +370,12 @@ def _get_fully_qualified_name(name, ir): def _get_adapted_cpp_buffer_type_for_field( - type_definition, size_in_bits, buffer_type, byte_order, parent_addressable_unit + type_definition, + size_in_bits, + buffer_type, + byte_order, + parent_addressable_unit, + bit_numbering="Lsb0", ): """Returns the adapted C++ type information needed to construct a view.""" if ( @@ -373,7 +383,9 @@ def _get_adapted_cpp_buffer_type_for_field( and type_definition.addressable_unit == ir_data.AddressableUnit.BIT ): assert byte_order - return _bytes_to_bits_convertor(buffer_type, byte_order, size_in_bits) + return _bytes_to_bits_convertor( + buffer_type, byte_order, size_in_bits, bit_numbering + ) else: assert ( parent_addressable_unit == type_definition.addressable_unit @@ -391,6 +403,7 @@ def _get_cpp_view_type_for_type_definition( byte_order, parent_addressable_unit, validator, + bit_numbering="Lsb0", ): """Returns the C++ type information needed to construct a view. @@ -410,13 +423,20 @@ def _get_cpp_view_type_for_type_definition( parent_addressable_unit: The addressable_unit_size of the structure containing this structure. validator: The name of the validator type to be injected into the view. + bit_numbering: The bit numbering scheme for the type, either "Lsb0" + (default) or "Msb0". Returns: A tuple of: the C++ view type and a (possibly-empty) list of the C++ types of Emboss parameters which must be passed to the view's constructor. """ adapted_buffer_type = _get_adapted_cpp_buffer_type_for_field( - type_definition, size, buffer_type, byte_order, parent_addressable_unit + type_definition, + size, + buffer_type, + byte_order, + parent_addressable_unit, + bit_numbering, ) if type_definition.has_field("external"): # Externals do not (yet) support runtime parameters. @@ -469,7 +489,14 @@ def _get_cpp_view_type_for_type_definition( def _get_cpp_view_type_for_physical_type( - type_ir, size, byte_order, ir, buffer_type, parent_addressable_unit, validator + type_ir, + size, + byte_order, + ir, + buffer_type, + parent_addressable_unit, + validator, + bit_numbering="Lsb0", ): """Returns the C++ type information needed to construct a field's view. @@ -488,6 +515,8 @@ def _get_cpp_view_type_for_physical_type( parent_addressable_unit: The addressable_unit_size of the structure containing this type. validator: The name of the validator type to be injected into the view. + bit_numbering: The bit numbering scheme for the type, either "Lsb0" + (default) or "Msb0". Returns: A tuple of: the C++ type for a view of the given Emboss Type and a list of @@ -514,6 +543,7 @@ def _get_cpp_view_type_for_physical_type( _offset_storage_adapter(buffer_type, element_size, 0), parent_addressable_unit, validator, + bit_numbering, ) ) return ( @@ -539,6 +569,14 @@ def _get_cpp_view_type_for_physical_type( referenced_type = ir_util.find_object(reference, ir) if parent_addressable_unit > referenced_type.addressable_unit: assert byte_order, repr(type_ir) + # Get bit_numbering from the referenced type (for bits types) + bit_numbering_attr = ir_util.get_attribute( + referenced_type.attribute, "bit_numbering" + ) + if bit_numbering_attr: + referenced_bit_numbering = bit_numbering_attr.string_constant.text + else: + referenced_bit_numbering = bit_numbering # Use passed value as default reader, parameter_types = _get_cpp_view_type_for_type_definition( referenced_type, size, @@ -547,6 +585,7 @@ def _get_cpp_view_type_for_physical_type( byte_order, parent_addressable_unit, validator, + referenced_bit_numbering, ) return reader, parameter_types, list(type_ir.atomic_type.runtime_parameter) @@ -885,7 +924,7 @@ def _alignment_of_location(location): def _get_cpp_type_reader_of_field( - field_ir, ir, buffer_type, validator, parent_addressable_unit + field_ir, ir, buffer_type, validator, parent_addressable_unit, bit_numbering="Lsb0" ): """Returns the C++ view type for a field.""" field_size = None @@ -911,11 +950,16 @@ def _get_cpp_type_reader_of_field( _offset_storage_adapter(buffer_type, field_alignment, field_offset), parent_addressable_unit, validator, + bit_numbering, ) def _generate_structure_field_methods( - enclosing_type_name, field_ir, ir, parent_addressable_unit + enclosing_type_name, + field_ir, + ir, + parent_addressable_unit, + bit_numbering="Lsb0", ): if ir_util.field_is_virtual(field_ir): return _generate_structure_virtual_field_methods( @@ -923,7 +967,7 @@ def _generate_structure_field_methods( ) else: return _generate_structure_physical_field_methods( - enclosing_type_name, field_ir, ir, parent_addressable_unit + enclosing_type_name, field_ir, ir, parent_addressable_unit, bit_numbering ) @@ -1108,7 +1152,11 @@ def _generate_validator_type_for(enclosing_type_name, field_ir, ir): def _generate_structure_physical_field_methods( - enclosing_type_name, field_ir, ir, parent_addressable_unit + enclosing_type_name, + field_ir, + ir, + parent_addressable_unit, + bit_numbering="Lsb0", ): """Generates C++ code for methods for a single physical field. @@ -1118,6 +1166,7 @@ def _generate_structure_physical_field_methods( ir: The full IR for the module. parent_addressable_unit: The addressable unit (BIT or BYTE) of the enclosing structure. + bit_numbering: The bit numbering scheme, either "Lsb0" (default) or "Msb0". Returns: A tuple of (declarations, definitions). The declarations can be inserted @@ -1131,7 +1180,8 @@ def _generate_structure_physical_field_methods( type_reader, unused_parameter_types, parameter_expressions = ( _get_cpp_type_reader_of_field( - field_ir, ir, "Storage", validator_type, parent_addressable_unit + field_ir, ir, "Storage", validator_type, parent_addressable_unit, + bit_numbering ) ) @@ -1347,6 +1397,12 @@ def _generate_structure_definition(type_ir, ir, config: Config): _generate_subtype_definitions(type_ir, ir, config) ) type_name = type_ir.name.name.text + # Get bit_numbering attribute from the type, defaulting to "Lsb0" + bit_numbering_attr = ir_util.get_attribute(type_ir.attribute, "bit_numbering") + if bit_numbering_attr: + bit_numbering = bit_numbering_attr.string_constant.text + else: + bit_numbering = "Lsb0" field_helper_type_definitions = [] field_method_declarations = [] field_method_definitions = [] @@ -1424,7 +1480,7 @@ def _generate_structure_definition(type_ir, ir, config: Config): for field_index in type_ir.structure.fields_in_dependency_order: field = type_ir.structure.field[field_index] helper_types, declaration, definition = _generate_structure_field_methods( - type_name, field, ir, type_ir.addressable_unit + type_name, field, ir, type_ir.addressable_unit, bit_numbering ) field_helper_type_definitions.append(helper_types) field_method_definitions.append(definition) diff --git a/compiler/front_end/attribute_checker.py b/compiler/front_end/attribute_checker.py index 8da67c1..8ad4569 100644 --- a/compiler/front_end/attribute_checker.py +++ b/compiler/front_end/attribute_checker.py @@ -41,6 +41,7 @@ {"BigEndian", "LittleEndian", "Null"} ) _VALID_TEXT_OUTPUT = attribute_util.string_from_list({"Emit", "Skip"}) +_VALID_BIT_NUMBERING = attribute_util.string_from_list({"Lsb0", "Msb0"}) def _valid_back_ends(attr, module_source_file): @@ -67,6 +68,7 @@ def _valid_back_ends(attr, module_source_file): # Attributes must be the same type no matter where they occur. _ATTRIBUTE_TYPES = { attributes.ADDRESSABLE_UNIT_SIZE: attribute_util.INTEGER_CONSTANT, + attributes.BIT_NUMBERING: _VALID_BIT_NUMBERING, attributes.BYTE_ORDER: _VALID_BYTE_ORDER, attributes.ENUM_MAXIMUM_BITS: attribute_util.INTEGER_CONSTANT, attributes.FIXED_SIZE: attribute_util.INTEGER_CONSTANT, @@ -79,10 +81,12 @@ def _valid_back_ends(attr, module_source_file): } _MODULE_ATTRIBUTES = { + (attributes.BIT_NUMBERING, True), (attributes.BYTE_ORDER, True), (attributes.BACK_ENDS, False), } _BITS_ATTRIBUTES = { + (attributes.BIT_NUMBERING, False), (attributes.FIXED_SIZE, False), (attributes.REQUIRES, False), } diff --git a/compiler/front_end/attributes.py b/compiler/front_end/attributes.py index e4561da..17a1cad 100644 --- a/compiler/front_end/attributes.py +++ b/compiler/front_end/attributes.py @@ -17,6 +17,7 @@ # Attribute names which may be used by other parts of the front end. ADDRESSABLE_UNIT_SIZE = "addressable_unit_size" BYTE_ORDER = "byte_order" +BIT_NUMBERING = "bit_numbering" RANGE = "range" FIXED_SIZE = "fixed_size_in_bits" IS_INTEGER = "is_integer" diff --git a/runtime/cpp/emboss_memory_util.h b/runtime/cpp/emboss_memory_util.h index fca71c9..b7efd22 100644 --- a/runtime/cpp/emboss_memory_util.h +++ b/runtime/cpp/emboss_memory_util.h @@ -975,6 +975,97 @@ class OffsetBitBlock final { const ::std::uint8_t ok_; }; +// OffsetBitBlockMsb0 is similar to OffsetBitBlock, but implements MSB-is-zero +// bit numbering. In MSB-is-zero numbering, bit 0 is the most significant bit +// of the first byte, rather than the least significant bit of the last byte. +// +// This is commonly used in some big-endian protocols (e.g., older RFCs like +// RFC 791 for IP and RFC 793 for TCP) where bits are numbered starting from +// the high-order bit. +// +// For a field of size `s` at user-specified offset `o` in a `bits` block of +// total size `N`, the physical offset is `N - o - s`. +template +class OffsetBitBlockMsb0 final { + public: + using ValueType = typename UnderlyingBitBlockType::ValueType; + // Bit blocks do not use alignment information, but generated code expects bit + // blocks to have the same methods and types as byte blocks, so even though + // kNewAlignment and kNewOffset are unused, they must be present as template + // parameters. + template + using OffsetStorageType = OffsetBitBlockMsb0; + + OffsetBitBlockMsb0() + : bit_block_(), offset_(0), size_(0), total_size_(0), ok_(false) {} + explicit OffsetBitBlockMsb0(UnderlyingBitBlockType bit_block, + ::std::size_t offset, ::std::size_t size, + ::std::size_t total_size, bool ok) + : bit_block_{bit_block}, + offset_{static_cast(offset)}, + size_{static_cast(size)}, + total_size_{static_cast(total_size)}, + ok_{offset == offset_ && size == size_ && total_size == total_size_ && + ok} {} + OffsetBitBlockMsb0(const OffsetBitBlockMsb0 &other) = default; + OffsetBitBlockMsb0 &operator=(const OffsetBitBlockMsb0 &other) = default; + + template + OffsetStorageType GetOffsetStorage( + ::std::size_t offset, ::std::size_t size) const { + return OffsetStorageType{ + bit_block_, offset_ + offset, size, total_size_, + ok_ && offset + size <= size_}; + } + + // ReadUInt transforms the MSB-is-zero offset to physical offset. + // User offset `o` with size `s` in total bits `N` maps to physical offset + // `N - o - s`. + ValueType ReadUInt() const { + EMBOSS_CHECK_GE(total_size_, offset_ + size_); + EMBOSS_CHECK(Ok()); + const ::std::size_t physical_offset = total_size_ - offset_ - size_; + return MaskToNBits(bit_block_.ReadUInt(), physical_offset + size_) >> + physical_offset; + } + ValueType UncheckedReadUInt() const { + const ::std::size_t physical_offset = total_size_ - offset_ - size_; + return MaskToNBits(bit_block_.UncheckedReadUInt(), + physical_offset + size_) >> + physical_offset; + } + + // WriteUInt transforms the MSB-is-zero offset to physical offset. + void WriteUInt(ValueType value) const { + EMBOSS_CHECK_EQ(value, MaskToNBits(value, size_)); + EMBOSS_CHECK(Ok()); + bit_block_.WriteUInt(MaskInValue(bit_block_.ReadUInt(), value)); + } + void UncheckedWriteUInt(ValueType value) const { + bit_block_.UncheckedWriteUInt( + MaskInValue(bit_block_.UncheckedReadUInt(), value)); + } + + ::std::size_t SizeInBits() const { return size_; } + bool Ok() const { return ok_; } + + private: + ValueType MaskInValue(ValueType original_value, ValueType new_value) const { + const ::std::size_t physical_offset = total_size_ - offset_ - size_; + ValueType original_mask = static_cast(~( + MaskToNBits(static_cast(~ValueType{0}), size_) + << physical_offset)); + return static_cast((original_value & original_mask) | + (new_value << physical_offset)); + } + + const UnderlyingBitBlockType bit_block_; + const ::std::uint8_t offset_; + const ::std::uint8_t size_; + const ::std::uint8_t total_size_; + const ::std::uint8_t ok_; +}; + // BitBlock is a view of a short, fixed-size sequence of bits somewhere in // memory. Big- and little-endian values are handled by BufferType, which is // typically BigEndianByteOrderer> or @@ -1048,6 +1139,75 @@ class BitBlock final { BufferType buffer_; }; +// BitBlockMsb0 is a view of a short, fixed-size sequence of bits somewhere in +// memory, with MSB-is-zero bit numbering. In MSB-is-zero numbering, bit 0 is +// the most significant bit of the first byte, rather than the least +// significant bit of the last byte. +// +// This is similar to BitBlock but uses OffsetBitBlockMsb0 for subfield access. +template +class BitBlockMsb0 final { + static_assert(kBufferSizeInBits % 8 == 0, + "BitBlockMsb0 can only operate on byte buffers."); + static_assert(kBufferSizeInBits <= 64, + "BitBlockMsb0 can only operate on small buffers."); + + public: + using ValueType = typename LeastWidthInteger::Unsigned; + // As with OffsetBitBlockMsb0::OffsetStorageType, the kNewAlignment and + // kNewOffset values are not used, but they must be template parameters so + // that generated code can work with both BitBlockMsb0 and ContiguousBuffer. + template + using OffsetStorageType = + OffsetBitBlockMsb0>; + + explicit BitBlockMsb0() : buffer_() {} + explicit BitBlockMsb0(BufferType buffer) : buffer_{buffer} {} + explicit BitBlockMsb0(typename BufferType::BufferType buffer) + : buffer_{buffer} {} + BitBlockMsb0(const BitBlockMsb0 &) = default; + BitBlockMsb0(BitBlockMsb0 &&) = default; + BitBlockMsb0 &operator=(const BitBlockMsb0 &) = default; + BitBlockMsb0 &operator=(BitBlockMsb0 &&) = default; + ~BitBlockMsb0() = default; + + static constexpr ::std::size_t Bits() { return kBufferSizeInBits; } + + template + OffsetStorageType GetOffsetStorage( + ::std::size_t offset, ::std::size_t size) const { + return OffsetStorageType{ + *this, offset, size, kBufferSizeInBits, + Ok() && offset + size <= kBufferSizeInBits}; + } + + // BitBlockMsb0 clients must read or write the entire BitBlockMsb0 value as an + // unsigned integer. OffsetBitBlockMsb0 can be used to extract a portion of + // the value via shift and mask, and individual view types such as IntView or + // BcdView are expected to convert ValueType to/from their desired types. + ValueType ReadUInt() const { + return buffer_.template ReadUInt(); + } + ValueType UncheckedReadUInt() const { + return buffer_.template UncheckedReadUInt(); + } + void WriteUInt(ValueType value) const { + EMBOSS_CHECK_EQ(value, MaskToNBits(value, kBufferSizeInBits)); + buffer_.template WriteUInt(value); + } + void UncheckedWriteUInt(ValueType value) const { + buffer_.template UncheckedWriteUInt(value); + } + + ::std::size_t SizeInBits() const { return kBufferSizeInBits; } + bool Ok() const { + return buffer_.Ok() && buffer_.SizeInBytes() * 8 == kBufferSizeInBits; + } + + private: + BufferType buffer_; +}; + } // namespace support } // namespace emboss diff --git a/testdata/bit_numbering.emb b/testdata/bit_numbering.emb new file mode 100644 index 0000000..ee5a866 --- /dev/null +++ b/testdata/bit_numbering.emb @@ -0,0 +1,48 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +-- Test `.emb` for the `bit_numbering` attribute on `bits` constructs. + +[$default byte_order: "BigEndian"] +[(cpp) namespace: "emboss::test"] + + +# Default LSB-is-zero numbering (current Emboss behavior) +bits Lsb0Bits: + 7 [+1] Flag high_bit + 0 [+4] UInt low_nibble + 4 [+4] UInt high_nibble + + +# MSB-is-zero numbering (new feature) +bits Msb0Bits: + [bit_numbering: "Msb0"] + + 0 [+1] Flag high_bit + 1 [+4] UInt high_nibble + 5 [+3] UInt low_bits + + +# Explicit LSB-is-zero numbering +bits ExplicitLsb0Bits: + [bit_numbering: "Lsb0"] + + 7 [+1] Flag high_bit + 0 [+4] UInt low_nibble + + +struct BitNumberingStruct: + 0 [+1] Lsb0Bits lsb0_bits + 1 [+1] Msb0Bits msb0_bits + 2 [+1] ExplicitLsb0Bits explicit_lsb0_bits From 7f5a9aef77f9465032bdaf8310df2d2b38720002 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 6 Dec 2025 03:47:56 +0000 Subject: [PATCH 3/3] Add tests and documentation for bit_numbering attribute Co-authored-by: AaronWebster <3766083+AaronWebster@users.noreply.github.com> --- compiler/front_end/attribute_checker_test.py | 69 ++++++++++++++++++++ doc/language-reference.md | 67 +++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/compiler/front_end/attribute_checker_test.py b/compiler/front_end/attribute_checker_test.py index fdab7bd..4369913 100644 --- a/compiler/front_end/attribute_checker_test.py +++ b/compiler/front_end/attribute_checker_test.py @@ -1010,6 +1010,75 @@ def test_rejects_unknown_enum_value_attribute(self): error.filter_errors(attribute_checker.normalize_and_verify(ir)), ) + def test_accepts_msb0_bit_numbering_on_bits(self): + ir = _make_ir_from_emb( + '[$default byte_order: "BigEndian"]\n' + "bits Foo:\n" + ' [bit_numbering: "Msb0"]\n' + " 0 [+1] Flag high_bit\n" + ) + self.assertEqual([], attribute_checker.normalize_and_verify(ir)) + + def test_accepts_lsb0_bit_numbering_on_bits(self): + ir = _make_ir_from_emb( + '[$default byte_order: "BigEndian"]\n' + "bits Foo:\n" + ' [bit_numbering: "Lsb0"]\n' + " 7 [+1] Flag high_bit\n" + ) + self.assertEqual([], attribute_checker.normalize_and_verify(ir)) + + def test_accepts_default_bit_numbering_on_module(self): + ir = _make_ir_from_emb( + '[$default bit_numbering: "Msb0"]\n' + '[$default byte_order: "BigEndian"]\n' + "bits Foo:\n" + " 0 [+1] Flag high_bit\n" + ) + self.assertEqual([], attribute_checker.normalize_and_verify(ir)) + + def test_rejects_unknown_bit_numbering(self): + ir = _make_ir_from_emb( + '[$default byte_order: "BigEndian"]\n' + "bits Foo:\n" + ' [bit_numbering: "BadValue"]\n' + " 0 [+1] Flag high_bit\n" + ) + bit_numbering_attr = ir.module[0].type[0].attribute[0] + self.assertEqual( + [ + [ + error.error( + "m.emb", + bit_numbering_attr.value.source_location, + "Attribute 'bit_numbering' must be 'Lsb0' or 'Msb0'.", + ) + ] + ], + attribute_checker.normalize_and_verify(ir), + ) + + def test_rejects_bit_numbering_on_struct(self): + ir = _make_ir_from_emb( + '[$default byte_order: "BigEndian"]\n' + "struct Foo:\n" + ' [bit_numbering: "Msb0"]\n' + " 0 [+1] UInt bar\n" + ) + bit_numbering_attr = ir.module[0].type[0].attribute[0] + self.assertEqual( + [ + [ + error.error( + "m.emb", + bit_numbering_attr.name.source_location, + "Unknown attribute 'bit_numbering' on struct 'Foo'.", + ) + ] + ], + attribute_checker.normalize_and_verify(ir), + ) + if __name__ == "__main__": unittest.main() diff --git a/doc/language-reference.md b/doc/language-reference.md index db8682e..727ef8b 100644 --- a/doc/language-reference.md +++ b/doc/language-reference.md @@ -171,6 +171,73 @@ byte-order-dependent field that is not exactly 8 bits has the `"Null"` byte order. +### `bit_numbering` + +The `bit_numbering` attribute is used to specify how bit positions are numbered +within a `bits` type. By default, Emboss uses LSB-is-zero numbering, where bit +0 is the lowest-order (least significant) bit. However, some big-endian +protocols (particularly older ones like those defined in RFC 791 for IP and RFC +793 for TCP) number bits with bit 0 as the highest-order (most significant) bit. + +`bit_numbering` takes a string value, which must be either `"Lsb0"` (default) or +`"Msb0"`: + +``` +[$default byte_order: "BigEndian"] + +bits Lsb0Example: + # Default LSB-is-zero numbering + 7 [+1] Flag high_bit # Bit 7 is the MSB + 0 [+4] UInt low_nibble # Bits 0-3 are the low nibble + 4 [+4] UInt high_nibble # Bits 4-7 are the high nibble + +bits Msb0Example: + [bit_numbering: "Msb0"] + # MSB-is-zero numbering (common in older RFCs) + 0 [+1] Flag high_bit # Bit 0 is the MSB + 1 [+4] UInt high_nibble # Bits 1-4 are the high nibble + 5 [+3] UInt low_bits # Bits 5-7 are the low bits + +struct Message: + 0 [+1] Lsb0Example lsb0_field + [byte_order: "BigEndian"] + 1 [+1] Msb0Example msb0_field + [byte_order: "BigEndian"] +``` + +For LSB-is-zero numbering (the default), bit 0 is the least significant bit: + +``` + byte 0 (for an 8-bit bits type) ++--------+ +|76543210| <- bit numbers ++--------+ + ^ ^ +MSB LSB +``` + +For MSB-is-zero numbering, bit 0 is the most significant bit: + +``` + byte 0 (for an 8-bit bits type) ++--------+ +|01234567| <- bit numbers ++--------+ + ^ ^ +MSB LSB +``` + +A `$default` bit numbering may be set on a module. The `bit_numbering` +attribute can be placed on a `bits` definition to override the default. + +Note that `bit_numbering` only affects how bit positions are interpreted in the +`.emb` file; it does not change the physical layout of bits in memory. The +same byte value `0x80` would be read as: +- `high_bit = true` in both `Lsb0Example` and `Msb0Example` +- `low_nibble = 0` in `Lsb0Example`, `low_bits = 0` in `Msb0Example` +- `high_nibble = 8` in `Lsb0Example`, `high_nibble = 0` in `Msb0Example` + + ### `requires` The `requires` attribute may be placed on an atomic field (e.g., type `UInt`,