…three useful things about Quartus – 2021-03-14
- Quartus can merge multiple PLLs into one.
I have a project with a PLL in the toplevel, which generates clocks for various housekeeping tasks, and then the meat of the project in a submodule which has a second PLL.
Quartus spotted that these two PLLs are driven from the same base clock, and have compatible factors and divisors, so merged them into one. Great!
… except …
The project has a timing constraints file shared between several different targets, and it expects to be able to reference the outputs of the inner PLL by path:
set_input_delay -clock [get_clocks {guest|pll|altpll_component...
Naturally, with the PLL merged into a different one higher up the hierarchy, this constraint now fails.
There are two ways we could solve this – the easiest is simply to disable PLL merging. This is as simple as adding the line
set_global_assignment -name AUTO_MERGE_PLLS OFF
to the project file. The other approach brings me to the second interesting thing I learned:
- Variables defined within a constraints file are visible from within subsequent constraints files.
Remember a few posts ago, I said that for any given question about Quartus the answer was quite likely to be “a TCL script”? Constraints files are no exception.
When a single project targets multiple boards there will very likely be constraints that are common to all boards, and constraints that are basically the same between boards but vary just according to pin naming, and then constraints that are specific to a particular board. Therefore it’s helpful to have the constraints split between a general and board-specific file.
As long as the board-specific file is evaluated first, variables defined within can be referenced from a more general project-oriented constraints file – so, for example, the board-specific file can define:
set RAM_CLK ram_clk
set RAM_OUT {ram_d* ram_a* ram_ba* ram_ras_n ram_cas_n ram_we_n ram_*dqm}
set RAM_IN {ram_d*}
and then the project’s constraints file can reference those ports using ${RAM_OUT}, ${RAM_IN} and ${RAM_CLK} instead of hard-coded pin names – so if another board has different names for the RAM pins (which trust me, it will do!) the names that need adapting aren’t scattered all over a lengthy constraints file.
Going back to the PLL merging problem above, we could therefore have solved the problem by finding out the post-merging path for the PLL output in question, defining a variable within each board-specific file pointing to its new path, and then referencing it by that variable instead of its absolute path in the main constraints file.
The other thing I learned (though strictly speaking not today!) is that
- it’s possible to specify compilation settings on a per-module basis.
I have a project in which I’m trying to minimise block RAM usage. I can prevent small arrays being inferred as RAMs by using a ramstyle=”logic” attribute but I had one stubborn multiplexer which was being inferred as block RAM. Because the structure Quartus was recognising was so complex there was nothing to which I could apply the ramstyle attribute. I could prevent it being recognised as a RAM by disabling ROM-to-RAM translation – but if I did that, the actual ROMs within the project were also turned into logic, and then I ran out of space!
The solution is to turn off ROM-to-RAM translation for just one compilation unit.
To do this, we make use of the altera_attribute… well… attribute.
The syntax looks like this:
architecture Behavioral of vdp_sprites is
attribute altera_attribute : string;
attribute altera_attribute of Behavioral : architecture is "-name AUTO_ROM_RECOGNITION OFF";
Two lines of code, the result of much head-scratching!
Finally, one other thing I learned today:
- Vectrex is really cool!