Occasionally I have been asked how to create and time a ring oscillator. My first answer is, "You don't". FPGA tools are not designed for this type of circuit, and generally have lots of resources(PLLs, ripple clocks) for creating clocks. So that is my first recommendation, but I have been talked out of it by designers doing exotic things in their designs. Random number generators, custom voltage/temperature monitors, calibration tools, etc. have been given as reasons why a ring oscillator might be put into the design. The other one is a designer who needs a self-generated clock inside the device(note that some devices already have internal oscillators). So though I recommend seeing if there is another way to do it, this document and project show how to build and time a ring oscillator.
First off, attached at the top is a project with a verilog design that instantiates the parameterized ring_osc.sv. Here is the RTL view of that file with the parameter NUM_LUTS set to 10:
This is a large loop of LUTs with a single inversion done at the XOR gate. This is different than normal ring oscillators in that a register is used to drive that XOR gate. The register is stuck and will always drive out a 0 which gets inverted into a 1 into the XOR gate, makingthe XOR gate behave like an inverter to the ring oscillator. The register does not get synthesized out due to synthesis attributes. The reason for the register is so we can do timing analysis on the ring oscillator. TimeQuest always needs a keeper node like a register or top-level port to do timing analysis, and if the ring oscillator were completely LUTs and Inverters, there would be no way to time it.
When compiling the design, both the Quartus II fit report and TimeQuest messages give the following warning:
This lets us know it found the loop and will do timing analysis of it.
To get the delay through it, I must put a create_clock assignment on the register, which can be found in test.sdc. At first, the user can put in any period as a placeholder. Once constrained, TimeQuest will now do analysis on that clock domain. To find out how fast the loop can run, launch TimeQuest, Create Timing Netlist using the Fast timing model, and then do a setup report on this clock domain. Be sure the report_timing -detail is set to full_path, so that the clock tree is broken out in detail:
From there, go to the Data Required Path and find the incremental value of the Loop:
This 1.342ns is the fastest delay through the loop. I then took that value and doubled it to get a 2.684ns clock period. I overconstrained the clock a bit to 2.5ns, just for good measure. To see how slow the loop could be, run TimeQuest against the slow timing model and look at the incremental Loop value in the Data Required Path during setup analysis. In this design it was 3.674ns, which can be doubled to a period of 7.348ns. So the period can vary from 2.684ns to 7.348ns, or 373MHz down to 136Mhz. This is a huge variation, and one of the reasons Ring Oscillators aren't recommended.
Locking Down the Ring Oscillator
The timing analysis was done manually, whereby the Loop value needs to be pulled out of a timing report. On the next fit, the loop could be placed or routed differently, resulting in different timing, which is why I strongly recommend locking it down. The first step is to lock down the placement, which can be done in one of two ways. The user can put the Ring Oscillator into a LogicLock Region, which ensures all the elements are packed into a tight rectangular region. This was done in the design, and is as easy as right-clicking on the ring_osc hierarchy in Quartus II and selecting LogicLock -> Create New LogicLock Region. This is easy, but there will still be a decent amount of variation as the placement within the LogicLock region can vary. Another option is to manually place each LUT and the register. I have also done this, and the assignments can be found in the Assignment Editor/.qsf. (I've included the self-documented script ba.tcl, which can be used to back-annotate the placement after a fit, but there's no reason not to just copy the syntax I have in the Assignment Editor/.qsf) I also strongly recommend putting the ring oscillator hierarchy into a partition early on, done by right-clicking on the ring_osc hierarchy and selecting Design Partition -> Set as Design Partition. Once the ring oscillator has timing and placement that meets the designer's needs, the partition can be set to Post-Fit, which will lock down the placement and routing of the ring oscillator, so that it never changes. This information is stored in the /incremental_db, so be sure to include that with your project, or export the partition as a .qpf. Once the partition is set to post-fit, the LogicLock Region and/or location assignments don't really do anything, but can be left in if the user wants.
Estimated Delay Warning
There is one last TimeQuest issue that may come up. For designs with large combinatorial loops, the number of paths through the loop can grow exponentially and eventually reach the point that they can't all be analyzed in an acceptable run-time. When this occurs, TimeQuest gives the warning:
# Warning: Design contains combinational loop of 120 nodes. Estimating the delays through the loop.
I see this occur when the NUM_LUTS parameter goes above 50. The user can get rid of this warning by added the following to the .sdc, where the -size should be as large as the value given in the warning:
set_scc_mode -size 120
This makes the warning go away. Now, a ring oscillator has only one path through it, and so it's very easy for TimeQuest to analyze the loop. In my tests, whether it estimated the delay through a large loop or I added set_scc_mode to get the exact analysis, the LOOP value in TimeQuest was identical, so the only thing this .sdc constraint did was get rid of the warning. If the design has other large combinatorial loops, it could cause TimeQuest run-time to increase. I have added this command to test.sdc in the project, but commented it out with an explanation.