This design is a pure VHDL quadrature signal generator.
With variable frequency. The frequency can be controlled with a scroll wheel.
The purpose of this design is to build an alternative local oscilator for Shabaz' Software Defined Radio (SDR) Experiment Board.
I tried to make everything as standard as possible, so that it can be used on many FPGA makes and sizes.
I'm working on a Xilinx Spartan 6, with a 12 MHz clock (Xess XuLA2 kit).
The design has 4 inputs
- rotary encoder pins A and B
- clock input
- reset
and 5 outputs:
- quadrature signals 0 -> 3
- the variable clock based on the input clock and the encoder setting
The design is broken up in 3 components:
- decoder to translate the movements of a scroll wheel into a numeric value.
- variable clock that will stretch the input clock based on the value coming out of the decoder
- quadrature signal generator will create 4 90° phase-shifted signals from that variable clock.
A wrapper glues the components together.
All this has been used before for my Pynq blog series and this Spartan 6 article: XuLA2 FPGA - Rotary Encoder and VHDL.
Because all the code has been documented before, I'm just putting the source here.
Check the link earlier in this post for explanations.
quad_decode_quad_osc.vhd
This code should be portable.
The external pins are defined in wrapper entity quad_decode_quad_osc.
-- ============================================== library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity quadrature_decoder is Port ( QuadA : in STD_LOGIC; QuadB : in STD_LOGIC; Clk : in STD_LOGIC; nReset : in STD_LOGIC; Position : out unsigned (7 downto 0)); end quadrature_decoder; architecture Behavioral of quadrature_decoder is signal QuadA_Delayed: std_logic_vector(2 downto 0) := (others=>'0'); signal QuadB_Delayed: std_logic_vector(2 downto 0) := (others=>'0'); signal Count_Enable: STD_LOGIC; signal Count_Direction: STD_LOGIC; signal Count: unsigned(Position'length-1 downto 0) := (others=>'0'); begin process (Clk, nReset) begin if (nReset = '0') then Count <= (others=>'0'); QuadA_Delayed <= (others=>'0'); QuadB_Delayed <= (others=>'0'); elsif rising_edge(Clk) then QuadA_Delayed <= (QuadA_Delayed(1), QuadA_Delayed(0), QuadA); QuadB_Delayed <= (QuadB_Delayed(1), QuadB_Delayed(0), QuadB); if Count_Enable='1' then if Count_Direction='1' then Count <= Count + 1; Position <= Count; else Count <= Count - 1; Position <= Count; end if; end if; end if; end process; Count_Enable <= QuadA_Delayed(1) xor QuadA_Delayed(2) xor QuadB_Delayed(1) xor QuadB_Delayed(2); Count_Direction <= QuadA_Delayed(1) xor QuadB_Delayed(2); end Behavioral; -- ============================================== library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity variable_clock is Port ( clk_i : in STD_LOGIC; resetn_i : in STD_LOGIC; ticks_i : in unsigned (7 downto 0); clk_o : out STD_LOGIC ); end variable_clock; architecture Behavioral of variable_clock is signal counter_s: natural range 0 to 2**ticks_i'length-1 := 0; signal clk_out_s: STD_LOGIC := '0'; begin process (clk_i, resetn_i) procedure perform_reset is -- this procedure contains the reset action begin counter_s <= 0; clk_out_s <= '0'; end procedure; procedure perform_action is -- this procedure contains the reset action begin if counter_s >= ticks_i then -- >= because the value can change counter_s <= 0; clk_out_s <= not clk_out_s; else counter_s <= counter_s + 1; end if; end procedure; begin if rising_edge(clk_i) then if resetn_i = '0' then perform_reset; else perform_action; end if; clk_o <= clk_out_s; end if; end process; end Behavioral; -- ============================================== library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity quadrature_oscillator is Port ( clk_i : in std_logic; resetn_i : in std_logic; outputs_o : out std_logic_vector (3 downto 0) ); end quadrature_oscillator; architecture Behavioral of quadrature_oscillator is signal pattern_s: std_logic_vector (6 downto 0) := "0011001"; signal counter_s: natural range 0 to 3 := 0; begin process (clk_i, resetn_i) procedure perform_reset is -- this procedure contains the reset action begin counter_s <= 0; end procedure; procedure perform_action is -- this procedure contains the reset action begin if counter_s = 3 then counter_s <= 0; else counter_s <= counter_s + 1; end if; end procedure; begin if rising_edge(clk_i) then if resetn_i = '0' then perform_reset; else perform_action; end if; outputs_o(0) <= pattern_s(counter_s + 3); outputs_o(1) <= pattern_s(counter_s + 2); outputs_o(2) <= pattern_s(counter_s + 1); outputs_o(3) <= pattern_s(counter_s + 0); end if; end process; end Behavioral; -- ============================================== library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; entity quad_decode_quad_osc is Port ( QuadA_in : in STD_LOGIC; QuadB_in : in STD_LOGIC; clk_in : in STD_LOGIC; nReset_in : in STD_LOGIC; oscillator_quad_out : out STD_LOGIC_VECTOR (3 downto 0); clk_out : out STD_LOGIC); end quad_decode_quad_osc; architecture Behavioral of quad_decode_quad_osc is signal position_s : unsigned (7 downto 0); signal clk_s : STD_LOGIC; component quadrature_decoder is Port ( QuadA : in STD_LOGIC; QuadB : in STD_LOGIC; Clk : in STD_LOGIC; nReset : in STD_LOGIC; Position : out unsigned (7 downto 0)); end component quadrature_decoder; component variable_clock is Port ( clk_i : in STD_LOGIC; resetn_i : in STD_LOGIC; ticks_i : in unsigned (7 downto 0); clk_o : out STD_LOGIC ); end component variable_clock; component quadrature_oscillator is Port ( clk_i : in std_logic; resetn_i : in std_logic; outputs_o : out std_logic_vector (3 downto 0) ); end component quadrature_oscillator; begin quadrature_decoder_i: component quadrature_decoder port map ( QuadA => QuadA_in, QuadB => QuadB_in, Clk => clk_in, nReset => nReset_in, Position (7 downto 0) => position_s (7 downto 0) ); variable_clock_i: component variable_clock port map ( clk_i => clk_in, resetn_i => nReset_in, ticks_i (7 downto 0) => position_s (7 downto 0), clk_o => clk_s ); quadrature_oscillator_i: component quadrature_oscillator port map ( clk_i => clk_s, resetn_i => nReset_in, outputs_o (3 downto 0) => oscillator_quad_out (3 downto 0) ); clk_out <= clk_s; end Behavioral;
constraints.ucf
The external pins defined in quad_decode_quad_osc are mapped here.
I've mapped them for my specific board. Adapt for your FPGA/kit.
net clk_in loc=a9; # 12 MHz clock to FPGA. net nReset_in loc=k15; # PM1 connections for rotary encoder module. net QuadA_in loc=r15; net QuadB_in loc=m15; # PM1 connections for the pwm outputs net oscillator_quad_out[0] loc=r7; net oscillator_quad_out[1] loc=r16; net oscillator_quad_out[2] loc=m16; net oscillator_quad_out[3] loc=k16; net clk_out loc=t7;
Untested. I'll update if there are errors.
If used for Shabaz' SDR, pins [0]and [2] should go to the JFETS for one opamp. Pins [1] and [3] to the JFETS for the other. Because they are the 2 differential pairs.