A PKS31 Beacon In An FPGA

June 1, 2014 at 9:08 pm | Posted in Uncategorized | 1 Comment
Tags: , , , ,

Back in January 2012 I described a technique for producing PSK31 signals that requires no multiplications or explicit wave shaping. It was based upon viewing the PSK signal not as a single modulated waveform but as as the sum of two unmodulated waveforms whose individual frequencies depend upon whether you are transmitting a one or zero.

I used the algorithm to create a PSK31 audio beacon and even looked into implementing it in an FPGA to generate signals directly at RF. However, it quickly became apparent that while the algorithm works well in a microcontroller, it doesn’t lend itself to easy implementation in hardware.

Then about six weeks ago I was considering the problem again and came to the conclusion that it wasn’t necessary to eliminate the multiplication and wave shaping if an efficient way to do it in hardware could be found. As it turns out, there is an algorithm well suited for doing this; the CORDIC (COordinate Rotation DIgital Computer) algorithm.

What is CORDIC?

The CORDIC algorithm was developed in 1959 by Jack E. Volder as a way to perform vector rotations and calculate trigonometric functions for the B-58 bomber’s navigation computer. It requires an x/y pair representing a vector to be rotated, the angle by which the vector will be rotated, and works by incrementally rotating the vector by smaller and smaller angles, eventually zeroing in on the final rotation angle. The rotation angles are chosen so the they can be done using only basic arithmetic operations (addition, subtraction, and shifts), making it highly suited for implementation in hardware. The process is illustrated in the figure below.

CORDIC Algorithm Illustration

For the case where y = 0, the final x’/y’ values can be calculated as x’ ~ x cos(angle) and y’ ~ x sin(angle), which is just what’s needed for shaping the PSK31 carrier. So to generate a PSK31 signal using the CORDIC algorithm:

  1. Start with a DDS (direct digital synthesis) signal generator producing the carrier to be modulated.

  2. On each hardware clock tick feed an x/y value and the modulating signal’s phase angle into the block implementing the CORDIC algorithm. X values are taken from the DDS output. Y values are set to 0.

  3. When transmitting a 1, keep the angle constant at +/-90 deg.

  4. When transmitting a 0, vary the angle linearly across 180 deg.

  5. The y outputs of the CORDIC block make up the modulated PSK31 signal.

Putting It Into Practice

To prove to myself it would work, I wrote a PSK31 modulator using the CORDIC algorithm in VHDL. It’s composed of four parts; a DDS signal generator and ROM to produce the carrier, a phase accumulator/bit generator, the CORDIC phase shifter, and a controller. The block diagram is shown below.

PSK31_Block_Diagram

DDS Signal Generator – The DDS signal generator is a standard design. It uses a 32-bit accumulator with the top 8 bits used to address a cosine ROM. The register value required for a specific frequency can be calculated as:

N_{reg} = 2^{32} * \frac{f_{out}}{f_{clock}}

You can precalculate a desired value or calculate it on the fly using the technique described here.

Phase Accumulator/Clock Generator – The phase accumulator has two functions; to generate a bit clock for the internal UART and to track the phase angle across the bit width.

The clock generator consists of an accumulator that rolls over and puts out a pulse every 31.25 msec (the PSK31 bit width), indicating when a new bit is being shifted into the block for transmission.

The phase accumulator tracks the phase angle to be fed to the CORDIC block. Internally, the phase is stored in an 8-bit register whose value depends on whether a 0 or 1 is being transmitted. When transmitting a 1 the value stays constant at 0. When transmitting a 0 its value increases from 0 to 255 across the bit width.

The register value is mapped to a bit vector representing the individual CORDIC phase shifts needed to produce the desired final phase shift. Each register value from 0 to 255 represents a phase shift in the range from -90 deg to +90 deg. The size of the bit vector depends upon the number of stages implemented in the CORDIC block.

To prevent carrier phase discontinuities when multiple 0s are being transmitted an inversion bit is provided. Assume a 0 is being transmitted. During the bit transmission the phase angle will vary from -90 deg to +90 deg. When the next 0 is transmitted jumping back to -90 deg would cause a carrier phase discontinuity. You actually want the phase to vary from +90 deg back to -90 deg. The inversion bit indicates to the CORDIC block it should invert the incoming x/y values before applying the phase shifts, which prevents the phase discontinuities from taking place.

CORDIC Block – There are plenty of resources available that describe the inner workings of the CORDIC algorithm so I won’t repeat it here. For the purpose of this description, it’s only necessary to know that on each system clock tick the CORDIC block takes in three values (an x/y pair and a bit vector representing the phase shifts to be applied) and puts out three values (the x’/y’ pair representing the rotated vector and the original bit vector for that pair).

As mentioned previously, x values at the input are taken directly from the DDS signal generator, y values are all set to 0, and the phase bit vector comes from the phase accumulator. Bit 0 of the bit vector is directly connected to the inversion bit. When set the incoming vector is inverted (180 deg phase shift) before applying the remaining phase shifts. The remaining bits represent the phase shifts defined by the CORDIC algorithm, which are presented in the table below. A 0 indicates a counterclockwise rotation, a 1 indicates a clockwise rotation.

Bit Number (LSB=0) Phase Shift (deg)
1 45
2 26.6
3 14.0
4 7.1
5 3.6
6 1.8
7 0.9

The algorithm is implemented as a pipelined structure, allowing it to process data at the full speed of the FPGA. Because of this there will be a delay between input and output that depends on the number of stages in the pipeline. The PSK31 signal is taken from the y values coming out the end of the pipeline.

Controller – To maximize compatibility and ease integration, the controller was designed to conform to the WISHBONE System-on-Chip (SoC) Interconnect Architecture. It handles the signals and control registers required to present that interface to the outside world. Internally it also monitors the bit clock from the phase accumulator/clock generator, shifting new bits in to be transmitted as required.

The block is controlled via a control and status register as defined in the table below.

Data
Direction
Address Name Description
IN 000 Varicode Data Varicode value to be transmitted
001 DDS Phase Increment

010 Control Bits 0 – Output enable
OUT 010 Status Bits 0 – Output enabled
1 – XMIT buffer empty
Notes:
1. Verilog data is 16 bits left aligned, to include the 00 separator.
2. Status and control bits are active high.

Testing The Modulator

To test the modulator I included a simple signal-delta DAC and integrated the combination with the ZPUino softcore processor on a Papilio One FPGA development board. Instructions for integrating new devices into the ZPUino core are available here.

The figure below shows the output being decoded using Digipan through a laptop microphone. There’s some harmonic energy there but nothing that can’t be filtered out. The control script is available below.

PSK_Screen_Capture

Final Notes

The sigma-delta DAC used in the test transmitter limits the PSK signals to the audio range. However, the core runs at the full speed of the FPGA so there’s no reason it couldn’t be modified to feed the PSK31 signal to an external DAC and generate signals directly at RF. That’s one of my next steps, along with integrating decoding software.

