In the previous blog I took a simple 555 oscillator that had been adapted for voltage control and used it as a component part of a phase-locked loop (PLL). In this final blog, of what has turned out to be a series, I'm attempting to use the PLL I constructed to make a very simple frequency synthesizer.
Here is what it looks like as a block diagram:
I have a fixed divider down from the board oscillator to give a reference signal of 1kHz. That's fed to one input of the phase comparator. The other input comes from a divided down signal from the output of the VCO. The loop, if it operates correctly, will lock those two together so that they have have the same frequency and are in phase (the phase comparator itself is trying for 0 degrees between the negative-going edge of each waveform, but in practice it will be offset slightly). When that happens, the vco output will be the divide value (n) times the reference frequency.
The circuit has been elaborated a little.
There's now an extra transistor current source to inject a fixed current into the timing capacitor. That gives rise to the oscillator having a frequency offset at a control voltage of 0V (a minimum frequency that the oscillator can't drop below - to ensure that the oscillator keeps going and that there are always edges to lock to). I've also adapted the filter values to give better locking at 1kHz (they aren't optimum, though - I'm sure they could be tuned better).
For testing, I have a divide of between 2 and 10, so the frequency will increase from 2kHz to 10kHz in steps of 1kHz.
Here's a video of it running a test where I increment the divide value at one second intervals. The top trace is the 1kHz reference. The bottom trace is the timing 555 capacitor waveform, so we can see what the VCO is doing.
It's not perfect and occasionally will lose the lock it gains after the step in frequency and have to do it again. The plug-in breadboard is a very messy environment, with fast digital signals mixed with sensitive analogue circuitry, so I'm not surprised. In any case, even with better layout, I wouldn't recommend using this for anything serious other than looking at the general principles and experimenting.
Finally, for interest, here are the two VHDL files
--------------------------------------------------------------- --- Filename: Phase-Comparator.vhd --- --- Target device: LFXP2-5E-6TN144 --- --- --- --- Phase comparator --- --- --- --- Formed of four SR flip-flops, though that won't be very --- --- evident looking at the signal connections of the --- --- component below unless you draw the circuit. --- --- This implementation is at a low level - wiring --- --- up the component 'logic gates' that I've created. --- --- --- --- Jon Clift 7th July 2022 --- --- --- --------------------------------------------------------------- --- Rev Date Comments --- --- 1.0 07-Jul-22 --- --------------------------------------------------------------- --- --- phase comparator component --- --- Two input signals, vco and ref, that will be compared --- Two outputs that are enables for the tristate buffers --- that will cause the filter capacitor to be either pumped up or down, --- if there is a phase error, or left alone if it's on track --- --- this is built with simple subcomponent logic gates library IEEE; use IEEE.std_logic_1164.all; entity phase_comparator_component is port ( vco_in: in std_logic; --- vco signal ref_in: in std_logic; --- reference signal en_up: out std_logic; --- enable up en_down: out std_logic --- enable down ); end phase_comparator_component; architecture phase_comparator_component_arch of phase_comparator_component is signal s1, s2, s3, s4: std_logic; --- S/R 'set' signals signal r1, r2, r3, r4: std_logic; --- S/R 'reset' signals signal re: std_logic; --- overall 'reset' signal --- declare components that are going to be used component nand_2 port ( in_a: in std_logic; in_b: in std_logic; out_o: out std_logic ); end component; component nand_3 port ( in_a: in std_logic; in_b: in std_logic; in_c: in std_logic; out_o: out std_logic ); end component; component nand_4 port ( in_a: in std_logic; in_b: in std_logic; in_c: in std_logic; in_d: in std_logic; out_o: out std_logic ); end component; begin --- instantiate actual components used in design --- they are 'wired up' within the port map statements G1: nand_2 port map (r1,vco_in,s1); G2: nand_2 port map (s1,r2,s2); G3: nand_2 port map (s2,re,r2); G4: nand_2 port map (re,s3,r3); G5: nand_2 port map (r3,s4,s3); G6: nand_2 port map (ref_in,r4,s4); G7: nand_3 port map (s1,s2,re,r1); G8: nand_3 port map (s3,s4,re,r4); G9: nand_4 port map (s1,s2,s3,s4,re); en_down <= r1; en_up <= r4; end phase_comparator_component_arch; --- following are the gate components --- it should be fairly obvious what they're doing --- two input nand gate component library IEEE; use IEEE.std_logic_1164.all; entity nand_2 is port ( in_a: in std_logic; in_b: in std_logic; out_o: out std_logic ); end nand_2; architecture nand_2_arch of nand_2 is begin out_o <= not (in_a and in_b); end nand_2_arch; --- three input nand gate component library IEEE; use IEEE.std_logic_1164.all; entity nand_3 is port ( in_a: in std_logic; in_b: in std_logic; in_c: in std_logic; out_o: out std_logic ); end nand_3; architecture nand_3_arch of nand_3 is begin out_o <= not ((in_a and in_b) and in_c); end nand_3_arch; --- four input nand gate component library IEEE; use IEEE.std_logic_1164.all; entity nand_4 is port ( in_a: in std_logic; in_b: in std_logic; in_c: in std_logic; in_d: in std_logic; out_o: out std_logic ); end nand_4; architecture nand_4_arch of nand_4 is begin out_o <= not (((in_a and in_b) and in_c) and in_d); end nand_4_arch;
--------------------------------------------------------------- --- Filename: Lattice-555-Freq-Synth.vhd --- --- Target device: LFXP2-5E-6TN144 --- --- --- --- Simple frequency synthesis using 555 PLL --- --- --- --- --- --- Jon Clift 7th July 2022 --- --- --- --------------------------------------------------------------- --- Rev Date Comments --- --- 1.0 07-Jul-22 --- --------------------------------------------------------------- library IEEE; use IEEE.std_logic_1164.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all; --- top level entity Lattice_555_Freq_Synth is port ( clk_in: in std_logic; --- input clock (50MHz osc) vco_in: in std_logic; --- vco signal (from 555 VCO) out_ref: out std_logic; --- output ref signal (for test) out_up: out std_logic; --- up out_down: out std_logic; --- down freq_step: out std_logic --- pulse at freq step point (for test) ); end entity Lattice_555_Freq_Synth; architecture Lattice_555_Freq_Synth_arch of Lattice_555_Freq_Synth is --- declare signals signal prescaler1: std_logic_vector(14 downto 0); signal prescaler2: std_logic_vector(11 downto 0); signal vco_count: std_logic_vector(3 downto 0); signal divisor: std_logic_vector(3 downto 0); signal divisor_1: std_logic_vector(3 downto 0); signal divisor_2: std_logic_vector(3 downto 0); signal half_milli_en: std_logic; signal one_second_en: std_logic; signal freq_sel: std_logic; signal in_vco: std_logic; signal ref_in: std_logic; signal en_up: std_logic; signal en_down: std_logic; --- declare components component phase_comparator_component port ( vco_in: in std_logic; ref_in: in std_logic; en_up: out std_logic; en_down: out std_logic ); end component; begin --- instantiate the phase comparator C1: phase_comparator_component port map (in_vco,ref_in,en_up,en_down); --- clocked process running from 50MHz board oscillator xtal_clocked_stuff: process (clk_in) begin if (clk_in'event and clk_in='1') then --- prescaler down to 500us period (divide by 25000 from 50MHz clock) if (prescaler1(14 downto 0) = "110000110100111") then --- if 24999 prescaler1(14 downto 0) <= "000000000000000"; --- reset to 0 half_milli_en <= '1'; --- and set enable (for one cycle) else --- else prescaler1 <= prescaler1 + 1; --- count up half_milli_en <= '0'; --- and keep enable low rest of time end if; --- prescaler2 down to 1s period (divide by 2000 from 0.5ms enable) if (half_milli_en = '1') then --- if enabled and if (prescaler2(11 downto 0) = "111110011111") then --- if 3999 prescaler2(11 downto 0) <= "000000000000"; --- reset to 0 one_second_en <= '1'; --- and set enable (for one 0.5ms period) else --- else prescaler2 <= prescaler2 + 1; --- count up one_second_en <= '0'; --- and keep enable low rest of time end if; end if; --- toggle on half_milli_en to give 1ms period squarewave (1kHz) if (half_milli_en = '1') then --- if enabled, toggle ref_in ref_in <= not ref_in; end if; --- count on both enables to give a 1s change of output frequency from the vco if ((half_milli_en = '1') and (one_second_en = '1')) then --- if enabled, count if (divisor(3 downto 0) = "1001") then --- if 9 divisor(3 downto 0) <= "0001"; --- reset to 1 else --- else divisor(3 downto 0) <= divisor(3 downto 0) + 1; end if; freq_step <= '1'; --- test output (pulse at freq change) else freq_step <= '0'; end if; end if; end process xtal_clocked_stuff; --- clocked process running from external VCO output vco_clocked_stuff: process (vco_in) begin if (vco_in'event and vco_in='1') then --- crossing from xtal clock to vco clock. --- possibly a bit ott for the application, but there are --- plenty of flip-flops to play with divisor_1(3 downto 0) <= divisor(3 downto 0); divisor_2(3 downto 0) <= divisor_1(3 downto 0); --- counter to divide down the vco if (vco_count(3 downto 0) = divisor_2(3 downto 0)) then --- if vco_count(3 downto 0) <= "0000"; --- reset to 0 in_vco <= '1'; --- in_vco high for one cycle) else --- else vco_count <= vco_count + 1; --- count up in_vco <= '0'; --- and keep in_vco low rest of time end if; end if; end process vco_clocked_stuff; out_ref <= ref_in; --- push ref_in to an output pin so it can be scoped --- tristate output control out_up <= '1' when en_up = '0' else 'Z'; out_down <= '0' when en_down = '0' else 'Z'; end Lattice_555_Freq_Synth_arch;
This worked with the Lattice XP2 part listed in the header (using Diamond 3.12). Be aware that the phase comparator may not synthesize properly or place-and-route properly with other devices and/or design software (see previous blogs, and their comments, for more details). As usual, use how you want but, no claim of fitness for any purpose, no warranty, no support, etc.
If you found this interesting and would like to see more blogs I've written, a list can be found here: blog index
References
[1] https://www.st.com/en/clocks-and-timers/ne555.html
[2] https://www.ti.com/lit/ds/symlink/ne555.pdf
[3] Frequency/Phase Comparator for Phase-locked-Loops. Peter Alfke. Page 6-41, The Programmable Gate
Array Data Book, Xilinx, 1989.