The same RTL can go into either. The decision is almost never about whether the logic fits — it is about volume, schedule, and how sure you are that the design is finished.

The cost crossover

Total cost is non-recurring engineering plus per-unit:

An ASIC has brutal NRE (masks, verification, packaging qualification — seven figures at a modern node) and tiny unit cost. An FPGA has near-zero NRE and a unit cost that never falls below the price of the part. Set them equal and the break-even volume is

For most mid-complexity digital designs that lands somewhere in the tens of thousands of units. Below it, the FPGA wins on arithmetic alone. Above it, the ASIC does — provided you only pay the NRE once.

The part nobody budgets for

FPGAASIC
NRE~0very high, per respin
Unit costhigh, flatvery low at volume
Time to first siliconminutesmonths
Cost of a logic bugrebuild the bitstreamanother mask set
Power at equal function5–10× worsebaseline
Max clocklowerhigher

That “cost of a logic bug” row is the one that moves projects. An FPGA respin is a coffee break. An ASIC respin is a quarter and a mask set, which is why ASIC verification budgets routinely exceed design budgets.

Which is why CDC discipline is not optional

Clock-domain crossings are the classic bug that simulates clean and fails in silicon. On an FPGA you find it in the lab and fix it that afternoon. On an ASIC you find it after tapeout. The rules are identical in both cases:

The two-flop synchronizer

Two flip-flops in series, both on the receiving clock. The first one is allowed to go metastable; it gets a full clock period to resolve before the second one samples it.

module sync_2ff #(parameter WIDTH = 1) (
    input  wire             clk,
    input  wire             rst_n,
    input  wire [WIDTH-1:0] async_in,
    output reg  [WIDTH-1:0] sync_out
);
    reg [WIDTH-1:0] meta;
 
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            meta     <= '0;
            sync_out <= '0;
        end else begin
            meta     <= async_in;   // may go metastable
            sync_out <= meta;       // has had a full period to settle
        end
    end
endmodule

Three rules that come with it:

  1. Never fan out the first stage. If meta drives anything other than sync_out, different loads can resolve to different values and you have just built a circuit that disagrees with itself.
  2. Only synchronize single bits this way. Two bits crossing together can resolve on different clock edges, so a bus can transit through values it never actually held. Use a handshake or an async FIFO with Gray-coded pointers instead.
  3. Tell the tools. Constrain the path as a false path or set_max_delay, otherwise static timing analysis will report a violation it cannot fix and you will be tempted to “solve” it by deleting the synchronizer.

The bug does not reproduce

Metastability failures are rare, temperature-dependent, and vanish under a logic analyzer. If a design fails once a week in the field and never on the bench, audit every clock-domain crossing before you audit anything else.

Link to original

A reasonable default

Prototype on an FPGA regardless. If the volume never materialises you have shipped anyway; if it does, you tape out RTL that has been running in the field instead of RTL that has only ever run in a simulator.

See also