Porting to Virtex-4: Who ate my IOB registers?

This post was written by eli on July 25, 2009
Posted Under: FPGA

Surprise, surprise!

When porting a design from Spartan-3 to Virtex-4, I discovered that many registers, which were correctly placed in the IOB in the Spartan-3, fell off into fabric-placed flip-flops. Which is very bad news, since keeping the registers in the IOB isn’t just a matter of better timing, but rather repeatable timing, which is much more important to me. I don’t want the timing to change when I reimplement, or a poorly designed board with marginal signal quality can make a new FPGA version to appear buggy, because something stopped to work.

It turns out, that the tools got lost somewhere in the transaction from plain IOBs to ILOGICs and OLOGICs. In other words, the synthesizer (XST J.39, with ISE 9.2.03i) or maybe the mapper failed to take obvious hints. My hunch is that the mapper is to blame.

What part of “ILOGIC” didn’t you understand?

There’s always the aggressive solution of instantiating the IOBUF and the relevant flip-flops explicitly. In fact, it may be enough to just instantiate the IOBUF itself. The only explanation I can think of why this would help, is that the synthesizer packs the registers during synthesis, and maybe also makes some minor fixes to allow this packing. It’s ugly, but it works.  Or if it doesn’t work, at least I know why: A major advantage of instantiating IDDR and ODDR (or IDDR_2CLK, if you want to feed it with two clocks) is that it forces the mapper to complain loudly when in refuses to put them in place. It can’t just hide the flip-flops in the fabric, say nothing, and hope I won’t notice.

In theory, differences between the clocking and reset schemes should be allowed between the ILOGIC and OLOGIC flip-flops. How do I know? Because I can get it done with the FPGA Editor. In practice, the packer is terribly picky about what it’s ready to pair into an ILOGIC/OLOGIC couple. I haven’t tested all combinations, but it appears it won’t try to pack a DDR flip-flop next to a non-DDR one. And if there’s a difference in clocking or reset, forget about it.

Example: I had a case where I used the PRESET input for the ODDR, but no reset at all for the IDDR, and got:

ERROR:Pack:1564 – The dual data rate register controller/ddr_bus_T[10] failed to join the OLOGIC component as required.  The OLOGIC SR signal does not match the ILOGIC SR signal, or the ILOGIC SR signal is absent.

Inference (and its black magic)

Just using the “IOB of … is TRUE” synthesis pragma seems to make the synthesizer do no more than to avoid eliminating equivalent registers, and duplicating them when their content is used in several sites. That’s nice, but sometimes not enough.

Let’s see a few examples of what worked and what didn’t work with the ISE foodchain, when instatiation was avoided. The target is Virtex-4. Note that no IOB pragma is used here.

First, let’s look at this:

module try(
	   input clk,
	   output reg toggle
	   );
   reg [1:0] 	  count;

   always @(posedge clk)
     begin
	count <= count + 1;

	case (count)
	  0: toggle <= 1'b0;
	  1: toggle <= 1'bz;
	  2: toggle <= 1'b1;
	  3: toggle <= 1'bz;
	endcase
     end

endmodule

This actually worked well: Both tri-state buffer and data register were placed in the OLOGIC element, resulting in optimal timing. But hey, what if we want to read from the data lines while they are tri-stated? So we go for this (note that it’s not functionally equivalent):

module try(
	   input clk,
	   inout toggle
	   );
   reg [1:0] 	  count;

   reg 		  toggle_reg;
   reg 		  z_reg;

   assign 	  toggle = z_reg ? 1'bz : toggle_reg;

   always @(posedge clk)
     begin
	count <= count + 1;
	z_reg <= count[1];
	case (count)
	  0: toggle_reg <= 1'b1;
	  1: toggle_reg <= 1'b0;
	  2: toggle_reg <= 1'b1;
	  3: toggle_reg <= 1'b1;
	endcase
     end

endmodule

This placed both registers in the OLOGIC as well (note that I didn’t bother to read from the IO, but never mind). The truth is that I was lucky here. If z_reg and toggle_reg would happen to be equivalent, they would melt into a single register, which would remain outside the OLOGIC element. Or let’s look at this example (note the subtle difference…):

module try(
	   input clk,
	   output toggle
	   );
   reg [1:0] 	  count;

   reg 		  toggle_reg;
   reg 		  z_reg;

   assign 	  toggle = !z_reg ? 1'bz : toggle_reg;

   always @(posedge clk)
     begin
	count <= count + 1;
	z_reg <= count[1];
	case (count)
	  0: toggle_reg <= 1'b1;
	  1: toggle_reg <= 1'b0;
	  2: toggle_reg <= 1'b1;
	  3: toggle_reg <= 1'b1;
	endcase
     end

endmodule

For those who missed the difference, here it is: The polarity of z_reg, as a data out enabler is reversed. The z_reg register which is implemented by the synthesizer turns out to be in the wrong polarity for the tri-state buffer in the pad, so the OLOGIC is used as a piece of combinatoric logic. A NOT gate, to be precise. It would be wiser and possible, of course, to implement both the NOT gate and the flip-flop inside the OLOGIC, but it looks like the tools don’t take it that far.

But let’s be a bit fair. These oddities could be fixed simply by adding a single synthesis hint:

// synthesis attribute IOB of z_reg is "TRUE"

The thing is, that the tools managed without this hint in several cases when targeting Spartan-3 devices. What’s ugly here,  is not that the synthesis pragma is necessary, but that the tools behave differently suddenly.

And another small pitfall: Don’t put double quotes around the register’s name (z_reg) in our case. That will cause the synthesizer to silently ignore the pragma comment. It’s OK to put the around “TRUE” but not around a name which lives in the synthesizer’s name space.

Botttom line

When porting to Virtex-4 (and most likely newer FPGAs) keep a very close eye on where the IOB registers are placed. Also, be aware of how picky the tools have become about the similarity between paired OLOGIC and ILOGIC.

Add a Comment

required, use real name
required, will not be published
optional, your blog address