For those who are interested, the VHDL and C code for the blocks and control script are given below.

DDS Signal Generator:

--
--  DDS Accumulator
-- 
--  Version: 1.0
--
--  Copyright 2014 J. Consugar
-- 
--  The FreeBSD license
--  
--  Redistribution and use in source and binary forms, with or without
--  modification, are permitted provided that the following conditions
--  are met:
--  
--  1. Redistributions of source code must retain the above copyright
--     notice, this list of conditions and the following disclaimer.
--  2. Redistributions in binary form must reproduce the above
--     copyright notice, this list of conditions and the following
--     disclaimer in the documentation and/or other materials
--     provided with the distribution.
--  
--  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
--  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
--  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
--  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
--  ZPU PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
--  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
--  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
--  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
--  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
--  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
--  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-- 
library ieee;
use ieee.std_logic_1164.ALL;
use ieee.numeric_std.ALL;
use ieee.math_real.all;

--
-- Core entity definition
--
entity zpuino_dds_acc is
  generic (
    N_LO: integer := 24;      -- number of lo acc bits
    N_HI: integer := 8;       -- number of hi acc bits
    M_LO: integer := 16777216;-- lo acc modulus - 2**24
    M_HI: integer := 256      -- hi acc modulus - 2**8
  );              
  port (
    clk:    in  std_logic;
    reset:  in  std_logic;
    inc_hi: in  std_logic_vector(N_HI-1 downto 0);
    inc_lo: in  std_logic_vector(N_LO-1 downto 0);
    carry:  out std_logic;
    q:      out std_logic_vector(N_HI-1 downto 0)
  );
end zpuino_dds_acc;

--
-- Core architecture
--
architecture arch of zpuino_dds_acc is
  --
  -- Internal signals
  --
  signal r_reg_lo: unsigned(N_LO-1 downto 0);   -- current lo accumulator
  signal r_next_lo: unsigned(N_LO downto 0);    -- next lo accumulator value
  signal r_reg_hi: unsigned(N_HI-1 downto 0);   -- current hi accumulator
  signal r_next_hi: unsigned(N_HI downto 0);    -- next hi accumulator value
  
begin
  --
  -- register process
  --
  process(clk, reset)
  begin
    if (reset = '1') then
      --
      -- Reset is set
      --
      r_reg_lo <= (others => '0');
      r_reg_hi <= (others => '0');
      
    elsif rising_edge(clk) then
      -- 
      -- Rising edge of the clock
      --
      r_reg_lo <= r_next_lo(N_LO-1 downto 0);
      r_reg_hi <= r_next_hi(N_HI-1 downto 0);
      
    end if;
  end process;
  
  --
  -- Next state.
  --
  process(r_reg_lo)
    --
    -- Temporary variable for range check.
    --
    variable temp_lo: unsigned(N_LO downto 0);
    variable temp_hi: unsigned(N_HI downto 0);
    
  begin
    --
    -- Calculate next value.
    --
    temp_lo := ('0' & r_reg_lo) + unsigned('0' & inc_lo);
    temp_hi := ('0' & r_reg_hi);
    
    --
    -- Lower register.
    --
    if (temp_lo > M_LO-1) then
      -- 
      -- If value exceeds the modulus then increment the next register
      -- and wrap around.
      --
      r_next_lo <= temp_lo - M_LO;
      temp_hi := temp_hi + 1;
    else
      --
      -- If value is still within the modulus clear the carry and keep the value.
      --
      r_next_lo <= temp_lo;
      temp_hi := temp_hi + 0;
    end if;
    
    --
    -- Upper register.
    --
    temp_hi := temp_hi + unsigned('0' & inc_hi);
    if (temp_hi > M_HI-1) then
      --
      -- If value exceeds the modules then set the carry and wrap around.
      --
      r_next_hi <= temp_hi - M_HI;
      carry <= '1';
    
    else
      --
      -- If value is still within the modulus clear the carry and keep the value.
      --
      r_next_hi <= temp_hi;
      carry <= '0';
      
    end if;
  end process;
  
  -- 
  -- Outgoing signals.
  --
  q <= std_logic_vector(r_reg_hi);
  
end arch;
--
--  DDS ROM
-- 
--  Version: 1.0
--
--  Copyright 2014 J. Consugar
-- 
--  The FreeBSD license
--  
--  Redistribution and use in source and binary forms, with or without
--  modification, are permitted provided that the following conditions
--  are met:
--  
--  1. Redistributions of source code must retain the above copyright
--     notice, this list of conditions and the following disclaimer.
--  2. Redistributions in binary form must reproduce the above
--     copyright notice, this list of conditions and the following
--     disclaimer in the documentation and/or other materials
--     provided with the distribution.
--  
--  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
--  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
--  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
--  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
--  ZPU PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
--  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
--  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
--  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
--  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
--  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
--  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-- 
library ieee;
use ieee.std_logic_1164.ALL;
use ieee.numeric_std.ALL;
use ieee.math_real.all;

--
-- Entity prototype.
--
entity zpuino_dds_rom is
  generic (
    ADDR_WIDTH       : integer := 8;        
    DATA_WIDTH       : integer := 8
  );
  port (
    addr_i    : in  std_logic_vector(ADDR_WIDTH - 1 downto 0);          
    data_o    : out signed(DATA_WIDTH - 1 downto 0)
  );
end zpuino_dds_rom;

--
-- Entity architecture.
--
architecture rtl of zpuino_dds_rom is
  -- 
  -- Defined constants.
  -- Memory has 250 8-bit signed values.
  --
  constant MEM_DEPTH : integer := 2**DATA_WIDTH;
  type mem_type is array (0 to MEM_DEPTH - 1) of signed(DATA_WIDTH - 1 downto 0);

  --
  -- Function to define the contents of the ROM
  --
  function init_mem return mem_type is
    constant SCALE : real := 2**(real(DATA_WIDTH - 2));
    variable temp_mem : mem_type;
  begin
    for i in 0 to MEM_DEPTH - 1 loop
      temp_mem(i) := 
        to_signed(integer(SCALE * cos(2.0 * MATH_PI * real(i) / real(MEM_DEPTH))), DATA_WIDTH); 
    end loop;
    return temp_mem;
  end;

  constant mem : mem_type := init_mem;

  begin

  process (addr_i)
  begin
	  data_o <= mem(to_integer(unsigned(addr_i)));
  end process;

end rtl;

Phase Accumulator/Clock Generator:

--
--  Phase Accumulator
-- 
--  Version: 1.0
--
--  Copyright 2014 J. Consugar
-- 
--  The FreeBSD license
--  
--  Redistribution and use in source and binary forms, with or without
--  modification, are permitted provided that the following conditions
--  are met:
--  
--  1. Redistributions of source code must retain the above copyright
--     notice, this list of conditions and the following disclaimer.
--  2. Redistributions in binary form must reproduce the above
--     copyright notice, this list of conditions and the following
--     disclaimer in the documentation and/or other materials
--     provided with the distribution.
--  
--  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
--  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
--  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
--  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
--  ZPU PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
--  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
--  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
--  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
--  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
--  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
--  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-- 
library ieee;
use ieee.std_logic_1164.ALL;
use ieee.numeric_std.ALL;
use ieee.math_real.all;

