diff --git a/examples/cdc_packet_tx.grc b/examples/cdc_packet_tx.grc
index 0e83b5edb54305cd88f568ef5b0727669fe81d6f..01a653bd08bc5944b7910958a9285989cb2d8f31 100644
--- a/examples/cdc_packet_tx.grc
+++ b/examples/cdc_packet_tx.grc
@@ -296,12 +296,16 @@ blocks:
     excess_bw: '0.35'
     maxoutbuf: '0'
     minoutbuf: '0'
+    show_burst: 'False'
+    show_header: 'False'
+    show_postcrc: 'False'
+    show_symbols: 'True'
     sps: '2'
   states:
     bus_sink: false
     bus_source: false
     bus_structure: null
-    coordinate: [560, 264.0]
+    coordinate: [560, 268.0]
     rotation: 0
     state: true
 - name: qtgui_const_sink_x_0
@@ -490,7 +494,7 @@ blocks:
     bus_sink: false
     bus_source: false
     bus_structure: null
-    coordinate: [968, 236.0]
+    coordinate: [1000, 236.0]
     rotation: 0
     state: true
 
diff --git a/examples/packet_phy_tx.grc b/examples/packet_phy_tx.grc
index 204c1dfceb6f1e008faa8a5ff01599961e6ce30e..7d6128ccbb351093c0398bc2f85f9863e2c3870b 100644
--- a/examples/packet_phy_tx.grc
+++ b/examples/packet_phy_tx.grc
@@ -11,7 +11,7 @@ options:
     gen_linking: dynamic
     generate_options: hb
     hier_block_src_path: '.:'
-    id: packet_phy_tx
+    id: packet_phy_tx_grc
     max_nouts: '0'
     output_language: python
     placement: (0,0)
@@ -22,7 +22,7 @@ options:
     run_options: prompt
     sizing_mode: fixed
     thread_safe_setters: ''
-    title: CDC Packet PHY TX
+    title: CDC Packet PHY TX (GRC)
     window_size: (1000,1000)
   states:
     bus_sink: false
@@ -296,7 +296,7 @@ blocks:
     bus_structure: null
     coordinate: [392, 124.0]
     rotation: 0
-    state: disabled
+    state: enabled
 - name: pad_sink_1_0
   id: pad_sink
   parameters:
@@ -314,7 +314,7 @@ blocks:
     bus_structure: null
     coordinate: [712, 116.0]
     rotation: 0
-    state: disabled
+    state: enabled
 - name: pad_sink_2
   id: pad_sink
   parameters:
@@ -350,7 +350,7 @@ blocks:
     bus_structure: null
     coordinate: [488, 388.0]
     rotation: 0
-    state: disabled
+    state: enabled
 - name: pad_source_0
   id: pad_source
   parameters:
diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt
index 12c160e81ec234c1dcf239618ff7dd0babb5eb30..7bd50626ef9ac1668c7b5c76b8132e59618f230f 100644
--- a/grc/CMakeLists.txt
+++ b/grc/CMakeLists.txt
@@ -11,6 +11,7 @@ install(FILES
     elen90089_moe_symbol_sync_cc.block.yml
     elen90089_symbol_mapper_c.block.yml
     elen90089_header_format_cdc.block.yml
+    elen90089_packet_phy_tx.block.yml
     elen90089_packet_mac_tx.block.yml
     DESTINATION share/gnuradio/grc/blocks
 )
