I'm trying to improve timing in my FPGA design. I need my logic to work with a 150MHz clock, the synthesizer is saying it can work only with ~138MHz maximum.
I know that one of the popular ways to improve timing is by pipelining some of the logic in the design.
I have a finite state machine (FSM) that reads data from an ADC, all the data reading, and the creating of SCLK for the interface occurs inside one process (I am using VHDL.)
In this FSM there are two counters that increment in every state. One of the counters is used to generate the SCLK (dividing the internal FPGA clock to create the SCLK for the ADC.) The other counter counta the bits I read/write, together these counters dictate when I switch states in the FSM.
One of the counters is counting as well as the condition to read bits from the ADC all in the same clock cycle.
The critical path is the path between the output of the counter flip-flop to the data shift register that occurs inside an "if (count = ...)" block.
I am using a Lattice MachXO3LF FPGA with a custom board.
As a result, I thought about pipelining the counter, which means to leave the counter as is in the process but just to assign inside the process the counter to another counter_reg, and use inside the if conditions the counter_reg instead of the counter:
(note that the pieces of code are incomplete and just to show the idea in example)
process(clk) if (rising_edge(clk)) then case (state) is when STATE0 => if (count = NUM) then state <= STATE1; count <= 0; else count <= count+1; end if; ...
process(clk) if (rising_edge(clk)) then count_reg <= count; case (state) is when STATE0 => if (count_reg = NUM-1) then state <= STATE1; count <= 0; else count <= count+1; end if; ...
Is this a valid way to make a pipeline in FPGA design if I need to pipeline and improve the timing between the counter and the logic created inside the if condition used by the counter?
I found that it does improve my timing.
If it improved my timing, what else do I want from you guys?
It is improved but not enough (now the maximum clock is now 148MHz,) so I tried something:
Another attempt to pipeline:
process(clk) begin count_reg <= count; if (rising_edge(clk)) then case (state) is when STATE0 => if (count_reg = NUM) then state <= STATE1; count <= 0; else count <= count+1; end if; ...
As you can see, I tried to make the assignment to my pipeline register outside of the clock rising edge, and my counter_reg now changing every change in clk (rising/falling edge.) As you can see now there no need to do NUM-1 anymore because there is no delay in the counter (because the counter_reg changing in the falling edge, and until next rising edge of clk, the counter reg is already ready to go.)
What I found out is that the synthesizer loved this move, now there is no timing error for this path anymore, and my maximum clock cycle is 153MHz, so from a timing report perspective, I solved my problem.
BUT, I am not sure if it's a good design practice I am using the rising and falling edges of the clock to do my logic and far as I know it's not recommended, but it does solve my timing errors and works perfectly.
What do you think about this? Is it ok to make a pipeline like this? What am I missing?