--
-- Core entity definition
-- clk             - system clock
-- reset           - system reset
-- serial_data_in  - indicates if 1 or 0 is being transmitted
-- q               - outgoing counter data
-- inversion       - flips state each time the one shot is triggered
--
entity zpuino_phase_acc is
  generic (
    N_HI:   integer := 8;       -- number of hi acc bits
    N_LO:   integer := 24;      -- number of lo acc bits
    M_HI:   integer := 256;     -- hi acc modulus 
    M_LO:   integer := 12000;   -- lo acc modulus 
    PSK_0:  std_logic := '0';
    PSK_1:  std_logic := '1'
  );              
  port (
    clk:             in  std_logic;
    reset:           in  std_logic;
    serial_data_in:  in  std_logic;
    q:               out unsigned(N_HI-1 downto 0);
    inversion:       out std_logic;
    uart_clock:      out std_logic
  );
end zpuino_phase_acc;

--
-- Core architecture
--
architecture arch of zpuino_phase_acc is
  --
  -- Counter registers.
  --
  signal r_reg_hi:  unsigned(N_HI-1 downto 0);  -- 7 downto 0
  signal r_reg_lo:  unsigned(N_LO-1 downto 0);  -- 23 downto 0
  
  signal r_reg_hi_next: unsigned(N_HI-1 downto 0);
  signal r_reg_lo_next: unsigned(N_LO-1 downto 0);

  signal r_reg_out:   unsigned(N_HI-1 downto 0);  -- register to hold out count 
  signal r_inversion: std_logic;
  --
  -- Generic signals
  --
  signal data_out_enable: std_logic;  -- Output increments when set hi
begin
  --
  -- Internally connected signals.
  --
  q <= r_reg_out;           -- Connect block data out to accumulator data out.
  inversion <= r_inversion; -- Connect inversion data to the out port.
  
  --
  -- Process to set output signals.
  -- For the most part it's moving the next-state signals
  -- to the appropriate state signals.
  --
  process(clk, reset)
    variable carry : integer;
    variable temp_lo : unsigned(N_LO-1 downto 0);
    variable temp_hi : unsigned(N_HI-1 downto 0);
  begin
    if (reset = '1') then
      --
      -- Reset output pins.
      --
      r_reg_hi <= (others => '0');
      r_reg_lo <= (others => '0');
      r_reg_hi_next <= r_reg_hi + 1;
      r_reg_lo_next <= r_reg_lo + 1;
    elsif rising_edge(clk) then
      -- 
      -- Rising edge of the clock moves states to the output pins.
      --
      r_reg_lo <= r_reg_lo_next;
      r_reg_hi <= r_reg_hi_next;
      
      --
      -- Next state calculations.
      -- Next value of the low register.
      --
      temp_lo := r_reg_lo_next + 1;
      carry := 0;
      if (temp_lo = M_LO) then
        temp_lo := to_unsigned(0, N_LO);
        carry := 1;
      end if;
      r_reg_lo_next <= temp_lo;
      
      temp_hi := r_reg_hi_next + carry;
      if (temp_hi = M_HI) then
        temp_hi := to_unsigned(0, N_HI);
      end if;
      r_reg_hi_next <= temp_hi;
    end if;
  end process;
  
  --
  -- Process for sampling the serial data stream.
  --
  process(clk, reset)
  begin
    if (reset = '1') then
      data_out_enable <= '0';
      r_inversion <= '0';
    elsif rising_edge(clk) then
      --
      -- Serial data sample flag.  This flag is set so the serial
      -- data is sampled on the leading edge of the PSK bit period.
      --
      if ((r_reg_lo_next = 0) and (r_reg_hi_next = 0)) then
        --
        -- Calculate the new inversion flag.  If the bit just 
        -- finished was a '1' then you want to keep the same
        -- inversion.  Otherwise, invert it.
        --
        if (data_out_enable = '0') then
          r_inversion <= not(r_inversion);
        else
          r_inversion <= r_inversion;
        end if;
        --
        -- The data out enable flag matches the data being sent.
        --
        data_out_enable <= serial_data_in;
      end if;
    end if;
  end process;
  
  --
  -- Set the output data.
  --
  r_reg_out <= r_reg_hi when (data_out_enable = PSK_0) else (others => '0');
  uart_clock <= '1' when ((r_reg_lo = 0) and (r_reg_hi = 0)) else '0';
  
end arch;
10100000
10100000
01100000
01100000
11100000
11100000
11100000
00010000
00010000
10010000
10010000
10010000
01010000
01010000
11010000
11010000
11010000
00110000
00110000
10110000
10110000
10110000
01110000
01110000
01110000
11110000
11110000
00001000
00001000
10001000
10001000
01001000
01001000
01001000
11001000
11001000
00101000
00101000
00101000
10101000
10101000
10101000
01101000
01101000
11101000
11101000
11101000
00011000
00011000
10011000
10011000
10011000
01011000
01011000
11011000
11011000
11011000
00111000
00111000
10111000
10111000
10111000
01111000
01111000
11111000
10000100
10000100
01000100
01000100
01000100
11000100
11000100
00100100
00100100
00100100
10100100
10100100
01100100
01100100
01100100
11100100
11100100
00010100
00010100
00010100
10010100
10010100
01010100
01010100
01010100
11010100
11010100
11010100
00110100
00110100
10110100
10110100
10110100
01110100
01110100
11110100
11110100
00001100
00001100
10001100
10001100
10001100
01001100
01001100
01001100
11001100
11001100
00101100
00101100
00101100
10101100
10101100
01101100
01101100
01101100
11101100
11101100
00011100
00011100
00011100
10011100
10011100
01011100
01011100
10100010
01100010
01100010
11100010
11100010
11100010
00010010
00010010
10010010
10010010
10010010
01010010
01010010
11010010
11010010
11010010
00110010
00110010
10110010
10110010
10110010
01110010
01110010
01110010
11110010
11110010
00001010
00001010
10001010
10001010
01001010
01001010
01001010
11001010
11001010
00101010
00101010
00101010
10101010
10101010
10101010
01101010
01101010
11101010
11101010
11101010
00011010
00011010
10011010
10011010
10011010
01011010
01011010
11011010
11011010
11011010
00111010
00111010
10111010
10111010
10111010
01111010
01111010
11111010
10000110
10000110
01000110
01000110
01000110
11000110
11000110
00100110
00100110
00100110
10100110
10100110
01100110
01100110
01100110
11100110
11100110
00010110
00010110
00010110
10010110
10010110
01010110
01010110
01010110
11010110
11010110
11010110
00110110
00110110
10110110
10110110
10110110
01110110
01110110
11110110
11110110
00001110
00001110
10001110
10001110
10001110
01001110
01001110
01001110
11001110
11001110
00101110
00101110
00101110
10101110
10101110
01101110
01101110
01101110
11101110
11101110
00011110
00011110
00011110
10011110
10011110
01011110

