# how to resolve large combinational logic delay?

I am new to systemverilog. Recently I am working on a project for decoding a data stream from a AVALON_ST. Using systemverilog. My target device is Stratix V and the clock is 200Mz.

module decoder(
// clk and reset
input logic reset_n,
input logic clk,

// avalon st
input logic in_vld,
input logic in_sop,
input logic in_eop,
input logic[255:0] in_data,
input logic[4:0] in_eop_pos,

// output
output logic trigger
);

// local logics
shortint pos;
shortint limit;
shortint offset;
logic[511:0] buffer;
logic[255:0] remainning_data;
business_object obj; // systemverilog struct for the packed data

always_ff @(posedge clk or negedge reset_n) begin
// (0)
// handle reset_n

// (1)
// whenever in_vld, combine remainning_data (if any) and in_data
// to buffer

// (2)
// update pos, limit and offset based on in_eop_pos

// (3)
// decode the buffer and populate business_object field by field

// (4)
// if a completed obj is decoded, applied the business rule to output trigger

// (5)
// if there are remainning data bits (either after a full decoded obj
// or remainning bits that are not enough to pouplate business_object),
// store it in remainning_data

end

endmodule


I use non-blocking assignment for part #0 and #4 and use blocking assignment for #1, #2, #3 and #5.

I use ModelSim to simulate the decoder. It works. However, it doesn't work in hardware.

After investigation, I found there are some red highlight in Quartus's Timing Analyzer saying that:

"The design contains failing setup paths with a worst-case slack of -9.561ns"

There are large combinational logic delay that exceed a clock period.

My questions are:

1) Does it matter to combinational logic delay if there are number of blocking assignments(#1,#2,#3,#5) within the always_ff block?

I know it is not the best practice to have blocking assignment inside always_ff but I learnt from somewhere that it is ok to have blocking assignment for local variable or the case that there are only one always_ff block in module.

If it does matter, can I reduce the delay by moving these blocking assignment logics to always_comb?

2) The avalon interface is 256 bits wide and I am already using pineline way in my design. Any other tips that can reduce the logic delay?

Update@20180806

I look into the RTL schematic together with timing analyzer and tried to understand why there are such large number of logic levels and fan-out signals. I found the followings are consuming sufficient large amount of delay in the data path:

a) if/else statement / switch-case on pos, offset etc

b) maths calculation (e.g. adder) on pos, offset

I used general shortint as data type for pos and limit, which were local signals for representing number of bits read / pending read bits / remaining bits etc. The business data range of pos is 12k bits (1500 bytes) and offset 256 bits (32 bytes) so original I used shortint (16 bits). I was too generous.

Now, I changed to bit[5:0] for offset and bit[9:0] for pos to represent number of BYTES read / pending. It reduces the ALM count of the module from 3800 to 1900, significantly reduce the data delay and the timing requirement is now met.

Thanks to all.

• I don't see any assignments at all. Are we supposed to divine what you're doing from the hand-wavy descriptions in the comments? I see all kinds of potential pitfalls, but with no actual code, I can't comment. – Dave Tweed Jul 28 '18 at 14:13
• Quartus is able to produce a schematic view showing the inputs and outputs of each LUT it generates. Have a look at this, and see why it's producing such a long chain, and try to figure out how to flatten the chain. – Jules Jul 28 '18 at 14:18
• Sorry about that due to my company policy I cannot paste the real code here. I will instead try to code some test codes for illustrating the issue. – Ken Tsang Aug 2 '18 at 11:19

It seems the code code doing assignments is missing. I'd expect something like "pos <= offset + 1", etc...and without the code its hard to tell you exactly where to split up your code.

But here's some tips to get you going: First, by virtue of the fact that you're have "or negedge reset_n" in your process sensitivity list, you're implying that it will use active-low asynchronous reset signals...meaning that a reset pulse will take effect immediately, and not wait until the next clock edge. When you include an async reset like this this, you'll need to make sure the rest of your code actually includes an "if/else" clause specifying WHAT TO DO when there is a reset condition (then the synthesizer will connect the async reset signal to the "clear/set" pin of the Flip-Flop. By accidentally omitting the "if !reset_n" clause, you create a bogus circuit that can't be correctly synthesized.

Second, the #delay values only happen in simulation. This is a bad habit to use them, as it will enable you to (falsely) create and view updates to a value in simulator "memory" at different points in time, but thats not really how hardware will work. In hardware, all the operations will be compounded, and a big circuit will be synthesized to do all the arithmetic, and the synthesizer may move operations out of order for better performance.

Thirdly, starting this "always block" with "always_ff @(posedge clk" means everything inside it operates synchronous to the clk signal, and that all code/assignments should be nonblocking...they will all happen in parallel synchronous to the clock edge. So if you truly want some blocking assignments, make a completely separate always_comb block and put your combinatorial logic there...I see people do this when they are calculating an intermediate value for example, but in general it should be thought of as a cloud of logic that will (depending on use) likely get absorbed into synchronous circuits where it is used upon synthesis.

And Finally, to solve your timing problem you need to pipeline your design. Do this by performing the arithmetic/bit manipulations in different clock cycles, not merely by changing whether they are blocking or nonblocking. For example, output <= ABCD; might be a very large circuit, but if you create a couple of temporary variables (registers) and in the first clock cycle do: temp1 <= AB; temp2 <= C*D; then on the second clock cycle you do output <= temp1*temp2, then you've successfully decomposed one huge circuit into three smaller ones...at the cost of an extra clock of latency. Typically in a streaming application that is acceptable, but you'll have to figure out how the rest of the design must change to accommodate latency.

After days of investigation, I still cannot fix the issue. However, I found this online training from intel altera that may help.

https://www.intel.com/content/www/us/en/programmable/support/training/course/ohdl1130.html

At least I can recognize some failure timing characteristics in my design:

1) high fan-out signals

2) high combinational logic level

3) unintended priority encoders