diff --git a/grc/elen90089_packet_phy_tx.block.yml b/grc/elen90089_packet_phy_tx.block.yml
new file mode 100644
index 0000000000000000000000000000000000000000..984f735aaf658221515d0f1c410f30222a78f20d
--- /dev/null
+++ b/grc/elen90089_packet_phy_tx.block.yml
@@ -0,0 +1,85 @@
+id: packet_phy_tx
+label: CDC Packet PHY TX
+category: '[elen90089]'
+
+parameters:
+-   id: excess_bw
+    label: Excess BW
+    dtype: real
+    default: '0.35'
+    hide: none
+-   id: sps
+    label: Samps per symb
+    dtype: int
+    default: '2'
+    hide: none
+
+-   id: show_postcrc
+    category: Debug
+    label: Show Post CRC
+    dtype: enum
+    default: False
+    options: ['False', 'True'] 
+    hide: part
+-   id: show_header
+    category: Debug
+    label: Show Header
+    dtype: enum
+    default: False
+    options: ['False', 'True'] 
+    hide: part
+-   id: show_symbols
+    category: Debug
+    label: Show Symbols
+    dtype: enum
+    default: False
+    options: ['False', 'True'] 
+    hide: part
+-   id: show_burst
+    category: Debug
+    label: Show Burst
+    dtype: enum
+    default: False
+    options: ['False', 'True'] 
+    hide: part
+
+inputs:
+-   label: phy_sdu
+    domain: message
+    dtype: message
+
+outputs:
+-   label: iq
+    dtype: complex
+    vlen: 1
+-   label: postcrc
+    domain: message
+    dtype: message
+    optional: true
+    hide: ${ show_postcrc == 'False' }
+-   label: header
+    domain: message
+    dtype: message
+    optional: true
+    hide: ${ show_header == 'False' }
+-   label: symbols
+    dtype: complex
+    vlen: 1
+    optional: true
+    hide: ${ show_symbols == 'False' }
+-   label: burst
+    dtype: complex
+    vlen: 1
+    optional: true
+    hide: ${ show_burst == 'False' }
+
+templates:
+    imports: 'from elen90089 import packet_phy_tx'
+    make: "packet_phy_tx(excess_bw=${ excess_bw }, sps=${ sps },)"
+    callbacks:
+    - set_excess_bw(${ excess_bw })
+    - set_sps(${ sps })
+
+#documentation:
+
+file_format: 1
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index 3b89790c61fe55ef4bec04b1833ee653eb493572..eb5106e643b31c808e9bdee3704037c6cb311451 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -22,6 +22,7 @@ add_subdirectory(bindings)
 GR_PYTHON_INSTALL(
     FILES
     __init__.py
+    packet_phy_tx.py
     packet_mac_tx.py
     DESTINATION ${GR_PYTHON_DIR}/elen90089
 )
diff --git a/python/__init__.py b/python/__init__.py
index d997af05f5d3e93aeddd2bcdb23193d36d18e18f..f7264423aa043081b2bd81ef0177f446a19fca92 100644
--- a/python/__init__.py
+++ b/python/__init__.py
@@ -20,5 +20,6 @@ except ModuleNotFoundError:
     pass
 
 # import any pure python here
+from .packet_phy_tx import packet_phy_tx
 from .packet_mac_tx import packet_mac_tx
 #