CORDIC Block:

--
--  CORDIC Phase Shifter
-- 
--  Version: 1.0
--
--  Copyright 2014 J. Consugar
-- 
--  The FreeBSD license
--  
--  Redistribution and use in source and binary forms, with or without
--  modification, are permitted provided that the following conditions
--  are met:
--  
--  1. Redistributions of source code must retain the above copyright
--     notice, this list of conditions and the following disclaimer.
--  2. Redistributions in binary form must reproduce the above
--     copyright notice, this list of conditions and the following
--     disclaimer in the documentation and/or other materials
--     provided with the distribution.
--  
--  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
--  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
--  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
--  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
--  ZPU PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
--  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
--  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
--  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
--  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
--  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
--  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--  
--
library ieee;
use ieee.std_logic_1164.ALL;
use ieee.numeric_std.ALL;
use ieee.math_real.ALL;
use ieee.std_logic_misc.ALL;
--
-- Core entity definition.
--
entity zpuino_phase_shifter is
  generic (
    NUMBER_OF_SHIFTS : integer := 7;
    BUS_WIDTH : integer := 8
  );
  port (
    clk:             in  std_logic;
    reset:           in  std_logic;
    i_data_in:       in  signed(BUS_WIDTH-1 downto 0);
    q_data_in:       in  signed(BUS_WIDTH-1 downto 0);
    phase_in:        in  std_logic_vector(NUMBER_OF_SHIFTS downto 0);
    i_data_out:      out signed(BUS_WIDTH-1 downto 0);
    q_data_out:      out signed(BUS_WIDTH-1 downto 0);
    phase_out:       out std_logic_vector(NUMBER_OF_SHIFTS downto 0)
  );
end zpuino_phase_shifter;

--
-- Core description.
--
architecture arch of zpuino_phase_shifter is
  --
  -- Descriptor for data describing a shift block.
  --
  type shift_block is record
    i_data     : signed(BUS_WIDTH+NUMBER_OF_SHIFTS-1 downto 0);
    q_data     : signed(BUS_WIDTH+NUMBER_OF_SHIFTS-1 downto 0);
    phase      : std_logic_vector(NUMBER_OF_SHIFTS   downto 0);
    shift_mask : std_logic_vector(NUMBER_OF_SHIFTS   downto 0);
  end record shift_block;
  constant shift_block_default : shift_block := (
    i_data => (others => '0'),
    q_data => (others => '0'),
    phase  => (others => '0'),
    shift_mask => (others => '0')
  );
  type shift_block_array is array(NUMBER_OF_SHIFTS downto 0) of shift_block;
    
  --
  -- Internal signals.
  --
  signal shift_stream : shift_block_array;
begin
  --
  -- Initialize the shift masks.
  -- 0 bit of the shift mask is always 0.
  -- Bits for the phase shifts go from the lowest order to the highest order.
  -- Lowest order bits are the largest angles.
  -- 0 indicates the angle at this point is negative.
  -- 1 indicates the angle at this point is positive.
  --
  process (reset) is 
    variable shift_mask_init : integer := 1;
  begin
    if (reset = '1') then
      --
      -- Index 0 of the shift stream will hold the incoming data.
      --
      shift_stream(0).shift_mask <= 
          std_logic_vector(to_unsigned(0, NUMBER_OF_SHIFTS));
          
      --
      -- Indices 1 to NUMBER_OF_SHIFTS will contain the shift masks
      -- for each of the stages.
      --
      shift_mask_init := 1;
      for i in 1 to NUMBER_OF_SHIFTS loop
        shift_stream(i).shift_mask <= 
            std_logic_vector(to_unsigned(shift_mask_init, NUMBER_OF_SHIFTS)) & "0";
        shift_mask_init := shift_mask_init * 2;
      end loop;
    end if;
  end process;
  
  --
  -- Connect the last stage to the outputs.
  --
  i_data_out <= shift_stream(NUMBER_OF_SHIFTS).i_data(
      BUS_WIDTH+NUMBER_OF_SHIFTS-1 downto NUMBER_OF_SHIFTS);
  q_data_out <= shift_stream(NUMBER_OF_SHIFTS).q_data(
      BUS_WIDTH+NUMBER_OF_SHIFTS-1 downto NUMBER_OF_SHIFTS);
  phase_out  <= shift_stream(NUMBER_OF_SHIFTS).phase;
      
  --
  -- Clocked process.
  -- Read in the data and pass it through the pipeline.
  --
  process(clk, reset)
    variable shift_right_i : signed(BUS_WIDTH+NUMBER_OF_SHIFTS-1 downto 0);
    variable shift_right_q : signed(BUS_WIDTH+NUMBER_OF_SHIFTS-1 downto 0);
  begin
    if (reset = '1') then
      --
      -- Reset the i and q data in the records.
      -- Don't do the first one since that's connected to the input.
      --
      if phase_in(0) = '0' then
        shift_stream(0).i_data <= signed(
            std_logic_vector(i_data_in) & std_logic_vector(to_unsigned(0, NUMBER_OF_SHIFTS)));
        shift_stream(0).q_data <= signed(
            std_logic_vector(q_data_in) & std_logic_vector(to_unsigned(0, NUMBER_OF_SHIFTS)));
      else
        shift_stream(0).i_data <= -signed(
            std_logic_vector(i_data_in) & std_logic_vector(to_unsigned(0, NUMBER_OF_SHIFTS)));
        shift_stream(0).q_data <= -signed(
            std_logic_vector(q_data_in) & std_logic_vector(to_unsigned(0, NUMBER_OF_SHIFTS)));
      end if;
      shift_stream(0).phase <= phase_in;
      
      for i in 1 to NUMBER_OF_SHIFTS loop
        shift_stream(i).i_data <= (others => '0');
        shift_stream(i).q_data <= (others => '0');
        shift_stream(i).phase  <= (others => '0');
      end loop;
    elsif rising_edge(clk) then
      -- 
      -- Pass the data down through the pipeline.
      --
      if phase_in(0) = '0' then
        shift_stream(0).i_data <= signed(
            std_logic_vector(i_data_in) & std_logic_vector(to_unsigned(0, NUMBER_OF_SHIFTS)));      
        shift_stream(0).q_data <= signed(
            std_logic_vector(q_data_in) & std_logic_vector(to_unsigned(0, NUMBER_OF_SHIFTS)));
      else
        shift_stream(0).i_data <= -signed(
            std_logic_vector(i_data_in) & std_logic_vector(to_unsigned(0, NUMBER_OF_SHIFTS)));      
        shift_stream(0).q_data <= -signed(
            std_logic_vector(q_data_in) & std_logic_vector(to_unsigned(0, NUMBER_OF_SHIFTS)));
      end if;      
      shift_stream(0).phase <= phase_in;
  
      for i in 1 to NUMBER_OF_SHIFTS loop
        --
        -- Calculate shifted values.
        -- Does this way to fix problem with using shift_right for
        -- division.
        --
        shift_right_i := shift_right(shift_stream(i-1).i_data, i-1);
        shift_right_q := shift_right(shift_stream(i-1).q_data, i-1);
        if (shift_right_i < 0) then
          shift_right_i := shift_right_i + 1;
        end if;
        if (shift_right_q < 0) then
          shift_right_q := shift_right_q + 1;
        end if;
        
        if or_reduce(shift_stream(i-1).phase and shift_stream(i).shift_mask) = '0' then
          --
          -- Mask is clear so angle to be rotated at this point is negative. 
          -- d = -1.
          --
          shift_stream(i).i_data <= 
              shift_stream(i-1).i_data + shift_right_q;
          shift_stream(i).q_data <= 
              shift_stream(i-1).q_data - shift_right_i;
          shift_stream(i).phase <= 
              shift_stream(i-1).phase;
        else
          --
          -- Mask is set so angle is to be rotated at this point is positive.  
          -- d = 1.
          --
          shift_stream(i).i_data <= 
              shift_stream(i-1).i_data - shift_right_q; 
          shift_stream(i).q_data <= 
              shift_stream(i-1).q_data + shift_right_i;
          shift_stream(i).phase <= 
              shift_stream(i-1).phase;          
        end if;
      end loop;
    end if;
  end process;
