Конкретная микросхема выбранного для разработки семейства ПЛИС обычно выбирается уже после разработки конфигурации и тестирования ее в симуляторе исходя из занимаемого объема. Тем не менее бывают случаи когда в конфигурациию добавляется новый функционал и она перестает помещаться в существующую микросхему, либо  когда еще при выборе процент переполнения очень мал (например синтезатор говорит что занят 101% LUT), и не хочется переплачивать много за следующую микросхему. В обоих случаях можно попробовать уменьшить объем занимаемый ПЛИС. Второй случай конечно не рекомендуется рассматривать если в дальнейшем функционал будет расширятся хотя бы минимально - всегда лучше оставлять небольшой запас.

Способ 1. Самый простой - стратегия сборки

Если код рабочий и изменять его совсем нехочеться можно попробовать самый простой вариант - задать стратегию сборки с минимизацией площади. В Xilinx ISE это делается в Project -> Design Goals & Strategies, выбрать Area Reduction. Этот способ может дать нембольшой выигрыш за счет того что синтезатор будет пытаться подбирать примитивы для реализации таким образом, чтобы максимально экономить площать, однако при этом может немного просесть максимальная частота clk, так что не забывайте следить за ней. Для примера в Xilinx ISE она показана в Syntesis Report: 

Timing Summary:
---------------
Speed Grade: -2

Minimum period: 9.490ns (Maximum Frequency: 105.374MHz)
Minimum input arrival time before clock: 6.082ns
Maximum output required time after clock: 4.118ns
Maximum combinational path delay: No path found

Способ 2. Оптимизация конфигурируемых ядер

С одной стороны может создаться ложное впечатление что конфигурируемые IP ядра которые вы используете используют исключительно дополнительные ресурсы ПЛИС (PLL, BRAM) и т.д. и по этому обращать внимание на их настройки не имеет смыла - все равно эти ресурсы будут простаивать. Однако на самом деле каждый выход генерируемого ядра помимо аппаратных специализированных ресурсов может занять кучу "мягкой" логики. К примеру стандартная FIFO на блочной памяти хоти и использует блоки BRAM но к ним добавляет логику для генерации выходов таких как Almoast Full/EmptyCount.

По этому в разработке ни когда не генерируйте ядра на перед "а вдруг этот выход мне понадобится" а четко определяйте минимальный набор функций которые нужны. Так, если у вас есть dual-clocked FIFO а вам нужен Count только на одном clock - обязательно отключайте второй Count. Если половина ваших FIFO используют Count а вторая половина нет - сгенерируйте два типа FIFO.

Отдельно отмечу что dual-clocked FIFO сами по себе занимают намного больше места чем однотактовые - по этому если вы используете такие, рассмотрите вариант самостоятельной синхронизации доменов. Если у вас это получится, вероятно вы очень хорошо уменьшите площадь. Конечно такая оптимизация имеет смысл если FIFO находится хотябы в кратных часовых доменах (например частоты 50 МГц и 1 МГц генерируемые одним источником с одинаковой фазой), но не на асинхронных - где действительно лучше полагаться на синхронизацию генерируемую IP ядром. Но в случае синхронизации асинхронных доменов с помощью FIFO  можно сделать небольшую  dual-clocked очередь на Distributed-логике для синхронизации асинхронных доменов а уже большую однотактовую сгенерировать поверх на BRAM.

Способ 3. Оптимизация элементарных описаний

Особенно важно оптимизировать код VHDL для элементов которые синтезируются многократно (например каналы интерфейсов). Для этого вы можете сделать сущность канала главной (Top Level Module) и быстро синтезировать ее меняя те или иные элементы кода и проверяя отчет после каждого изменения.  Что и как оптимизировать зависит от конкретной цели.

Переполненения LUT

1. Обращайте внимание на конечные автоматы, и старайтесь избавится от лишних стейтов. Помните что каждый стеит в логике выльется пачкой дополнительных венетелей к каждому буфферу который учавствует в обработки всего автомата.

Так если у вас есть

case state is
WHEN StateStart => ... WHEN ... ... WHEN StateReUp => read_enable <= '1';
state <= StateReDown; WHEN StateReDown => read_enable <= '0';

Такой автомат может быть оптимизирован за счет исключения стейта StateReDown: 

if read_enable = '1' then
    read_enable <= '0';
end if;

case state is
    WHEN StateStart =>
       ...
    WHEN ...
       ...
    WHEN StateReUp =>
       read_enable <= '1';
       state <= StateStart ;

Также ни когда не делайте лишних присвоений типо "не хочу сейчас думать нужно тут это присвоение или нет - поставлю на всякий случай". Лучше сразу продумать что и где нужно присваивать а значение чего вам на одном из шагов вообще не понадобится.

2. Оптимизируйте компараторы

Проанализируйте ваши сравнения. Если вам нужно сравнивать что-то вроде:

if to_integer(unsigned(cnt)) >= 256 then

Оптимальнее его заменить на:

if cnt(8) = '1' then

Такие условия будут эквивалентны для 8и битных сигналов, если же сигнал содержит разряды старше (например делать то проверять надо и их, например cnt(8) ='1' or cnt(9)='1'). Конечно только сравнения со степенями двойки могут быть так оптимизированы, но алгоритм или условия задачи можно изменить чтобы оперировать такими сравнениями. 

Переполнения триггеров в слайсах.

Одной из причин переполнения с которыми сталкивался я были счетчики. Допустим если основная частота вашей схемы 50 МГц а вам нужно отсчитать один бод на частоте 9600, для этого вы можете поставить счетчик на 50e6/9600 = 5208 тактов. Затактировав такой счетчик частотой он насчитает свое значение за 1 бод. Для реализации счетчика понадобится ⌈log2(5208)⌉ =  13 бит (13 триггеров). Если у вас много каналов то в ркзультате это привратиться в большое число триггеров. Лучше сгенерировать небольшую частоту с помощью PLL и уже использовать ее с меньшим числом регистров. Например 1 МГц с счетчиком 1e6/9600 = 105, такой счетчик займет ⌈log2(105,2)⌉ = 7 триггеров. Только при таких оптимизациях стоит следить за дискретизацией - чем меньше частоту вы используете как оппорную, тем менее точным будут отсчеты. Рассмотрим пример. Идеальный отсчет в 9600 должен длиться 1.0/9600 = 104.166 мкс. Используя частоту 50МГц  и счетчик 5208 мы получим бод длительностью 5208/50e6 =104.160 мкс. А вот используя счетчик 104 на 1МГц бод будет 104/1e6= 104.0 мкс. Тогда накапливая погрешность при захвате каждого бода мы получим погрешность на 10ом боде  0.166  мкс * 10 = 1.66 мкс, что очевидно не сыграет особой роли. 

Конечно на первый взгляд экономия в 5 триггеров не столь велеика, однако если у вас 10 канналов в каждом из которых 2 счетчика, вы сэкономите 100 триггеров, что тоже может помочь. 

Надеюсь эти простые советы помогут вам хотя бы немного оптимизировать площать вашей конфигурации. Оставляйте свои "грабли" и вариманты оптимизаций в комментариях - возможно они сэкомномят время другим.