diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..55faf5ad6503ff0e054698ef0765085f8da24f4a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+build/
+__pycache__/
+*.swp
diff --git a/grc/elen90089_header_format_cdc.block.yml b/grc/elen90089_header_format_cdc.block.yml
index 0bee652f6d423161eb42cba630cbd3bde1c584c2..df139f0f7033b9cfbad8072d24582eb06e3f4cb7 100644
--- a/grc/elen90089_header_format_cdc.block.yml
+++ b/grc/elen90089_header_format_cdc.block.yml
@@ -5,13 +5,14 @@ flags: [ show_id ]
 
 templates:
   imports: import elen90089
-  make: elen90089.header_format_cdc(${access_code}, ${threshold}, ${bps})
+  var_make: |-
+    self.${id} = ${id} = elen90089.header_format_cdc(${access_code}, ${threshold}, ${bps})
 
 parameters:
 - id: access_code
   label: Access Code
   dtype: string
-  default: '11001010'
+  default: digital.packet_utils.default_access_code
 - id: threshold
   label: Threshold
   dtype: int
@@ -20,5 +21,7 @@ parameters:
   label: Bits per symbol
   dtype: int
   default: 1
-  
+
+value: ${ elen90089.header_format_cdc(access_code, threshold, bps) }
+
 file_format: 1
diff --git a/grc/elen90089_symbol_mapper_c.block.yml b/grc/elen90089_symbol_mapper_c.block.yml
index e10c2d5b35612308548b540bc209fec5602b621e..1704c1d64863dce0b94ae640ad50427a2608626f 100644
--- a/grc/elen90089_symbol_mapper_c.block.yml
+++ b/grc/elen90089_symbol_mapper_c.block.yml
@@ -4,7 +4,7 @@ category: '[elen90089]'
 
 templates:
   imports: import elen90089
-  make: elen90089.symbol_mapper_c(elen90089.symbol_mapper_c.constel.${constel_header}, {tsb_tag_key})
+  make: elen90089.symbol_mapper_c(elen90089.symbol_mapper_c.constel.${constel_header}, ${tsb_tag_key})
 
 parameters:
 - id: constel_header
diff --git a/include/elen90089/header_format_cdc.h b/include/elen90089/header_format_cdc.h
index 42d4e3144c4ee83713f153005ece883790ea965c..21df1b0fc5838943557b93a049a6e43c86dfbf9d 100644
--- a/include/elen90089/header_format_cdc.h
+++ b/include/elen90089/header_format_cdc.h
@@ -11,26 +11,28 @@
 #include <elen90089/api.h>
 #include <gnuradio/digital/header_format_default.h>
 #include <pmt/pmt.h>