end architecture arch;

Controller:

--
--  PSK Transmitter for ZPUINO
-- 
--  Version: 1.0
--
--  Copyright 2014 J. Consugar
-- 
--  The FreeBSD license
--  
--  Redistribution and use in source and binary forms, with or without
--  modification, are permitted provided that the following conditions
--  are met:
--  
--  1. Redistributions of source code must retain the above copyright
--     notice, this list of conditions and the following disclaimer.
--  2. Redistributions in binary form must reproduce the above
--     copyright notice, this list of conditions and the following
--     disclaimer in the documentation and/or other materials
--     provided with the distribution.
--  
--  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
--  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
--  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
--  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
--  ZPU PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
--  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
--  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
--  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
--  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
--  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
--  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
--  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--  
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.ALL;
use ieee.std_logic_misc.ALL;
use std.textio.all;

library work;
use work.zpu_config.all;
use work.zpupkg.all;
use work.zpuinopkg.all;

entity zpuino_psk is
  generic (
    pskwidth: integer := 8;
    
    -- Constants associated with the phase shifter.
    IQ_BUS_WIDTH : integer := 8;
    
    NUMBER_OF_SHIFTS : integer := 7;
    PSK_ROM_ADDRESS_WIDTH : integer := 8;   -- Used to map phase to phase shifts
    PSK_ROM_DATA_WIDTH : integer := 8;      -- NUMBER_OF_SHIFTS+1
    
    DDS_ROM_DATA_WIDTH : integer := 8;
    DDS_ROM_ADDRESS_WIDTH : integer := 8;
    DDS_INC_HI_WIDTH : integer := 8;
    
    NUMBER_OF_CHANNELS : integer := 2;
    I_CHANNEL : integer := 1;
    Q_CHANNEL : integer := 0
  );
  port (
    -- Wishbone signals.
    -- ZPUino has a 32 bit word size.
    wb_clk_i: in std_logic;     -- FPGA clock signal
    wb_rst_i: in std_logic;     -- reset signal
    wb_dat_o: out std_logic_vector(wordSize-1 downto 0);      -- data out signal
    wb_dat_i: in std_logic_vector(wordSize-1 downto 0);       -- data in signal
    wb_adr_i: in std_logic_vector(maxIObit downto minIObit);  -- read/write address
    wb_we_i:  in std_logic;     -- write enable
    wb_cyc_i: in std_logic;
    wb_stb_i: in std_logic;
    wb_ack_o: out std_logic;
    wb_inta_o:out std_logic;

    -- Other required signals.
    tx: out std_logic_vector(NUMBER_OF_CHANNELS-1 downto 0);
    debug : out std_logic_vector(7 downto 0)
  );
end entity zpuino_psk;