diff --git a/python/packet_phy_tx.py b/python/packet_phy_tx.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce7df6e730826b0eb1abdd6cc085aecd68ff1582
--- /dev/null
+++ b/python/packet_phy_tx.py
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+
+#
+# SPDX-License-Identifier: GPL-3.0
+#
+# GNU Radio Python Flow Graph
+# Title: CDC Packet PHY TX
+# Copyright: University of Melbourne
+# GNU Radio version: 3.9.5.0
+
+from gnuradio import blocks
+from gnuradio import digital
+from gnuradio import fec
+from gnuradio import gr
+from gnuradio.filter import firdes
+from gnuradio.fft import window
+import sys
+import signal
+from gnuradio.filter import pfb
+import elen90089
+
+
+class packet_phy_tx(gr.hier_block2):
+    def __init__(self, excess_bw=0.35, sps=2):
+        gr.hier_block2.__init__(
+            self, "CDC Packet PHY TX",
+                gr.io_signature(0, 0, 0),
+                gr.io_signature.makev(3, 3, [gr.sizeof_gr_complex*1,
+                                             gr.sizeof_gr_complex*1,
+                                             gr.sizeof_gr_complex*1]),
+        )
+        self.message_port_register_hier_in("phy_sdu")
+        self.message_port_register_hier_out("postcrc")
+        self.message_port_register_hier_out("header")
+
+        ##################################################
+        # Parameters
+        ##################################################
+        self.excess_bw = excess_bw
+        self.sps = sps
+
+        ##################################################
+        # Variables
+        ##################################################
+        self.nfilts = nfilts = 32
+        self.rrc_taps = rrc_taps = firdes.root_raised_cosine(nfilts/2, nfilts,1.0, excess_bw, 5*sps*nfilts)
+        self.taps_per_filt = taps_per_filt = len(rrc_taps)/nfilts
+        self.header_format_cdc_0 = header_format_cdc_0 = elen90089.header_format_cdc(digital.packet_utils.default_access_code, 3, 1)
+        self.hdr_encoder = hdr_encoder = fec.dummy_encoder_make(1500)
+        self.filt_delay = filt_delay = int(1+(taps_per_filt-1)//2)
+
+        ##################################################
+        # Blocks
+        ##################################################
+        self.pfb_arb_resampler_xxx_0_0 = pfb.arb_resampler_ccf(
+            sps,
+            taps=rrc_taps,
+            flt_size=nfilts)
+        self.pfb_arb_resampler_xxx_0_0.declare_sample_delay(filt_delay)
+        self.fec_async_encoder_0_0 = fec.async_encoder(hdr_encoder, True, False, False, 1500)
+        self.elen90089_symbol_mapper_c_0 = elen90089.symbol_mapper_c(elen90089.symbol_mapper_c.constel.CONSTEL_BPSK, 'packet_len')
+        self.digital_protocol_formatter_async_0 = digital.protocol_formatter_async(header_format_cdc_0)
+        self.digital_crc32_async_bb_1 = digital.crc32_async_bb(False)
+        self.digital_burst_shaper_xx_0 = digital.burst_shaper_cc(firdes.window(window.WIN_HANN, 20, 0), 0, filt_delay, True, 'packet_len')
+        self.blocks_tagged_stream_multiply_length_0 = blocks.tagged_stream_multiply_length(gr.sizeof_gr_complex*1, 'packet_len', sps)
+
+
+        ##################################################
+        # Connections
+        ##################################################
+        self.msg_connect((self.digital_crc32_async_bb_1, 'out'), (self.digital_protocol_formatter_async_0, 'in'))
+        self.msg_connect((self.digital_crc32_async_bb_1, 'out'), (self, 'postcrc'))
+        self.msg_connect((self.digital_protocol_formatter_async_0, 'payload'), (self.elen90089_symbol_mapper_c_0, 'pld'))
+        self.msg_connect((self.digital_protocol_formatter_async_0, 'header'), (self.fec_async_encoder_0_0, 'in'))
+        self.msg_connect((self.digital_protocol_formatter_async_0, 'header'), (self, 'header'))
+        self.msg_connect((self.fec_async_encoder_0_0, 'out'), (self.elen90089_symbol_mapper_c_0, 'hdr'))
+        self.msg_connect((self, 'phy_sdu'), (self.digital_crc32_async_bb_1, 'in'))
+        self.connect((self.blocks_tagged_stream_multiply_length_0, 0), (self, 0))
+        self.connect((self.digital_burst_shaper_xx_0, 0), (self, 2))
+        self.connect((self.digital_burst_shaper_xx_0, 0), (self.pfb_arb_resampler_xxx_0_0, 0))
+        self.connect((self.elen90089_symbol_mapper_c_0, 0), (self.digital_burst_shaper_xx_0, 0))
+        self.connect((self.elen90089_symbol_mapper_c_0, 0), (self, 1))
+        self.connect((self.pfb_arb_resampler_xxx_0_0, 0), (self.blocks_tagged_stream_multiply_length_0, 0))
+
+
+    def get_excess_bw(self):
+        return self.excess_bw
+
+    def set_excess_bw(self, excess_bw):
+        self.excess_bw = excess_bw
+        self.set_rrc_taps(firdes.root_raised_cosine(self.nfilts/2, self.nfilts, 1.0, self.excess_bw, 5*self.sps*self.nfilts))
+
+    def get_sps(self):
+        return self.sps
+
+    def set_sps(self, sps):
+        self.sps = sps
+        self.set_rrc_taps(firdes.root_raised_cosine(self.nfilts/2, self.nfilts, 1.0, self.excess_bw, 5*self.sps*self.nfilts))
+        self.blocks_tagged_stream_multiply_length_0.set_scalar(self.sps)
+        self.pfb_arb_resampler_xxx_0_0.set_rate(self.sps)
+
+    def get_nfilts(self):
+        return self.nfilts
+
+    def set_nfilts(self, nfilts):
+        self.nfilts = nfilts
+        self.set_rrc_taps(firdes.root_raised_cosine(self.nfilts/2, self.nfilts, 1.0, self.excess_bw, 5*self.sps*self.nfilts))
+        self.set_taps_per_filt(len(self.rrc_taps)/self.nfilts)
+
+    def get_rrc_taps(self):
+        return self.rrc_taps
+
+    def set_rrc_taps(self, rrc_taps):
+        self.rrc_taps = rrc_taps
+        self.set_taps_per_filt(len(self.rrc_taps)/self.nfilts)
+        self.pfb_arb_resampler_xxx_0_0.set_taps(self.rrc_taps)
+
+    def get_taps_per_filt(self):
+        return self.taps_per_filt
+
+    def set_taps_per_filt(self, taps_per_filt):
+        self.taps_per_filt = taps_per_filt
+        self.set_filt_delay(int(1+(self.taps_per_filt-1)//2))
+
+    def get_header_format_cdc_0(self):
+        return self.header_format_cdc_0
+
+    def set_header_format_cdc_0(self, header_format_cdc_0):
+        self.header_format_cdc_0 = header_format_cdc_0
+
+    def get_hdr_encoder(self):
+        return self.hdr_encoder
+
+    def set_hdr_encoder(self, hdr_encoder):
+        self.hdr_encoder = hdr_encoder
+
+    def get_filt_delay(self):
+        return self.filt_delay
+
+    def set_filt_delay(self, filt_delay):
+        self.filt_delay = filt_delay
+