0
\$\begingroup\$

This question is purely about synthesized verliog, not simulated.

I have read that blocking assignments are executed in order, but also that blocking assignments assign values to the RHS immediately. These two seem to contradict each other.

Eg. take a for loop adder:

reg signed [15:0] signalHist [127:0];
integer i;
reg signed [142:0] sum = 0;
wire triggerSum = 0;
...

always @ (posedge clk) begin
if (triggerSum==1) begin
    sum = 0;
    for (i=0; i<128; i=i+1) begin
        sum = sum + signalHist[i];
    end
end

In this example's case, how can the value for sum be assigned to its register immediately? What happens if we are summing 1000 or 100000 int16s?

Or, does the word immediately as I used above just mean "after the required amount of processing time"?

If the number of integers being summed gets to be very large (or more generally, if we have too many blocking statements each which depends on the previous), could it be possible that the synthesized circuit would take more than a clock cycle to evaluate?

EDIT:

Below is a simplified version of the code Im trying to run. Its basically a FIR filter. I stripped out the ADC/DAC interfacing sections so that this code:

  • Loads data from INDATA[7:0] every 1000 clock cycles (inDataCounter flips every 500 clock cycles)
  • On next clock cycle, shifts the array signalHist[] by 1 and puts the new INDATA into the first index
  • On next clock cycle, multiplies signalHist[] .* taps[] (point by point) and saves the result in product[]
  • On the next clock cycle, sums all of product[] into sumSum
  • On the next clock cycle, sets part of the resulting sumSum to OUTDATA (output pins)
    always @ (posedge clk)
    begin
        //Simulate new data clock, every 1000 cycles
        if (inDataCounter < 500) begin
            inDataCounter <= inDataCounter+1;
        end else if (inDataCounter == 500) begin
            inDataCounter <= 0;
            inDataClk<= ~inDataClk;
            
            // Filter taps, hard coded for now, update-able here
            taps[0] <= 271;
            taps[1] <= -961;
            taps[2] <= 2574;
            ...
            taps[2047] <= 15;
        end


        triggerSampleAddLast <= triggerSampleAdd;
        if (triggerSampleAddLast != triggerSampleAdd) begin
            //shift the time array and add the new element
            for (jj=2047; jj>0; jj=jj-1) begin
                signalHist[jj] <= signalHist[jj-1];
            end
            signalHist[0] <= adcSample[17:2];
            signalHistTrig <= ~signalHistTrig;
        end
        
        
        signalHistTrigLast <= signalHistTrig;
        if (signalHistTrigLast != signalHistTrig) begin
            for (kk=0; kk<2048; kk=kk+1) begin
                product[kk] <= taps[kk]*signalHist[kk];
            end
            productTrig <= ~productTrig;
        end
        
        productTrigLast <= productTrig;
        if (productTrigLast != productTrig) begin
            sumSum = 0;
            for (ll=0; ll<2048; ll=ll+1) begin //4096 or 256 loops
                    sumSum = sumSum + product[ll];
            end
            doneReading = ~doneReading;
        end

        doneReadingLast <= doneReading;
        if (doneReadingLast != doneReading) begin
            OUTDATA[15:0] <= sumSum[35:20];//signalHist[0];//sumSum[18:3]; //sumSum[18:3];//adc0_sample[17:2];//;//output dac spi word  
        end
    end

And below, the new data sample coming in

    always @ (posedge inDataClk) begin
        adcSample <= INDATA;
        triggerSampleAdd<=~triggerSampleAdd;
    end

This takes 269k logic blocks to complete, 1000+% of my FPGA. Is there any way to reduce the number of logic blocks required, by splitting up into multiple cycles?

Is there a better way to split up things onto multiple cycles?

\$\endgroup\$
16
  • 1
    \$\begingroup\$ Is this code synthesizable? The semantics of the word "immediately" changes depending if the code is running on a simulation or if it is being synthesized. I'm not with synthesizer here to check if your code synthesizes, that's why I've asked if it synthesizes. Anyway, if it does, I strongly recommend you to check the RTL synthesis output and check if indeed does what was intended \$\endgroup\$ May 17 at 0:04
  • 2
    \$\begingroup\$ Probably your code is going to generate a large adder tree in order to implement the for loop before registering on the flip flop, thus satisfying the "immediately" \$\endgroup\$ May 17 at 0:07
  • 2
    \$\begingroup\$ Can you post the entire code? It is taking too long probably because the adder tree is so big that it is failing to route the signals. In many cases, when there is a for loop in verilog, it is synthesized as a combinational circuit that gets registered only at the last level \$\endgroup\$ May 17 at 0:19
  • 1
    \$\begingroup\$ I believe it is possible to design a simpler architecture for your circuit, but first we need to understand what are you trying to achieve. Could you provide some drawings (schematics) of the circuit you're trying to describe? \$\endgroup\$ May 17 at 20:34
  • 1
    \$\begingroup\$ Please understand that HDL is not "code" that "runs". There is no "processing time" as in software. This describes a hardware and the synthesis tool tries to translate the HDL into the hardware it supports. Before there were 128 chained adders, consequentially with a considerable delay (if even practical). After the edit, there are now 2048 multipliers and 2048 chained adders! \$\endgroup\$
    – devnull
    May 17 at 22:43

2 Answers 2

1
\$\begingroup\$

The context of blocking assignments being updated immediately applies only to simulation.

For a synthesis flow (targeting physical resources is an FPGA or ASIC) blocking assignments model combinational logic which has propagation delay. The propagation delay depends heavily on the logic being modeled, on the technology that the synthesis, the place & route tools are performing the technology mapping, and the physical targeted device itself.

In general, the combinational logic propagation delays are not known at simulation time, a model known as the stratified event scheduler is used for simulation and behaves such that combinational and sequential logic is reliably modeled to behave like hardware without knowing the delays.

It works but its still only a simulation model.

Synthesis and place & route tools provide reports on the physical delays. More specifically the static timing analysis tool provides an analysis of each path in the design post synthesis and post place & route.

\$\endgroup\$
1
\$\begingroup\$

Every operation with in an "always @ (posedge clk)" block has to happen within the same clock cycle, and therefore gets synthesized into separate hardware. So in the case of this code:

always @ (posedge clk) begin
    if (triggerSum==1) begin
        sum = 0;
    for (i=0; i<128; i=i+1) begin
        sum = sum + signalHist[i];
    end
end

That synthesizes into a chain of 128 separate adders. This is why you have no space left.

In order to make this work, you will have to pull the loop out of the always block and turn it into a clock+counter combination. Instead of trying to use 128 adders in one clock cycle, use one adder over and over for 128 clock cycles.

Looking at your longer code, trying to do 2048 operations over 1000 clock cycles will be a problem, so you'll need to have something like

always @ (posedge clk) begin
   if(accumulatorRunning==1) begin
      sum <= signalHist[count]*tap[count] + signalHist[count+1]*tap[count+1] + signalHist[count+2]*tap[count+2] signalHist[count+3]*tap[count+3];
      count <= count + 4;
   else
      sum <= 0;
      count <= 0;
   end
   resultReady <= (count == 2048 - 4);
end

For learning this style, you may find it useful to try writing some SIMD assembler. That way you can quickly iterate, use a debugger, etc.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.