+#include <boost/crc.hpp>
 
 namespace gr {
 namespace elen90089 {
 
 /*!
- * \brief Header formatter for CDC packets that adds payload bits/symbol
- * format and a packet number counter
+ * \brief Header formatter for CDC packets that adds counter, packet length,
+ * payload bits/symbol, and 8-bit CRC.
  *
  * \details
  *
- * Same as digital::header_format_counter but updates \p bps field of header
+ * Child class of header_format_default. The \p bps field of header is updated
  * based on value found in packet metadata dictionary. Packet structure is:
  *
  *   | access_code | hdr | payload |
  *
  * Where the access_code is <= 64 bits and hdr is:
  *
- *   |  0 -- 15 | 16 -- 31 |   
- *   | pkt len  | pkt len  |
- *   | bits/sym | counter  |
+ *   |  0 --  7 |  8 -- 15 |
+ *   |       counter       |
+ *   |       pkt len       |
+ *   | bits/sym |   crc8   |
  *
  * Access code and header are formatted for network byte order (big endian)
  *
@@ -51,7 +53,7 @@ public:
      * Creates a header from the access code and packet length to build an
      * output packet in the form:
      *
-     *   | access code | pkt len | pkt len | bps | counter |
+     *   | access code | counter | pkt len | bps | crc8 |
      *
      * \param nbytes        The length (in bytes) of the \p input payload
      * \param input         An array of unsigned chars of the packet payload
@@ -85,7 +87,8 @@ public:
                      int bps);
 
 protected:
-    uint16_t d_counter; //!< keeps track of the number of packets transmitted
+    uint16_t d_counter;
+    boost::crc_optimal<8, 0x07, 0xFF, 0x00, false, false> d_crc_impl;
 
     //! Verify that the header is valid
     bool header_ok() override;
@@ -95,9 +98,7 @@ protected:
      *
      * Extracts the header of the form:
      *
-     * \verbatim
-     *   | access code | pkt len | pkt len | bps | counter | payload |
-     * \endverbatim
+     *  | access code | counter | pkt len | bps | crc8 | payload |
      */
     int header_payload() override;
 };
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index e6470911aa94bebf40d3c57e3d4d1cb2aca035f7..505cbddc305651cced6842d7a1cbb6d1eb7b500a 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -68,7 +68,10 @@ list(APPEND test_elen90089_sources
     qa_header_format_cdc.cc
 )
 # Anything we need to link to for the unit tests go here
-list(APPEND GR_TEST_TARGET_DEPS gnuradio-elen90089)
+list(APPEND GR_TEST_TARGET_DEPS
+    gnuradio-elen90089
+    gnuradio-blocks
+)
 
 if(NOT test_elen90089_sources)
     MESSAGE(STATUS "No C++ unit tests... skipping")
diff --git a/lib/header_format_cdc.cc b/lib/header_format_cdc.cc
index 568f63f14cc1809cc75f018c2a8dc57a22fbb4bb..efcac9486d04fce0be3813de7827f441df1d463e 100644
--- a/lib/header_format_cdc.cc
+++ b/lib/header_format_cdc.cc
@@ -13,7 +13,9 @@ namespace gr {
 namespace elen90089 {
 
 header_format_cdc::sptr
-header_format_cdc::make(const std::string& access_code, int threshold, int bps)
+header_format_cdc::make(const std::string& access_code,
+                        int threshold,
+                        int bps)
 {
     return header_format_cdc::sptr(
         new header_format_cdc(access_code, threshold, bps));
@@ -22,9 +24,10 @@ header_format_cdc::make(const std::string& access_code, int threshold, int bps)
 header_format_cdc::header_format_cdc(const std::string& access_code,
                                      int threshold,
                                      int bps)
-    : header_format_default(access_code, threshold, bps)
+    : header_format_default(access_code, threshold, bps),
+      d_counter(0)
 {
-    d_counter = 0;
+    assert(bps > 0 && bps < 5);
 }
 
 header_format_cdc::~header_format_cdc() {}
@@ -35,59 +38,77 @@ bool header_format_cdc::format(int nbytes_in,
                                pmt::pmt_t& info)
 
 {
-    // update bps from metadata if present
-    int bps = d_bps;
-    if (pmt::is_dict(info) && pmt::dict_has_key(info, pmt::mp("bps"))) {
-        pmt::pmt_t val = pmt::dict_ref(info, pmt::mp("bps"), pmt::PMT_NIL);
-        bps = pmt::to_long(val);
-    }
-    assert(bps > 0 && bps < 5);
-
     // Creating the output pmt copies data; free our own here when done.
     volk::vector<uint8_t> bytes_out(header_nbytes());
 
+    // update bps from metadata dict if present
+    uint8_t bps = (uint8_t)pmt::to_long(pmt::dict_ref(info,
+                                                      pmt::mp("bps"),
+                                                      pmt::from_long(d_bps)));
+    assert(bps > 0 && bps < 5);
+
+    // calculate 8-bit CRC
+    d_crc_impl.reset();
+    d_crc_impl.process_bytes((void const*)&d_counter, 2);
+    d_crc_impl.process_bytes((void const*)&nbytes_in, 2);
+    d_crc_impl.process_bytes((void const*)&bps,       1);
+    uint8_t crc = d_crc_impl();
+
+    // create header
     digital::header_buffer header(bytes_out.data());
     header.add_field64(d_access_code, d_access_code_len);
-    header.add_field16((uint16_t)(nbytes_in));
-    header.add_field16((uint16_t)(nbytes_in));
-    header.add_field16((uint16_t)(bps));
-    header.add_field16((uint16_t)(d_counter));
+    header.add_field16(d_counter);
+    header.add_field16(nbytes_in);
+    header.add_field8 (bps);
+    header.add_field8 (crc);
+
+    d_counter++;
 
     // Package output data into a PMT vector
     output = pmt::init_u8vector(header_nbytes(), bytes_out.data());
 
-    d_counter++;
-
     return true;
 }
 
 size_t header_format_cdc::header_nbits() const
 {
-    return d_access_code_len + 8 * 4 * sizeof(uint16_t);
+    return d_access_code_len + 8 * 3 * sizeof(uint16_t);
 }
 
 bool header_format_cdc::header_ok()
 {
-    // confirm that two copies of header info are identical
-    uint16_t len0 = d_hdr_reg.extract_field16(0);
-    uint16_t len1 = d_hdr_reg.extract_field16(16);
-    return (len0 ^ len1) == 0;
+    uint16_t counter  = d_hdr_reg.extract_field16(0);
+    uint16_t pktlen   = d_hdr_reg.extract_field16(16);
+    uint8_t  bps      = d_hdr_reg.extract_field8(32);
+    uint8_t  crc_rcvd = d_hdr_reg.extract_field8(40);
+
+    // check crc8
+    d_crc_impl.reset();
+    d_crc_impl.process_bytes((void const*)&counter, 2);
+    d_crc_impl.process_bytes((void const*)&pktlen,2);
+    d_crc_impl.process_bytes((void const*)&bps, 1);
+    uint8_t crc_clcd = d_crc_impl();
+
+    return (crc_rcvd == crc_clcd);
 }
 
 int header_format_cdc::header_payload()
 {
-    uint16_t len = d_hdr_reg.extract_field16(0);
-    uint16_t bps = d_hdr_reg.extract_field16(32);
-    uint16_t counter = d_hdr_reg.extract_field16(48);
+    uint16_t counter = d_hdr_reg.extract_field16(0);
+    uint16_t pktlen  = d_hdr_reg.extract_field16(16);
+    uint8_t bps      = d_hdr_reg.extract_field8(32);
 
     d_bps = bps;
 
     d_info = pmt::make_dict();
     d_info = pmt::dict_add(
-        d_info, pmt::intern("payload symbols"), pmt::from_long(8 * len / d_bps));
-    d_info = pmt::dict_add(d_info, pmt::intern("bps"), pmt::from_long(bps));
-    d_info = pmt::dict_add(d_info, pmt::intern("counter"), pmt::from_long(counter));
-    return static_cast<int>(len);
+        d_info, pmt::intern("counter"), pmt::from_long(counter));
+    d_info = pmt::dict_add(
+        d_info, pmt::intern("payload symbols"), pmt::from_long(8*pktlen/d_bps));
+    d_info = pmt::dict_add(
+        d_info, pmt::intern("bps"), pmt::from_long(bps));
+
+    return static_cast<int>(pktlen);
 }
 
 } /* namespace elen90089 */
diff --git a/lib/qa_header_format_cdc.cc b/lib/qa_header_format_cdc.cc
index 4896e160e9247aea970650db4b27cabf18585e16..423589624f63f9a3133baf26b66a30a4cd2facd1 100644
--- a/lib/qa_header_format_cdc.cc
+++ b/lib/qa_header_format_cdc.cc
@@ -2,57 +2,106 @@
 
 #include <elen90089/header_format_cdc.h>
 #include <gnuradio/attributes.h>
+#include <gnuradio/blocks/unpack_k_bits.h>
+#include <volk/volk_alloc.hh>
 #include <boost/test/unit_test.hpp>
 
 namespace gr {
 namespace elen90089 {
 
-BOOST_AUTO_TEST_CASE(test_header_format_cdc_bps)
+BOOST_AUTO_TEST_CASE(test_cdc_format)
 {
     static const int N = 4;
     std::string ac = "10110010"; // 0xB2
     std::vector<unsigned char> data{ 0x01, 0x02, 0x03, 0x04 };
     int threshold = 3;
-    int bps = 1;
     header_format_cdc::sptr hdr_format;
-    hdr_format = header_format_cdc::make(ac, threshold, bps);
+    hdr_format = header_format_cdc::make(ac, threshold, 1);
 
+    int bps = 2;
     pmt::pmt_t output;
     pmt::pmt_t info = pmt::make_dict();
-    info = pmt::dict_add(info, pmt::mp("bps"), pmt::from_long(2));
+    info = pmt::dict_add(info, pmt::mp("bps"), pmt::from_long(bps));
 
     bool ret = hdr_format->format(N, data.data(), output, info);
     size_t length = pmt::length(output);
 
     BOOST_REQUIRE(ret);
     BOOST_REQUIRE_EQUAL(length, hdr_format->header_nbytes());
-    BOOST_REQUIRE_EQUAL(8 * length, hdr_format->header_nbits());
+    BOOST_REQUIRE_EQUAL(8*length, hdr_format->header_nbits());
 
-    // Test access code
+    // Test access code - 0xb2
     unsigned char h0 = pmt::u8vector_ref(output, 0);
-    BOOST_REQUIRE_EQUAL(0xB2, (int)h0);
+    BOOST_REQUIRE_EQUAL(0xb2, (int)h0);
 
-    // Test packet length - 0x0404
+    // Test counter - 0x0000
     unsigned char h1 = pmt::u8vector_ref(output, 1);
     unsigned char h2 = pmt::u8vector_ref(output, 2);
+    BOOST_REQUIRE_EQUAL(0x00, (int)h1);
+    BOOST_REQUIRE_EQUAL(0x00, (int)h2);
+
+    // Test packet length - 0x0004
     unsigned char h3 = pmt::u8vector_ref(output, 3);
     unsigned char h4 = pmt::u8vector_ref(output, 4);
-    BOOST_REQUIRE_EQUAL(0x00, (int)h1);
-    BOOST_REQUIRE_EQUAL(0x04, (int)h2);
     BOOST_REQUIRE_EQUAL(0x00, (int)h3);
     BOOST_REQUIRE_EQUAL(0x04, (int)h4);
 
-    // Test bits/symbol - 0x0002
+    // Test bits/symbol - 0x02
     unsigned char h5 = pmt::u8vector_ref(output, 5);
+    BOOST_REQUIRE_EQUAL(0x02, (int)h5);
+
+    // Test CRC - 0x9c
     unsigned char h6 = pmt::u8vector_ref(output, 6);
-    BOOST_REQUIRE_EQUAL(0x00, (int)h5);
-    BOOST_REQUIRE_EQUAL(0x02, (int)h6);
+    BOOST_REQUIRE_EQUAL(0x9c, (int)h6);
+}
 
-    // Test counter - 0x0000
-    unsigned char h7 = pmt::u8vector_ref(output, 7);
-    unsigned char h8 = pmt::u8vector_ref(output, 8);
-    BOOST_REQUIRE_EQUAL(0x00, (int)h7);
-    BOOST_REQUIRE_EQUAL(0x00, (int)h8);
+BOOST_AUTO_TEST_CASE(test_cdc_parse)
+{
+    static const int nbytes = 11;
+    static const int nbits = 8 * nbytes;
+    volk::vector<unsigned char> bytes(nbytes);
+    volk::vector<unsigned char> bits(nbits);
+
+    int index = 0;
+    bytes[index +  0] = 0xb2; // access code
+    bytes[index +  1] = 0x00; // counter
+    bytes[index +  2] = 0x00;
+    bytes[index +  3] = 0x00; // pkt len
+    bytes[index +  4] = 0x04; 
+    bytes[index +  5] = 0x02; // bps
+    bytes[index +  6] = 0x9c; // crc8
+    bytes[index +  7] = 0x01; // payload
+    bytes[index +  8] = 0x02;
+    bytes[index +  9] = 0x03;
+    bytes[index + 10] = 0x04;
+
+    blocks::kernel::unpack_k_bits unpacker = blocks::kernel::unpack_k_bits(8);
+    unpacker.unpack(bits.data(), bytes.data(), nbytes);
+
+    uint8_t expected_bps = 2;
+    std::string ac = "10110010";
+    header_format_cdc::sptr hdr_format = header_format_cdc::make(ac, 0, 1);
+
+    int count = 0;
+    std::vector<pmt::pmt_t> info;
+    bool ret = hdr_format->parse(nbits, bits.data(), info, count);
+
+    BOOST_REQUIRE(ret);
+    BOOST_REQUIRE_EQUAL((size_t)1, info.size());
+
+    pmt::pmt_t dict = info[0];
+    int counter = pmt::to_long(
+        pmt::dict_ref(dict, pmt::intern("counter"), pmt::from_long(-1)));
+    int payload_syms = pmt::to_long(
+        pmt::dict_ref(dict, pmt::intern("payload symbols"), pmt::from_long(-1)));
+    int bps = pmt::to_long(
+        pmt::dict_ref(dict, pmt::intern("bps"), pmt::from_long(-1)));
+
+    int hdr_bits = (int)hdr_format->header_nbits();
+    int expected_bits = nbits - hdr_bits;
+    BOOST_REQUIRE_EQUAL(0, counter);
+    BOOST_REQUIRE_EQUAL(expected_bits, payload_syms * bps);
+    BOOST_REQUIRE_EQUAL(expected_bps, (uint8_t)bps);
 }
 
 } /* namespace elen90089 */
diff --git a/lib/symbol_mapper_c_impl.cc b/lib/symbol_mapper_c_impl.cc
index 02ee584903488a407d5cbfab190c326177a7dfec..2a562cd4f0db489bfc517814bfd8803215d133d2 100644
--- a/lib/symbol_mapper_c_impl.cc
+++ b/lib/symbol_mapper_c_impl.cc
@@ -90,7 +90,7 @@ int symbol_mapper_c_impl::calculate_output_stream_length(const gr_vector_int &ni
 
             d_metadata = pmt::car(msg);
             d_bps_payload = pmt::to_long(pmt::dict_ref(
-                d_metadata, pmt::mp("bps"), pmt::PMT_NIL));
+                d_metadata, pmt::mp("bps"), pmt::from_long(1)));
 
             d_header = pmt::cdr(msg);
             d_len_header = pmt::blob_length(d_header);
@@ -177,20 +177,25 @@ int symbol_mapper_c_impl::work(int noutput_items,
     bytes = static_cast<const uint8_t*>(pmt::uniform_vector_elements(d_payload, io));
     map_to_symbols(bytes, d_len_payload, out, d_bps_payload);
 
+    // add start of burst and end of burst tags
+    int offset = nitems_written(0);
+    add_item_tag(0, offset,
+                 pmt::mp("tx_sob"),
+                 pmt::PMT_T,
+                 alias_pmt());
+    add_item_tag(0, offset + nout - 1,
+                 pmt::mp("tx_eob"),
+                 pmt::PMT_T,
+                 alias_pmt());
+
     // convert header metadata to stream tags
     pmt::pmt_t items(pmt::dict_items(d_metadata));
     for (size_t i = 0; i < pmt::length(items); i++) {
         pmt::pmt_t item(pmt::nth(i, items));
-        pmt::pmt_t key(pmt::car(item));
-        pmt::pmt_t val(pmt::cdr(item));
-
-        // determine absolute sample offset of tag
-        int offset = nitems_written(0);
-        if (pmt::equal(key, pmt::string_to_symbol("tx_eob"))) {
-            offset += nout - 1;
-        }
-
-        add_item_tag(0, offset, key, val, alias_pmt());
+        add_item_tag(0, offset,
+                     pmt::car(item),
+                     pmt::cdr(item),
+                     alias_pmt());
     }
 
     d_len_header = 0;
diff --git a/python/bindings/header_format_cdc_python.cc b/python/bindings/header_format_cdc_python.cc
index 5ac77bc8d69570f12fc385cec9ac1e688d562f87..28af348675f208e9876fc9db1a326e847ea17864 100644
--- a/python/bindings/header_format_cdc_python.cc
+++ b/python/bindings/header_format_cdc_python.cc
@@ -14,7 +14,7 @@
 /* BINDTOOL_GEN_AUTOMATIC(0)                                                       */
 /* BINDTOOL_USE_PYGCCXML(0)                                                        */
 /* BINDTOOL_HEADER_FILE(header_format_cdc.h)                                        */
-/* BINDTOOL_HEADER_FILE_HASH(a83bb767baf65cce828e12e815183c6a)                     */
+/* BINDTOOL_HEADER_FILE_HASH(54efca7bed3ffdffd52613e3cdfea044)                     */
 /***********************************************************************************/
 
 #include <pybind11/complex.h>