Skip to content

Commit

Permalink
pw_checksum: Add additional CRC32 variants
Browse files Browse the repository at this point in the history
CRC32 implementations make size/performance tradeoffs.  The current
eight bits per iteration, 256 entry table implementations balances
heavily towards increasing size for better performance.  This patch
adds two additional implmentations that make different tradeoffs.

Change-Id: Ib693d3aed7a511e892068b5d9ea94adaa633f8bf
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/106650
Commit-Queue: Auto-Submit <[email protected]>
Pigweed-Auto-Submit: Erik Gilling <[email protected]>
Reviewed-by: Wyatt Hepler <[email protected]>
  • Loading branch information
konkers authored and CQ Bot Account committed Aug 19, 2022
1 parent 8d21bcb commit 620bb53
Show file tree
Hide file tree
Showing 11 changed files with 344 additions and 74 deletions.
1 change: 1 addition & 0 deletions pw_checksum/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pw_cc_library(
hdrs = [
"public/pw_checksum/crc16_ccitt.h",
"public/pw_checksum/crc32.h",
"public/pw_checksum/internal/config.h",
],
includes = ["public"],
deps = [
Expand Down
35 changes: 31 additions & 4 deletions pw_checksum/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,32 @@
import("//build_overrides/pigweed.gni")

import("$dir_pw_bloat/bloat.gni")
import("$dir_pw_build/module_config.gni")
import("$dir_pw_build/target_types.gni")
import("$dir_pw_docgen/docs.gni")
import("$dir_pw_unit_test/test.gni")

config("default_config") {
declare_args() {
# The build target that overrides the default configuration options for this
# module. This should point to a source set that provides defines through a
# public config (which may -include a file or add defines directly).
pw_checksum_CONFIG = pw_build_DEFAULT_MODULE_CONFIG
}

config("public_include_path") {
include_dirs = [ "public" ]
}

pw_source_set("config") {
sources = [ "public/pw_checksum/internal/config.h" ]
public_configs = [ ":public_include_path" ]
public_deps = [ pw_checksum_CONFIG ]
visibility = [ ":*" ] # Only allow this module to depend on ":config"
friend = [ ":*" ] # Allow this module to access the config.h header.
}

pw_source_set("pw_checksum") {
public_configs = [ ":default_config" ]
public_configs = [ ":public_include_path" ]
public = [
"public/pw_checksum/crc16_ccitt.h",
"public/pw_checksum/crc32.h",
Expand All @@ -34,6 +50,7 @@ pw_source_set("pw_checksum") {
"crc32.cc",
]
public_deps = [
":config",
dir_pw_bytes,
dir_pw_span,
]
Expand Down Expand Up @@ -78,9 +95,19 @@ pw_size_diff("size_report") {
label = "CRC16 with 256-entry table"
},
{
target = "size_report:crc32_checksum"
target = "size_report:crc32_8bit_checksum"
base = "size_report:noop_checksum"
label = "CRC32: 8 bits per iteration, 256-entry table"
},
{
target = "size_report:crc32_4bit_checksum"
base = "size_report:noop_checksum"
label = "CRC32: 4 bits per iteration, 16-entry table"
},
{
target = "size_report:crc32_1bit_checksum"
base = "size_report:noop_checksum"
label = "CRC32 with 256-entry table"
label = "CRC32: 1 bit per iteration, no table"
},
{
target = "size_report:fletcher16_checksum"
Expand Down
17 changes: 17 additions & 0 deletions pw_checksum/config.gni
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2022 The Pigweed Authors
#
# 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.

import("//build_overrides/pigweed.gni")

import("$dir_pw_build/module_config.gni")
102 changes: 53 additions & 49 deletions pw_checksum/crc32.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,60 +17,64 @@
namespace pw::checksum {
namespace {

constexpr uint32_t kCrc32Table[] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d};
template <std::size_t kBits, uint32_t kPolynomial>
constexpr uint32_t Crc32ProcessDataChunk(uint32_t value) {
for (uint32_t j = 0; j < kBits; j++)
value = (value >> 1) ^ (-(int32_t)(value & 1) & kPolynomial);

return value;
}

template <std::size_t kBits, uint32_t kPolynomial>
constexpr std::array<uint32_t, (1 << kBits)> GenerateCrc32Table() {
std::array<uint32_t, (1 << kBits)> table{};
for (uint32_t i = 0; i < (1 << kBits); i++) {
table[i] = Crc32ProcessDataChunk<kBits, kPolynomial>(i);
}
return table;
}

constexpr uint32_t kCrc32Polynomial = 0xEDB88320;

} // namespace

extern "C" uint32_t _pw_checksum_InternalCrc32(const void* data,
size_t size_bytes,
uint32_t state) {
const uint8_t* array = static_cast<const uint8_t*>(data);
extern "C" uint32_t _pw_checksum_InternalCrc32EightBit(const void* data,
size_t size_bytes,
uint32_t state) {
static constexpr std::array<uint32_t, 256> kCrc32Table =
GenerateCrc32Table<8, kCrc32Polynomial>();
const uint8_t* data_bytes = static_cast<const uint8_t*>(data);

for (size_t i = 0; i < size_bytes; ++i) {
state = kCrc32Table[(state ^ data_bytes[i]) & 0xFFu] ^ (state >> 8);
}

return state;
}

extern "C" uint32_t _pw_checksum_InternalCrc32FourBit(const void* data,
size_t size_bytes,
uint32_t state) {
static constexpr std::array<uint32_t, 16> kCrc32Table =
GenerateCrc32Table<4, kCrc32Polynomial>();
const uint8_t* data_bytes = static_cast<const uint8_t*>(data);

for (size_t i = 0; i < size_bytes; ++i) {
state ^= data_bytes[i];
state = kCrc32Table[state & 0x0f] ^ (state >> 4);
state = kCrc32Table[state & 0x0f] ^ (state >> 4);
}

return state;
}

extern "C" uint32_t _pw_checksum_InternalCrc32OneBit(const void* data,
size_t size_bytes,
uint32_t state) {
const uint8_t* data_bytes = static_cast<const uint8_t*>(data);

for (size_t i = 0; i < size_bytes; ++i) {
state = kCrc32Table[(state ^ array[i]) & 0xFFu] ^ (state >> 8);
state = Crc32ProcessDataChunk<8, kCrc32Polynomial>(state ^ data_bytes[i]);
}

return state;
Expand Down
60 changes: 52 additions & 8 deletions pw_checksum/crc32_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <string_view>

#include "gtest/gtest.h"
#include "public/pw_checksum/crc32.h"
#include "pw_bytes/array.h"
#include "pw_span/span.h"

Expand All @@ -40,43 +41,86 @@ constexpr uint32_t kStringCrc = 0x9EC87F88;

TEST(Crc32, Empty) {
EXPECT_EQ(Crc32::Calculate(span<std::byte>()), PW_CHECKSUM_EMPTY_CRC32);
EXPECT_EQ(Crc32EightBit::Calculate(span<std::byte>()),
PW_CHECKSUM_EMPTY_CRC32);
EXPECT_EQ(Crc32FourBit::Calculate(span<std::byte>()),
PW_CHECKSUM_EMPTY_CRC32);
EXPECT_EQ(Crc32OneBit::Calculate(span<std::byte>()), PW_CHECKSUM_EMPTY_CRC32);
}

TEST(Crc32, Buffer) {
EXPECT_EQ(Crc32::Calculate(as_bytes(span(kBytes))), kBufferCrc);
EXPECT_EQ(Crc32EightBit::Calculate(as_bytes(span(kBytes))), kBufferCrc);
EXPECT_EQ(Crc32FourBit::Calculate(as_bytes(span(kBytes))), kBufferCrc);
EXPECT_EQ(Crc32OneBit::Calculate(as_bytes(span(kBytes))), kBufferCrc);
}

TEST(Crc32, String) {
EXPECT_EQ(Crc32::Calculate(as_bytes(span(kString))), kStringCrc);
EXPECT_EQ(Crc32EightBit::Calculate(as_bytes(span(kString))), kStringCrc);
EXPECT_EQ(Crc32FourBit::Calculate(as_bytes(span(kString))), kStringCrc);
EXPECT_EQ(Crc32OneBit::Calculate(as_bytes(span(kString))), kStringCrc);
}

TEST(Crc32Class, ByteByByte) {
Crc32 crc;
template <typename CrcVariant>
void TestByByte() {
CrcVariant crc;
for (std::byte b : kBytes) {
crc.Update(b);
}
EXPECT_EQ(crc.value(), kBufferCrc);
}

TEST(Crc32Class, Buffer) {
Crc32 crc32;
TEST(Crc32Class, ByteByByte) {
TestByByte<Crc32>();
TestByByte<Crc32EightBit>();
TestByByte<Crc32FourBit>();
TestByByte<Crc32OneBit>();
}

template <typename CrcVariant>
void TestBuffer() {
CrcVariant crc32;
crc32.Update(as_bytes(span(kBytes)));
EXPECT_EQ(crc32.value(), kBufferCrc);
}

TEST(Crc32Class, BufferAppend) {
Crc32 crc32;
TEST(Crc32Class, Buffer) {
TestBuffer<Crc32>();
TestBuffer<Crc32EightBit>();
TestBuffer<Crc32FourBit>();
TestBuffer<Crc32OneBit>();
}

template <typename CrcVariant>
void TestBufferAppend() {
CrcVariant crc32;
crc32.Update(kBytesPart0);
crc32.Update(kBytesPart1);
EXPECT_EQ(crc32.value(), kBufferCrc);
}

TEST(Crc32Class, String) {
Crc32 crc32;
TEST(Crc32Class, BufferAppend) {
TestBufferAppend<Crc32>();
TestBufferAppend<Crc32EightBit>();
TestBufferAppend<Crc32FourBit>();
TestBufferAppend<Crc32OneBit>();
}

template <typename CrcVariant>
void TestString() {
CrcVariant crc32;
crc32.Update(as_bytes(span(kString)));
EXPECT_EQ(crc32.value(), kStringCrc);
}

TEST(Crc32Class, String) {
TestString<Crc32>();
TestString<Crc32EightBit>();
TestString<Crc32FourBit>();
TestString<Crc32OneBit>();
}

extern "C" uint32_t CallChecksumCrc32(const void* data, size_t size_bytes);
extern "C" uint32_t CallChecksumCrc32Append(const void* data,
size_t size_bytes,
Expand Down
64 changes: 64 additions & 0 deletions pw_checksum/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,52 @@ pw_checksum/crc32.h
uint32_t crc = Crc32(my_data);
crc = Crc32(more_data, crc);
.. _CRC32 Implementations:

Implementations
---------------
Pigweed provides 3 different CRC32 implementations with different size and
runtime tradeoffs. The below table summarizes the variants. For more detailed
size information see the :ref:`Size report` below. Instructions counts were
calculated by hand by analyzing the
`assembly <https://godbolt.org/z/nY1bbb5Pb>`_.


.. list-table::
:header-rows: 1

* - Variant
- Relative size (see Size Report below)
- Speed
- Lookup table size (entries)
- Instructions/byte (M33/-Os)
* - 8 bits per iteration (default)
- large
- fastest
- 256
- 8
* - 4 bits per iteration
- small
- fast
- 16
- 13
* - 1 bit per iteration
- smallest
- slow
- 0
- 43

The default implementation provided by the APIs above can be selected through
:ref:`Module Configuration Options`. Additionally ``pw_checksum`` provides
variants of the C++ API to explicitly use each of the implementations. These
classes provide the same API as ``Crc32``:

* ``Crc32EightBit``
* ``Crc32FourBit``
* ``Crc32OneBit``

.. _Size report:

Size report
===========
The CRC module currently optimizes for speed instead of binary size, by using
Expand All @@ -71,6 +117,24 @@ Dependencies
============
* ``pw_span``

.. _Module Configuration Options:

Module Configuration Options
============================
The following configurations can be adjusted via compile-time configuration of
this module, see the
:ref:`module documentation <module-structure-compile-time-configuration>` for
more details.

.. c:macro:: PW_CHECKSUM_CRC32_DEFAULT_IMPL
Selects which of the :ref:`CRC32 Implementations` the default CRC32 APIs
use. Set to one of the following values:

* ``PW_CHECKSUM_CRC32_8BITS``
* ``PW_CHECKSUM_CRC32_4BITS``
* ``PW_CHECKSUM_CRC32_1BITS``

Zephyr
======
To enable ``pw_checksum`` for Zephyr add ``CONFIG_PIGWEED_CHECKSUM=y`` to the
Expand Down
Loading

0 comments on commit 620bb53

Please sign in to comment.