diff options
Diffstat (limited to 'gr-digital/python')
-rw-r--r-- | gr-digital/python/CMakeLists.txt | 2 | ||||
-rw-r--r-- | gr-digital/python/ofdm_txrx.py | 280 | ||||
-rwxr-xr-x | gr-digital/python/qa_ofdm_chanest_vcvc.py | 26 | ||||
-rwxr-xr-x | gr-digital/python/qa_ofdm_frame_equalizer_vcvc.py | 219 | ||||
-rwxr-xr-x | gr-digital/python/qa_ofdm_serializer_vcc.py | 108 | ||||
-rwxr-xr-x | gr-digital/python/qa_ofdm_sync_sc_cfb.py | 117 | ||||
-rwxr-xr-x | gr-digital/python/qa_ofdm_txrx.py | 120 | ||||
-rwxr-xr-x | gr-digital/python/qa_packet_headerparser_b.py | 16 |
8 files changed, 658 insertions, 230 deletions
diff --git a/gr-digital/python/CMakeLists.txt b/gr-digital/python/CMakeLists.txt index 7846345970..e73efb70f8 100644 --- a/gr-digital/python/CMakeLists.txt +++ b/gr-digital/python/CMakeLists.txt @@ -77,6 +77,8 @@ list(APPEND GR_TEST_PYTHON_DIRS ${CMAKE_BINARY_DIR}/gr-blocks/swig ${CMAKE_BINARY_DIR}/gr-fft/python ${CMAKE_BINARY_DIR}/gr-fft/swig + ${CMAKE_BINARY_DIR}/gr-channels/python + ${CMAKE_BINARY_DIR}/gr-channels/swig ) list(APPEND GR_TEST_TARGET_DEPS gnuradio-digital gnuradio-filter gnuradio-fft gnuradio-analog gnuradio-blocks) diff --git a/gr-digital/python/ofdm_txrx.py b/gr-digital/python/ofdm_txrx.py index 37c4086cc3..8f1b4c5f02 100644 --- a/gr-digital/python/ofdm_txrx.py +++ b/gr-digital/python/ofdm_txrx.py @@ -1,5 +1,5 @@ # -# Copyright 2005-2007,2013 Free Software Foundation, Inc. +# Copyright 2013 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -25,6 +25,9 @@ For simple configurations, no need to connect all the relevant OFDM blocks to form an OFDM Tx/Rx--simply use these. """ +# Reminder: All frequency-domain stuff is in shifted form, i.e. DC carrier +# in the middle! + import numpy from gnuradio import gr import digital_swig as digital @@ -44,20 +47,59 @@ except ImportError: _def_fft_len = 64 _def_cp_len = 16 _def_frame_length_tag_key = "frame_length" -_def_packet_length_tag_key = "frame_length" -_def_packet_num_tag_key = "" -_def_occupied_carriers=(range(1, 27) + range(38, 64),) -_def_pilot_carriers=((0,),) -_def_pilot_symbols=((100,),) +_def_packet_length_tag_key = "packet_length" +_def_packet_num_tag_key = "packet_num" +# Data and pilot carriers are same as in 802.11a +_def_occupied_carriers = (range(-26, -21) + range(-20, -7) + range(-6, 0) + range(1, 7) + range(8, 21) + range(22, 27),) +_def_pilot_carriers=((-21, -7, 7, 21,),) +_pilot_sym_scramble_seq = ( + 1,1,1,1, -1,-1,-1,1, -1,-1,-1,-1, 1,1,-1,1, -1,-1,1,1, -1,1,1,-1, 1,1,1,1, 1,1,-1,1, + 1,1,-1,1, 1,-1,-1,1, 1,1,-1,1, -1,-1,-1,1, -1,1,-1,-1, 1,-1,-1,1, 1,1,1,1, -1,-1,1,1, + -1,-1,1,-1, 1,-1,1,1, -1,-1,-1,1, 1,-1,-1,-1, -1,1,-1,-1, 1,-1,1,1, 1,1,-1,1, -1,1,-1,1, + -1,-1,-1,-1, -1,1,-1,1, 1,-1,1,-1, 1,1,1,-1, -1,1,-1,-1, -1,1,1,1, -1,-1,-1,-1, -1,-1,-1 +) +_def_pilot_symbols= tuple([(x, x, x, -x) for x in _pilot_sym_scramble_seq]) _seq_seed = 42 -def _make_sync_word(fft_len, occupied_carriers, constellation): - """ Makes a random sync sequence """ - occupied_carriers = list(occupied_carriers[0]) - occupied_carriers = [occupied_carriers[x] + fft_len if occupied_carriers[x] < 0 else occupied_carriers[x] for x in range(len(occupied_carriers))] + +def _get_active_carriers(fft_len, occupied_carriers, pilot_carriers): + active_carriers = list() + for carrier in list(occupied_carriers[0]) + list(pilot_carriers[0]): + if carrier < 0: + carrier += fft_len + active_carriers.append(carrier) + return active_carriers + +def _make_sync_word1(fft_len, occupied_carriers, pilot_carriers): + """ Creates a random sync sequence for fine frequency offset and timing + estimation. This is the first of typically two sync preamble symbols + for the Schmidl & Cox sync algorithm. + The relevant feature of this symbols is that every second sub-carrier + is zero. In the time domain, this results in two identical halves of + the OFDM symbols. + Symbols are always BPSK symbols. Carriers are scaled by sqrt(2) to keep + total energy constant. + Carrier 0 (DC carrier) is always zero. If used, carrier 1 is non-zero. + This means the sync algorithm has to check on odd carriers! + """ + active_carriers = _get_active_carriers(fft_len, occupied_carriers, pilot_carriers) numpy.random.seed(_seq_seed) - sync_sequence = [constellation.map_to_points_v(numpy.random.randint(constellation.arity()))[0] * numpy.sqrt(2) if x in occupied_carriers and x % 3 else 0 for x in range(fft_len)] - return sync_sequence + bpsk = {0: numpy.sqrt(2), 1: -numpy.sqrt(2)} + sw1 = [bpsk[numpy.random.randint(2)] if x in active_carriers and x % 2 else 0 for x in range(fft_len)] + return numpy.fft.fftshift(sw1) + +def _make_sync_word2(fft_len, occupied_carriers, pilot_carriers): + """ Creates a random sync sequence for coarse frequency offset and channel + estimation. This is the second of typically two sync preamble symbols + for the Schmidl & Cox sync algorithm. + Symbols are always BPSK symbols. + """ + active_carriers = _get_active_carriers(fft_len, occupied_carriers, pilot_carriers) + numpy.random.seed(_seq_seed) + bpsk = {0: 1, 1: -1} + sw2 = [bpsk[numpy.random.randint(2)] if x in active_carriers else 0 for x in range(fft_len)] + sw2[0] = 0j + return numpy.fft.fftshift(sw2) def _get_constellation(bps): """ Returns a modulator block for a given number of bits per symbol """ @@ -73,22 +115,28 @@ def _get_constellation(bps): exit(1) class ofdm_tx(gr.hier_block2): - """ - Hierarchical block for OFDM modulation. + """ Hierarchical block for OFDM modulation. The input is a byte stream (unsigned char) and the output is the complex modulated signal at baseband. Args: fft_len: The length of FFT (integer). - cp_len: The length of cyclic prefix (integer). - occupied_carriers: ?? - pilot_carriers: ?? - pilot_symbols: ?? - length_tag_key: The name of the tag giving packet length. + cp_len: The length of cyclic prefix in total samples (integer). + packet_length_tag_key: The name of the tag giving packet length at the input. + occupied_carriers: A vector of vectors describing which OFDM carriers are occupied. + pilot_carriers: A vector of vectors describing which OFDM carriers are occupied with pilot symbols. + pilot_symbols: The pilot symbols. + bps_header: Bits per symbol (header). + bps_payload: Bits per symbol (payload). + sync_word1: The first sync preamble symbol. This has to be with zeros on alternating carriers. + Used for fine and coarse frequency offset and timing estimation. + sync_word2: The second sync preamble symbol. This has to be filled entirely. Also used for + coarse frequency offset and channel estimation. + rolloff: The rolloff length in samples. Must be smaller than the CP. """ def __init__(self, fft_len=_def_fft_len, cp_len=_def_cp_len, - frame_length_tag_key=_def_frame_length_tag_key, + packet_length_tag_key=_def_packet_length_tag_key, occupied_carriers=_def_occupied_carriers, pilot_carriers=_def_pilot_carriers, pilot_symbols=_def_pilot_symbols, @@ -96,84 +144,112 @@ class ofdm_tx(gr.hier_block2): bps_payload=1, sync_word1=None, sync_word2=None, - rolloff=0 + rolloff=0, + debug_log=False ): gr.hier_block2.__init__(self, "ofdm_tx", gr.io_signature(1, 1, gr.sizeof_char), gr.io_signature(1, 1, gr.sizeof_gr_complex)) + ### Param init / sanity check ######################################## self.fft_len = fft_len self.cp_len = cp_len - self.frame_length_tag_key = frame_length_tag_key + self.packet_length_tag_key = packet_length_tag_key self.occupied_carriers = occupied_carriers self.pilot_carriers = pilot_carriers self.pilot_symbols = pilot_symbols self.bps_header = bps_header self.bps_payload = bps_payload n_sync_words = 1 - header_constellation = _get_constellation(bps_header) - header_mod = digital.chunks_to_symbols_bc(header_constellation.points()) self.sync_word1 = sync_word1 if sync_word1 is None: - self.sync_word1 = _make_sync_word(fft_len, occupied_carriers, header_constellation) + self.sync_word1 = _make_sync_word1(fft_len, occupied_carriers, pilot_carriers) else: if len(sync_word1) != self.fft_len: raise ValueError("Length of sync sequence(s) must be FFT length.") - self.sync_words = [sync_word1,] + self.sync_words = [self.sync_word1,] self.sync_word2 = () - if sync_word2 is not None: - if len(sync_word2) != fft_len: + if sync_word2 is None: + self.sync_word2 = _make_sync_word2(fft_len, occupied_carriers, pilot_carriers) + if len(self.sync_word2): + if len(self.sync_word2) != fft_len: raise ValueError("Length of sync sequence(s) must be FFT length.") - self.sync_word2 = sync_word2 + self.sync_word2 = list(self.sync_word2) n_sync_words = 2 self.sync_words.append(self.sync_word2) - crc = digital.crc32_bb(False, self.frame_length_tag_key) + ### Header modulation ################################################ + crc = digital.crc32_bb(False, self.packet_length_tag_key) + header_constellation = _get_constellation(bps_header) + header_mod = digital.chunks_to_symbols_bc(header_constellation.points()) formatter_object = digital.packet_header_ofdm( occupied_carriers=occupied_carriers, n_syms=1, - bits_per_sym=self.bps_header + bits_per_header_sym=self.bps_header, + bits_per_payload_sym=self.bps_payload ) - header_gen = digital.packet_headergenerator_bb(formatter_object.base(), self.frame_length_tag_key) - header_payload_mux = blocks.tagged_stream_mux(gr.sizeof_gr_complex*1, self.frame_length_tag_key) + header_gen = digital.packet_headergenerator_bb(formatter_object.base(), self.packet_length_tag_key) + header_payload_mux = blocks.tagged_stream_mux(gr.sizeof_gr_complex*1, self.packet_length_tag_key) self.connect(self, crc, header_gen, header_mod, (header_payload_mux, 0)) + if debug_log: + self.connect(header_gen, blocks.file_sink(1, 'tx-hdr.dat')) + ### Payload modulation ############################################### payload_constellation = _get_constellation(bps_payload) payload_mod = digital.chunks_to_symbols_bc(payload_constellation.points()) self.connect( crc, - blocks.repack_bits_bb(8, bps_payload, frame_length_tag_key), + blocks.repack_bits_bb( + 8, # Unpack 8 bits per byte + bps_payload, + self.packet_length_tag_key + ), payload_mod, (header_payload_mux, 1) ) + ### Create OFDM frame ################################################ allocator = digital.ofdm_carrier_allocator_cvc( self.fft_len, occupied_carriers=self.occupied_carriers, pilot_carriers=self.pilot_carriers, pilot_symbols=self.pilot_symbols, sync_words=self.sync_words, - len_tag_key=self.frame_length_tag_key + len_tag_key=self.packet_length_tag_key + ) + ffter = fft.fft_vcc( + self.fft_len, + False, # Inverse FFT + (), # No window + True # Shift ) - ffter = fft.fft_vcc(self.fft_len, False, (), True) cyclic_prefixer = digital.ofdm_cyclic_prefixer( self.fft_len, self.fft_len+self.cp_len, rolloff, - self.frame_length_tag_key + self.packet_length_tag_key ) self.connect(header_payload_mux, allocator, ffter, cyclic_prefixer, self) + if debug_log: + self.connect(allocator, blocks.file_sink(8*64, 'tx-post-allocator.dat')) + self.connect(cyclic_prefixer, blocks.file_sink(8, 'tx-signal.dat')) class ofdm_rx(gr.hier_block2): - """ - Hierarchical block for OFDM demodulation. + """ Hierarchical block for OFDM demodulation. - The input is a byte stream (unsigned char) and the - output is the complex modulated signal at baseband. + The input is a complex baseband signal (e.g. from a UHD source). + The detected packets are output as a stream of packed bits on the output. Args: fft_len: The length of FFT (integer). - cp_len: The length of cyclic prefix (integer). - occupied_carriers: ?? - pilot_carriers: ?? - pilot_symbols: ?? - length_tag_key: The name of the tag giving packet length. + cp_len: The length of cyclic prefix in total samples (integer). + frame_length_tag_key: Used internally to tag the length of the OFDM frame. + packet_length_tag_key: The name of the tag giving packet length at the input. + occupied_carriers: A vector of vectors describing which OFDM carriers are occupied. + pilot_carriers: A vector of vectors describing which OFDM carriers are occupied with pilot symbols. + pilot_symbols: The pilot symbols. + bps_header: Bits per symbol (header). + bps_payload: Bits per symbol (payload). + sync_word1: The first sync preamble symbol. This has to be with zeros on alternating carriers. + Used for fine and coarse frequency offset and timing estimation. + sync_word2: The second sync preamble symbol. This has to be filled entirely. Also used for + coarse frequency offset and channel estimation. """ def __init__(self, fft_len=_def_fft_len, cp_len=_def_cp_len, frame_length_tag_key=_def_frame_length_tag_key, @@ -185,11 +261,13 @@ class ofdm_rx(gr.hier_block2): bps_header=1, bps_payload=1, sync_word1=None, - sync_word2=None + sync_word2=None, + debug_log=False ): gr.hier_block2.__init__(self, "ofdm_rx", gr.io_signature(1, 1, gr.sizeof_gr_complex), gr.io_signature(1, 1, gr.sizeof_char)) + ### Param init / sanity check ######################################## self.fft_len = fft_len self.cp_len = cp_len self.frame_length_tag_key = frame_length_tag_key @@ -198,44 +276,60 @@ class ofdm_rx(gr.hier_block2): self.bps_header = bps_header self.bps_payload = bps_payload n_sync_words = 1 - header_constellation = _get_constellation(bps_header) if sync_word1 is None: - self.sync_word1 = _make_sync_word(fft_len, occupied_carriers, header_constellation) + self.sync_word1 = _make_sync_word1(fft_len, occupied_carriers, pilot_carriers) else: if len(sync_word1) != self.fft_len: raise ValueError("Length of sync sequence(s) must be FFT length.") self.sync_word1 = sync_word1 self.sync_word2 = () - if sync_word2 is not None: + if sync_word2 is None: + self.sync_word2 = _make_sync_word2(fft_len, occupied_carriers, pilot_carriers) + n_sync_words = 2 + elif len(sync_word2): if len(sync_word2) != fft_len: raise ValueError("Length of sync sequence(s) must be FFT length.") self.sync_word2 = sync_word2 n_sync_words = 2 - else: - sync_word2 = () - # Receiver path + ### Sync ############################################################ sync_detect = digital.ofdm_sync_sc_cfb(fft_len, cp_len) + delay = blocks.delay(gr.sizeof_gr_complex, fft_len+cp_len) oscillator = analog.frequency_modulator_fc(-2.0 / fft_len) - delay = gr.delay(gr.sizeof_gr_complex, fft_len+cp_len) - mixer = gr.multiply_cc() - hpd = digital.header_payload_demux(n_sync_words, fft_len, cp_len, - frame_length_tag_key, "", True) + mixer = blocks.multiply_cc() + hpd = digital.header_payload_demux( + n_sync_words+1, # Number of OFDM symbols before payload (sync + 1 sym header) + fft_len, cp_len, # FFT length, guard interval + frame_length_tag_key, # Frame length tag key + "", # We're not using trigger tags + True # One output item is one OFDM symbol (False would output complex scalars) + ) self.connect(self, sync_detect) - self.connect((sync_detect, 0), oscillator, (mixer, 0)) - self.connect(self, delay, (mixer, 1)) - self.connect(mixer, (hpd, 0)) + self.connect(self, delay, (mixer, 0), (hpd, 0)) + self.connect((sync_detect, 0), oscillator, (mixer, 1)) self.connect((sync_detect, 1), (hpd, 1)) - # Header demodulation - header_fft = fft.fft_vcc(self.fft_len, True, (), True) - chanest = digital.ofdm_chanest_vcvc(self.sync_word1, self.sync_word2, 1) - header_equalizer = digital.ofdm_equalizer_simpledfe( - fft_len, header_constellation.base(), - occupied_carriers, pilot_carriers, pilot_symbols - ) - header_eq = digital.ofdm_frame_equalizer_vcvc(header_equalizer.base(), frame_length_tag_key, True) - header_serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers) + if debug_log: + self.connect((sync_detect, 0), blocks.file_sink(gr.sizeof_float, 'freq-offset.dat')) + self.connect((sync_detect, 1), blocks.file_sink(gr.sizeof_char, 'sync-detect.dat')) + ### Header demodulation ############################################## + header_fft = fft.fft_vcc(self.fft_len, True, (), True) + chanest = digital.ofdm_chanest_vcvc(self.sync_word1, self.sync_word2, 1) header_constellation = _get_constellation(bps_header) - header_demod = digital.constellation_decoder_cb(header_constellation.base()) + header_equalizer = digital.ofdm_equalizer_simpledfe( + fft_len, header_constellation.base(), + occupied_carriers, pilot_carriers, pilot_symbols, 0, 0 + ) + header_eq = digital.ofdm_frame_equalizer_vcvc( + header_equalizer.base(), + cp_len, + self.frame_length_tag_key, + True, + 1 # Header is 1 symbol long + ) + header_serializer = digital.ofdm_serializer_vcc( + fft_len, occupied_carriers, + self.frame_length_tag_key + ) + header_demod = digital.constellation_decoder_cb(header_constellation.base()) header_formatter = digital.packet_header_ofdm( occupied_carriers, 1, packet_length_tag_key, @@ -246,16 +340,46 @@ class ofdm_rx(gr.hier_block2): header_parser = digital.packet_headerparser_b(header_formatter.formatter()) self.connect((hpd, 0), header_fft, chanest, header_eq, header_serializer, header_demod, header_parser) self.msg_connect(header_parser, "header_data", hpd, "header_data") - # Payload demodulation + if debug_log: + self.connect((chanest, 1), blocks.file_sink(512, 'channel-estimate.dat')) + self.connect((chanest, 0), blocks.file_sink(512, 'post-hdr-chanest.dat')) + self.connect(header_eq, blocks.file_sink(512, 'post-hdr-eq.dat')) + self.connect(header_serializer, blocks.file_sink(8, 'post-hdr-serializer.dat')) + self.connect(header_demod, blocks.file_sink(1, 'post-hdr-demod.dat')) + self.connect(header_demod, blocks.tag_debug(1, 'post-hdr-demod.dat')) + ### Payload demod #################################################### payload_fft = fft.fft_vcc(self.fft_len, True, (), True) + payload_constellation = _get_constellation(bps_payload) payload_equalizer = digital.ofdm_equalizer_simpledfe( - fft_len, header_constellation.base(), - occupied_carriers, pilot_carriers, pilot_symbols, 1 + fft_len, payload_constellation.base(), + occupied_carriers, + pilot_carriers, + pilot_symbols, + 1 # Skip 1 symbol (that was already in the header) + ) + payload_eq = digital.ofdm_frame_equalizer_vcvc( + payload_equalizer.base(), + cp_len, + self.frame_length_tag_key + ) + payload_serializer = digital.ofdm_serializer_vcc( + fft_len, occupied_carriers, + self.frame_length_tag_key, + self.packet_length_tag_key, + 1 # Skip 1 symbol (that was already in the header) ) - payload_eq = digital.ofdm_frame_equalizer_vcvc(payload_equalizer.base(), frame_length_tag_key) - payload_serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers) - payload_constellation = _get_constellation(bps_payload) payload_demod = digital.constellation_decoder_cb(payload_constellation.base()) - bit_packer = blocks.repack_bits_bb(bps_payload, 8, packet_length_tag_key, True) - self.connect((hpd, 1), payload_fft, payload_eq, payload_serializer, payload_demod, bit_packer, self) + repack = blocks.repack_bits_bb(bps_payload, 8, self.packet_length_tag_key, True) + crc = digital.crc32_bb(True, self.packet_length_tag_key) + self.connect((hpd, 1), payload_fft, payload_eq, payload_serializer, payload_demod, repack, crc, self) + if debug_log: + self.connect((hpd, 1), blocks.tag_debug(8*64, 'post-hpd')); + self.connect(payload_fft, blocks.file_sink(8*64, 'post-payload-fft.dat')) + self.connect(payload_eq, blocks.file_sink(8*64, 'post-payload-eq.dat')) + self.connect(payload_serializer, blocks.file_sink(8, 'post-payload-serializer.dat')) + self.connect(payload_demod, blocks.file_sink(1, 'post-payload-demod.dat')) + self.connect(repack, blocks.file_sink(1, 'post-payload-repack.dat')) + self.connect(crc, blocks.file_sink(1, 'post-payload-crc.dat')) + self.connect(crc, blocks.tag_debug(1, 'post-payload-crc')) + diff --git a/gr-digital/python/qa_ofdm_chanest_vcvc.py b/gr-digital/python/qa_ofdm_chanest_vcvc.py index b11bb4c556..ae11c0534c 100755 --- a/gr-digital/python/qa_ofdm_chanest_vcvc.py +++ b/gr-digital/python/qa_ofdm_chanest_vcvc.py @@ -19,14 +19,16 @@ # Boston, MA 02110-1301, USA. # +import sys +import numpy +import random from gnuradio import gr, gr_unittest import pmt import blocks_swig as blocks import analog_swig as analog import digital_swig as digital -import sys -import numpy -import random +import blocks_swig as blocks +from ofdm_txrx import ofdm_tx def shift_tuple(vec, N): """ Shifts a vector by N elements. Fills up with zeros. """ @@ -129,7 +131,9 @@ class qa_ofdm_sync_eqinit_vcvc (gr_unittest.TestCase): chan = blocks.multiply_const_vcc(channel) chanest = digital.ofdm_chanest_vcvc(sync_symbol1, sync_symbol2, 1) sink = blocks.vector_sink_c(fft_len) + sink_chanest = blocks.vector_sink_c(fft_len) self.tb.connect(src, chan, chanest, sink) + self.tb.connect((chanest, 1), sink_chanest) self.tb.run() tags = sink.tags() self.assertEqual(shift_tuple(sink.data(), -carr_offset), tuple(numpy.multiply(data_symbol, channel))) @@ -138,6 +142,7 @@ class qa_ofdm_sync_eqinit_vcvc (gr_unittest.TestCase): self.assertEqual(pmt.to_long(tag.value), carr_offset) if pmt.symbol_to_string(tag.key) == 'ofdm_sync_chan_taps': self.assertEqual(pmt.c32vector_elements(tag.value), channel) + self.assertEqual(sink_chanest.data(), channel) def test_004_channel_no_carroffset_1sym (self): """ Add a channel, check if it's correctly estimated. @@ -147,13 +152,17 @@ class qa_ofdm_sync_eqinit_vcvc (gr_unittest.TestCase): sync_symbol = (0, 0, 0, 1, 0, 1, 0, -1, 0, 1, 0, -1, 0, 1, 0, 0) data_symbol = (0, 0, 0, 1, -1, 1, -1, 1, 0, 1, -1, -1, -1, 1, 0, 0) tx_data = sync_symbol + data_symbol - channel = (0, 0, 0, 2, 2, 2, 2.5, 3, 2.5, 2, 2.5, 3, 2, 1, 1, 0) + channel = (0, 0, 0, 2, 2, 2, 2, 3, 3, 2.5, 2.5, -3, -3, 1j, 1j, 0) + #channel = (0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0) src = blocks.vector_source_c(tx_data, False, fft_len) chan = blocks.multiply_const_vcc(channel) chanest = digital.ofdm_chanest_vcvc(sync_symbol, (), 1) sink = blocks.vector_sink_c(fft_len) + sink_chanest = blocks.vector_sink_c(fft_len) self.tb.connect(src, chan, chanest, sink) + self.tb.connect((chanest, 1), sink_chanest) self.tb.run() + self.assertEqual(sink_chanest.data(), channel) tags = sink.tags() for tag in tags: if pmt.symbol_to_string(tag.key) == 'ofdm_sync_carr_offset': @@ -194,6 +203,7 @@ class qa_ofdm_sync_eqinit_vcvc (gr_unittest.TestCase): data_symbol = (0, 0, 0, 1, -1, 1, -1, 1, 0, 1, -1, -1, -1, 1, 0, 0) # Channel 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Shifted (0, 0, 0, 0, 0, 1j, -1, 1, -1j, 1j, 0, 1, -1j, -1, -1j, 1) + chanest_exp = (0, 0, 0, 5, 6, 7, 8, 9, 0, 11, 12, 13, 14, 15, 0, 0) tx_data = shift_tuple(sync_symbol1, carr_offset) + \ shift_tuple(sync_symbol2, carr_offset) + \ shift_tuple(data_symbol, carr_offset) @@ -211,9 +221,7 @@ class qa_ofdm_sync_eqinit_vcvc (gr_unittest.TestCase): self.assertEqual(pmt.to_long(tag.value), carr_offset) if pmt.symbol_to_string(tag.key) == 'ofdm_sync_chan_taps': chan_est = pmt.c32vector_elements(tag.value) - for i in range(fft_len): - if shift_tuple(sync_symbol2, carr_offset)[i]: # Only here the channel can be estimated - self.assertEqual(chan_est[i], channel[i]) + self.assertEqual(chan_est, chanest_exp) self.assertEqual(sink.data(), tuple(numpy.multiply(shift_tuple(data_symbol, carr_offset), channel))) @@ -227,7 +235,7 @@ class qa_ofdm_sync_eqinit_vcvc (gr_unittest.TestCase): wgn_amplitude = 0.05 min_chan_ampl = 0.1 max_chan_ampl = 5 - n_iter = 20 + n_iter = 20 # The more the accurater def run_flow_graph(sync_sym1, sync_sym2, data_sym): top_block = gr.top_block() carr_offset = random.randint(-max_offset/2, max_offset/2) * 2 @@ -253,7 +261,7 @@ class qa_ofdm_sync_eqinit_vcvc (gr_unittest.TestCase): carr_offset_hat = pmt.to_long(tag.value) self.assertEqual(carr_offset, carr_offset_hat) if pmt.symbol_to_string(tag.key) == 'ofdm_sync_chan_taps': - channel_est = pmt.c32vector_elements(tag.value) + channel_est = shift_tuple(pmt.c32vector_elements(tag.value), carr_offset) shifted_carrier_mask = shift_tuple(carrier_mask, carr_offset) for i in range(fft_len): if shifted_carrier_mask[i] and channel_est[i]: diff --git a/gr-digital/python/qa_ofdm_frame_equalizer_vcvc.py b/gr-digital/python/qa_ofdm_frame_equalizer_vcvc.py index a7cf78e928..0c79125669 100755 --- a/gr-digital/python/qa_ofdm_frame_equalizer_vcvc.py +++ b/gr-digital/python/qa_ofdm_frame_equalizer_vcvc.py @@ -34,7 +34,14 @@ class qa_ofdm_frame_equalizer_vcvc (gr_unittest.TestCase): self.tb = None def test_001_simple (self): - """ Very simple functionality testing """ + """ Very simple functionality testing: + - static equalizer + - init channel state with all ones + - transmit all ones + - make sure we rx all ones + - Tag check: put in frame length tag and one other random tag, + make sure they're propagated + """ fft_len = 8 equalizer = digital.ofdm_equalizer_static(fft_len) n_syms = 3 @@ -48,18 +55,135 @@ class qa_ofdm_frame_equalizer_vcvc (gr_unittest.TestCase): chan_tag.offset = 0 chan_tag.key = pmt.string_to_symbol("ofdm_sync_chan_taps") chan_tag.value = pmt.init_c32vector(fft_len, (1,) * fft_len) - src = blocks.vector_source_c(tx_data, False, fft_len, (len_tag, chan_tag)) - eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), len_tag_key) + random_tag = gr.tag_t() + random_tag.offset = 1 + random_tag.key = pmt.string_to_symbol("foo") + random_tag.value = pmt.from_long(42) + src = blocks.vector_source_c(tx_data, False, fft_len, (len_tag, chan_tag, random_tag)) + eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), 0, len_tag_key) sink = blocks.vector_sink_c(fft_len) self.tb.connect(src, eq, sink) self.tb.run () # Check data self.assertEqual(tx_data, sink.data()) + # Check tags + tag_dict = dict() for tag in sink.tags(): - self.assertEqual(pmt.symbol_to_string(tag.key), len_tag_key) - self.assertEqual(pmt.to_long(tag.value), n_syms) + ptag = gr.tag_to_python(tag) + tag_dict[ptag.key] = ptag.value + expected_dict = { + 'frame_len': n_syms, + 'foo': 42 + } + self.assertEqual(tag_dict, expected_dict) + + def test_001b_simple_skip_nothing (self): + """ + Same as before, but put a skip-header in there + """ + fft_len = 8 + equalizer = digital.ofdm_equalizer_static(fft_len, symbols_skipped=1) + n_syms = 3 + len_tag_key = "frame_len" + tx_data = (1,) * fft_len * n_syms + len_tag = gr.tag_t() + len_tag.offset = 0 + len_tag.key = pmt.string_to_symbol(len_tag_key) + len_tag.value = pmt.from_long(n_syms) + chan_tag = gr.tag_t() + chan_tag.offset = 0 + chan_tag.key = pmt.string_to_symbol("ofdm_sync_chan_taps") + chan_tag.value = pmt.init_c32vector(fft_len, (1,) * fft_len) + src = blocks.vector_source_c(tx_data, False, fft_len, (len_tag, chan_tag)) + eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), 0, len_tag_key) + sink = blocks.vector_sink_c(fft_len) + self.tb.connect(src, eq, sink) + self.tb.run () + # Check data + self.assertEqual(tx_data, sink.data()) + + def test_001c_carrier_offset_no_cp (self): + """ + Same as before, but put a carrier offset in there + """ + fft_len = 8 + cp_len = 0 + n_syms = 1 + carr_offset = 1 + occupied_carriers = ((-2, -1, 1, 2),) + tx_data = ( + 0, 0, 0, -1j, -1j, 0, -1j, -1j, + ) + # The rx'd signal is shifted + rx_expected = (0, 0, 1, 1, 0, 1, 1, 0) * n_syms + equalizer = digital.ofdm_equalizer_static(fft_len, occupied_carriers) + len_tag_key = "frame_len" + len_tag = gr.tag_t() + len_tag.offset = 0 + len_tag.key = pmt.string_to_symbol(len_tag_key) + len_tag.value = pmt.from_long(n_syms) + chan_tag = gr.tag_t() + chan_tag.offset = 0 + chan_tag.key = pmt.string_to_symbol("ofdm_sync_chan_taps") + # Note: this is shifted to the correct position! + chan_tag.value = pmt.init_c32vector(fft_len, (0, 0, -1j, -1j, 0, -1j, -1j, 0)) + offset_tag = gr.tag_t() + offset_tag.offset = 0 + offset_tag.key = pmt.string_to_symbol("ofdm_sync_carr_offset") + offset_tag.value = pmt.from_long(carr_offset) + src = blocks.vector_source_c(tx_data, False, fft_len, (len_tag, chan_tag, offset_tag)) + eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), cp_len, len_tag_key) + sink = blocks.vector_sink_c(fft_len) + self.tb.connect(src, eq, sink) + self.tb.run () + # Check data + self.assertComplexTuplesAlmostEqual(rx_expected, sink.data(), places=4) + + def test_001c_carrier_offset_cp (self): + """ + Same as before, but put a carrier offset in there and a CP + """ + fft_len = 8 + cp_len = 2 + n_syms = 3 + # cp_len/fft_len == 1/4, therefore, the phase is rotated by + # carr_offset * \pi/2 in every symbol + occupied_carriers = ((-2, -1, 1, 2),) + carr_offset = -1 + tx_data = ( + 0,-1j,-1j, 0,-1j,-1j, 0, 0, + 0, -1, -1, 0, -1, -1, 0, 0, + 0, 1j, 1j, 0, 1j, 1j, 0, 0, + ) + # Rx'd signal is corrected + rx_expected = (0, 0, 1, 1, 0, 1, 1, 0) * n_syms + equalizer = digital.ofdm_equalizer_static(fft_len, occupied_carriers) + len_tag_key = "frame_len" + len_tag = gr.tag_t() + len_tag.offset = 0 + len_tag.key = pmt.string_to_symbol(len_tag_key) + len_tag.value = pmt.from_long(n_syms) + chan_tag = gr.tag_t() + chan_tag.offset = 0 + chan_tag.key = pmt.string_to_symbol("ofdm_sync_chan_taps") + chan_tag.value = pmt.init_c32vector(fft_len, (0, 0, 1, 1, 0, 1, 1, 0)) + offset_tag = gr.tag_t() + offset_tag.offset = 0 + offset_tag.key = pmt.string_to_symbol("ofdm_sync_carr_offset") + offset_tag.value = pmt.from_long(carr_offset) + src = blocks.vector_source_c(tx_data, False, fft_len, (len_tag, chan_tag, offset_tag)) + eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), cp_len, len_tag_key) + sink = blocks.vector_sink_c(fft_len) + self.tb.connect(src, eq, sink) + self.tb.run () + # Check data + self.assertComplexTuplesAlmostEqual(rx_expected, sink.data(), places=4) def test_002_static (self): + """ + - Add a simple channel + - Make symbols QPSK + """ fft_len = 8 # 4 5 6 7 0 1 2 3 tx_data = [-1, -1, 1, 2, -1, 3, 0, -1, # 0 @@ -76,14 +200,18 @@ class qa_ofdm_frame_equalizer_vcvc (gr_unittest.TestCase): equalizer = digital.ofdm_equalizer_static(fft_len, occupied_carriers, pilot_carriers, pilot_symbols) channel = [ 0, 0, 1, 1, 0, 1, 1, 0, - 0, 0, 1, 1, 0, 1, 1, 0, # These coefficients will be rotated slightly... + 0, 0, 1, 1, 0, 1, 1, 0, # These coefficients will be rotated slightly (but less than \pi/2) 0, 0, 1j, 1j, 0, 1j, 1j, 0, # Go crazy here! - 0, 0, 1j, 1j, 0, 1j, 1j, 0 # ...and again here. + 0, 0, 1j, 1j, 0, 1j, 1j, 0 + ] + channel = [ + 0, 0, 1, 1, 0, 1, 1, 0, + 0, 0, 1, 1, 0, 1, 1, 0, # These coefficients will be rotated slightly (but less than \pi/2) + 0, 0, 1j, 1j, 0, 1j, 1j, 0, # Go crazy here! + 0, 0, 1j, 1j, 0, 1j, 1j, 0 ] for idx in range(fft_len, 2*fft_len): channel[idx] = channel[idx-fft_len] * numpy.exp(1j * .1 * numpy.pi * (numpy.random.rand()-.5)) - idx2 = idx+2*fft_len - channel[idx2] = channel[idx2] * numpy.exp(1j * 0 * numpy.pi * (numpy.random.rand()-.5)) len_tag_key = "frame_len" len_tag = gr.tag_t() len_tag.offset = 0 @@ -94,17 +222,75 @@ class qa_ofdm_frame_equalizer_vcvc (gr_unittest.TestCase): chan_tag.key = pmt.string_to_symbol("ofdm_sync_chan_taps") chan_tag.value = pmt.init_c32vector(fft_len, channel[:fft_len]) src = blocks.vector_source_c(numpy.multiply(tx_signal, channel), False, fft_len, (len_tag, chan_tag)) - eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), len_tag_key, True) sink = blocks.vector_sink_c(fft_len) + eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), 0, len_tag_key, True) self.tb.connect(src, eq, sink) self.tb.run () rx_data = [cnst.decision_maker_v((x,)) if x != 0 else -1 for x in sink.data()] + # Check data self.assertEqual(tx_data, rx_data) + # Check tags + tag_dict = dict() for tag in sink.tags(): - if pmt.symbol_to_string(tag.key) == len_tag_key: - self.assertEqual(pmt.to_long(tag.value), 4) - if pmt.symbol_to_string(tag.key) == "ofdm_sync_chan_taps": - self.assertEqual(list(pmt.c32vector_elements(tag.value)), channel[-fft_len:]) + ptag = gr.tag_to_python(tag) + tag_dict[ptag.key] = ptag.value + if ptag.key == 'ofdm_sync_chan_taps': + tag_dict[ptag.key] = list(pmt.c32vector_elements(tag.value)) + else: + tag_dict[ptag.key] = pmt.to_python(tag.value) + expected_dict = { + 'frame_len': 4, + 'ofdm_sync_chan_taps': channel[-fft_len:] + } + self.assertEqual(tag_dict, expected_dict) + + def test_002_static_wo_tags (self): + """ Same as before, but the input stream has no tag. + We specify the frame size in the constructor. + We also specify a tag key, so the output stream *should* have + a length tag. + """ + fft_len = 8 + n_syms = 4 + # 4 5 6 7 0 1 2 3 + tx_data = [-1, -1, 1, 2, -1, 3, 0, -1, # 0 + -1, -1, 0, 2, -1, 2, 0, -1, # 8 + -1, -1, 3, 0, -1, 1, 0, -1, # 16 (Pilot symbols) + -1, -1, 1, 1, -1, 0, 2, -1] # 24 + cnst = digital.constellation_qpsk() + tx_signal = [cnst.map_to_points_v(x)[0] if x != -1 else 0 for x in tx_data] + occupied_carriers = ((1, 2, 6, 7),) + pilot_carriers = ((), (), (1, 2, 6, 7), ()) + pilot_symbols = ( + [], [], [cnst.map_to_points_v(x)[0] for x in (1, 0, 3, 0)], [] + ) + equalizer = digital.ofdm_equalizer_static(fft_len, occupied_carriers, pilot_carriers, pilot_symbols) + channel = [ + 0, 0, 1, 1, 0, 1, 1, 0, + 0, 0, 1, 1, 0, 1, 1, 0, # These coefficients will be rotated slightly (below)... + 0, 0, 1j, 1j, 0, 1j, 1j, 0, # Go crazy here! + 0, 0, 1j, 1j, 0, 1j, 1j, 0 # ...and again here. + ] + for idx in range(fft_len, 2*fft_len): + channel[idx] = channel[idx-fft_len] * numpy.exp(1j * .1 * numpy.pi * (numpy.random.rand()-.5)) + idx2 = idx+2*fft_len + channel[idx2] = channel[idx2] * numpy.exp(1j * 0 * numpy.pi * (numpy.random.rand()-.5)) + src = gr.vector_source_c(numpy.multiply(tx_signal, channel), False, fft_len) + # We do specify a length tag, it should then appear at the output + eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), 0, "frame_len", False, n_syms) + sink = blocks.vector_sink_c(fft_len) + self.tb.connect(src, eq, sink) + self.tb.run () + rx_data = [cnst.decision_maker_v((x,)) if x != 0 else -1 for x in sink.data()] + self.assertEqual(tx_data, rx_data) + # Check len tag + tags = sink.tags() + len_tag = dict() + for tag in tags: + ptag = gr.tag_to_python(tag) + if ptag.key == 'frame_len': + len_tag[ptag.key] = ptag.value + self.assertEqual(len_tag, {'frame_len': 4}) def test_002_static_wo_tags (self): fft_len = 8 @@ -132,14 +318,15 @@ class qa_ofdm_frame_equalizer_vcvc (gr_unittest.TestCase): idx2 = idx+2*fft_len channel[idx2] = channel[idx2] * numpy.exp(1j * 0 * numpy.pi * (numpy.random.rand()-.5)) src = blocks.vector_source_c(numpy.multiply(tx_signal, channel), False, fft_len) - eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), "", False, 4) sink = blocks.vector_sink_c(fft_len) + eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), 0, "", False, 4) self.tb.connect(src, eq, sink) self.tb.run () rx_data = [cnst.decision_maker_v((x,)) if x != 0 else -1 for x in sink.data()] self.assertEqual(tx_data, rx_data) def test_002_simpledfe (self): + """ Use the simple DFE equalizer. """ fft_len = 8 # 4 5 6 7 0 1 2 3 tx_data = [-1, -1, 1, 2, -1, 3, 0, -1, # 0 @@ -176,7 +363,7 @@ class qa_ofdm_frame_equalizer_vcvc (gr_unittest.TestCase): chan_tag.key = pmt.string_to_symbol("ofdm_sync_chan_taps") chan_tag.value = pmt.init_c32vector(fft_len, channel[:fft_len]) src = blocks.vector_source_c(numpy.multiply(tx_signal, channel), False, fft_len, (len_tag, chan_tag)) - eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), len_tag_key, True) + eq = digital.ofdm_frame_equalizer_vcvc(equalizer.base(), 0, len_tag_key, True) sink = blocks.vector_sink_c(fft_len) self.tb.connect(src, eq, sink) self.tb.run () diff --git a/gr-digital/python/qa_ofdm_serializer_vcc.py b/gr-digital/python/qa_ofdm_serializer_vcc.py index 4449c1a24d..c53b4e4955 100755 --- a/gr-digital/python/qa_ofdm_serializer_vcc.py +++ b/gr-digital/python/qa_ofdm_serializer_vcc.py @@ -20,13 +20,13 @@ # Boston, MA 02110-1301, USA. # +import numpy from gnuradio import gr, gr_unittest +import pmt import blocks_swig as blocks import fft_swig as fft import analog_swig as analog import digital_swig as digital -import pmt -import numpy class qa_ofdm_serializer_vcc (gr_unittest.TestCase): @@ -39,7 +39,6 @@ class qa_ofdm_serializer_vcc (gr_unittest.TestCase): def test_001_simple (self): """ Standard test """ fft_len = 16 - tx_symbols = range(1, 16); tx_symbols = (0, 1, 1j, 2, 3, 0, 0, 0, 0, 0, 0, 4, 5, 2j, 6, 0, 0, 7, 8, 3j, 9, 0, 0, 0, 0, 0, 0, 10, 4j, 11, 12, 0, 0, 13, 1j, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 2j, 0, 0) @@ -52,7 +51,34 @@ class qa_ofdm_serializer_vcc (gr_unittest.TestCase): tag.key = pmt.string_to_symbol(tag_name) tag.value = pmt.from_long(n_syms) src = blocks.vector_source_c(tx_symbols, False, fft_len, (tag,)) - serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers, tag_name, "", 0, False) + serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers, tag_name, "", 0, "", False) + sink = blocks.vector_sink_c() + self.tb.connect(src, serializer, sink) + self.tb.run () + self.assertEqual(sink.data(), expected_result) + self.assertEqual(len(sink.tags()), 1) + result_tag = sink.tags()[0] + self.assertEqual(pmt.symbol_to_string(result_tag.key), tag_name) + self.assertEqual(pmt.to_long(result_tag.value), n_syms * len(occupied_carriers[0])) + + def test_001b_shifted (self): + """ Same as before, but shifted, because that's the normal mode in OFDM Rx """ + fft_len = 16 + tx_symbols = ( + 0, 0, 0, 0, 0, 0, 1, 2, 0, 3, 4, 5, 0, 0, 0, 0, + 0, 0, 0, 0, 6, 1j, 7, 8, 0, 9, 10, 1j, 11, 0, 0, 0, + 0, 0, 0, 0, 0, 12, 13, 14, 0, 15, 16, 17, 0, 0, 0, 0, + ) + expected_result = tuple(range(18)) + occupied_carriers = ((13, 14, 15, 1, 2, 3), (-4, -2, -1, 1, 2, 4),) + n_syms = len(tx_symbols)/fft_len + tag_name = "len" + tag = gr.tag_t() + tag.offset = 0 + tag.key = pmt.string_to_symbol(tag_name) + tag.value = pmt.from_long(n_syms) + src = blocks.vector_source_c(tx_symbols, False, fft_len, (tag,)) + serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers, tag_name) sink = blocks.vector_sink_c() self.tb.connect(src, serializer, sink) self.tb.run () @@ -83,8 +109,15 @@ class qa_ofdm_serializer_vcc (gr_unittest.TestCase): offsettag.key = pmt.string_to_symbol("ofdm_sync_carr_offset") offsettag.value = pmt.from_long(carr_offset) src = blocks.vector_source_c(tx_symbols, False, fft_len, (tag, offsettag)) - serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers, tag_name, "", 0, False) sink = blocks.vector_sink_c() + serializer = digital.ofdm_serializer_vcc( + fft_len, + occupied_carriers, + tag_name, + "", 0, + "ofdm_sync_carr_offset", + False + ) self.tb.connect(src, serializer, sink) self.tb.run () self.assertEqual(sink.data(), expected_result) @@ -96,29 +129,39 @@ class qa_ofdm_serializer_vcc (gr_unittest.TestCase): def test_003_connect (self): """ Connect carrier_allocator to ofdm_serializer, make sure output==input """ - fft_len = 32 - n_syms = 10 + fft_len = 8 + n_syms = 1 occupied_carriers = ((1, 2, 6, 7),) pilot_carriers = ((3,),(5,)) pilot_symbols = ((1j,),(-1j,)) - sync_word = (range(fft_len),) - tx_data = tuple([numpy.random.randint(0, 10) for x in range(4 * n_syms)]) + #tx_data = tuple([numpy.random.randint(0, 10) for x in range(4 * n_syms)]) + tx_data = (1, 2, 3, 4) tag_name = "len" tag = gr.tag_t() tag.offset = 0 tag.key = pmt.string_to_symbol(tag_name) tag.value = pmt.from_long(len(tx_data)) src = blocks.vector_source_c(tx_data, False, 1, (tag,)) - alloc = digital.ofdm_carrier_allocator_cvc(fft_len, - occupied_carriers, - pilot_carriers, - pilot_symbols, sync_word, - tag_name) - serializer = digital.ofdm_serializer_vcc(alloc) + alloc = digital.ofdm_carrier_allocator_cvc( + fft_len, + occupied_carriers, + pilot_carriers, + pilot_symbols, + (), # No sync word + tag_name, + True # Output is shifted (default) + ) + serializer = digital.ofdm_serializer_vcc( + alloc, + "", # Len tag key + 0, # Symbols skipped + "", # Carrier offset key + True # Input is shifted (default) + ) sink = blocks.vector_sink_c() self.tb.connect(src, alloc, serializer, sink) self.tb.run () - self.assertEqual(sink.data()[4:], tx_data) + self.assertEqual(sink.data(), tx_data) def test_004_connect (self): """ @@ -129,15 +172,13 @@ class qa_ofdm_serializer_vcc (gr_unittest.TestCase): - Frequency offset is -2 carriers """ fft_len = 8 - n_syms = 2 + n_syms = 1 carr_offset = -2 - freq_offset = 2 * numpy.pi * carr_offset / fft_len # If the sampling rate == 1 - occupied_carriers = ((1, 2, -2, -1),) - pilot_carriers = ((3,),(5,)) + freq_offset = 1.0 / fft_len * carr_offset # Normalized frequency + occupied_carriers = ((-2, -1, 1, 2),) + pilot_carriers = ((-3,),(3,)) pilot_symbols = ((1j,),(-1j,)) - sync_word = (range(fft_len),) - tx_data = tuple([numpy.random.randint(0, 10) for x in range(4 * n_syms)]) - #tx_data = (1,) * occupied_carriers[0] * n_syms + tx_data = (1, 2, 3, 4) tag_name = "len" tag = gr.tag_t() tag.offset = 0 @@ -151,13 +192,17 @@ class qa_ofdm_serializer_vcc (gr_unittest.TestCase): alloc = digital.ofdm_carrier_allocator_cvc(fft_len, occupied_carriers, pilot_carriers, - pilot_symbols, sync_word, + pilot_symbols, (), tag_name) - tx_ifft = fft.fft_vcc(fft_len, False, ()) - offset_sig = analog.sig_source_c(1.0, analog.GR_COS_WAVE, freq_offset, 1.0) + tx_ifft = fft.fft_vcc(fft_len, False, (1.0/fft_len,)*fft_len, True) + oscillator = analog.sig_source_c(1.0, analog.GR_COS_WAVE, freq_offset, 1.0/fft_len) mixer = blocks.multiply_cc() rx_fft = fft.fft_vcc(fft_len, True, (), True) - serializer = digital.ofdm_serializer_vcc(alloc) + sink2 = blocks.vector_sink_c(fft_len) + self.tb.connect(rx_fft, sink2) + serializer = digital.ofdm_serializer_vcc( + alloc, "", 0, "ofdm_sync_carr_offset", True + ) sink = blocks.vector_sink_c() self.tb.connect( src, alloc, tx_ifft, @@ -166,10 +211,9 @@ class qa_ofdm_serializer_vcc (gr_unittest.TestCase): blocks.stream_to_vector(gr.sizeof_gr_complex, fft_len), rx_fft, serializer, sink ) - self.tb.connect(offset_sig, (mixer, 1)) + self.tb.connect(oscillator, (mixer, 1)) self.tb.run () - # FIXME check this - #self.assertEqual(sink.data(), tx_data) + self.assertComplexTuplesAlmostEqual(sink.data()[-len(occupied_carriers[0]):], tx_data, places=4) def test_005_packet_len_tag (self): """ Standard test """ @@ -191,7 +235,7 @@ class qa_ofdm_serializer_vcc (gr_unittest.TestCase): tag2.key = pmt.string_to_symbol("packet_len") tag2.value = pmt.from_long(len(expected_result)) src = blocks.vector_source_c(tx_symbols, False, fft_len, (tag, tag2)) - serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers, tag_name, "packet_len", 0, False) + serializer = digital.ofdm_serializer_vcc(fft_len, occupied_carriers, tag_name, "packet_len", 0, "", False) sink = blocks.vector_sink_c() self.tb.connect(src, serializer, sink) self.tb.run () @@ -204,7 +248,7 @@ class qa_ofdm_serializer_vcc (gr_unittest.TestCase): def test_099 (self): """ Make sure it fails if it should """ fft_len = 16 - occupied_carriers = ((1, 3, 4, 11, 12, 17),) + occupied_carriers = ((1, 3, 4, 11, 12, 112),) tag_name = "len" self.assertRaises(RuntimeError, digital.ofdm_serializer_vcc, fft_len, occupied_carriers, tag_name) diff --git a/gr-digital/python/qa_ofdm_sync_sc_cfb.py b/gr-digital/python/qa_ofdm_sync_sc_cfb.py index 06c5a9dc35..3a9bdb5d14 100755 --- a/gr-digital/python/qa_ofdm_sync_sc_cfb.py +++ b/gr-digital/python/qa_ofdm_sync_sc_cfb.py @@ -26,6 +26,7 @@ import random from gnuradio import gr, gr_unittest import blocks_swig as blocks import analog_swig as analog +import channels_swig as channels try: # This will work when feature #505 is added. @@ -78,26 +79,23 @@ class qa_ofdm_sync_sc_cfb (gr_unittest.TestCase): self.assertEqual(numpy.sum(sig1_detect), 1) self.assertEqual(numpy.sum(sig2_detect), 1) - def test_002_freq (self): """ Add a fine frequency offset and see if that get's detected properly """ fft_len = 32 cp_len = 4 - freq_offset = 0.1 # Must stay < 2*pi/fft_len = 0.196 (otherwise, it's coarse) + # This frequency offset is normalized to rads, i.e. \pi == f_s/2 + max_freq_offset = 2*numpy.pi/fft_len # Otherwise, it's coarse + freq_offset = ((2 * random.random()) - 1) * max_freq_offset sig_len = (fft_len + cp_len) * 10 sync_symbol = [(random.randint(0, 1)*2)-1 for x in range(fft_len/2)] * 2 tx_signal = sync_symbol[-cp_len:] + \ sync_symbol + \ [(random.randint(0, 1)*2)-1 for x in range(sig_len)] - mult = blocks.multiply_cc() - add = blocks.add_cc() - sync = digital.ofdm_sync_sc_cfb(fft_len, cp_len) + sync = digital.ofdm_sync_sc_cfb(fft_len, cp_len, True) sink_freq = blocks.vector_sink_f() sink_detect = blocks.vector_sink_b() - self.tb.connect(blocks.vector_source_c(tx_signal), (mult, 0), (add, 0)) - self.tb.connect(analog.sig_source_c(2 * numpy.pi, analog.GR_SIN_WAVE, freq_offset, 1.0), (mult, 1)) - self.tb.connect(analog.noise_source_c(analog.GR_GAUSSIAN, .01), (add, 1)) - self.tb.connect(add, sync) + channel = channels.channel_model(0.005, freq_offset / 2.0 / numpy.pi) + self.tb.connect(blocks.vector_source_c(tx_signal), channel, sync) self.tb.connect((sync, 0), sink_freq) self.tb.connect((sync, 1), sink_detect) self.tb.run() @@ -105,7 +103,6 @@ class qa_ofdm_sync_sc_cfb (gr_unittest.TestCase): est_freq_offset = 2 * phi_hat / fft_len self.assertAlmostEqual(est_freq_offset, freq_offset, places=2) - def test_003_multiburst (self): """ Send several bursts, see if the number of detects is correct. Burst lengths and content are random. @@ -124,75 +121,55 @@ class qa_ofdm_sync_sc_cfb (gr_unittest.TestCase): sync = digital.ofdm_sync_sc_cfb(fft_len, cp_len) sink_freq = blocks.vector_sink_f() sink_detect = blocks.vector_sink_b() - self.tb.connect(blocks.vector_source_c(tx_signal), (add, 0)) - self.tb.connect(analog.noise_source_c(analog.GR_GAUSSIAN, .005), (add, 1)) - self.tb.connect(add, sync) + channel = channels.channel_model(0.005) + self.tb.connect(blocks.vector_source_c(tx_signal), channel, sync) self.tb.connect((sync, 0), sink_freq) self.tb.connect((sync, 1), sink_detect) self.tb.run() - self.assertEqual(numpy.sum(sink_detect.data()), n_bursts, + n_bursts_detected = numpy.sum(sink_detect.data()) + # We allow for one false alarm or missed burst + self.assertTrue(abs(n_bursts_detected - n_bursts) <= 1, msg="""Because of statistics, it is possible (though unlikely) that the number of detected bursts differs slightly. If the number of detects is off by one or two, run the test again and see what happen. Detection error was: %d """ % (numpy.sum(sink_detect.data()) - n_bursts) ) - # FIXME ofdm_mod is currently not working - #def test_004_ofdm_packets (self): - #""" - #Send several bursts, see if the number of detects is correct. - #Burst lengths and content are random. - #""" - #n_bursts = 42 - #fft_len = 64 - #cp_len = 12 - #tx_signal = [] - #packets = [] - #tagname = "length" - #min_packet_length = 100 - #max_packet_length = 100 - #sync_sequence = [random.randint(0, 1)*2-1 for x in range(fft_len/2)] - #for i in xrange(n_bursts): - #packet_length = random.randint(min_packet_length, - #max_packet_length+1) - #packet = [random.randint(0, 255) for i in range(packet_length)] - #packets.append(packet) - #data, tags = tagged_streams.packets_to_vectors( - #packets, tagname, vlen=1) - #total_length = len(data) - - #src = blocks.vector_source_b(data, False, 1, tags) - #mod = ofdm_tx( - #fft_len=fft_len, - #cp_len=cp_len, - #length_tag_name=tagname, - #occupied_carriers=(range(1, 27) + range(38, 64),), - #pilot_carriers=((0,),), - #pilot_symbols=((100,),), - #) - #rate_in = 16000 - #rate_out = 48000 - #ratio = float(rate_out) / rate_in - #throttle1 = gr.throttle(gr.sizeof_gr_complex, rate_in) - #sink_countbursts = gr.vector_sink_c() - #head = gr.head(gr.sizeof_gr_complex, int(total_length * ratio*2)) - #add = gr.add_cc() - #sync = digital.ofdm_sync_sc_cfb(fft_len, cp_len) - #sink_freq = blocks.vector_sink_f() - #sink_detect = blocks.vector_sink_b() - #noise_level = 0.01 - #noise = gr.noise_source_c(gr.GR_GAUSSIAN, noise_level) - #self.tb.connect(src, mod, blocks.null_sink(gr.sizeof_gr_complex)) - #self.tb.connect(insert_zeros, sink_countbursts) - #self.tb.connect(noise, (add, 1)) - #self.tb.connect(add, sync) - #self.tb.connect((sync, 0), sink_freq) - #self.tb.connect((sync, 1), sink_detect) - #self.tb.run() - #count_data = sink_countbursts.data() - #count_tags = sink_countbursts.tags() - #burstcount = tagged_streams.count_bursts(count_data, count_tags, tagname) - #self.assertEqual(numpy.sum(sink_detect.data()), burstcount) + def test_004_ofdm_packets (self): + """ + Send several bursts using ofdm_tx, see if the number of detects is correct. + Burst lengths and content are random. + """ + n_bursts = 42 + fft_len = 64 + cp_len = 16 + # Here, coarse freq offset is allowed + max_freq_offset = 2*numpy.pi/fft_len * 4 + freq_offset = ((2 * random.random()) - 1) * max_freq_offset + tx_signal = [] + packets = [] + tagname = "packet_length" + min_packet_length = 10 + max_packet_length = 50 + sync_sequence = [random.randint(0, 1)*2-1 for x in range(fft_len/2)] + for i in xrange(n_bursts): + packet_length = random.randint(min_packet_length, + max_packet_length+1) + packet = [random.randint(0, 255) for i in range(packet_length)] + packets.append(packet) + data, tags = tagged_streams.packets_to_vectors(packets, tagname, vlen=1) + total_length = len(data) + src = blocks.vector_source_b(data, False, 1, tags) + mod = ofdm_tx(packet_length_tag_key=tagname) + sync = digital.ofdm_sync_sc_cfb(fft_len, cp_len) + sink_freq = blocks.vector_sink_f() + sink_detect = blocks.vector_sink_b() + noise_level = 0.005 + channel = channels.channel_model(noise_level, freq_offset / 2 / numpy.pi) + self.tb.connect(src, mod, channel, sync, sink_freq) + self.tb.connect((sync, 1), sink_detect) + self.tb.run() + self.assertEqual(numpy.sum(sink_detect.data()), n_bursts) if __name__ == '__main__': diff --git a/gr-digital/python/qa_ofdm_txrx.py b/gr-digital/python/qa_ofdm_txrx.py index 15c5b90fe9..568ae0d40a 100755 --- a/gr-digital/python/qa_ofdm_txrx.py +++ b/gr-digital/python/qa_ofdm_txrx.py @@ -21,13 +21,47 @@ # import numpy +import scipy import random from gnuradio import gr, gr_unittest -import digital_swig as digital import blocks_swig as blocks +import digital_swig as digital +import channels_swig as channels from ofdm_txrx import ofdm_tx, ofdm_rx from utils import tagged_streams +# Set this to true if you need to write out data +LOG_DEBUG_INFO=False + +class ofdm_tx_fg (gr.top_block): + def __init__(self, data, len_tag_key): + gr.top_block.__init__(self, "ofdm_tx") + tx_data, tags = tagged_streams.packets_to_vectors((data,), len_tag_key) + src = blocks.vector_source_b(data, False, 1, tags) + self.tx = ofdm_tx(packet_length_tag_key=len_tag_key, debug_log=LOG_DEBUG_INFO) + self.sink = blocks.vector_sink_c() + self.connect(src, self.tx, self.sink) + + def get_tx_samples(self): + return self.sink.data() + +class ofdm_rx_fg (gr.top_block): + def __init__(self, samples, len_tag_key, channel=None, prepend_zeros=100): + gr.top_block.__init__(self, "ofdm_rx") + if prepend_zeros: + samples = (0,) * prepend_zeros + tuple(samples) + src = blocks.vector_source_c(tuple(samples) + (0,) * 1000) + self.rx = ofdm_rx(frame_length_tag_key=len_tag_key, debug_log=LOG_DEBUG_INFO) + if channel is not None: + self.connect(src, channel, self.rx) + else: + self.connect(src, self.rx) + self.sink = blocks.vector_sink_b() + self.connect(self.rx, self.sink) + + def get_rx_bytes(self): + return self.sink.data() + class test_ofdm_txrx (gr_unittest.TestCase): def setUp (self): @@ -36,26 +70,72 @@ class test_ofdm_txrx (gr_unittest.TestCase): def tearDown (self): self.tb = None - def test_001 (self): - pass - #len_tag_key = 'frame_len' - #n_bytes = 100 - #test_data = [random.randint(0, 255) for x in range(n_bytes)] - #tx_data, tags = tagged_streams.packets_to_vectors((test_data,), len_tag_key) - #src = blocks.vector_source_b(test_data, False, 1, tags) - #tx = ofdm_tx(frame_length_tag_key=len_tag_key) - #rx = ofdm_rx(frame_length_tag_key=len_tag_key) - #self.assertEqual(tx.sync_word1, rx.sync_word1) - #self.assertEqual(tx.sync_word2, rx.sync_word2) - #delay = blocks.delay(gr.sizeof_gr_complex, 100) - #noise = analog.noise_source_c(analog.GR_GAUSSIAN, 0.05) - #add = blocks.add_cc() - #sink = blocks.vector_sink_b() - ##self.tb.connect(src, tx, add, rx, sink) - ##self.tb.connect(noise, (add, 1)) - #self.tb.connect(src, tx, blocks.null_sink(gr.sizeof_gr_complex)) - #self.tb.run() + def test_001_tx (self): + """ Just make sure the Tx works in general """ + len_tag_key = 'frame_len' + n_bytes = 52 + n_samples_expected = (numpy.ceil(1.0 * (n_bytes + 4) / 6) + 3) * 80 + test_data = [random.randint(0, 255) for x in range(n_bytes)] + tx_data, tags = tagged_streams.packets_to_vectors((test_data,), len_tag_key) + src = blocks.vector_source_b(test_data, False, 1, tags) + tx = ofdm_tx(packet_length_tag_key=len_tag_key) + tx_fg = ofdm_tx_fg(test_data, len_tag_key) + tx_fg.run() + self.assertEqual(len(tx_fg.get_tx_samples()), n_samples_expected) + + def test_002_rx_only_noise(self): + """ Run the RX with only noise, check it doesn't crash + or return a burst. """ + len_tag_key = 'frame_len' + samples = (0,) * 1000 + channel = channels.channel_model(0.1) + rx_fg = ofdm_rx_fg(samples, len_tag_key, channel) + rx_fg.run() + self.assertEqual(len(rx_fg.get_rx_bytes()), 0) + + def test_003_tx1packet(self): + """ Transmit one packet, with slight AWGN and slight frequency + timing offset. + Check packet is received and no bit errors have occurred. """ + len_tag_key = 'frame_len' + n_bytes = 21 + fft_len = 64 + test_data = tuple([random.randint(0, 255) for x in range(n_bytes)]) + # 1.0/fft_len is one sub-carrier, a fine freq offset stays below that + freq_offset = 1.0 / fft_len * 0.7 + #channel = channels.channel_model(0.01, freq_offset) + channel = None + # Tx + tx_fg = ofdm_tx_fg(test_data, len_tag_key) + tx_fg.run() + tx_samples = tx_fg.get_tx_samples() + # Rx + rx_fg = ofdm_rx_fg(tx_samples, len_tag_key, channel, prepend_zeros=100) + rx_fg.run() + rx_data = rx_fg.get_rx_bytes() + self.assertEqual(tuple(tx_fg.tx.sync_word1), tuple(rx_fg.rx.sync_word1)) + self.assertEqual(tuple(tx_fg.tx.sync_word2), tuple(rx_fg.rx.sync_word2)) + self.assertEqual(test_data, rx_data) + def test_004_tx1packet_large_fO(self): + """ Transmit one packet, with slight AWGN and large frequency offset. + Check packet is received and no bit errors have occurred. """ + fft_len = 64 + len_tag_key = 'frame_len' + n_bytes = 21 + test_data = tuple([random.randint(0, 255) for x in range(n_bytes)]) + #test_data = tuple([255 for x in range(n_bytes)]) + # 1.0/fft_len is one sub-carrier + frequency_offset = 1.0 / fft_len * 2.5 + channel = channels.channel_model(0.00001, frequency_offset) + # Tx + tx_fg = ofdm_tx_fg(test_data, len_tag_key) + tx_fg.run() + tx_samples = tx_fg.get_tx_samples() + # Rx + rx_fg = ofdm_rx_fg(tx_samples, len_tag_key, channel, prepend_zeros=100) + rx_fg.run() + rx_data = rx_fg.get_rx_bytes() + self.assertEqual(test_data, rx_data) if __name__ == '__main__': gr_unittest.run(test_ofdm_txrx, "test_ofdm_txrx.xml") diff --git a/gr-digital/python/qa_packet_headerparser_b.py b/gr-digital/python/qa_packet_headerparser_b.py index ff74da5657..cf8e1e932e 100755 --- a/gr-digital/python/qa_packet_headerparser_b.py +++ b/gr-digital/python/qa_packet_headerparser_b.py @@ -48,8 +48,11 @@ class qa_packet_headerparser_b (gr_unittest.TestCase): 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ) packet_len_tagname = "packet_len" - - src = blocks.vector_source_b(encoded_headers) + random_tag = gr.tag_t() + random_tag.offset = 5 + random_tag.key = pmt.string_to_symbol("foo") + random_tag.value = pmt.from_long(42) + src = blocks.vector_source_b(encoded_headers, tags=(random_tag,)) parser = digital.packet_headerparser_b(32, packet_len_tagname) sink = blocks.message_debug() self.tb.connect(src, parser) @@ -62,7 +65,7 @@ class qa_packet_headerparser_b (gr_unittest.TestCase): msg1 = pmt.to_python(sink.get_message(0)) msg2 = pmt.to_python(sink.get_message(1)) msg3 = pmt.to_python(sink.get_message(2)) - self.assertEqual(msg1, {'packet_len': 4, 'packet_num': 0}) + self.assertEqual(msg1, {'packet_len': 4, 'packet_num': 0, 'foo': 42}) self.assertEqual(msg2, {'packet_len': 2, 'packet_num': 1}) self.assertEqual(msg3, False) @@ -124,8 +127,11 @@ class qa_packet_headerparser_b (gr_unittest.TestCase): self.assertEqual(sink.num_messages(), 2) msg1 = pmt.to_python(sink.get_message(0)) msg2 = pmt.to_python(sink.get_message(1)) - self.assertEqual(msg1, {'packet_len': 193, 'frame_len': 25, 'packet_num': 0}) - self.assertEqual(msg2, {'packet_len': 8, 'frame_len': 1, 'packet_num': 1}) + # Multiply with 4 because unpacked bytes have only two bits + self.assertEqual(msg1, {'packet_len': 193*4, 'frame_len': 25, 'packet_num': 0}) + self.assertEqual(msg2, {'packet_len': 8*4, 'frame_len': 1, 'packet_num': 1}) if __name__ == '__main__': gr_unittest.run(qa_packet_headerparser_b, "qa_packet_headerparser_b.xml") + + |