From Hamsterworks Wiki!
This FPGA project was completed in May 2015, with inspiration from a fellow FPGA user Shahin.
What is a ring oscillator?
A ring oscillator is made when you have an unstable loop in your logic and it spontaneously oscillates. The easiest way to create one is to connect the output of a NOT gate back into it's own input, and if the it will oscillate at a frequency determined by propagation delay of gate.
Normally a longer chain of logic is used (e.g. 6 buffers and a not gate), which not only gives a cleaner square-wave output, but allows you to determine the propagation delay more accurately.
Creating one in and FPGA using VHDL is hard, as the tools don't like to synthesize the structure, and the optimizer can remove all the redundant logic. But with a bit of persistence it can be made to work.
Here's the floorplan of the FPGA with the ring highlighted:
Here's how I made it...
How is it implemented
The ring oscillator has an AND get in the loop, with one of its inputs acting as an enable signal for the oscillator.
A eight-bit counter is driven by the ring oscillator, and the counter has an async reset, allowing it to be cleared from a different clock domain.
A counter in the 32 MHz clock domain is used to perform the following actions
- Enable the oscillator
- Leave it running for 100 cycles.
- Disable the oscillator
- Wait for a short while
- Read the count of how many cycles have been occurred in the ring oscillator, and display it on the LEDs
- Reset the count
- Wait a long while - a little over 0.5 s - so you can observer a static set of LEDs.
And that is it!
First, here are the source files.
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.ALL; Library UNISIM; use UNISIM.vcomponents.all; entity ring_test is Port ( clk : in STD_LOGIC; led : out STD_LOGIC_VECTOR (7 downto 0)); end ring_test; architecture Behavioral of ring_test is COMPONENT my_and PORT( a : IN std_logic; b : IN std_logic; o : OUT std_logic ); END COMPONENT; COMPONENT my_not PORT( a : IN std_logic; o : OUT std_logic ); END COMPONENT; signal signals : std_logic_vector(75 downto 0) := (others => '0'); signal reset_count : std_logic := '0'; signal enable : std_logic := '0'; signal ref_counter : unsigned(23 downto 0) := (others => '0'); signal pulse_counter : unsigned(9 downto 0) := (others => '0'); begin g1: for i in 1 to signals'high generate i_my_not : my_not port map ( a => signals(i-1), -- LUT input O => signals(i) -- LUT general output ); end generate; i_my_and: my_and PORT MAP( a => enable, b => signals(signals'high), o => signals(0) ); process(reset_count,signals(signals'high)) begin if reset_count = '1' then pulse_counter <= (others =>'0'); elsif rising_edge(signals(signals'high)) then pulse_counter <= pulse_counter+1; end if; end process; control_proc: process(clk) begin if rising_edge(clk) then case to_integer(ref_counter) is when 0 => enable <= '1'; when 100 => enable <= '0'; when 128 => led(7 downto 0) <= std_logic_vector(pulse_counter(7 downto 0)); when 200 => reset_count <= '1'; when 230 => reset_count <= '0'; when others => NULL; end case; ref_counter <= ref_counter+1; end if; end process; end Behavioral;
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity my_not is Port ( a : in STD_LOGIC; o : out STD_LOGIC); end my_not; architecture Behavioral of my_not is begin o <= not a; end Behavioral;
library IEEE; use IEEE.STD_LOGIC_1164.ALL; entity my_and is Port ( a : in STD_LOGIC; b : in STD_LOGIC; o : out STD_LOGIC); end my_and; architecture Behavioral of my_and is begin o <= a and b; end Behavioral;
NET CLK LOC="P94" | IOSTANDARD=LVTTL | PERIOD=31.25ns; # CLK NET LED(7) LOC="P123" | IOSTANDARD=LVTTL; # C8 NET LED(6) LOC="P124" | IOSTANDARD=LVTTL; # C9 NET LED(5) LOC="P126" | IOSTANDARD=LVTTL; # C10 NET LED(4) LOC="P127" | IOSTANDARD=LVTTL; # C11 NET LED(3) LOC="P131" | IOSTANDARD=LVTTL; # C12 NET LED(2) LOC="P132" | IOSTANDARD=LVTTL; # C13 NET LED(1) LOC="P133" | IOSTANDARD=LVTTL; # C14 NET LED(0) LOC="P134" | IOSTANDARD=LVTTL; # C15
To ensure the VHDL tools don't optimize your logic out you need to set the "Keep hierarchy" option in the design's Synthesis settings.
The LEDs alternated between 0x2D (45) and 0x2E (46). As the Papilio Plus's clock speed is 31.25 ns and I let the oscillator run for 100 cycles (3,125 ns), giving the ring's period of 67.93 ns. The ring consists of 75 NOT gates and an AND gate (each implemented in a LUT) giving a per-LUT delay of 0.893 ns (which includes the routing delays).
I then updated the code to have a ring of 49 NOT gates and an AND gate, and while it was building calculated that it should have a period of about 41.4 ns. This should give a count of around 0x4B (75) or 0x4C (76) displayed on the LEDs. It actually got 0x4E or 0x4F - just a tiny bit faster than predicted, so I'm pretty happy that this is working correctly.
Note to self - the lotto numbers were - 8 10 14 17 33 34 (9) - must check my ticket later!