From 35e5a6ee06f625608ee5e11e12fba418ac0d54d5 Mon Sep 17 00:00:00 2001 From: modustollens Date: Tue, 7 Oct 2014 23:40:54 -0400 Subject: [PATCH] hdl: Added ATSC TX FPGA implementation This revision introduces an FPGA-based ATSC transmitter to offload the pilot insertion, filtering, and shift to baseband. This design expects that 4-bit ATSC symbols are written to the device in little-endian 32-bit words, as shown below. |<------- 32 Bit Word ------->| Bit |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00| Symbol [ X| 7 ][X| 6 ][X| 5 ][X| 4 ][X| 3 ][X| 2 ][X| 1 ][X| 0 ] The FPGA transmits Symbol 0 first, and Symbol 7 last. The three least-significant bits in each symbol's nibble contain values 0 to 7, mapping to values of -7 to 7, with the most-significant bit left unused. This FPGA image may be built by specifying the "atsc_tx" image to the build_bladerf.sh script. The resulting output files will be atsc_txx40.rbf and atsc_txx115.rbf for the x40 and the x115, respectively. Transmitting a pre-made stream on channel 14-1 can be achieved with the following commands in the bladeRF-cli: set frequency 473000000 set samplerate 32286713 2867 10000 set txvga1 -4 set txvga2 20 tx config file= repeat=0 delay=0 tx start --- hdl/fpga/ip/nuand/nuand.do | 12 + hdl/fpga/ip/nuand/simulation/util.vhd | 170 +++++ hdl/fpga/ip/nuand/synthesis/atsc_tx.vhd | 442 +++++++++++ hdl/fpga/ip/nuand/synthesis/bit_stripper.vhd | 104 +++ .../nuand/synthesis/constellation_mapper.vhd | 128 ++-- hdl/fpga/ip/nuand/synthesis/fir_filter.vhd | 93 +++ .../nuand/synthesis/signal_processing_p.vhd | 16 + hdl/fpga/ip/nuand/synthesis/symbol_mapper.vhd | 10 + hdl/fpga/ip/nuand/synthesis/tb/atsc_tx_tb.vhd | 295 ++++++++ .../ip/nuand/synthesis/tb/fir_filter_tb.vhd | 578 +++++++++++++++ .../platforms/bladerf/bladerf-atsc_tx.qip | 31 + .../bladerf/vhdl/bladerf-atsc_tx.vhd | 697 ++++++++++++++++++ hdl/quartus/bladerf.tcl | 3 + hdl/quartus/build_bladerf.sh | 3 +- 14 files changed, 2535 insertions(+), 47 deletions(-) create mode 100644 hdl/fpga/ip/nuand/synthesis/atsc_tx.vhd create mode 100644 hdl/fpga/ip/nuand/synthesis/bit_stripper.vhd create mode 100644 hdl/fpga/ip/nuand/synthesis/fir_filter.vhd create mode 100644 hdl/fpga/ip/nuand/synthesis/signal_processing_p.vhd create mode 100644 hdl/fpga/ip/nuand/synthesis/symbol_mapper.vhd create mode 100644 hdl/fpga/ip/nuand/synthesis/tb/atsc_tx_tb.vhd create mode 100644 hdl/fpga/ip/nuand/synthesis/tb/fir_filter_tb.vhd create mode 100644 hdl/fpga/platforms/bladerf/bladerf-atsc_tx.qip create mode 100644 hdl/fpga/platforms/bladerf/vhdl/bladerf-atsc_tx.vhd diff --git a/hdl/fpga/ip/nuand/nuand.do b/hdl/fpga/ip/nuand/nuand.do index 4cf7eae4d..7e7226170 100644 --- a/hdl/fpga/ip/nuand/nuand.do +++ b/hdl/fpga/ip/nuand/nuand.do @@ -1,6 +1,9 @@ proc compile_nuand { root } { vlib nuand + vcom -work nuand -2008 [file join $root ../altera/tx_fifo/tx_fifo.vhd] + + vcom -work nuand -2008 [file join $root ./synthesis/constellation_mapper.vhd] vcom -work nuand -2008 [file join $root ./synthesis/sync_fifo.vhd] vcom -work nuand -2008 [file join $root ./synthesis/uart.vhd] @@ -18,7 +21,16 @@ proc compile_nuand { root } { vcom -work nuand -2008 [file join $root ./synthesis/handshake.vhd] vcom -work nuand -2008 [file join $root ./synthesis/tb/handshake_tb.vhd] + vcom -work nuand -2008 [file join $root ./synthesis/signal_processing_p.vhd] + + vcom -work nuand -2008 [file join $root ./synthesis/bit_stripper.vhd] + vcom -work nuand -2008 [file join $root ./synthesis/fir_filter.vhd] + vcom -work nuand -2008 [file join $root ./synthesis/atsc_tx.vhd] vcom -work nuand -2008 [file join $root ./simulation/util.vhd] + vcom -work nuand -2008 [file join $root ./synthesis/tb/fir_filter_tb.vhd] + + vcom -work nuand -2008 [file join $root ./synthesis/tb/atsc_tx_tb.vhd] + vcom -work nuand -2008 [file join $root ./simulation/fx3_model.vhd] vcom -work nuand -2008 [file join $root ./simulation/lms6002d_model.vhd] } diff --git a/hdl/fpga/ip/nuand/simulation/util.vhd b/hdl/fpga/ip/nuand/simulation/util.vhd index f169ae75c..94100ce8f 100644 --- a/hdl/fpga/ip/nuand/simulation/util.vhd +++ b/hdl/fpga/ip/nuand/simulation/util.vhd @@ -1,6 +1,10 @@ library ieee ; use ieee.std_logic_1164.all ; +library std; + use std.textio.all; + + -- Utility package package util is @@ -19,3 +23,169 @@ package body util is end package body ; +library ieee ; + use ieee.std_logic_1164.all ; + use ieee.numeric_std.all; +library std; + use std.textio.all; + + +entity data_saver is + generic( + FILENAME : string := "file.dat"; + DATA_WIDTH : natural := 16 + ); + port( + reset : in std_logic; + clock : in std_logic; + data : std_logic_vector(DATA_WIDTH-1 downto 0); + data_valid : std_logic + ); +end entity; + + +architecture arch of data_saver is +begin + + handler : process + FILE fp : text; + variable line_data : line; + begin + -- + wait until falling_edge(reset); + + file_open(fp, FILENAME, WRITE_MODE); + + while (reset = '0') loop + wait until rising_edge(data_valid); + write(line_data, data); + writeline(fp,line_data); + end loop; + file_close(fp); + end process; +end architecture; + + +library ieee ; + use ieee.std_logic_1164.all ; + use ieee.numeric_std.all; +library std; + use std.textio.all; + + +entity signed_saver is + generic( + FILENAME : string := "file.dat"; + DATA_WIDTH : natural := 16 + ); + port( + reset : in std_logic; + clock : in std_logic; + data : signed(DATA_WIDTH-1 downto 0); + data_valid : std_logic + ); +end entity; + + +architecture arch of signed_saver is +begin + + handler : process + FILE fp : text; + variable line_data : line; + begin + -- + wait until falling_edge(reset); + + file_open(fp, FILENAME, WRITE_MODE); + + while (reset = '0') loop + wait until rising_edge(clock); + + if data_valid = '1' then + write(line_data, (to_integer(data))); + writeline(fp,line_data); + end if; + end loop; + file_close(fp); + end process; +end architecture; + + + +library ieee ; + use ieee.std_logic_1164.all ; + use ieee.numeric_std.all; +library std; + use std.textio.all; + + +entity data_reader is + generic( + FILENAME : string := "file.dat"; + DATA_WIDTH : natural := 16 + ); + port( + reset : in std_logic; + clock : in std_logic; + data_request : in std_logic; + data : out std_logic_vector(DATA_WIDTH-1 downto 0); + data_valid : out std_logic + ); +end entity; + + +architecture arch of data_reader is + + type character_array_t is array (natural range <>) of character; +begin + + handler : process + variable line_data : line; + variable tmp : integer; + variable c : character;--_array_t(0 to 3); + + type bin_t is file of character ; + file fp : bin_t ; + variable fs : file_open_status ; + begin + -- + data <= (others => '0'); + data_valid <= '0'; + wait until falling_edge(reset); + + file_open(fs, fp, FILENAME, READ_MODE); + + if( fs /= OPEN_OK ) then + report "File open issues" severity failure ; + end if ; + + --readline(fp,line_data); + while (reset = '0') loop + + wait until rising_edge(clock); + data_valid <= '0'; + + if data_request = '1' then + read(fp, c); + tmp := integer(natural(character'pos(c))); + data(7 downto 0) <= std_logic_vector(to_unsigned(tmp,8)); + read(fp, c); + tmp := integer(natural(character'pos(c))); + data(15 downto 8) <= std_logic_vector(to_unsigned(tmp,8)); + read(fp, c); + tmp := integer(natural(character'pos(c))); + data(23 downto 16) <= std_logic_vector(to_unsigned(tmp,8)); + read(fp, c); + tmp := integer(natural(character'pos(c))); + data(31 downto 24) <= std_logic_vector(to_unsigned(tmp,8)); + + data_valid <= '1'; + wait until rising_edge(clock); + data_valid <= '0'; + end if; + + end loop; + file_close(fp); + end process; +end architecture; diff --git a/hdl/fpga/ip/nuand/synthesis/atsc_tx.vhd b/hdl/fpga/ip/nuand/synthesis/atsc_tx.vhd new file mode 100644 index 000000000..aedc4c8f0 --- /dev/null +++ b/hdl/fpga/ip/nuand/synthesis/atsc_tx.vhd @@ -0,0 +1,442 @@ +library ieee; + use ieee.std_logic_1164.all; + use ieee.numeric_std.all; + +--library work; +-- use work.nco_p.all; + +library nuand; +-- use nuand.util.all; + use nuand.constellation_mapper_p.all; + +entity atsc_tx is + generic( + INPUT_WIDTH : positive := 32; + OUTPUT_WIDTH : positive:= 16; + SYMBOL_DOWNLOAD_WIDTH : positive := 4; + SPS : natural := 3; + CPS : natural := 2 + ); + port( + clock : in std_logic; + reset : in std_logic; + + -- + tx_enable : in std_logic; + + data_in : in std_logic_vector(INPUT_WIDTH-1 downto 0); + data_in_request : out std_logic; + data_in_valid : in std_logic; + + sample_out_i : out signed(OUTPUT_WIDTH-1 downto 0); + sample_out_q : out signed(OUTPUT_WIDTH-1 downto 0); + sample_out_valid : out std_logic + ); +end entity; + + +architecture arch of atsc_tx is + + --bit stripper signals + signal strip_enable : std_logic; + signal symbol_bits : std_logic_vector(SYMBOL_DOWNLOAD_WIDTH-1 downto 0); + signal symbol_bits_request : std_logic; + signal symbol_bits_valid : std_logic; + + --constellation mapper + signal map_inputs : constellation_mapper_inputs_t; + signal map_outputs : constellation_mapper_outputs_t; + + --new mixed signal + signal symbol_mixed : complex_fixed_t; + signal symbol_mixed_valid : std_logic; + + --new upsampled signal + signal symbol_mixed_up : complex_fixed_t; + signal symbol_mixed_up_valid : std_logic; + + --filtered signal + signal filtered_sample : complex_fixed_t; + signal filtered_sample_valid : std_logic; + + --filtered signal + signal tx_symbol : complex_fixed_t; + signal tx_symbol_valid : std_logic; + + --filtered signal + signal registered_symbol : complex_fixed_t; + signal registered_symbol_valid : std_logic; + + --pilot + signal tx_pilot : complex_fixed_t; + + -- todo:create tone dynamically + function create_tone( fs, ftone : real) return complex_fixed_t is + variable rv : complex_fixed_t := (to_signed(0,16),to_signed(0,16)); + begin + return rv; + end function; + + + constant atsc_lo_table : complex_fixed_array_t := ( (to_signed( integer(4096.0*1.5/7.0),16), to_signed( integer(1.5/7.0 * 0.0),16)), + (to_signed( integer(1.5/7.0 * 3547.0),16), to_signed( integer(1.5/7.0 * (-2048.0)),16)), + (to_signed( integer(1.5/7.0 * 2048.0),16), to_signed( integer(1.5/7.0 * (-3547.0)),16)), + (to_signed( integer(1.5/7.0 * 0.0),16), to_signed( integer(1.5/7.0 * (-4096.0)),16)), + (to_signed( integer(1.5/7.0 * (-2048.0)),16), to_signed( integer(1.5/7.0 * (-3547.0)),16)), + (to_signed( integer(1.5/7.0 * (-3547.0)),16), to_signed( integer(1.5/7.0 * (-2048.0)),16)), + (to_signed( integer(1.5/7.0 * (-4096.0)),16), to_signed( integer(1.5/7.0 * 0.0),16)), + (to_signed( integer(1.5/7.0 * (-3547.0)),16), to_signed( integer(1.5/7.0 * 2048.0),16)), + (to_signed( integer(1.5/7.0 * (-2048.0)),16), to_signed( integer(1.5/7.0 * 3547.0),16)), + (to_signed( integer(1.5/7.0 * 0.0),16), to_signed( integer(1.5/7.0 * 4096.0),16)), + (to_signed( integer(1.5/7.0 * 2048.0),16), to_signed( integer(1.5/7.0 * 3547.0),16)), + (to_signed( integer(1.5/7.0 * 3547.0),16), to_signed( integer(1.5/7.0 * 2048.0),16) )); + + + + function cmult( a, b : complex_fixed_t; q : natural ) return complex_fixed_t is + variable rv : complex_fixed_t := (to_signed(0,16),to_signed(0,16)); + begin + rv.re := resize(shift_right(a.re * b.re,q) - shift_right(a.im * b.im, q),rv.re'length); + rv.re := resize(shift_right(a.re * b.re,q) + shift_right(a.im * b.re, q),rv.im'length); + return rv; + end function; + + function cmult_fs4(a : complex_fixed_t; fs_4 : natural) return complex_fixed_t is + begin + case fs_4 is + when 0 => return (a.re,a.im); + when 1 => return (a.im,-a.re); + when 2 => return (-a.re,-a.im); + when 3 => return (-a.im, a.re); + when others => return a; + end case; + end function; + + + constant ATSC_FIR : real_array_t := ( + 0.000952541947487, + 0.001021339440824, + 0.000713161320156, + 0.000099293834852, + -0.000631484665308, + -0.001227676654677, + -0.001457043942307, + -0.001190147219421, + -0.000456941151589, + 0.000545581017437, + 0.001502219876242, + 0.002074087248050, + 0.002011614083297, + 0.001249974660528, + -0.000046895490230, + -0.001513860357388, + -0.002682283457258, + -0.003123186912762, + -0.002593740652748, + -0.001139501073282, + 0.000885746160816, + 0.002897996639881, + 0.004244432533851, + 0.004405493902086, + 0.003177417184785, + 0.000774939833928, + -0.002187174361577, + -0.004841259681936, + -0.006310376881971, + -0.005982741937804, + -0.003734744853560, + -0.000027621448548, + 0.004166072719588, + 0.007602382204908, + 0.009117175655313, + 0.007993866438765, + 0.004237744374945, + -0.001336553068967, + -0.007251920699642, + -0.011732810079681, + -0.013203082387958, + -0.010786054755306, + -0.004660238809862, + 0.003849830264059, + 0.012505380718689, + 0.018680222433748, + 0.020069392108675, + 0.015404316532634, + 0.004979659322029, + -0.009171254616962, + -0.023537317903158, + -0.033804674326444, + -0.035814465077870, + -0.026588972738348, + -0.005178629408275, + 0.026889883444145, + 0.065699566102954, + 0.105575674164579, + 0.140146665827966, + 0.163608592331722, + 0.171912865925582, + 0.163608592331722, + 0.140146665827966, + 0.105575674164579, + 0.065699566102954, + 0.026889883444145, + -0.005178629408275, + -0.026588972738348, + -0.035814465077870, + -0.033804674326444, + -0.023537317903158, + -0.009171254616962, + 0.004979659322029, + 0.015404316532634, + 0.020069392108675, + 0.018680222433748, + 0.012505380718689, + 0.003849830264059, + -0.004660238809862, + -0.010786054755306, + -0.013203082387958, + -0.011732810079681, + -0.007251920699642, + -0.001336553068967, + 0.004237744374945, + 0.007993866438765, + 0.009117175655313, + 0.007602382204908, + 0.004166072719588, + -0.000027621448548, + -0.003734744853560, + -0.005982741937804, + -0.006310376881971, + -0.004841259681936, + -0.002187174361577, + 0.000774939833928, + 0.003177417184785, + 0.004405493902086, + 0.004244432533851, + 0.002897996639881, + 0.000885746160816, + -0.001139501073282, + -0.002593740652748, + -0.003123186912762, + -0.002682283457258, + -0.001513860357388, + -0.000046895490230, + 0.001249974660528, + 0.002011614083297, + 0.002074087248050, + 0.001502219876242, + 0.000545581017437, + -0.000456941151589, + -0.001190147219421, + -0.001457043942307, + -0.001227676654677, + -0.000631484665308, + 0.000099293834852, + 0.000713161320156, + 0.001021339440824, + 0.000952541947487); + + +begin + + + --map outputs + sample_out_i <= registered_symbol.re; + sample_out_q <= registered_symbol.im; + sample_out_valid <= registered_symbol_valid; + + strip_enable <= tx_enable; + + U_stripper : entity work.bit_stripper(arch) + generic map( + INPUT_WIDTH => 32, + OUTPUT_WIDTH => 4 + ) + + port map( + clock => clock, + reset => reset, + + enable => strip_enable, + + in_data => data_in, + in_data_request => data_in_request, + in_data_valid => data_in_valid, + + out_data => symbol_bits, + out_data_request => symbol_bits_request, + out_data_valid => symbol_bits_valid + ); + + --strip symbols + pull_symbols : process(clock,reset) + variable downcount : natural range 0 to 5; + variable cps_downcount : natural range 0 to 5; + begin + -- + if (reset = '1') then + symbol_bits_request <= '0'; + downcount := 2; + elsif rising_edge(clock) then + + if strip_enable = '1' then + + symbol_bits_request <= '0'; + if(downcount = 0) then + symbol_bits_request <= '1'; + downcount := 5; + else + downcount := downcount - 1 ; + end if; + end if; + + end if; + end process; + + map_inputs.bits(map_inputs.bits'length -1 downto 3) <= (others => '0'); + map_inputs.bits(2 downto 0) <= symbol_bits(2 downto 0); + map_inputs.modulation <= VSB_8; + map_inputs.valid <= symbol_bits_valid; + + --map bits to constellation point + U_mapper : entity work.constellation_mapper(arch) + generic map( + MODULATIONS => (VSB_8, MSK) + ) + port map( + clock => clock, + + inputs => map_inputs, + outputs => map_outputs + ); + + --apply fs/4 multiply to center + generate_nco_phase : process(clock,reset) + subtype phases_t is natural range 0 to 3; + variable dphase : phases_t; + begin + if (reset = '1') then + dphase := 0; + symbol_mixed_valid <= '0'; + symbol_mixed <= ((others=> '0'), (others => '0')); + elsif (rising_edge(clock)) then + symbol_mixed_valid <= '0'; + + if map_outputs.valid = '1' then + + symbol_mixed <= cmult_fs4(map_outputs.symbol, dphase); + symbol_mixed_valid <= '1'; + + if dphase = phases_t'high then + dphase := 0; + else + dphase := dphase + 1; + end if; + end if; + end if; + end process; + + --zero stuff to interpolate since this isn't available in the fir yet + upsample : process(clock,reset) + variable downcount : natural range 0 to 2; + variable cps_downcount : natural range 0 to 2; + begin + if (reset = '1') then + downcount := 0; + symbol_mixed_up_valid <= '0'; + elsif rising_edge(clock) then + symbol_mixed_up_valid <= '0'; + + if symbol_mixed_valid = '1' then + symbol_mixed_up <= symbol_mixed; + downcount := SPS-1; + cps_downcount := CPS-1; + symbol_mixed_up_valid <= '1'; + + elsif downcount > 0 then + symbol_mixed_up <= ((others => '0'), (others => '0')); + if cps_downcount = 0 then + symbol_mixed_up_valid <= '1'; + downcount := downcount -1; + cps_downcount := CPS-1; + else + cps_downcount := cps_downcount -1; + end if; + end if; + end if; + end process; + + --filter to bandwidth + U_filter_re : entity work.fir_filter(systolic) + generic map ( + CPS => 1, + H => ATSC_FIR + ) + port map( + clock => clock, + reset => reset, + + in_sample => symbol_mixed_up.re, + in_valid => symbol_mixed_up_valid, + + out_sample => filtered_sample.re, + out_valid => filtered_sample_valid + ); + + U_filter_im : entity work.fir_filter(systolic) + generic map ( + CPS => 1, + H => ATSC_FIR + ) + port map( + clock => clock, + reset => reset, + + in_sample => symbol_mixed_up.im, + in_valid => symbol_mixed_up_valid, + + out_sample => filtered_sample.im, + out_valid => open + ); + + add_pilot : process(clock,reset) + variable pilot : natural range atsc_lo_table'range; + begin + if(reset = '1') then + tx_symbol <= ((others=>'0'),(others=>'0')); + tx_pilot <= ((others=>'0'),(others=>'0')); + tx_symbol_valid <= '0'; + pilot := 0; + elsif rising_edge(clock) then + tx_symbol_valid <= '0'; + + if filtered_sample_valid = '1' then + tx_symbol.re <= filtered_sample.re + shift_right(atsc_lo_table(pilot).re,2); + tx_symbol.im <= filtered_sample.im + shift_right(atsc_lo_table(pilot).im,2); + + tx_pilot.re <= shift_right(atsc_lo_table(pilot).re,1); + tx_pilot.im <= shift_right(atsc_lo_table(pilot).im,1); + tx_symbol_valid <= '1'; + + if pilot = atsc_lo_table'high then + pilot := 0; + else + pilot := pilot +1; + end if; + end if; + end if; + end process; + + register_output : process(clock,reset) + begin + if (reset = '1' ) then + registered_symbol <= ((others=> '0'),(others=>'0')); + registered_symbol_valid <= '0'; + elsif rising_edge(clock) then + registered_symbol.re <= shift_right(tx_symbol.re,2); + registered_symbol.im <= shift_right(tx_symbol.im,2); + registered_symbol_valid <= tx_symbol_valid; + end if; + end process; + +end architecture; diff --git a/hdl/fpga/ip/nuand/synthesis/bit_stripper.vhd b/hdl/fpga/ip/nuand/synthesis/bit_stripper.vhd new file mode 100644 index 000000000..44a11cf6e --- /dev/null +++ b/hdl/fpga/ip/nuand/synthesis/bit_stripper.vhd @@ -0,0 +1,104 @@ +library ieee; + use ieee.std_logic_1164.all; + use ieee.numeric_std.all; + + +entity bit_stripper is + generic( + INPUT_WIDTH : positive := 32; + OUTPUT_WIDTH : positive := 1; + CPS : positive := 2 + ); + + port( + clock : in std_logic; + reset : in std_logic; + + enable : in std_logic; + + in_data : in std_logic_vector(INPUT_WIDTH-1 downto 0); + in_data_request : out std_logic; + in_data_valid : in std_logic; + + out_data : out std_logic_vector(OUTPUT_WIDTH-1 downto 0); + out_data_request : in std_logic; + out_data_valid : out std_logic + ); +end entity; + +architecture arch of bit_stripper is + + signal data_buffer : std_logic_vector( (INPUT_WIDTH + OUTPUT_WIDTH)-1 downto 0); + + type stripper_fsm_t is (FLUSH,SHIFT_BUFFER,OUTPUT_DATA); + signal stripper_fsm : stripper_fsm_t; + + constant THRESHOLD : positive := OUTPUT_WIDTH; + + +begin + + rip_bit : process(clock,reset) + variable bit_count : natural range 0 to (INPUT_WIDTH + THRESHOLD + 1); + begin + if reset = '1' then + data_buffer <= (others =>'0'); + bit_count := 0; + stripper_fsm <= FLUSH; + + out_data <= (others => '0'); + out_data_valid <= '0'; + in_data_request <= '0'; + + elsif rising_edge(clock) then + + out_data_valid <= '0'; + in_data_request <= '0'; + + if enable = '0' then + stripper_fsm <= FLUSH; + end if; + + case stripper_fsm is + when FLUSH => + bit_count := 0; + data_buffer <= (others => '0'); + if enable = '1' then + stripper_fsm <= SHIFT_BUFFER; + end if; + + when SHIFT_BUFFER => + -- + case bit_count is + when 0 => data_buffer <= "0000" & in_data; + when 1 => data_buffer <= "000" & in_data & data_buffer(0); + when 2 => data_buffer <= "00" & in_data & data_buffer(1 downto 0); + when 3 => data_buffer <= '0' & in_data & data_buffer(2 downto 0); + when 4 => data_buffer <= in_data & data_buffer(3 downto 0); + when others => data_buffer <= "0000" & in_data; + end case; + bit_count := bit_count + INPUT_WIDTH; + stripper_fsm <= OUTPUT_DATA; + in_data_request <= '1'; + + when OUTPUT_DATA => + + if out_data_request = '1' then + out_data <= data_buffer(OUTPUT_WIDTH -1 downto 0); + out_data_valid <= '1'; + --shift the data down + data_buffer <= (OUTPUT_WIDTH-1 downto 0 => '0') & data_buffer( (INPUT_WIDTH+OUTPUT_WIDTH)-1 downto OUTPUT_WIDTH); + bit_count := bit_count - OUTPUT_WIDTH; + + end if; + + if bit_count <= THRESHOLD then + stripper_fsm <= SHIFT_BUFFER; + end if; + + + end case; + + end if; + end process; +end architecture; diff --git a/hdl/fpga/ip/nuand/synthesis/constellation_mapper.vhd b/hdl/fpga/ip/nuand/synthesis/constellation_mapper.vhd index 0c09d7568..a1aa6ca9e 100644 --- a/hdl/fpga/ip/nuand/synthesis/constellation_mapper.vhd +++ b/hdl/fpga/ip/nuand/synthesis/constellation_mapper.vhd @@ -44,9 +44,21 @@ package constellation_mapper_p is QAM_512, QAM_1024, QAM_2048, - QAM_4096 + QAM_4096, + VSB_8, + MAX_MODULATION ) ; + type complex_fixed_t is record + re : signed(15 downto 0); + im : signed(15 downto 0); + end record; + + type complex_fixed_array_t is array(natural range <>) of complex_fixed_t; + + type real_array_t is array(natural range <>) of real ; + type complex_array_t is array(natural range <>) of complex ; + -- Array of modulations type constellation_mapper_modulation_array_t is array(natural range <>) of constellation_mapper_modulation_t ; @@ -59,7 +71,7 @@ package constellation_mapper_p is -- Outputs of the mapper type constellation_mapper_outputs_t is record - symbol : complex ; + symbol : complex_fixed_t ; valid : std_logic ; end record ; @@ -76,7 +88,8 @@ library work ; entity constellation_mapper is generic ( - MODULATIONS : constellation_mapper_modulation_array_t := ( MSK, PSK_2, QAM_16 ) + SCALE_FACTOR : real := 4096.0; + MODULATIONS : constellation_mapper_modulation_array_t := ( MSK, PSK_2, QAM_16, VSB_8 ) ) ; port ( clock : in std_logic ; @@ -87,16 +100,13 @@ end entity ; -- constellation_mapper architecture arch of constellation_mapper is - type real_array_t is array(natural range <>) of real ; - type complex_array_t is array(natural range <>) of complex ; - type modulation_info_t is record index : natural ; mask : std_logic_vector(15 downto 0) ; end record ; -- NOTE: If QAM_4096 is ever not the end, this needs to change - type modulation_index_t is array(0 to constellation_mapper_modulation_t'pos(QAM_4096)-1) of modulation_info_t ; + type modulation_index_t is array(0 to constellation_mapper_modulation_t'pos(MAX_MODULATION)-1) of modulation_info_t ; -- Get the number of entries of each of the supported modulations function size( x : constellation_mapper_modulation_t ) return natural is @@ -120,131 +130,150 @@ architecture arch of constellation_mapper is when QAM_1024 => rv := 1024 ; when QAM_2048 => rv := 2048 ; when QAM_4096 => rv := 4096 ; + when VSB_8 => rv := 8; + when MAX_MODULATION => rv := 0; end case ; return rv ; end function ; -- 16-APSK Modulation points - function constellation_apsk_16 return complex_array_t is - variable rv : complex_array_t(0 to 15) := (others =>(0.0,0.0)) ; + function constellation_apsk_16 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 15) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 32-APSK Modulation Points - function constellation_apsk_32 return complex_array_t is - variable rv : complex_array_t(0 to 31) := (others =>(0.0,0.0)) ; + function constellation_apsk_32 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 31) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- MSK Modulation Points - function constellation_msk return complex_array_t is - variable rv : complex_array_t(0 to 1) := (others =>(0.0,0.0)) ; + function constellation_msk return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 1) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- BPSK Modulation Points - function constellation_psk_2 return complex_array_t is - variable rv : complex_array_t(0 to 1) := (others =>(0.0,0.0)) ; + function constellation_psk_2 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 1) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- QPSK Modulation Points - function constellation_psk_4 return complex_array_t is - variable rv : complex_array_t(0 to 3) := (others =>(0.0,0.0)) ; + function constellation_psk_4 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 3) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 8-PSK Modulation Points - function constellation_psk_8 return complex_array_t is - variable rv : complex_array_t(0 to 7) := (others =>(0.0,0.0)) ; + function constellation_psk_8 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 7) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 8-PSK Offset Modulation Points - function constellation_psk_8_gray return complex_array_t is - variable rv : complex_array_t(0 to 7) := (others =>(0.0,0.0)) ; + function constellation_psk_8_gray return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 7) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 16-PSK Modulation Points - function constellation_psk_16 return complex_array_t is - variable rv : complex_array_t(0 to 15) := (others =>(0.0,0.0)) ; + function constellation_psk_16 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 15) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 16-QAM Modulation Points - function constellation_qam_16 return complex_array_t is - variable rv : complex_array_t(0 to 15) := (others =>(0.0,0.0)) ; + function constellation_qam_16 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 15) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 32-QAM Modulation Points - function constellation_qam_32 return complex_array_t is - variable rv : complex_array_t(0 to 31) := (others =>(0.0,0.0)) ; + function constellation_qam_32 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 31) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 64-QAM Modulation Points - function constellation_qam_64 return complex_array_t is - variable rv : complex_array_t(0 to 63) := (others =>(0.0,0.0)) ; + function constellation_qam_64 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 63) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 128-QAM Modulation Points - function constellation_qam_128 return complex_array_t is - variable rv : complex_array_t(0 to 127) := (others =>(0.0,0.0)) ; + function constellation_qam_128 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 127) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 256-QAM Modulation Points - function constellation_qam_256 return complex_array_t is - variable rv : complex_array_t(0 to 255) := (others =>(0.0,0.0)) ; + function constellation_qam_256 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 255) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 512-QAM Modulation Points - function constellation_qam_512 return complex_array_t is - variable rv : complex_array_t(0 to 511) := (others =>(0.0,0.0)) ; + function constellation_qam_512 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 511) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 1024-QAM Modulation Points - function constellation_qam_1024 return complex_array_t is - variable rv : complex_array_t(0 to 1023) := (others =>(0.0,0.0)) ; + function constellation_qam_1024 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 1023) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 2048-QAM Modulation Points - function constellation_qam_2048 return complex_array_t is - variable rv : complex_array_t(0 to 2047) := (others =>(0.0,0.0)) ; + function constellation_qam_2048 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 2047) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; -- 4096-QAM Modulation Points - function constellation_qam_4096 return complex_array_t is - variable rv : complex_array_t(0 to 4096) := (others =>(0.0,0.0)) ; + function constellation_qam_4096 return complex_fixed_array_t is + variable rv : complex_fixed_array_t(0 to 4096) := (others =>(to_signed(0,16),to_signed(0,16))) ; begin return rv ; end function ; + + -- 8- VSB + function constellation_vsb_8 return complex_fixed_array_t is + -- (-7.0,0.0)(-5.0,0.0),(-3.0,0.0),(-1.0,0.0),(1.0,0.0),(3.0,0.0)(5.0,0.0),(7.0,0.0) + variable rv : complex_fixed_array_t(0 to 7) := ( + (to_signed(natural(-7.0/7.0 * SCALE_FACTOR),16), to_signed(0,16)), + (to_signed(natural(-5.0/7.0 * SCALE_FACTOR),16),to_signed(0,16)), + (to_signed(natural(-3.0/7.0 * SCALE_FACTOR),16),to_signed(0,16)), + (to_signed(natural(-1.0/7.0 * SCALE_FACTOR),16),to_signed(0,16)), + (to_signed(natural(1.0/7.0 * SCALE_FACTOR),16),to_signed(0,16)), + (to_signed(natural(3.0/7.0 * SCALE_FACTOR),16),to_signed(0,16)), + (to_signed(natural(5.0/7.0 * SCALE_FACTOR),16),to_signed(0,16)), + (to_signed(natural(7.0/7.0 * SCALE_FACTOR),16),to_signed(0,16))); + begin + return rv; + end function; + -- Get each of the constellation arrays - function constellation( x : constellation_mapper_modulation_t ) return complex_array_t is + function constellation( x : constellation_mapper_modulation_t ) return complex_fixed_array_t is begin case x is when APSK_16 => return constellation_apsk_16 ; @@ -264,6 +293,8 @@ architecture arch of constellation_mapper is when QAM_1024 => return constellation_qam_1024 ; when QAM_2048 => return constellation_qam_2048 ; when QAM_4096 => return constellation_qam_4096 ; + when VSB_8 => return constellation_vsb_8 ; + when MAX_MODULATION => return constellation_msk; end case ; end function ; @@ -281,18 +312,23 @@ architecture arch of constellation_mapper is end function ; -- Take in the array and create the modulations recursively - function generate_modulation_table( x : constellation_mapper_modulation_array_t ) return complex_array_t is + function generate_modulation_table( x : constellation_mapper_modulation_array_t ) return complex_fixed_array_t is begin - return constellation(x(x'low)) & generate_modulation_table(x(x'low+1 to x'high)) ; + report "x'low is " & integer'image(x'low) & " x'high is " &integer'image(x'high); + if (x'low + 1 <= x'high) then + return constellation(x(x'low)) & generate_modulation_table(x(x'low+1 to x'high)) ; + else + return constellation(x(x'low)); + end if; end function ; -- Constant values generated constant modulation_index : modulation_index_t := calculate_indices(MODULATIONS) ; - constant modulation_table : complex_array_t := generate_modulation_table(MODULATIONS) ; + constant modulation_table : complex_fixed_array_t := generate_modulation_table(MODULATIONS) ; -- Actual signals! signal base : modulation_info_t ; - signal symbol : complex ; + signal symbol : complex_fixed_t ; begin diff --git a/hdl/fpga/ip/nuand/synthesis/fir_filter.vhd b/hdl/fpga/ip/nuand/synthesis/fir_filter.vhd new file mode 100644 index 000000000..1ff103c32 --- /dev/null +++ b/hdl/fpga/ip/nuand/synthesis/fir_filter.vhd @@ -0,0 +1,93 @@ +library ieee; + use ieee.std_logic_1164.all; + use ieee.numeric_std.all; + use ieee.math_real.all; + +library work; + use work.constellation_mapper_p.all; + +entity fir_filter is + generic ( + INPUT_WIDTH : positive := 16; + OUTPUT_WIDTH : positive := 16; + + CPS : positive := 1; + Q : positive := 12; + + H : real_array_t := (1.0, 0.0 ,1.0); + + ACCUM_SCALE : positive := 32; + OUTPUT_SHIFT : positive := 12 + + ); + port( + clock : in std_logic; + reset : in std_logic; + + --input signal + in_sample : in signed(INPUT_WIDTH-1 downto 0); + in_valid : in std_logic; + + --output signal + out_sample : out signed(OUTPUT_WIDTH-1 downto 0); + out_valid : out std_logic + ); + +end entity; + +architecture systolic of fir_filter is + + function mult (a,b : signed; Q : positive) return signed is + begin + return resize(shift_right(a*b,Q),a'length); + end function; + + --integer(ceil(log2( real(H'length*(2**(INPUT_WIDTH-1))) ))) + type accum_t is array( natural range <>) of signed( ACCUM_SCALE-1 downto 0); + signal accum : accum_t(H'range); + + + type coeff_t is array( natural range <>) of signed( (INPUT_WIDTH-1 ) downto 0); + signal coeff : coeff_t(H'range); + + function scale_coeffecients (h : real_array_t; q : positive) return coeff_t is + variable retval : coeff_t(h'range); + begin + for i in h'range loop + retval(i) := to_signed( integer(round(h(i) * real(2**q))), OUTPUT_WIDTH); + end loop; + return retval; + end function; + + constant COEF : coeff_t(H'range) := scale_coeffecients(H,Q); + +begin + + mac : process(clock, reset) + begin + if reset = '1' then + accum <= (others => (others => '0')); + out_valid <= '0'; + out_sample <= (others =>'0'); + + elsif rising_edge(clock) then + -- + out_valid <= '0'; + if in_valid = '1' then + + for i in accum'range loop + if i = accum'high then + accum(i) <= COEF(i)*in_sample; + else + accum(i) <= accum(i+1) + COEF(i)*in_sample; + end if; + end loop; + + out_valid <= '1'; + out_sample <= resize(shift_right(accum(0),OUTPUT_SHIFT),out_sample'length); + end if; + end if; + + end process; + +end architecture; diff --git a/hdl/fpga/ip/nuand/synthesis/signal_processing_p.vhd b/hdl/fpga/ip/nuand/synthesis/signal_processing_p.vhd new file mode 100644 index 000000000..65035e1e1 --- /dev/null +++ b/hdl/fpga/ip/nuand/synthesis/signal_processing_p.vhd @@ -0,0 +1,16 @@ +library ieee; + use ieee.std_logic_1164.all; + use ieee.numeric_std.all; + +package signal_processing_p is + + type real_array_t is array (natural range <>) of real; + +end package signal_processing_p; + + +package body signal_processing_p is + + + +end package body; \ No newline at end of file diff --git a/hdl/fpga/ip/nuand/synthesis/symbol_mapper.vhd b/hdl/fpga/ip/nuand/synthesis/symbol_mapper.vhd new file mode 100644 index 000000000..2d675391c --- /dev/null +++ b/hdl/fpga/ip/nuand/synthesis/symbol_mapper.vhd @@ -0,0 +1,10 @@ + +library ieee; + use ieee.std_logic_1164.all; + use ieee.numeric_std.all; + + +entity symbol_mapper is + generic( + + ); diff --git a/hdl/fpga/ip/nuand/synthesis/tb/atsc_tx_tb.vhd b/hdl/fpga/ip/nuand/synthesis/tb/atsc_tx_tb.vhd new file mode 100644 index 000000000..5e83d5ae3 --- /dev/null +++ b/hdl/fpga/ip/nuand/synthesis/tb/atsc_tx_tb.vhd @@ -0,0 +1,295 @@ +library ieee; + use ieee.std_logic_1164.all; + use ieee.numeric_std.all; + use ieee.math_real.all; + use ieee.math_complex.all; + +library std; + use std.env.all; + +library work; + use work.nco_p.all; + +library nuand; + use nuand.util.all; + use nuand.constellation_mapper_p.all; + +entity atsc_tx_tb is +end entity; + +architecture arch of atsc_tx_tb is + + signal clock : std_logic := '1'; + signal reset : std_logic := '1'; + + signal nco_inputs : nco_input_t; + signal nco_outputs : nco_output_t; + + + --fifo management + type fifo_t is record + aclr : std_logic ; + + wclock : std_logic ; + wdata : std_logic_vector(31 downto 0) ; + wreq : std_logic ; + wempty : std_logic ; + wfull : std_logic ; + wused : std_logic_vector(11 downto 0) ; + + rclock : std_logic ; + rdata : std_logic_vector(31 downto 0) ; + rreq : std_logic ; + rempty : std_logic ; + rfull : std_logic ; + rused : std_logic_vector(11 downto 0) ; + end record ; + + signal tx_sample_fifo :fifo_t; + + signal tx_enable : std_logic; + signal tx_valid : std_logic; + + --filtered signal + signal tx_symbol : complex_fixed_t; + signal tx_symbol_valid : std_logic; + + -- + constant vsb_mix_table : complex_fixed_array_t := ( (to_signed(4096,16), to_signed(0,16)), + (to_signed(0,16), to_signed(-4096,16)), + (to_signed(-4096,16), to_signed(0,16)), + (to_signed(0,16), to_signed(4096,16) )); + + function cmult( a, b : complex_fixed_t; q : natural ) return complex_fixed_t is + variable rv : complex_fixed_t := (to_signed(0,16),to_signed(0,16)); + begin + rv.re := resize(shift_right(a.re * b.re,q) - shift_right(a.im * b.im, q),rv.re'length); + rv.re := resize(shift_right(a.re * b.re,q) + shift_right(a.im * b.re, q),rv.im'length); + return rv; + end function; + + function cmult_fs4(a : complex_fixed_t; fs_4 : natural) return complex_fixed_t is + begin + case fs_4 is + when 0 => return (a.re,a.im); + when 1 => return (a.im,-a.re); + when 2 => return (-a.re,-a.im); + when 3 => return (-a.im, a.re); + when others => return a; + end case; + end function; + + + + procedure nop( signal clock : in std_logic; count : in natural) is + begin + for i in 1 to count loop + wait until rising_edge(clock); + end loop; + end procedure; + + constant SPS : natural := 3; + + constant RANDOM_MULT : unsigned(63 downto 0) := x"2545f4914f6cdd1d"; + + function random_xor (old : unsigned) return unsigned is + variable x : unsigned(old'range) := (others=>'0'); + variable y : unsigned(old'range) := (others=>'0'); + variable z : unsigned(old'range) := (others=>'0'); + begin + x := old xor shift_right(old,12); + y := x xor shift_left(x,25); + z := y xor shift_right(y,27); + return resize( shift_right(z * RANDOM_MULT,z'length),z'length); + end function; + + signal data_request_reader : std_logic; + signal delay_request_reader : std_logic; +begin + + clock <= not clock after 1 ns; + + --construct fir filter under test + --U_filter : entity work.fir_filter(systolic) + -- generic map() + -- port map(); + + tb : process + variable delta : signed(15 downto 0); + + begin + reset <= '1'; + delay_request_reader <='0'; + --setup nco input + delta := to_signed(1,delta'length); + nco_inputs.valid <= '0'; + nco_inputs.dphase <= (others => '0'); + + nop(clock, 10); + + --setup state + + --release reset; + reset <= '0'; + nop(clock, 10); + + delay_request_reader <='1'; + + --clock in data + for i in 0 to 100000 loop + + + + if (nco_inputs.dphase = 4096) then + delta := to_signed(-1,delta'length); + elsif(nco_inputs.dphase = -4096) then + delta := to_signed(1,delta'length); + end if; + + nco_inputs.dphase <= nco_inputs.dphase + delta; + nco_inputs.valid <= '1' ; + wait until rising_edge(clock); + nco_inputs.valid <= '0'; + + nop(clock,10); + end loop; + --save result + + + end process; + + data_request_reader <= '1' when ((unsigned(tx_sample_fifo.wused) < to_unsigned(2048,tx_sample_fifo.wused'length)) and delay_request_reader ='1' )else '0'; + + U_file_reader : entity work.data_reader + generic map ( + FILENAME => "input.dat", + DATA_WIDTH => 32 + ) + port map ( + reset => reset, + clock => clock, + + data_request => data_request_reader, + data => tx_sample_fifo.wdata, + data_valid => tx_sample_fifo.wreq + ); + + + feed_fifo: process + variable in_data: unsigned(31 downto 0) := x"1337cafe"; + begin + tx_enable <= '0'; + --tx_sample_fifo.wdata <= (others => '0'); + --tx_sample_fifo.wreq <= '0'; + nop(clock,10); + + + + for i in 0 to 10 loop + --tx_sample_fifo.wdata <= std_logic_vector(in_data); + --tx_sample_fifo.wreq <= '1'; + in_data :=random_xor(in_data); + + wait until rising_edge(clock); + --tx_sample_fifo.wreq <= '0'; + nop(clock,10); + end loop; + + tx_enable <= '1'; + + for i in 0 to 100000 loop + --tx_sample_fifo.wdata <= std_logic_vector(in_data); + --tx_sample_fifo.wreq <= '1'; + in_data :=random_xor(in_data); + + wait until rising_edge(clock); + --tx_sample_fifo.wreq <= '0'; + nop(clock,20); + end loop; + end process; + + --tmp + tx_sample_fifo.aclr <= reset ; + tx_sample_fifo.rclock <= clock ; + tx_sample_fifo.wclock <= clock ; + + U_bit_fifo : entity work.tx_fifo + port map ( + aclr => tx_sample_fifo.aclr, + data => tx_sample_fifo.wdata, + rdclk => tx_sample_fifo.rclock, + rdreq => tx_sample_fifo.rreq, + wrclk => tx_sample_fifo.wclock, + wrreq => tx_sample_fifo.wreq, + q => tx_sample_fifo.rdata, + rdempty => tx_sample_fifo.rempty, + rdfull => tx_sample_fifo.rfull, + rdusedw => tx_sample_fifo.rused, + wrempty => tx_sample_fifo.wempty, + wrfull => tx_sample_fifo.wfull, + wrusedw => tx_sample_fifo.wused + ); + + tx_valid_register : process(reset, clock) + begin + if (reset = '1') then + tx_valid <= '0'; + elsif rising_edge(clock) then + tx_valid <= tx_sample_fifo.rreq; + end if; + end process; + + U_atsc_transmitter : entity work.atsc_tx(arch) + generic map( + INPUT_WIDTH => tx_sample_fifo.rdata'length, + OUTPUT_WIDTH => tx_symbol.re'length + ) + port map ( + reset => reset, + clock => clock, + + tx_enable => tx_enable, + data_in => tx_sample_fifo.rdata, + data_in_request => tx_sample_fifo.rreq, + data_in_valid => tx_valid, + + sample_out_i => tx_symbol.re, + sample_out_q => tx_symbol.im, + sample_out_valid => tx_symbol_valid + + ); + + + U_data_saver_im : entity work.signed_saver(arch) + generic map ( + FILENAME => "tx_sym.im" + ) + port map( + clock => clock, + reset => reset, + data => (tx_symbol.im), + data_valid => tx_symbol_valid + ); + + U_data_saver_re : entity work.signed_saver(arch) + generic map ( + FILENAME => "tx_sym.re" + ) + port map( + clock => clock, + reset => reset, + data => (tx_symbol.re), + data_valid => tx_symbol_valid + ); + + --U_nco_test : entity work.nco(arch) + -- port map ( + -- clock => clock, + -- reset => reset, + + -- inputs => nco_inputs, + -- outputs => nco_outputs + -- ); + + +end architecture; \ No newline at end of file diff --git a/hdl/fpga/ip/nuand/synthesis/tb/fir_filter_tb.vhd b/hdl/fpga/ip/nuand/synthesis/tb/fir_filter_tb.vhd new file mode 100644 index 000000000..c23857a65 --- /dev/null +++ b/hdl/fpga/ip/nuand/synthesis/tb/fir_filter_tb.vhd @@ -0,0 +1,578 @@ +library ieee; + use ieee.std_logic_1164.all; + use ieee.numeric_std.all; + use ieee.math_real.all; + use ieee.math_complex.all; + +library std; + use std.env.all; + +library work; + use work.nco_p.all; + +library nuand; + use nuand.util.all; + use nuand.constellation_mapper_p.all; + +entity fir_filter_tb is +end entity; + +architecture arch of fir_filter_tb is + + signal clock : std_logic := '1'; + signal reset : std_logic := '1'; + + signal nco_inputs : nco_input_t; + signal nco_outputs : nco_output_t; + + + + + + --fifo management + type fifo_t is record + aclr : std_logic ; + + wclock : std_logic ; + wdata : std_logic_vector(31 downto 0) ; + wreq : std_logic ; + wempty : std_logic ; + wfull : std_logic ; + wused : std_logic_vector(11 downto 0) ; + + rclock : std_logic ; + rdata : std_logic_vector(31 downto 0) ; + rreq : std_logic ; + rempty : std_logic ; + rfull : std_logic ; + rused : std_logic_vector(11 downto 0) ; + end record ; + + signal tx_sample_fifo :fifo_t; + + --bit stripper signals + signal strip_enable : std_logic; + signal symbol_bits : std_logic_vector(2 downto 0); + signal symbol_bits_request : std_logic; + signal symbol_bits_valid : std_logic; + + --constellation mapper + signal map_inputs : constellation_mapper_inputs_t; + signal map_outputs : constellation_mapper_outputs_t; + + --new mixed signal + signal symbol_mixed : complex_fixed_t; + signal symbol_mixed_valid : std_logic; + + --new upsampled signal + signal symbol_mixed_up : complex_fixed_t; + signal symbol_mixed_up_valid : std_logic; + + --filtered signal + signal filtered_sample : complex_fixed_t; + signal filtered_sample_valid : std_logic; + + --filtered signal + signal tx_symbol : complex_fixed_t; + signal tx_symbol_valid : std_logic; + + -- + constant vsb_mix_table : complex_fixed_array_t := ( (to_signed(4096,16), to_signed(0,16)), + (to_signed(0,16), to_signed(-4096,16)), + (to_signed(-4096,16), to_signed(0,16)), + (to_signed(0,16), to_signed(4096,16) )); + + + -- + +-- 4.0960e+03 - 0.0000e+00i +-- 3.5472e+03 - 2.0480e+03i +-- 2.0480e+03 - 3.5472e+03i +-- 2.5081e-13 - 4.0960e+03i +-- -2.0480e+03 - 3.5472e+03i +-- -3.5472e+03 - 2.0480e+03i +-- -4.0960e+03 - 5.0162e-13i +-- 3.5472e+03 + 2.0480e+03i +-- -2.0480e+03 + 3.5472e+03i +-- -7.5242e-13 + 4.0960e+03i +-- 2.0480e+03 + 3.5472e+03i +-- 3.5472e+03 + 2.0480e+03i +-- 4.0960e+03 + 1.0032e-12i + + constant atsc_lo_table : complex_fixed_array_t := ( (to_signed(4096,16), to_signed(0,16)), + (to_signed(3547,16), to_signed(-2048,16)), + (to_signed(2048,16), to_signed(-3547,16)), + (to_signed(0,16), to_signed(-4096,16)), + (to_signed(-2048,16), to_signed(-3547,16)), + (to_signed(-3547,16), to_signed(-2048,16)), + (to_signed(-4096,16), to_signed(0,16)), + (to_signed(3547,16), to_signed(2048,16)), + (to_signed(-2048,16), to_signed(3547,16)), + (to_signed(0,16), to_signed(4096,16)), + (to_signed(2048,16), to_signed(3547,16)), + (to_signed(3547,16), to_signed(4096,16) )); + + + function cmult( a, b : complex_fixed_t; q : natural ) return complex_fixed_t is + variable rv : complex_fixed_t := (to_signed(0,16),to_signed(0,16)); + begin + rv.re := resize(shift_right(a.re * b.re,q) - shift_right(a.im * b.im, q),rv.re'length); + rv.re := resize(shift_right(a.re * b.re,q) + shift_right(a.im * b.re, q),rv.im'length); + return rv; + end function; + + function cmult_fs4(a : complex_fixed_t; fs_4 : natural) return complex_fixed_t is + begin + case fs_4 is + when 0 => return (a.re,a.im); + when 1 => return (a.im,-a.re); + when 2 => return (-a.re,-a.im); + when 3 => return (-a.im, a.re); + when others => return a; + end case; + end function; + + + + procedure nop( signal clock : in std_logic; count : in natural) is + begin + for i in 1 to count loop + wait until rising_edge(clock); + end loop; + end procedure; + + constant SPS : natural := 3; + + constant ATSC_FIR : real_array_t := ( + 0.000952541947487, + 0.001021339440824, + 0.000713161320156, + 0.000099293834852, + -0.000631484665308, + -0.001227676654677, + -0.001457043942307, + -0.001190147219421, + -0.000456941151589, + 0.000545581017437, + 0.001502219876242, + 0.002074087248050, + 0.002011614083297, + 0.001249974660528, + -0.000046895490230, + -0.001513860357388, + -0.002682283457258, + -0.003123186912762, + -0.002593740652748, + -0.001139501073282, + 0.000885746160816, + 0.002897996639881, + 0.004244432533851, + 0.004405493902086, + 0.003177417184785, + 0.000774939833928, + -0.002187174361577, + -0.004841259681936, + -0.006310376881971, + -0.005982741937804, + -0.003734744853560, + -0.000027621448548, + 0.004166072719588, + 0.007602382204908, + 0.009117175655313, + 0.007993866438765, + 0.004237744374945, + -0.001336553068967, + -0.007251920699642, + -0.011732810079681, + -0.013203082387958, + -0.010786054755306, + -0.004660238809862, + 0.003849830264059, + 0.012505380718689, + 0.018680222433748, + 0.020069392108675, + 0.015404316532634, + 0.004979659322029, + -0.009171254616962, + -0.023537317903158, + -0.033804674326444, + -0.035814465077870, + -0.026588972738348, + -0.005178629408275, + 0.026889883444145, + 0.065699566102954, + 0.105575674164579, + 0.140146665827966, + 0.163608592331722, + 0.171912865925582, + 0.163608592331722, + 0.140146665827966, + 0.105575674164579, + 0.065699566102954, + 0.026889883444145, + -0.005178629408275, + -0.026588972738348, + -0.035814465077870, + -0.033804674326444, + -0.023537317903158, + -0.009171254616962, + 0.004979659322029, + 0.015404316532634, + 0.020069392108675, + 0.018680222433748, + 0.012505380718689, + 0.003849830264059, + -0.004660238809862, + -0.010786054755306, + -0.013203082387958, + -0.011732810079681, + -0.007251920699642, + -0.001336553068967, + 0.004237744374945, + 0.007993866438765, + 0.009117175655313, + 0.007602382204908, + 0.004166072719588, + -0.000027621448548, + -0.003734744853560, + -0.005982741937804, + -0.006310376881971, + -0.004841259681936, + -0.002187174361577, + 0.000774939833928, + 0.003177417184785, + 0.004405493902086, + 0.004244432533851, + 0.002897996639881, + 0.000885746160816, + -0.001139501073282, + -0.002593740652748, + -0.003123186912762, + -0.002682283457258, + -0.001513860357388, + -0.000046895490230, + 0.001249974660528, + 0.002011614083297, + 0.002074087248050, + 0.001502219876242, + 0.000545581017437, + -0.000456941151589, + -0.001190147219421, + -0.001457043942307, + -0.001227676654677, + -0.000631484665308, + 0.000099293834852, + 0.000713161320156, + 0.001021339440824, + 0.000952541947487); +begin + + clock <= not clock after 1 ns; + + --construct fir filter under test + --U_filter : entity work.fir_filter(systolic) + -- generic map() + -- port map(); + + tb : process + variable delta : signed(15 downto 0); + begin + reset <= '1'; + + --setup nco input + delta := to_signed(1,delta'length); + nco_inputs.valid <= '0'; + nco_inputs.dphase <= (others => '0'); + + nop(clock, 10); + + --setup state + + --release reset; + reset <= '0'; + nop(clock, 10); + + --clock in data + for i in 0 to 100000 loop + + + + if (nco_inputs.dphase = 4096) then + delta := to_signed(-1,delta'length); + elsif(nco_inputs.dphase = -4096) then + delta := to_signed(1,delta'length); + end if; + + nco_inputs.dphase <= nco_inputs.dphase + delta; + nco_inputs.valid <= '1' ; + wait until rising_edge(clock); + nco_inputs.valid <= '0'; + + nop(clock,10); + end loop; + + + + --save result + + + end process; + + feed_fifo: process + variable in_data: unsigned(31 downto 0); + begin + strip_enable <= '0'; + tx_sample_fifo.wdata <= (others => '0'); + tx_sample_fifo.wreq <= '0'; + in_data := to_unsigned(0,in_data'length); + nop(clock,10); + + nop(clock,10); + + + for i in 0 to 10 loop + tx_sample_fifo.wdata <= std_logic_vector(in_data); + tx_sample_fifo.wreq <= '1'; + in_data := in_data + 1; + + wait until rising_edge(clock); + tx_sample_fifo.wreq <= '0'; + nop(clock,10); + end loop; + + strip_enable <= '1'; + + for i in 0 to 1000 loop + tx_sample_fifo.wdata <= std_logic_vector(in_data); + tx_sample_fifo.wreq <= '1'; + in_data := in_data + 1; + + wait until rising_edge(clock); + tx_sample_fifo.wreq <= '0'; + nop(clock,20); + end loop; + end process; + + --tmp + tx_sample_fifo.aclr <= reset ; + tx_sample_fifo.rclock <= clock ; + tx_sample_fifo.wclock <= clock ; + U_bit_fifo : entity work.tx_fifo + port map ( + aclr => tx_sample_fifo.aclr, + data => tx_sample_fifo.wdata, + rdclk => tx_sample_fifo.rclock, + rdreq => tx_sample_fifo.rreq, + wrclk => tx_sample_fifo.wclock, + wrreq => tx_sample_fifo.wreq, + q => tx_sample_fifo.rdata, + rdempty => tx_sample_fifo.rempty, + rdfull => tx_sample_fifo.rfull, + rdusedw => tx_sample_fifo.rused, + wrempty => tx_sample_fifo.wempty, + wrfull => tx_sample_fifo.wfull, + wrusedw => tx_sample_fifo.wused + ); + + + U_stripper : entity work.bit_stripper(arch) + generic map( + INPUT_WIDTH => 32, + OUTPUT_WIDTH => 3 + ) + + port map( + clock => clock, + reset => reset, + + enable => strip_enable, + + in_data => tx_sample_fifo.rdata, + in_data_request => tx_sample_fifo.rreq, + in_data_valid => '1', + + out_data => symbol_bits, + out_data_request => symbol_bits_request, + out_data_valid => symbol_bits_valid + ); + + --strip symbols + pull_symbols : process(clock,reset) + variable downcount : natural range 0 to 2; + begin + -- + if (reset = '1') then + symbol_bits_request <= '0'; + downcount := 2; + elsif rising_edge(clock) then + + if strip_enable = '1' then + + symbol_bits_request <= '0'; + if(downcount = 0) then + symbol_bits_request <= '1'; + downcount := 2; + else + downcount := downcount - 1 ; + end if; + end if; + + end if; + end process; + + map_inputs.bits(map_inputs.bits'length -1 downto 3) <= (others => '0'); + map_inputs.bits(2 downto 0) <= symbol_bits; + map_inputs.modulation <= VSB_8; + map_inputs.valid <= symbol_bits_valid; + + --map bits to constellation point + U_mapper : entity work.constellation_mapper(arch) + generic map( + MODULATIONS => (VSB_8, MSK) + ) + port map( + clock => clock, + + inputs => map_inputs, + outputs => map_outputs + ); + + generate_nco_phase : process(clock,reset) + variable dphase : natural range 0 to 3; + begin + if (reset = '1') then + dphase := 0; + symbol_mixed_valid <= '0'; + symbol_mixed <= ((others=> '0'), (others => '0')); + elsif (rising_edge(clock)) then + symbol_mixed_valid <= '0'; + + if map_outputs.valid = '1' then + + symbol_mixed <= cmult_fs4(map_outputs.symbol, dphase); + symbol_mixed_valid <= '1'; + + if dphase = vsb_mix_table'high then + dphase := 0; + else + dphase := dphase + 1; + end if; + end if; + end if; + end process; + + --zero stuff to interpolate since this isn't available in the fir yet + upsample : process(clock,reset) + variable downcount : natural range 0 to 2; + begin + if (reset = '1') then + downcount := 0; + symbol_mixed_up_valid <= '0'; + elsif rising_edge(clock) then + symbol_mixed_up_valid <= '0'; + if symbol_mixed_valid = '1' then + symbol_mixed_up <= symbol_mixed; + downcount := SPS-1; + symbol_mixed_up_valid <= '1'; + elsif downcount > 0 then + downcount := downcount -1; + symbol_mixed_up <= ((others => '0'), (others => '0')); + symbol_mixed_up_valid <= '1'; + end if; + end if; + end process; + + --filter to bandiwdth + U_filter_re : entity work.fir_filter(systolic) + generic map ( + CPS => 1, + H => ATSC_FIR + ) + port map( + clock => clock, + reset => reset, + + in_sample => symbol_mixed_up.re, + in_valid => symbol_mixed_up_valid, + + out_sample => filtered_sample.re, + out_valid => filtered_sample_valid + ); + + + U_filter_im : entity work.fir_filter(systolic) + generic map ( + CPS => 1, + H => ATSC_FIR + ) + port map( + clock => clock, + reset => reset, + + in_sample => symbol_mixed_up.im, + in_valid => symbol_mixed_up_valid, + + out_sample => filtered_sample.im, + out_valid => open + ); + + add_pilot : process(clock,reset) + variable pilot : natural range atsc_lo_table'range; + begin + if(reset = '1') then + tx_symbol <= ((others=>'0'),(others=>'0')); + tx_symbol_valid <= '0'; + elsif rising_edge(clock) then + tx_symbol_valid <= '0'; + + if filtered_sample_valid = '1' then + tx_symbol.re <= symbol_mixed.re + atsc_lo_table(pilot).re; + tx_symbol.im <= symbol_mixed.im + atsc_lo_table(pilot).im; + tx_symbol_valid <= '1'; + + if pilot = atsc_lo_table'high then + pilot := 0; + else + pilot := pilot +1; + end if; + end if; + end if; + end process; + + U_data_saver_im : entity work.signed_saver(arch) + generic map ( + FILENAME => "tx_sym.im" + ) + port map( + clock => clock, + reset => reset, + data => (tx_symbol.im), + data_valid => tx_symbol_valid + ); + + U_data_saver_re : entity work.signed_saver(arch) + generic map ( + FILENAME => "tx_sym.re" + ) + port map( + clock => clock, + reset => reset, + data => (tx_symbol.re), + data_valid => tx_symbol_valid + ); + + + + --U_nco_test : entity work.nco(arch) + -- port map ( + -- clock => clock, + -- reset => reset, + + -- inputs => nco_inputs, + -- outputs => nco_outputs + -- ); + + + + +end architecture; \ No newline at end of file diff --git a/hdl/fpga/platforms/bladerf/bladerf-atsc_tx.qip b/hdl/fpga/platforms/bladerf/bladerf-atsc_tx.qip new file mode 100644 index 000000000..5fb6f94e9 --- /dev/null +++ b/hdl/fpga/platforms/bladerf/bladerf-atsc_tx.qip @@ -0,0 +1,31 @@ +# Convenience variable +set here $::quartus(qip_path) + +# Altera IP +set_global_assignment -name QIP_FILE [file normalize [file join $here ../../ip/altera/pll/pll.qip]] +set_global_assignment -name QIP_FILE [file normalize [file join $here ../../ip/altera/fx3_pll/fx3_pll.qip]] +set_global_assignment -name QIP_FILE [file normalize [file join $here ../../ip/altera/nios_system/synthesis/nios_system.qip]] +set_global_assignment -name QIP_FILE [file normalize [file join $here ../../ip/altera/rx_fifo/rx_fifo.qip]] +set_global_assignment -name QIP_FILE [file normalize [file join $here ../../ip/altera/tx_fifo/tx_fifo.qip]] + +# Explicitly include Nios mem_init +set_global_assignment -name QIP_FILE [file normalize [file join $here ../../ip/altera/nios_system/software/lms_spi_controller/mem_init/meminit.qip]] + +# Implementation details +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/fir_filter.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/bit_stripper.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/constellation_mapper.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/atsc_tx.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/tan_table.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/iq_correction.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/signal_generator.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/synchronizer.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/reset_synchronizer.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/fifo_reader.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/fifo_writer.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here ../../ip/nuand/synthesis/lms6002d/vhdl/lms6002d.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here vhdl/fx3_gpif.vhd]] +set_global_assignment -name VHDL_FILE [file normalize [file join $here vhdl/bladerf-atsc_tx.vhd]] + +# SDC Constraints +set_global_assignment -name SDC_FILE [file normalize [file join $here constraints/bladerf.sdc]] diff --git a/hdl/fpga/platforms/bladerf/vhdl/bladerf-atsc_tx.vhd b/hdl/fpga/platforms/bladerf/vhdl/bladerf-atsc_tx.vhd new file mode 100644 index 000000000..2dd4efd20 --- /dev/null +++ b/hdl/fpga/platforms/bladerf/vhdl/bladerf-atsc_tx.vhd @@ -0,0 +1,697 @@ +-- Copyright (c) 2013 Nuand LLC +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +library ieee ; + use ieee.std_logic_1164.all ; + use ieee.numeric_std.all ; + use ieee.math_real.all ; + use ieee.math_complex.all ; + +architecture atsc_tx of bladerf is + + attribute noprune : boolean ; + attribute keep : boolean ; + + component nios_system is + port ( + clk_clk : in std_logic := 'X'; -- clk + reset_reset_n : in std_logic := 'X'; -- reset_n + dac_MISO : in std_logic := 'X'; -- MISO + dac_MOSI : out std_logic; -- MOSI + dac_SCLK : out std_logic; -- SCLK + dac_SS_n : out std_logic; -- SS_n + spi_MISO : in std_logic := 'X'; -- MISO + spi_MOSI : out std_logic; -- MOSI + spi_SCLK : out std_logic; -- SCLK + spi_SS_n : out std_logic; -- SS_n + uart_rxd : in std_logic; + uart_txd : out std_logic; + oc_i2c_scl_pad_o : out std_logic; + oc_i2c_scl_padoen_o : out std_logic; + oc_i2c_sda_pad_i : in std_logic; + oc_i2c_sda_pad_o : out std_logic; + oc_i2c_sda_padoen_o : out std_logic; + oc_i2c_arst_i : in std_logic; + oc_i2c_scl_pad_i : in std_logic; + gpio_export : out std_logic_vector(31 downto 0); + correction_rx_phase_gain_export : out std_logic_vector(31 downto 0); + correction_tx_phase_gain_export : out std_logic_vector(31 downto 0) + ); + end component nios_system; + + alias sys_rst is fx3_ctl(7) ; + alias tx_clock is c4_tx_clock ; + alias rx_clock is lms_rx_clock_out ; + + signal \80MHz\ : std_logic ; + signal \80MHz locked\ : std_logic ; + signal \80MHz reset\ : std_logic ; + + signal nios_gpio : std_logic_vector(31 downto 0) := x"0000_00d7" ; + + + signal correction_rx_phase_gain : std_logic_vector(31 downto 0); + signal correction_tx_phase_gain : std_logic_vector(31 downto 0); + + signal i2c_scl_in : std_logic ; + signal i2c_scl_out : std_logic ; + signal i2c_scl_oen : std_logic ; + + signal i2c_sda_in : std_logic ; + signal i2c_sda_out : std_logic ; + signal i2c_sda_oen : std_logic ; + + type fifo_t is record + aclr : std_logic ; + + wclock : std_logic ; + wdata : std_logic_vector(31 downto 0) ; + wreq : std_logic ; + wempty : std_logic ; + wfull : std_logic ; + wused : std_logic_vector(11 downto 0) ; + + rclock : std_logic ; + rdata : std_logic_vector(31 downto 0) ; + rreq : std_logic ; + rempty : std_logic ; + rfull : std_logic ; + rused : std_logic_vector(11 downto 0) ; + end record ; + + signal rx_sample_fifo : fifo_t ; + signal tx_sample_fifo : fifo_t ; + + signal sys_rst_sync : std_logic ; + + signal usb_speed : std_logic ; + signal usb_speed_rx : std_logic ; + signal usb_speed_tx : std_logic ; + + signal tx_reset : std_logic ; + signal rx_reset : std_logic ; + + signal pclk_tx_enable : std_logic ; + signal pclk_rx_enable : std_logic ; + + signal tx_enable : std_logic ; + signal rx_enable : std_logic ; + + signal rx_sample_raw_i : signed(11 downto 0); + signal rx_sample_raw_q : signed(11 downto 0); + signal rx_sample_raw_valid : std_logic; + + signal rx_sample_i : signed(11 downto 0) ; + signal rx_sample_q : signed(11 downto 0) ; + signal rx_sample_valid : std_logic ; + + signal rx_gen_i : signed(11 downto 0) ; + signal rx_gen_q : signed(11 downto 0) ; + signal rx_gen_valid : std_logic ; + + signal tx_sample_raw_i : signed(15 downto 0); + signal tx_sample_raw_q : signed(15 downto 0); + signal tx_sample_raw_valid : std_logic; + + signal tx_sample_i : signed(15 downto 0) ; + signal tx_sample_q : signed(15 downto 0) ; + signal tx_sample_valid : std_logic ; + + signal fx3_gpif_in : std_logic_vector(31 downto 0) ; + signal fx3_gpif_out : std_logic_vector(31 downto 0) ; + signal fx3_gpif_oe : std_logic ; + + signal fx3_ctl_in : std_logic_vector(12 downto 0) ; + signal fx3_ctl_out : std_logic_vector(12 downto 0) ; + signal fx3_ctl_oe : std_logic_vector(12 downto 0) ; + + signal nios_uart_rxd : std_logic ; + signal nios_uart_txd : std_logic ; + + signal tx_underflow_led : std_logic ; + signal tx_underflow_count : unsigned(63 downto 0) ; + + signal rx_overflow_led : std_logic ; + signal rx_overflow_count : unsigned(63 downto 0) ; + + signal lms_rx_data_reg : signed(11 downto 0) ; + signal lms_rx_iq_select_reg : std_logic ; + + signal rx_mux_sel : std_logic ; + + signal rx_mux_i : signed(11 downto 0) ; + signal rx_mux_q : signed(11 downto 0) ; + signal rx_mux_valid : std_logic ; + + signal rx_sample_corrected_i : signed(15 downto 0); + signal rx_sample_corrected_q : signed(15 downto 0); + signal rx_sample_corrected_valid : std_logic; + + + signal correction_valid : std_logic; + + signal correction_tx_phase : signed(15 downto 0);--to_signed(integer(round(real(2**Q_SCALE) * PHASE_OFFSET)),DC_WIDTH); + signal correction_tx_gain : signed(15 downto 0);--to_signed(integer(round(real(2**Q_SCALE) * DC_OFFSET_REAL)),DC_WIDTH); + signal correction_rx_phase : signed(15 downto 0);--to_signed(integer(round(real(2**Q_SCALE) * PHASE_OFFSET)),DC_WIDTH); + signal correction_rx_gain : signed(15 downto 0);--to_signed(integer(round(real(2**Q_SCALE) * DC_OFFSET_REAL)),DC_WIDTH); + + constant FPGA_DC_CORRECTION : signed(15 downto 0) := to_signed(integer(0), 16); + + signal fx3_pclk_pll : std_logic ; + signal fx3_pll_locked : std_logic ; + + --atsc signals + signal tx_data : std_logic_vector(31 downto 0); + signal tx_valid : std_logic; + signal tx_request : std_logic; + + +begin + + correction_tx_phase <= signed(correction_tx_phase_gain(31 downto 16)); + correction_tx_gain <= signed(correction_tx_phase_gain(15 downto 0)); + correction_rx_phase <= signed(correction_rx_phase_gain(31 downto 16)); + correction_rx_gain <= signed(correction_rx_phase_gain(15 downto 0)); + correction_valid <= '1'; + + + -- Create 80MHz from 38.4MHz coming from the c4_clock source + U_pll : entity work.pll + port map ( + inclk0 => c4_clock, + c0 => \80MHz\, + locked => \80MHz locked\ + ) ; + + U_fx3_pll : entity work.fx3_pll + port map ( + inclk0 => fx3_pclk, + c0 => fx3_pclk_pll, + locked => fx3_pll_locked + ) ; + + -- Cross domain synchronizer chains + U_usb_speed : entity work.synchronizer + generic map ( + RESET_LEVEL => '0' + ) port map ( + reset => '0', + clock => fx3_pclk_pll, + async => nios_gpio(7), + sync => usb_speed + ) ; + + U_usb_speed_rx : entity work.synchronizer + generic map ( + RESET_LEVEL => '0' + ) port map ( + reset => '0', + clock => rx_clock, + async => nios_gpio(7), + sync => usb_speed_rx + ) ; + + U_usb_speed_tx : entity work.synchronizer + generic map ( + RESET_LEVEL => '0' + ) port map ( + reset => '0', + clock => tx_clock, + async => nios_gpio(7), + sync => usb_speed_tx + ) ; + + + U_rx_source : entity work.synchronizer + generic map ( + RESET_LEVEL => '0' + ) port map ( + reset => '0', + clock => rx_clock, + async => nios_gpio(8), + sync => rx_mux_sel + ) ; + + U_sys_reset_sync : entity work.reset_synchronizer + generic map ( + INPUT_LEVEL => '1', + OUTPUT_LEVEL => '1' + ) port map ( + clock => fx3_pclk_pll, + async => sys_rst, + sync => sys_rst_sync + ) ; + + U_tx_reset : entity work.reset_synchronizer + generic map ( + INPUT_LEVEL => '1', + OUTPUT_LEVEL => '1' + ) port map ( + clock => c4_tx_clock, + async => sys_rst_sync, + sync => tx_reset + ) ; + + U_rx_clock_reset : entity work.reset_synchronizer + generic map ( + INPUT_LEVEL => '1', + OUTPUT_LEVEL => '1' + ) port map ( + clock => rx_clock, + async => sys_rst_sync, + sync => rx_reset + ) ; + + U_rx_enable_sync : entity work.synchronizer + generic map ( + RESET_LEVEL => '0' + ) port map ( + reset => rx_reset, + clock => rx_clock, + async => pclk_rx_enable, + sync => rx_enable + ) ; + + U_tx_enable_sync : entity work.synchronizer + generic map ( + RESET_LEVEL => '0' + ) port map ( + reset => tx_reset, + clock => tx_clock, + async => pclk_tx_enable, + sync => tx_enable + ) ; + +-- U_80MHz_reset : entity work.reset_synchronizer +-- generic map ( +-- INPUT_LEVEL => '1', +-- OUTPUT_LEVEL => '0' +-- ) port map ( +-- clock => \80MHz\, +-- async => sys_rst, +-- sync => \80MHz reset\ +-- ) ; + + -- TX sample fifo + tx_sample_fifo.aclr <= tx_reset ; + tx_sample_fifo.wclock <= fx3_pclk_pll ; + tx_sample_fifo.rclock <= tx_clock ; + U_tx_sample_fifo : entity work.tx_fifo + port map ( + aclr => tx_sample_fifo.aclr, + data => tx_sample_fifo.wdata, + rdclk => tx_sample_fifo.rclock, + rdreq => tx_sample_fifo.rreq, + wrclk => tx_sample_fifo.wclock, + wrreq => tx_sample_fifo.wreq, + q => tx_sample_fifo.rdata, + rdempty => tx_sample_fifo.rempty, + rdfull => tx_sample_fifo.rfull, + rdusedw => tx_sample_fifo.rused, + wrempty => tx_sample_fifo.wempty, + wrfull => tx_sample_fifo.wfull, + wrusedw => tx_sample_fifo.wused + ); + + -- RX sample fifo + rx_sample_fifo.wclock <= rx_clock ; + rx_sample_fifo.rclock <= fx3_pclk_pll ; + U_rx_sample_fifo : entity work.rx_fifo + port map ( + aclr => rx_sample_fifo.aclr, + data => rx_sample_fifo.wdata, + rdclk => rx_sample_fifo.rclock, + rdreq => rx_sample_fifo.rreq, + wrclk => rx_sample_fifo.wclock, + wrreq => rx_sample_fifo.wreq, + q => rx_sample_fifo.rdata, + rdempty => rx_sample_fifo.rempty, + rdfull => rx_sample_fifo.rfull, + rdusedw => rx_sample_fifo.rused, + wrempty => rx_sample_fifo.wempty, + wrfull => rx_sample_fifo.wfull, + wrusedw => rx_sample_fifo.wused + ); + + -- FX3 GPIF + U_fx3_gpif : entity work.fx3_gpif + port map ( + pclk => fx3_pclk_pll, + reset => sys_rst_sync, + + usb_speed => usb_speed, + + meta_enable => '0', + rx_enable => pclk_rx_enable, + tx_enable => pclk_tx_enable, + + gpif_in => fx3_gpif_in, + gpif_out => fx3_gpif_out, + gpif_oe => fx3_gpif_oe, + ctl_in => fx3_ctl_in, + ctl_out => fx3_ctl_out, + ctl_oe => fx3_ctl_oe, + + tx_fifo_write => tx_sample_fifo.wreq, + tx_fifo_full => tx_sample_fifo.wfull, + tx_fifo_empty => tx_sample_fifo.wempty, + tx_fifo_usedw => tx_sample_fifo.wused, + tx_fifo_data => tx_sample_fifo.wdata, + + tx_timestamp => (others => '0'), + tx_meta_fifo_write => open, + tx_meta_fifo_full => '0', + tx_meta_fifo_empty => '0', + tx_meta_fifo_usedw => "00", + tx_meta_fifo_data => open, + + rx_fifo_read => rx_sample_fifo.rreq, + rx_fifo_full => rx_sample_fifo.rfull, + rx_fifo_empty => rx_sample_fifo.rempty, + rx_fifo_usedw => rx_sample_fifo.rused, + rx_fifo_data => rx_sample_fifo.rdata, + + rx_meta_fifo_read => open, + rx_meta_fifo_full => '0', + rx_meta_fifo_empty => '0', + rx_meta_fifo_usedr => "00", + rx_meta_fifo_data => (others => '0') + ) ; + + -- Sample bridges + U_fifo_writer : entity work.fifo_writer + port map ( + clock => rx_clock, + reset => rx_reset, + enable => rx_enable, + + usb_speed => usb_speed_rx, + meta_en => '0', + timestamp => (others => '0'), + + fifo_clear => rx_sample_fifo.aclr, + fifo_full => rx_sample_fifo.wfull, + fifo_usedw => rx_sample_fifo.wused, + fifo_data => rx_sample_fifo.wdata, + fifo_write => rx_sample_fifo.wreq, + + meta_fifo_full => '0', + meta_fifo_usedw => (others => '0'), + meta_fifo_data => open, + meta_fifo_write => open, + + in_i => rx_sample_corrected_i, + in_q => rx_sample_corrected_q, + in_valid => rx_sample_corrected_valid, + + overflow_led => rx_overflow_led, + overflow_count => rx_overflow_count, + overflow_duration => x"ffff" + ) ; + + U_rx_iq_correction : entity work.iq_correction(rx) + generic map( + INPUT_WIDTH => rx_sample_corrected_i'length + ) port map( + reset => rx_reset, + clock => rx_clock, + + in_real => resize(rx_mux_i,16), + in_imag => resize(rx_mux_q,16), + in_valid => rx_mux_valid, + + out_real => rx_sample_corrected_i, + out_imag => rx_sample_corrected_q, + out_valid => rx_sample_corrected_valid, + + dc_real => FPGA_DC_CORRECTION, + dc_imag => FPGA_DC_CORRECTION, + gain => correction_rx_gain, + phase => correction_rx_phase, + correction_valid => correction_valid + ); + + U_fifo_reader : entity work.fifo_reader + port map ( + clock => tx_clock, + reset => tx_reset, + enable => tx_enable, + + usb_speed => usb_speed_tx, + meta_en => '0', + timestamp => (others => '0'), + + fifo_empty => tx_sample_fifo.rempty, + fifo_usedw => tx_sample_fifo.rused, + fifo_data => tx_sample_fifo.rdata, + fifo_read => open, + + meta_fifo_empty => '0', + meta_fifo_usedw => (others => '0'), + meta_fifo_data => (others => '0'), + meta_fifo_read => open, + + out_i => open, + out_q => open, + out_valid => open, + + underflow_led => tx_underflow_led, + underflow_count => tx_underflow_count, + underflow_duration => x"ffff" + ) ; + + --delay the valid by one clock from the request + tx_valid_register : process(tx_reset, tx_clock) + begin + if (tx_reset = '1') then + tx_valid <= '0'; + elsif rising_edge(tx_clock) then + tx_valid <= tx_sample_fifo.rreq; + end if; + end process; + + U_atsc_transmitter : entity work.atsc_tx(arch) + generic map( + INPUT_WIDTH => tx_sample_fifo.rdata'length, + OUTPUT_WIDTH => tx_sample_i'length + ) + port map ( + reset => tx_reset, + clock => tx_clock, + + tx_enable => tx_enable, + data_in => tx_sample_fifo.rdata, + data_in_request => tx_sample_fifo.rreq, + data_in_valid => tx_valid, + + sample_out_i => tx_sample_raw_i, + sample_out_q => tx_sample_raw_q, + sample_out_valid => tx_sample_raw_valid + ); + + U_tx_iq_correction : entity work.iq_correction(tx) + generic map ( + INPUT_WIDTH => tx_sample_raw_i'length + ) port map ( + reset => tx_reset, + clock => tx_clock, + + in_real => tx_sample_raw_i, + in_imag => tx_sample_raw_q, + in_valid => tx_sample_raw_valid, + + out_real => tx_sample_i, + out_imag => tx_sample_q, + out_valid => tx_sample_valid, + + dc_real => FPGA_DC_CORRECTION, + dc_imag => FPGA_DC_CORRECTION, + gain => correction_tx_gain, + phase => correction_tx_phase, + correction_valid => correction_valid + ); + + -- LMS6002D IQ interface + U_lms6002d : entity work.lms6002d + port map ( + rx_clock => rx_clock, + rx_reset => rx_reset, + rx_enable => rx_enable, + + rx_lms_data => lms_rx_data_reg, + rx_lms_iq_sel => lms_rx_iq_select_reg, + rx_lms_enable => open, + + rx_sample_i => rx_sample_i, + rx_sample_q => rx_sample_q, + rx_sample_valid => rx_sample_valid, + + tx_clock => tx_clock, + tx_reset => tx_reset, + tx_enable => tx_enable, + + tx_sample_i => tx_sample_i(11 downto 0), + tx_sample_q => tx_sample_q(11 downto 0), + tx_sample_valid => tx_sample_valid, + + tx_lms_data => lms_tx_data, + tx_lms_iq_sel => lms_tx_iq_select, + tx_lms_enable => open + ) ; + + rx_mux : process(rx_reset, rx_clock) + begin + if( rx_reset = '1' ) then + rx_mux_i <= (others =>'0') ; + rx_mux_q <= (others =>'0') ; + rx_mux_valid <= '0' ; + elsif( rising_edge(rx_clock) ) then + if( rx_mux_sel = '0' ) then + rx_mux_i <= rx_sample_i ; + rx_mux_q <= rx_sample_q ; + rx_mux_valid <= rx_sample_valid ; + else + rx_mux_i <= rx_gen_i ; + rx_mux_q <= rx_gen_q ; + rx_mux_valid <= rx_gen_valid ; + end if ; + end if ; + end process ; + + -- Register the inputs immediately + lms_rx_data_reg <= lms_rx_data when rising_edge(rx_clock) ; + lms_rx_iq_select_reg <= lms_rx_iq_select when rising_edge(rx_clock) ; + + -- FX3 GPIF bidirectional signals + register_gpif : process(sys_rst_sync, fx3_pclk_pll) + begin + if( sys_rst_sync = '1' ) then + fx3_gpif <= (others =>'Z') ; + elsif( rising_edge(fx3_pclk_pll) ) then + fx3_gpif_in <= fx3_gpif ; + if( fx3_gpif_oe = '1' ) then + fx3_gpif <= fx3_gpif_out ; + else + fx3_gpif <= (others =>'Z') ; + end if ; + end if ; + end process ; + + generate_ctl : for i in fx3_ctl'range generate + fx3_ctl(i) <= fx3_ctl_out(i) when fx3_ctl_oe(i) = '1' else 'Z'; + end generate ; + + fx3_ctl_in <= fx3_ctl ; + + nios_uart_txd <= fx3_uart_txd when sys_rst_sync = '0' else '1' ; + fx3_uart_rxd <= nios_uart_rxd when sys_rst_sync = '0' else 'Z' ; + + -- NIOS control system for si5338, vctcxo trim and lms control + U_nios_system : nios_system + port map ( + clk_clk => \80MHz\, + reset_reset_n => '1', + dac_MISO => dac_sdo, + dac_MOSI => dac_sdi, + dac_SCLK => dac_sclk, + dac_SS_n => dac_csx, + spi_MISO => lms_sdo, + spi_MOSI => lms_sdio, + spi_SCLK => lms_sclk, + spi_SS_n => lms_sen, + uart_rxd => fx3_uart_txd, + uart_txd => fx3_uart_rxd, + gpio_export => nios_gpio, + correction_tx_phase_gain_export => correction_tx_phase_gain, + correction_rx_phase_gain_export => correction_rx_phase_gain, + oc_i2c_scl_pad_o => i2c_scl_out, + oc_i2c_scl_padoen_o => i2c_scl_oen, + oc_i2c_sda_pad_i => i2c_sda_in, + oc_i2c_sda_pad_o => i2c_sda_out, + oc_i2c_sda_padoen_o => i2c_sda_oen, + oc_i2c_arst_i => '0', + oc_i2c_scl_pad_i => i2c_scl_in + ) ; + + -- IO for NIOS + si_scl <= i2c_scl_out when i2c_scl_oen = '0' else 'Z' ; + si_sda <= i2c_sda_out when i2c_sda_oen = '0' else 'Z' ; + + i2c_scl_in <= si_scl ; + i2c_sda_in <= si_sda ; + + toggle_led1 : process(fx3_pclk_pll) + variable count : natural range 0 to 100_000_000 := 100_000_000 ; + begin + if( rising_edge(fx3_pclk_pll) ) then + count := count - 1 ; + if( count = 0 ) then + count := 100_000_00 ; + led(1) <= not led(1) ; + end if ; + end if ; + end process ; + + led(2) <= tx_underflow_led ; + led(3) <= rx_overflow_led ; + +-- toggle_led2 : process(rx_clock) +-- variable count : natural range 0 to 38_400_00 := 38_400_00 ; +-- begin +-- if( rising_edge(rx_clock) ) then +-- count := count - 1 ; +-- if( count = 0 ) then +-- count := 38_400_00 ; +-- led(2) <= not led(2) ; +-- end if ; +-- end if ; +-- end process ; +-- +-- toggle_led3 : process(rx_clock) +-- variable count : natural range 0 to 19_200_000 := 19_200_000 ; +-- begin +-- if( rising_edge(rx_clock) ) then +-- count := count - 1 ; +-- if( count = 0 ) then +-- count := 19_200_000 ; +-- led(3) <= not led(3) ; +-- end if ; +-- end if ; +-- end process ; + + lms_reset <= nios_gpio(0) ; + + lms_rx_enable <= nios_gpio(1) ; + lms_tx_enable <= nios_gpio(2) ; + + lms_tx_v <= nios_gpio(4 downto 3) ; + lms_rx_v <= nios_gpio(6 downto 5) ; + + -- CTS and the SPI CSx are tied to the same signal. When we are in reset, allow for SPI accesses + fx3_uart_cts <= '1' when sys_rst_sync = '0' else 'Z' ; + + exp_spi_clock <= '0' ; + exp_spi_mosi <= '0' ; + exp_gpio <= (others =>'Z') ; + + mini_exp1 <= 'Z'; + mini_exp2 <= 'Z'; + +end architecture ; -- arch diff --git a/hdl/quartus/bladerf.tcl b/hdl/quartus/bladerf.tcl index 23ec765c7..4607b4589 100644 --- a/hdl/quartus/bladerf.tcl +++ b/hdl/quartus/bladerf.tcl @@ -78,6 +78,9 @@ make_revision fsk_bridge # Create QPSK transmitter make_revision qpsk_tx +# Create ATSC transmitter +make_revision atsc_tx + # Projects created! puts "bladeRF projects created. Please use the build.tcl script to build images.\n" puts "Revisions:" diff --git a/hdl/quartus/build_bladerf.sh b/hdl/quartus/build_bladerf.sh index af118581c..8486e5f1b 100755 --- a/hdl/quartus/build_bladerf.sh +++ b/hdl/quartus/build_bladerf.sh @@ -18,6 +18,7 @@ function usage() echo "" echo "Supported revisions:" echo " hosted" + echo " atsc_tx" echo " headless" echo " fsk_bridge" echo " qpsk_tx" @@ -79,7 +80,7 @@ if [ "$rev" == "" ]; then exit 1 fi -if [ "$rev" != "hosted" ] && [ "$rev" != "headless" ] && \ +if [ "$rev" != "hosted" ] && [ "$rev" != "atsc_tx" ] && [ "$rev" != "headless" ] && \ [ "$rev" != "fsk_bridge" ] && [ "$rev" != "qpsk_tx" ]; then echo -e "\nError: Invalid revision (\"$rev\")\n" >&2 usage