Overpipelining Predicates

A previous post discussed why overpipelining occurs.  Overpipelinig was defined as adding register stages for no logicial or performance reason, but simply as a code convenience.  It is a common issue with design styles that favor single clocked processes.  Subexpressions get registered because it reduces the line lengths, even though the subexpressions are not terribly complex.  This post aims to show one of the more sinister aspects of overpipelining.

One of more sinister aspects of overpipelining is that it is often done with what are essentially predicates — single bits that define “this is happening” or “this is not happening”.  Things like “the fifo is empty” or “state machine is in the write state”.  More likely these will be called “empty” and “is_wstate”, or some other simple name.

These concepts are usually unambiguous — “empty” means “currently empty” instead of “wasn’t not empty last cycle”.  The empty case is the one that is most likely to come up with most high-speed designs, but the other cases are the ones most likely to become issues later.  Here is an example of a commonly used structure:

always @ (posedge clk) begin
  fifo_rd <= (!empty) && (csm == S_READ) && (dst_avail);
end

Which is left a bit shorter for the webpage’s shorter line limits.  One overpipelined version would be:

always @ (posedge clk) begin
  want_read <= !fifo_empty && dst_avail;
  fifo_rd   <= want_read && (csm == S_READ);
end

Which very subtly breaks in many cases.  The issue here is that the signal “want_read” is being delayed by one cycle, and thus the empty signal’s impact is delayed by one cycle.  If S_READ lasts several cycles, then it’s possible that the fifo will go empty.  The fifo_rd signal will be help high for one additional cycle, as the empty signal will not be able to affect the fifo_rd signal on that first cycle.  Often, fifo_rd will drive other logic, and this extra read will not be welcomed.

The second possible mistake can be seen with another way of overpipelining the same system:

always @ (posedge clk) begin
  want_read <= (csm == S_READ) && dst_avial;
  fifo_rd   <= !empty && want_read;
end

This time, there are two possible issues.  The first is that dst_avail might have just transitioned low.  Similar to the fifo_empty case, this will cause an extra read of the fifo.  At least the output will be valid, but the destination might not want the data.

The second issue is that (csm == S_READ) might not be true on the current cycle.  This again leads to what is likely an accidental read.

The next common case is the “busy” flag.

always @ (posedge clk) begin
  case (csm)
    S_IDLE : begin
      ...
    end 
    ...
  endcase
  busy <= (csm != S_IDLE);
end

The issue here that other state machines may check this busy flag.  It is possible for the above state machine to transition to a non-idle state while advertising that it has not done so.  Again, the name “busy” implies the current status of the state machine while it actually contains the previous status.

always @ (posedge clk) begin
  case (csm)
    S_IDLE : begin
      ...
    end 
    ...
  endcase
  busy      <= (csm != S_IDLE);
  dst_avail <= !busy && (wsm == S_IDLE);
end

Notice that csm’s effect on dst_available is delayed by two cycles, while wsm’s is only delayed by one.  Notice that busy is already late by one cycle, and then is used in dst_avail one cycle later.  From a previous example, it was shown how dst_avail could be used incorrectly to generate outputs even later.  To make things better, the names still indicate that the destination is currently available.

This entry was posted in FPGA, Verilog. Bookmark the permalink.

Comments are closed.