architecture behave of zpuino_psk is
  --
  -- Define the accumulator associated with the NCO
  --
  component zpuino_dds_acc is
  port (
    clk:    in  std_logic;
    reset:  in  std_logic;
    inc_hi: in  std_logic_vector(DDS_INC_HI_WIDTH-1 downto 0);
    inc_lo: in  std_logic_vector(31-DDS_INC_HI_WIDTH downto 0);
    carry:  out std_logic;
    q:      out std_logic_vector(DDS_ROM_ADDRESS_WIDTH-1 downto 0)
  );
  end component zpuino_dds_acc;

  -- 
  -- Define the ROM used to hold the NCO values.
  --
  component zpuino_dds_rom is
  port (
    addr_i    : in  std_logic_vector(DDS_ROM_ADDRESS_WIDTH-1 downto 0); -- 8 bits wide
    data_o    : out signed(DDS_ROM_DATA_WIDTH-1 downto 0) -- 8 bits wide
  );
  end component zpuino_dds_rom;
  
  --
  -- PSK phase accumulator.
  --
  component zpuino_phase_acc is
  port (
    clk:             in  std_logic;
    reset:           in  std_logic;
    serial_data_in:  in  std_logic;
    q:               out unsigned(PSK_ROM_ADDRESS_WIDTH-1 downto 0);
    inversion:       out std_logic;
    uart_clock:      out std_logic
  );
  end component zpuino_phase_acc;
  
  --
  -- PSK phase shifter
  --
  component zpuino_phase_shifter is
  port (
    clk:             in  std_logic;
    reset:           in  std_logic;
    i_data_in:       in  signed(DDS_ROM_DATA_WIDTH-1 downto 0);
    q_data_in:       in  signed(DDS_ROM_DATA_WIDTH-1 downto 0);
    phase_in:        in  std_logic_vector(PSK_ROM_DATA_WIDTH-1 downto 0);
    i_data_out:      out signed(DDS_ROM_DATA_WIDTH-1 downto 0);
    q_data_out:      out signed(DDS_ROM_DATA_WIDTH-1 downto 0);
    phase_out:       out std_logic_vector(PSK_ROM_DATA_WIDTH-1 downto 0)
  );
  end component zpuino_phase_shifter;

  --
  -- Simple D2A converter
  --
  component simple_sigmadelta is
  generic (
    BITS: integer := 8
  );
  port (
    clk:      in std_logic;
    rst:      in std_logic;
    data_in:  in std_logic_vector(BITS-1 downto 0);
    data_out: out std_logic
    );
  end component simple_sigmadelta;
  
  -- Signals associated with the DDS NCO accumulator.
  signal dds_acc_reg_o    : std_logic_vector(DDS_ROM_ADDRESS_WIDTH-1 downto 0); -- register to hold accumulator value
  signal dds_acc_inc_hi_i : std_logic_vector(DDS_INC_HI_WIDTH-1 downto 0);      -- upper accumulator increment.
  signal dds_acc_inc_lo_i : std_logic_vector(31-DDS_INC_HI_WIDTH downto 0);     -- lower accumulator increment.

  -- Signals associated with the DDS rom.
  signal dds_rom_addr_i   : std_logic_vector(DDS_ROM_ADDRESS_WIDTH-1 downto 0); -- psk rom address
  signal dds_rom_o        : signed(DDS_ROM_DATA_WIDTH-1 downto 0);              -- rom output

  -- Signals associated with the phase accumulator.
  signal psk_phase_acc_count  : unsigned(PSK_ROM_ADDRESS_WIDTH-1 downto 0);     -- phase accumulator output.
  signal psk_phase_inversion  : std_logic;
  signal psk_serial_data_in   : std_logic;
  
  -- Signals associated with the phase shifter.
  signal i_data_in        : signed(IQ_BUS_WIDTH-1 downto 0);
  signal q_data_in        : signed(IQ_BUS_WIDTH-1 downto 0);
  signal phase_in         : std_logic_vector(PSK_ROM_DATA_WIDTH-1 downto 0);
  signal i_data_out       : signed(IQ_BUS_WIDTH-1 downto 0);
  signal q_data_out       : signed(IQ_BUS_WIDTH-1 downto 0);
  signal phase_out        : std_logic_vector(PSK_ROM_DATA_WIDTH-1 downto 0);
  
  -- Signals associated with the audio out
  signal i_audio_out      : std_logic_vector(IQ_BUS_WIDTH-1 downto 0);
  signal q_audio_out      : std_logic_vector(IQ_BUS_WIDTH-1 downto 0);
  
  -- Signals associated with the phase shift ROM.
  signal phase_shift_index: unsigned(PSK_ROM_ADDRESS_WIDTH-1 downto 0);
  
  -- Connecting signals.
  signal psk_dat_o        : std_logic_vector(pskwidth - 1 downto 0);  -- psk output signal
  
  -- PSK transmit data
  signal uart_clock         : std_logic;
  signal psk_xmit_data      : std_logic_vector(15 downto 0);
  signal psk_xmit_data_flag : std_logic;
  signal psk_xmit_reg       : std_logic_vector(15 downto 0);
  signal psk_xmit_reg_flag  : std_logic;
  signal psk_xmit_reg_empty : std_logic;
  signal psk_output_enable  : std_logic;
  --
  -- Declarations used to define the array of ROM data.
  --
  type rom_array is array(2**PSK_ROM_ADDRESS_WIDTH-1 downto 0) 
      of std_logic_vector(PSK_ROM_DATA_WIDTH-1 downto 0);
      
  impure function rom_init(filename : string) return rom_array is
    file rom_file : text open read_mode is filename;
    variable rom_line : line;
    variable rom_value : bit_vector(PSK_ROM_DATA_WIDTH-1 downto 0);
    variable temp : rom_array;
  begin
    for rom_index in 0 to 2**PSK_ROM_ADDRESS_WIDTH-1 loop
      readline(rom_file, rom_line);
      read(rom_line, rom_value);
      temp(rom_index) := to_stdlogicvector(rom_value);
    end loop;
    return temp;
  end function;
  
  constant phase_shift_rom_array : rom_array := rom_init(filename =>
      "/home/joseph/Papilio/ZPUino-HDL/zpu/hdl/zpuino/phase_shift_rom.txt");    
  
