2
\$\begingroup\$

I need to select between 10 different 164-bit buses using four-bit BCD (8421, unsigned binary). What is the most efficient way to do so?

Currently I have the following SystemVerilog implementation

case (bcdIn)
  4'd0: muxOut = optionA;
  4'd1: muxOut = optionB;
  4'd2: muxOut = optionC;
  4'd3: muxOut = optionD;
  4'd4: muxOut = optionE;
  4'd5: muxOut = optionF;
  4'd6: muxOut = optionG;
  4'd7: muxOut = optionH;
  4'd8: muxOut = optionI;
  4'd9: muxOut = optionJ;
  default: muxOut = 'x;
endcase

where muxOut and each of the "options" are 164 bits wide. The timing report for this snippet reveals a huge delay due to the fanout of the select lines (the digits of bcdIn).

Is there a more efficient way of selecting between large buses? Perhaps a tree construct of MUXes of some sort?

I am willing to code this by hand using assign statements and the like, but if there's a way to infer faster logic using case, I'm all ears.

\$\endgroup\$
2
  • \$\begingroup\$ Can't you make a mathematical expression for muxOut depending on bcdIn? Depending on the expression, that might be faster than a case. You could also try a lookup table, but I don't know if verilog can handle those. \$\endgroup\$
    – user17592
    May 6, 2013 at 6:21
  • \$\begingroup\$ Does the muxout go out of the device or is it an internal signal that connects to other logic in your design? \$\endgroup\$
    – FarhadA
    May 6, 2013 at 13:22

4 Answers 4

1
\$\begingroup\$

I think the issue is not that you're selecting between 10 busses, but rather that your select signal has to fanout to several hundred multiplexors (~4 * 164 bits).

Driving a large fanout of several hundred pins will either create a huge capacitive load (if you have dumb synthesizer), or a large buffer tree (if you have better synthesizer). Using a buffer tree is better, but I can give better advice if you paste the timing report that's giving you trouble.

If there's no way to meet timing in a single cycle, you could latch bcdIn into several parallel registers, and use these parallel registers to drive different bits of the output (i.e. latch bcdIn into 4 identical parallel flip flops, and drive muxout[40:0] from the first flop, drive muxout[82:41] from the second, etc). This will reduce any one signal's fanout during a clock cycle, it is somewhat inefficient though.

Ultimately your fanout doesn't seem unreasonable to me, and I'm surprised that you'd have trouble meeting it on any kind of process technology, but perhaps it was just not optimized well by the synthesizer.

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

I have two suggestions. use parallel case synthesizer directive and/or one-hot select coding.

Try coding as onehot to balance the loading. This way each select signal has a ~164 fan-out load. This should help with the buffer tree and routing.

always @* begin
  bcdIn_1hot = 10'b0;
  if (bcdIn<4'd10) bcdIn_1hot[bcdIn] = 1'b1;
  case (1'b1) // synthesizer's  parallel_case keyword OR prefix unique if SystemVerilog is supported
    bcdIn_1hot[0]: muxOut = optionA;
    bcdIn_1hot[1]: muxOut = optionB;
    /* ... other connections here ... */
    bcdIn_1hot[9]: muxOut = optionJ;
    default: muxOut = 'x;
  endcase
end

Even with original code using synthesizer case options can make a big difference. parallel_case and full_case are common keywords between different tools, but you'll need to check the manual as there is not universal style. Without the option your synthesizer will generate the equivalent of a nested step though else-if and that will have poor performance.

If your tool supports SystemVerilog, then I highly recommend using unique case

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

RTL only implies the transfer of data with a loose guide on the gate level implementation. A case statement is a look up table and could easily be synthesised as a tree of muxes. May be create smaller look up tables broken up by flip flops.

The timing problem here is with the width of the bus getting all the bits to with in an equivalent ripple delay is quite hard braking up the block with an flop will help:

always @(posedge clk)
casez (bcdIn)
  4'b000?: muxOutA <= optionA;
  4'b001?: muxOutA <= optionC;
  4'b010?: muxOutA <= optionE;
  4'b011?: muxOutA <= optionG;
  4'b100?: muxOutA <= optionI;
  default: muxOutA <= 'x;
endcase
end

always @(posegde clk) begin
casez (bcdIn)
  4'b000?: muxOutB <= optionB;
  4'b001?: muxOutB <= optionD;
  4'b010?: muxOutB <= optionF;
  4'b011?: muxOutB <= optionH;
  4'b100?: muxOutB <= optionJ;
  default: muxOutB <= 'x;
endcase
end

always @(posedge clk) begin
case (bcdIn[0])
  1'b0: muxOut <= muxOutA;
  1'b1: muxOut <= muxOutB;
endcase
end
\$\endgroup\$
0
\$\begingroup\$

The code you have will create 167 10-1 mux with an output enable, which means you will have 2 CLBs for each bit of your output bus. Since most FPGAs have 5 or 6 input LUTs, you will need at least 2 of them for each mux, which means you will have close to 400 LUts just for the mux and probably much more because of the routing congestion inside an FPGA.

What @pre_randomize said is actually a good way of breaking the design into 2 blocks that makes it more 'FPGA' friendly and it may speed up your design significantly, it could be even better to divide it to 3 to give the P&R more room to place the logic inside your FPGA.

One other option is to create a bus, and instead of having a mux, use the logic to create an output enable for each option, depending on the FPGA you are using, this can create a faster implementation for your design, but I am not sure if it makes it smaller.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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