TL;DR: New to Vivado Clocking Wizard, using the clock pin from the FPGA for anything other than the input to MMCM/PLL IP fails timing.
I'm working with Vivado ML 2022 in VHDL targeting an Artix-7 FPGA (Digilent Basys 3 and/or Nexys A7-100T to be specific). I recently discovered the Vivado Clocking Wizard as a method for frequency synthesis and have been trying to figure out how to use it. I'm familiar enough with component instantiation and have been able to instantiate the generated IP core and connect its signals to the rest of my hardware, as is done here. For the purposes of this question, what the rest of my hardware entails isn't exactly important (and often changes) - I can elaborate if necessary, but fundamentally I'm using the IP core to easily generate an arbitrary clock for an arbitrary hardware component, primarily to determine its minimum delay.
When I first tried out this IP core, I found that implementing it caused my timing to go negative regardless of what I did, very similar to this problem. I remember I saw duplicate clocks just like that post at some point, but I don't recall what I did to resolve that (maybe it was the decimal precision thing) and I can't seem to recreate it (nor do I want to). In any case, that part of the issue doesn't seem to be present anymore. That post also informed me about the constraints generated by the clocking wizard, which helped me identify my main problem, which I'm currently trying to figure out: if I have a create_clock
constraint for the FPGA source clock (as I do with every other hardware I've used) and I try to use that clock elsewhere in my design, I encounter a clock redefinition critical warning, and the design fails to meet timing. If I don't use the create_clock
constraint, I still can't feasibly use that clock in the design outside of the input to the IP core - or at least, Vivado can't analyze timing properly from that, which makes sense.
The relevant constraints are as follows:
set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk }];
# This is the problem constraint
create_clock -add -name sys_clk_pin -period 10.000 -waveform {0 5} [get_ports { clk }];
Side note: now that I think about it, perhaps there's a way to constrain the clock such that it uses the one defined by the IP core instead of trying to override it?
Here's my customized IP from the clock wizard:
The following is an example code that I used with the IP core to manually verify that the frequency I was generating was actually correct. My use of a buffer signal and a conditional check for the locked
signal from the IP core may be excessive - if there's a better way to do this, or if it's not necessary, please let me know.
entity top is
port (
clk : in std_logic;
reset : in std_logic;
leds : out std_logic_vector(15 downto 0)
);
end top;
architecture rtl of top is
signal counter : std_logic_vector(31 downto 0);
component clk_wiz_0 is
port (
reset : in std_logic;
clk_in1 : in std_logic;
passthrough : out std_logic;
clk_out2 : out std_logic;
locked : out std_logic
);
end component;
signal div_clk : std_logic;
signal div_clk_buf : std_logic;
signal mmcm_locked : std_logic;
signal clk_pt : std_logic;
begin
c_div : clk_wiz_0
port map (
reset => reset,
clk_in1 => clk,
passthrough => clk_pt,
clk_out2 => div_clk_buf,
locked => mmcm_locked
);
-- This may be unnecessary
div_clk <= div_clk_buf when (mmcm_locked = '1') else '0';
leds_proc :
process (clk, reset) begin
if (reset = '1') then
leds <= (others => '0');
elsif (clk'event and clk = '1') then
leds <= counter(counter'left downto counter'left - 15);
end if;
end process;
counter_proc :
process (div_clk, reset) begin
if (reset = '1') then
counter <= (others => '0');
elsif (mmcm_locked = '1') then
if (div_clk'event and div_clk = '1') then
if (and counter) then
counter <= (others => '0');
else
counter <= counter + 1;
end if;
end if;
end if;
end process;
end architecture rtl;
As you can see from the above code, I attempted to resolve this by creating a passthrough signal, clk_pt
, that matches the input clock clk
directly. Using clk_pt
instead of clk
in leds_proc
passes timing. However, I don't understand why I can't simply use clk
directly like that. I also found this source which implies that having logic on the input side of the MMCM is undesirable, but I don't know enough to understand why that is. Furthermore, when trying to integrate the IP core into a design but not (instantiated) at the top-level, having logic on the input side becomes unavoidable. Despite extensive searching, I wasn't able to find any other sources related to this, nor could I find any in the relevant Xilinx documentation (though, I could've completely missed something).
I did find a method of structuring components more horizontally so that the IP core could be at the top-level thanks to this post. I'm going to try and rework my hardware to this paradigm, but I still want to at least understand why what I was trying to do doesn't work. Is putting logic on the input side of an MMCM/PLL generally frowned upon? Are MMCMs or PLLs never put anywhere other than the top level of a design? Am I constraining something incorrectly? Is there another way I can "wrest control from Vivado" as this answer puts it? Is there a fundamental concept I'm completely missing?
counter
variable with thediv_clk
but are reading it withclock
. \$\endgroup\$