begin
  --
  -- Declare component instances.
  --
  
  --
  -- REGION  DDS NCO
  --
  -- Instance of the NCO accumulator
  --
  dds_acc: zpuino_dds_acc
  port map (
    clk     => wb_clk_i,            -- wishbone clock signal
    reset   => wb_rst_i,            -- wishbone reset signal
    inc_hi  => dds_acc_inc_hi_i,    -- 7 downto 0
    inc_lo  => dds_acc_inc_lo_i,    -- 23 downto 0
    carry   => open,
    q       => dds_acc_reg_o        -- 7 downto 0
  );
  
  --
  -- Instance of the NCO rom.
  --
  dds_rom: zpuino_dds_rom
  port map (
    addr_i  => dds_rom_addr_i,      -- 7 downto 0
    data_o  => dds_rom_o            -- 7 downto 0
  );
  --
  -- END DDS NCO
  --
  
  --
  -- REGION PSK PHASE ACCUMULATOR
  --
  -- Instance of the phase accumulator.
  --
  psk_phase_acc: zpuino_phase_acc
  port map (
    clk => wb_clk_i,                      -- wishbone clock signal
    reset => wb_rst_i,                    -- wishbone reset signal
    serial_data_in => psk_serial_data_in, -- serial data in
    q => psk_phase_acc_count,             -- phase acc output
    inversion => psk_phase_inversion,     -- phase inversion output
    uart_clock => uart_clock              -- uart clock from the phase acc
  );
  --
  -- END PSK PHASE ACCUMULATOR
  --
  
  --
  -- REGION CORDIC PHASE SHIFTER 
  --
  -- Instance of the phase shifter.
  --
  phase_shifter: zpuino_phase_shifter
  port map (
    clk => wb_clk_i,                -- wishbone clock signal
    reset => wb_rst_i,              -- wishbone reset signal
    i_data_in => i_data_in,         -- in-phase data in
    q_data_in => q_data_in,         -- quadrature data in
    phase_in => phase_in,           -- phase shift in
    i_data_out => i_data_out,       -- in-phase data out
    q_data_out => q_data_out,       -- quadrature data out
    phase_out => phase_out          -- phase shift out
  );
  --
  -- END CORDIC PHASE SHIFTER
  -- 
  
  --
  -- REGION D2A CONVERTERS
  --
  -- Instance of a simple 2 channel D2A converter.
  --
  psk_d2a_i: simple_sigmadelta
  generic map (
    BITS => 8
  ) 
  port map (
    clk       => wb_clk_i,
    rst       => wb_rst_i,
    data_in   => std_logic_vector(i_audio_out),
    data_out  => tx(I_CHANNEL)
  );

  psk_d2a_q: simple_sigmadelta
  generic map (
    BITS => 8
  ) 
  port map (
    clk       => wb_clk_i,
    rst       => wb_rst_i,
    data_in   => std_logic_vector(q_audio_out),
    data_out  => tx(Q_CHANNEL)
  );
  --
  -- END D2A CONVERTERS
  --
      
  --
  -- REGION ARCHITECTURE CODE
  --
  -- Acknowledge all tranfers per the wishbone spec.
  --
  wb_ack_o <= wb_stb_i and wb_cyc_i; 
  
  -- 
  -- Tie interrupt to '0', we never interrupt 
  --
  wb_inta_o <= '0';
  
  --
  -- Connect accumulator register output to the ROM address lines.
  --
  dds_rom_addr_i <= dds_acc_reg_o;  
  q_data_in <= X"00";
  i_data_in <= dds_rom_o;
  
  -- debug line
  debug(0) <= psk_phase_inversion;
  
  --
  -- DDS processing loop.
  --
  process(wb_clk_i, wb_rst_i)
    variable address      : unsigned(PSK_ROM_ADDRESS_WIDTH-1 downto 0);
    variable mapped_phase : std_logic_vector(NUMBER_OF_SHIFTS downto 0); 
  begin
    if (wb_rst_i = '1') then
      --
      -- Reset signal is set.
      --
      phase_in  <= (others => '0');
      dds_acc_inc_hi_i <= (others => '0');
      dds_acc_inc_lo_i <= (others => '0');
      psk_xmit_data_flag <= '0';
      psk_output_enable <= '0';
      
    elsif (rising_edge(wb_clk_i)) then
      --
      -- On the rising edge of the clock...
      --
      if (wb_cyc_i='1' and wb_stb_i='1' and wb_we_i='1') then
        case wb_adr_i(4 downto 2) is
          when "000" =>
            -- 
            -- Store the value to be transmitted.
            -- Only store the value if the flags are the same, indicating
            -- the xmit data has been read.
            --
            if (psk_xmit_data_flag = psk_xmit_reg_flag) then
              psk_xmit_data <= wb_dat_i(15 downto 0);
              psk_xmit_data_flag <= not(psk_xmit_data_flag);
            end if;
          when "001" =>
            --
            -- Update the DDS increment value.
            --
            dds_acc_inc_hi_i <= 
                std_logic_vector(wb_dat_i(31 downto 31-DDS_INC_HI_WIDTH+1));
            dds_acc_inc_lo_i <= 
                std_logic_vector(wb_dat_i(31-DDS_INC_HI_WIDTH downto 0));
          when "010" =>
            --
            -- Status register.
            --
            psk_output_enable <= wb_dat_i(0);
          when others =>
        end case;
      end if;

      -- Load the phase from the accumulator to the phase shifter.
      address := psk_phase_acc_count;
      mapped_phase := phase_shift_rom_array(to_integer(address));     
      phase_in(NUMBER_OF_SHIFTS downto 1) <= 
          mapped_phase(NUMBER_OF_SHIFTS downto 1);
      phase_in(0) <= 
          psk_phase_inversion;
          
    end if;
  end process;
  
  --
  -- Serial data taken from lowest bit of the psk transmit data
  -- Then rotate the transmit data 1 bit to the right.
  --
  process(wb_clk_i, wb_rst_i)
  begin
    if (wb_rst_i = '1') then
      --
      -- Initialize on reset.
      --
      psk_xmit_reg <= (others => '0');
      psk_serial_data_in <= '0';
      psk_xmit_reg_flag <= '0';
    elsif rising_edge(wb_clk_i) then
      --
      -- On the rising edge of the clock...
      --
      if (uart_clock = '1') then
        -- 
        -- It's time to swap in a new bit.
        --
        if (psk_xmit_reg_empty = '1') then
          --
          -- Done transmitting the old value.
          --
          if (psk_xmit_data_flag = psk_xmit_reg_flag) then
            --
            -- New value isn't ready so just send 0.
            --
            psk_serial_data_in <= '0';
            psk_xmit_reg <= (others => '1');
            psk_xmit_reg_flag <= psk_xmit_reg_flag;
          else
            --
            -- New value is ready.  Read it in and send the first bit.
            --
            psk_serial_data_in <= psk_xmit_data(15);
            psk_xmit_reg <= psk_xmit_data(14 downto 0) & '1';
            psk_xmit_reg_flag <= not(psk_xmit_reg_flag);
          end if;
        else
          --
          -- Still transmitting the current value so rotate the new bit in.
          -- 
          psk_serial_data_in <= psk_xmit_reg(15);
          psk_xmit_reg <= psk_xmit_reg(14 downto 0) & '1';
          psk_xmit_reg_flag <= psk_xmit_reg_flag;
        end if;
      end if;
    end if;
  end process;
  psk_xmit_reg_empty <= and_reduce(psk_xmit_reg);
  
  --
  -- Load the output data when address is read.
  --
  process(wb_adr_i, psk_output_enable)
  begin
    case wb_adr_i(4 downto 2) is
      when "010" =>
        -- 
        -- Store the value to be returned.
        --
        wb_dat_o(31 downto 0) <= (others => '0');
        wb_dat_o(1) <= psk_xmit_data_flag xnor psk_xmit_reg_flag;
        wb_dat_o(0) <= psk_output_enable;
      when others =>
        wb_dat_o(31 downto 0) <= (others => '0');
    end case;
  end process;
  
  --
  -- Load the audio output data.
  --
  process(i_data_out, q_data_out, psk_output_enable)
  begin
    if (psk_output_enable = '1') then
      i_audio_out <= std_logic_vector(i_data_out + "10000000");
      q_audio_out <= std_logic_vector(q_data_out + "10000000");
    else
      i_audio_out <= "10000000";
      q_audio_out <= "10000000";
    end if;
  end process;  
  
  --
  -- END ARCHITECTURE CODE
  --
  
end behave;

Control Program:

/* Define our Base IO address to slot 8
*/
#define PSK_SLOT     8

/* Define the PSK registers.
*/
#define PSK_BASE    IO_SLOT(PSK_SLOT)
#define PSK_DATA    REGISTER(PSK_BASE, 0)
#define DDS_DATA    REGISTER(PSK_BASE, 1)
#define CONTROL_REG REGISTER(PSK_BASE, 2)
#define STATUS_REG  REGISTER(PSK_BASE, 2)

/* Control values
*/
#define PSK_ENABLE  0x01

/* Status masks
*/
#define PSK_ENABLED     ((STATUS_REG & 0x01) != 0)
#define DATA_REG_EMPTY  ((STATUS_REG & 0x02) != 0)

/* Define PPS channels.
*/
#define PSK_I_CHANNEL  8
#define PSK_Q_CHANNEL  7
#define DEBUG_0        40

/* Assign FPGA pins to the signal lines.
*/
#define PSK_I_AUDIO  15
#define PSK_Q_AUDIO  14
#define DEBUG_PIN_0  15

/* Prototypes
*/
unsigned CalculateDDSInc(unsigned frequency_hz);

/* Code is stored as two values, the upper and lower bytes.
 * The varicode bits are left aligned, followed by the
 * 00 bits that separate varicode elements and one filled.
 * So for example, the NULL is stored as:
 * 1010101011_00_1111
 * (Varicode)_(zeros)_(fill)
 */
unsigned const varicode[256] =	{
    B10101010, B11001111, // 0 NUL
    B10110110, B11001111, // 1 SOH
    B10111011, B01001111, // 2 STX
    B11011101, B11001111, // 3 ETX
    B10111010, B11001111, // 4 EOT
    B11010111, B11001111, // 5 ENQ
    B10111011, B11001111, // 6 ACK
    B10111111, B01001111, // 7 BEL
    B10111111, B11001111, // 8 BS
    B11101111, B00111111, // 9 HT
    B11101001, B11111111, // 10 LF
    B11011011, B11001111, // 11 VT
    B10110111, B01001111, // 12 FF
    B11111001, B11111111, // 13 CR
    B11011101, B01001111, // 14 SO
    B11101010, B11001111, // 15 SI
    B10111101, B11001111, // 16 DLE
    B10111101, B01001111, // 17 DC1
    B11101011, B01001111, // 18 DC2
    B11101011, B11001111, // 19 DC3
    B11010110, B11001111, // 20 DC4
    B11011010, B11001111, // 21 NAK
    B11011011, B01001111, // 22 SYN
    B11010101, B11001111, // 23 ETB
    B11011110, B11001111, // 24 CAN
    B11011111, B01001111, // 25 EM
    B11101101, B11001111, // 26 SUB
    B11010101, B01001111, // 27 ESC
    B11010111, B01001111, // 28 FS
    B11101110, B11001111, // 29 GS
    B10111110, B11001111, // 30 RS
    B11011111, B11001111, // 31 US
    B10011111, B11111111, // 32 SP
    B11111111, B10011111, // 33 !
    B10101111, B10011111, // 34 "
    B11111010, B10011111, // 35 #
    B11101101, B10011111, // 36 $
    B10110101, B01001111, // 37 %
    B10101110, B11001111, // 38 &
    B10111111, B10011111, // 39 '
    B11111011, B00111111, // 40 (
    B11110111, B00111111, // 41 )
    B10110111, B10011111, // 42 *
    B11101111, B10011111, // 43 +
    B11101010, B00111111, // 44 ,
    B11010100, B11111111, // 45 -
    B10101110, B01111111, // 46 .
    B11010111, B10011111, // 47 /
    B10110111, B00111111, // 48 0
    B10111101, B00111111, // 49 1
    B11101101, B00111111, // 50 2
    B11111111, B00111111, // 51 3
    B10111011, B10011111, // 52 4
    B10101101, B10011111, // 53 5
    B10110101, B10011111, // 54 6
    B11010110, B10011111, // 55 7
    B11010101, B10011111, // 56 8
    B11011011, B10011111, // 57 9
    B11110101, B00111111, // 58 :
    B11011110, B10011111, // 59 ;
    B11110110, B10011111, // 60 <
    B10101010, B01111111, // 61 =
    B11101011, B10011111, // 62 >
    B10101011, B11001111, // 63 ?
    B10101111, B01001111, // 64 @
    B11111010, B01111111, // 65 A
    B11101011, B00111111, // 66 B
    B10101101, B00111111, // 67 C
    B10110101, B00111111, // 68 D
    B11101110, B01111111, // 69 E
    B11011011, B00111111, // 70 F
    B11111101, B00111111, // 71 G
    B10101010, B10011111, // 72 H
    B11111110, B01111111, // 73 I
    B11111110, B10011111, // 74 J
    B10111110, B10011111, // 75 K
    B11010111, B00111111, // 76 L
    B10111011, B00111111, // 77 M
    B11011101, B00111111, // 78 N
    B10101011, B00111111, // 79 O
    B11010101, B00111111, // 80 P
    B11101110, B10011111, // 81 Q
    B10101111, B00111111, // 82 R
    B11011110, B01111111, // 83 S
    B11011010, B01111111, // 84 T
    B10101011, B10011111, // 85 U
    B11011010, B10011111, // 86 V
    B10101110, B10011111, // 87 W
    B10111010, B10011111, // 88 X
    B10111101, B10011111, // 89 Y
    B10101011, B01001111, // 90 Z
    B11111011, B10011111, // 91 [
    B11110111, B10011111, // 92 \ (backslash)
    B11111101, B10011111, // 93 ]
    B10101111, B11001111, // 94 ^
    B10110110, B10011111, // 95 _ (underline)
    B10110111, B11001111, // 96 `
    B10110011, B11111111, // 97 a
    B10111110, B01111111, // 98 B
    B10111100, B11111111, // 99 c
    B10110100, B11111111, // 100 d
    B11001111, B11111111, // 101 e
    B11110100, B11111111, // 102 f
    B10110110, B01111111, // 103 g
    B10101100, B11111111, // 104 h
    B11010011, B11111111, // 105 i
    B11110101, B10011111, // 106 j
    B10111111, B00111111, // 107 k
    B11011001, B11111111, // 108 l
    B11101100, B11111111, // 109 m
    B11110011, B11111111, // 110 n
    B11100111, B11111111, // 111 o
    B11111100, B11111111, // 112 p
    B11011111, B10011111, // 113 q
    B10101001, B11111111, // 114 r
    B10111001, B11111111, // 115 s
    B10100111, B11111111, // 116 t
    B11011100, B11111111, // 117 u
    B11110110, B01111111, // 118 v
    B11010110, B01111111, // 119 w
    B11011111, B00111111, // 120 x
    B10111010, B01111111, // 121 y
    B11101010, B10011111, // 122 z
    B10101101, B11001111, // 123 {
    B11011101, B10011111, // 124 |
    B10101101, B01001111, // 125 }
    B10110101, B11001111, // 126 ~
    B11101101, B01001111, // 127 (del)
};

void setup()
{
    // Set the pin modes.
    pinMode(PSK_I_AUDIO, OUTPUT);
    pinModePPS(PSK_I_AUDIO, HIGH);
    outputPinForFunction(PSK_I_AUDIO, PSK_I_CHANNEL);
    
    pinMode(PSK_Q_AUDIO, OUTPUT);
    pinModePPS(PSK_Q_AUDIO, HIGH);
    outputPinForFunction(PSK_Q_AUDIO, PSK_Q_CHANNEL);

    // Set the PSK frequency.
    unsigned frequency_hz = 1000;       // DDS frequency in Hz.
    unsigned dds_increment = CalculateDDSInc(frequency_hz);
    
    Serial.begin(9600);
    
    DDS_DATA = dds_increment;
    CONTROL_REG = PSK_ENABLE;
}

void loop() 
{

    if (DATA_REG_EMPTY)
    {
        if (Serial.available() > 0)
        {
            int incomingByte = Serial.read();
            unsigned pskBeaconVaricode = 
                ((varicode[2*incomingByte+0] & 0x00FF) << 8) +
                ((varicode[2*incomingByte+1] & 0x00FF) << 0);
            PSK_DATA = pskBeaconVaricode;
        }
    }
}

unsigned CalculateDDSInc(unsigned frequency_hz)
{
    unsigned quotient = 0;
    unsigned acc = frequency_hz;
    
    for (int i = 0; i < 32; i++)
    {
        quotient = quotient << 1;
        acc = acc << 1;
        if (acc > 96000000)
        {
            quotient += 1;
            acc -= 96000000;
        }
    }
    return quotient;
}
Next Page »

Blog at WordPress.com. | The Pool Theme.
Entries and comments feeds.

Follow

Get every new post delivered to your Inbox.