DocsTech
/
YOSYS
/

~ cd 3.1. синтез в деталях

Синтез, как правило, можно разделить на coarse-grain, and fine-grain синтез. Мы видели это в Синтезатор, где конструкция была загружена и проработана, а затем прошла через серию coarse-grain оптимизаций, прежде чем был отображен на аппаратные(IP) блоки и fine-grain ячейки. Большинство команд в Yosys нацелены либо на coarse-grain, либо на fine-grain представление, и лишь некоторые из них совместимы с обоими состояниями.

Такие команды, как proc, fsm и memory, опираются на дополнительную информацию, содержащуюся в coarse-grain представлении, а также на ряд оптимизаций, таких как wreduce, share и alumacc. opt обеспечивает оптимизации, полезные в обоих состояниях, а techmap используется для преобразования coarse-grain ячеек в соответствующее fine-grain представление.

Однобитные ячейки (логические вентили, FFs), а также LUT, полусумматоры и полные сумматоры составляют основную часть fine-grain представления и необходимы для таких команд, как abc /abc9, simplemap, dfflegalize и memory_map .

3.1.1. Команды синтезатора

3.1.1.1. Пакетные команды synth_

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

3.1.1.2. Общий синтез

В дополнение к перечисленным выше командам синтеза, специфичным для конкретного оборудования, существует также prep — общий скрипт синтеза. Эта команда ограничивается coarse-grain синтезом, не вдаваясь в какие-либо специфические для архитектуры сопоставления или оптимизации. Помимо прочего, она полезна для верификации конструкции.

Следующие команды выполняются командой prep:
...
Копировать
begin:
    hierarchy -check [-top <top> | -auto-top]
coarse:
    proc [-ifx]
    flatten (if -flatten)
    future
    opt_expr -keepdc
    opt_clean
    check
    opt -noff -keepdc
    wreduce -keepdc [-memx]
    memory_dff (if -rdff)
    memory_memx (if -memx)
    opt_clean
    memory_collect
    opt -noff -keepdc -fast
check:
    stat
    check

Синтезатор рассказывает о большинстве этих команд и о том, что они делают.

3.1.2. Преобразование процедурных блоков

Фронтенде Verilog происходит преобразование блоков always в RTL нетлисты для выражений и «processess» для элементов управления и памяти. Затем команда proc преобразует эти «processess» в нетлисты RTL мультиплексоров и регистровых ячеек. Это также макрокоманда, которая вызывает другие команды proc_* в установленном порядке:

Листинг 3.1: Команды, вызываемые proc
...
Копировать
proc_clean # removes empty branches and processes
proc_rmdead # removes unreachable branches
proc_prune
proc_init # special handling of “initial” blocks
proc_arst # identifies modeling of async resets
proc_rom
proc_mux # converts decision trees to multiplexer networks
proc_dlatch
proc_dff # extracts registers from processes
proc_memwr
proc_clean # this should remove all the processes, provided all went fine
opt_expr -keepdc

После всех команд proc_* вызывается opt_expr. Это можно отключить, вызвав proc -noopt.

Многие команды не могут работать с модулями, содержащими «processess». Обычно вызов proc является первой командой в процедуре синтеза после элаборации конструкции.

Пример

docs/source/code_examples/synth_flow.

Листинг 3.2: proc_01.v
...
Копировать
module test(input D, C, R, output reg Q);
    always @(posedge C, posedge R)
        if (R)
            Q <= 0;
        else
            Q <= D;
endmodule

Листинг 3.3: proc_01.ys

...
Копировать
read_verilog proc_01.v
hierarchy -check -top test
proc;;
Листинг 3.4: proc_02.v
...
Копировать
module test(input D, C, R, RV, output reg Q);
    always @(posedge C, posedge R)
        if (R)
            Q <= RV;
        else
            Q <= D;
endmodule

Листинг 3.5: proc_02.ys

...
Копировать
read_verilog proc_02.v
hierarchy -check -top test
proc;;
Листинг 3.6: proc_03.ys
...
Копировать
read_verilog proc_03.v
hierarchy -check -top test
proc;;
Листинг 3.7: proc_03.v
...
Копировать
module test(input A, B, C, D, E, output reg Y);
    always @* begin
    Y <= A;
    if (B)
        Y <= C;
    if (D)
        Y <= E;
    end
endmodule

3.1.3. Обработка FSM

Команда fsm определяет, извлекает, оптимизирует (перекодирует) и пересинтезирует конечные автоматы. Она представляет собой макрос, вызывающий ряд других команд:

Листинг 3.8: Команды, вызываемые fsm
...
Копировать
# Нахождение и извлечение FSMs:
fsm_detect
fsm_extract

# Базовая оптимизация:
fsm_opt
opt_clean
fsm_opt

# Расширение на логику вентилей (если был вызов с параметром -expand):
fsm_expand
opt_clean
fsm_opt

# Перекодирование состояний FSM (если не было при вызове параметра -norecode):
fsm_recode
# Print information about FSMs:
fsm_info

# Экспортирование FSM в Формат файла KYSS2 (если был вызов с параметром -export):
fsm_export

# Отображения FSMs в RTL ячейках (если не было при вызове параметра -nomap):
fsm_map

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

3.1.3.1. Обнаружение FSM

Команда fsm_detect идентифицирует регистры состояния FSM. Она устанавливает атрибут \fsm_encoding = «auto» для любого (многобитного) провода, который соответствует следующему описанию:

Эта система хорошо себя зарекомендовала. Ее можно переписать, установив \fsm_encoding = «auto» для регистров, которые должны считаться регистрами состояния FSM, и установив \fsm_encoding = «none» для регистров, которые соответствуют вышеуказанным критериям, но не должны считаться регистрами состояния FSM.

Однако обратите внимание, что маркировка регистров состояний с \fsm_encoding, которые не подходят для перекодировки FSM, может привести к сбою синтеза или недостоверным результатам.

3.1.3.2. Извлечение FSM

Команда fsm_extract действует на все сигналы состояния, помеченные атрибутом (\fsm_encoding != «none»).

Для каждого сигнала состояния определяется следующая информация:

Регистры состояния (и сигналы асинхронного сброса, если применимо) просто определяются путем идентификации источника для сигнала состояния.

Отсюда рекурсивно обходится $mux-дерево, управляемое входами регистра состояний. Все входы селектора являются управляющими сигналами, а входы $mux-дерева — состояниями. Алгоритм терпит неудачу, если найден неконстантный вход, не являющийся сигналом состояния.

Список управляющих выходов инициализируется битами из сигнала состояния. Затем он расширяется путем добавления всех значений, которые вычисляются ячейками, сравнивающими сигнал состояния с постоянным значением.

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

Наконец, генерируется таблица переходов для FSM. Для этого используется вспомогательный класс ConstEval C++ (определен в kernel/consteval.h), который может быть использован для оценки частей конструкции. Класс ConstEval можно попросить вычислить заданный набор результирующих сигналов, используя набор назначений сигнал-значение. Ему также может быть передан список стоп-сигналов, которые прерывают алгоритм ConstEval, если для вычисления результирующих сигналов требуется значение стоп-сигнала.

Процедура fsm_extract использует класс ConstEval следующим образом для создания таблицы переходов. Для каждого состояния:

  1. Создает объект ConstEval для модуля, содержащего FSM
  2. Добавляет все управляющие входы в список стоп-сигналов
  3. Устанавливает сигнал состояния в текущее состояние
  4. Попробуйте оценить следующее состояние и управляющий выход
  5. Если шаг 4 не увенчался успехом:
    1. Рекурсивно перейдите к шагу 4, установив для нарушающего стоп-сигнала значение 0.
    2. Рекурсивно перейдите к шагу 4, установив для нарушающего стоп-сигнала значение 1.
  6. Если шаг 4 прошел успешно: Эмитировать переход

Наконец, создается ячейка $fsm со сгенерированной таблицей переходов и добавляется в модуль. Эта новая ячейка подключается к управляющим сигналам, а старые источники управляющих выходов отключаются.

3.1.3.3. Оптимизация FSM

Команда fsm_opt выполняет базовые оптимизации для ячеек $fsm (не включая перекодировку состояний). Выполняются следующие оптимизации (в этом порядке):

  1. Неиспользуемые управляющие выводы удаляются из ячейки $fsm. Атрибут \unused_bits (который обычно устанавливается передачей opt_clean) используется для определения неиспользуемых управляющих выводов.
  2. Входы управления, подключенные к одному и тому же источнику, объединяются.
  3. Если управляющий вход управляется управляющим выходом, управляющий вход удаляется, а таблица переходов изменяется таким образом, чтобы обеспечить ту же производительность без внешнего пути обратной связи.
  4. Записи в таблице переходов, которые дают одинаковый выход и отличаются только значением одного бита управляющего входа, объединяются, а отличающийся бит удаляется из списка чувствительности
  5. Постоянные входы удалены, а таблица переходов изменена, чтобы обеспечить неизменное поведение.
  6. Неиспользуемые входы удаляются.

3.1.3.4. Перекодирование FSM

Команда fsm_recode присваивает состояниям новую битовую схему. Обычно это также подразумевает изменение ширины сигнала состояния. На момент написания этой статьи(30.09.2024) поддерживается только одноточечное кодирование со всеми нулями для состояния сброса.

Передача fsm_recode также может записать текстовый файл с выполненными изменениями, который можно использовать при верификации проектов, синтезированных в Yosys с помощью Synopsys Formality.

3.1.4. Операции с памятью

3.1.4.1. Команда памяти

В RTL-нетлисте чтение и запись памяти — это отдельные ячейки. Это упрощает консолидацию количества портов для памяти. Команды memory преобразует память в имплементацию. По умолчанию это логика для дешифраторов адреса и регистров. Это макрокоманда, которая вызывает другие команды memory_* в установленном порядке:

Листинг 3.9: Команды, вызываемые memory
...
Копировать
opt_mem
opt_mem_priority
opt_mem_feedback
memory_bmux2rom
memory_dff
opt_clean
memory_share
opt_mem_widen
memory_memx (если был вызов с параметром -memx)
opt_clean
memory_collect
memory_bram -rules <bram_rules> (если был вызов с параметром -bram)
memory_map (если не было при вызове параметра -nomap)

Небольшие заметки:

  1. memory_dff объединяет регистры в ячейки чтения и записи памяти.
  2. memory_collect собирает все ячейки чтения и записи для памяти и преобразует их в одну многопортовую ячейку памяти.
  3. memory_map берет многопортовую ячейку памяти и преобразует ее в логику и регистры дешифратора адреса.

Для получения дополнительной информации о memory, например, об отключении определенных подкоманд, см. раздел Память — перевод памяти в базовые ячейки.

3.1.4.2. Пример

docs/source/code_examples/synth_flow.

Листинг 3.10: memory_01.ys
...
Копировать
read_verilog memory_01.v
hierarchy -check -top test
proc;; memory; opt

Листинг 3.11: memory_01.v

...
Копировать
module test(input      CLK, ADDR,
            input      [7:0] DIN,
        output reg [7:0] DOUT);
    reg [7:0] mem [0:1];
    always @(posedge CLK) begin
        mem[ADDR] <= DIN;
    DOUT <= mem[ADDR];
    end
endmodule
Листинг 3.12: memory_02.v
...
Копировать
module test(
    input             WR1_CLK,  WR2_CLK,
    input             WR1_WEN,  WR2_WEN,
    input      [7:0]  WR1_ADDR, WR2_ADDR,
    input      [7:0]  WR1_DATA, WR2_DATA,
    input             RD1_CLK,  RD2_CLK,
    input      [7:0]  RD1_ADDR, RD2_ADDR,
    output reg [7:0]  RD1_DATA, RD2_DATA
);

reg [7:0] memory [0:255];

always @(posedge WR1_CLK)
    if (WR1_WEN)
        memory[WR1_ADDR] <= WR1_DATA;

always @(posedge WR2_CLK)
    if (WR2_WEN)
        memory[WR2_ADDR] <= WR2_DATA;

always @(posedge RD1_CLK)
    RD1_DATA <= memory[RD1_ADDR];

always @(posedge RD2_CLK)
    RD2_DATA <= memory[RD2_ADDR];

endmodule
Листинг 3.13: memory_02.ys
...
Копировать
read_verilog memory_02.v
hierarchy -check -top test
proc;; memory -nomap
opt -mux_undef -mux_bool

3.1.4.3. Размещение памяти

Обычно предпочтительнее использовать для памяти ресурсы RAM, специфичные для конкретной архитектуры. Например:
...
Копировать
memory -nomap
memory_libmap -lib my_memory_map.txt
techmap -map my_memory_map.v
memory_map

memory_libmap пытается преобразовать ячейки памяти ($mem_v2 и т.д.) в аппаратно поддерживаемую память, используя предоставленную библиотеку (my_memory_map.txt в примере выше). При необходимости добавляется логика эмуляции для обеспечения функциональной эквивалентности до и после этого преобразования. techmap -map my_memory_map.v затем использует techmap для сопоставления с аппаратными примитивами. Все оставшиеся ячейки памяти, которые не могут быть преобразованы, затем подхватываются memory_map и отображаются на DFF и дешифраторы адресов.

Примечание: Более подробную информацию о доступных вариантах размещения и расходе ячеек каждого из них можно найти, включив вывод отладочных сообщений. Это можно сделать с помощью команды debug или используя флаг -g при вызове Yosys для глобального включения отладочных сообщений.

3.1.4.4. Поддерживаемые паттерны памяти

Обратите внимание, что в этот документ включены не все поддерживаемые модели. Особо следует отметить, что комбинации нескольких моделей, как правило, работают. Например, wbe можно использовать в сочетании с любой из моделей простой двухпортовой (SDP). В общем случае, если определение аппаратной памяти не поддерживает заданную конфигурацию, для обеспечения соответствия поведения моделированию будет добавлена дополнительная логика.

3.1.4.5. Примечания

3.1.4.5.1. Выбор типа памяти

Код вывода памяти автоматически выбирает примитив памяти устройства, основываясь на геометрии памяти и используемых функциях. В зависимости от цели, для выбора может быть доступно до четырех классов примитивов памяти:

  1. FF RAM (она же логика): аппаратный примитив не используется, память сводится к куче FF(триггеров) и мультиплексоров
    1. Может работать с произвольным количеством портов записи, если все порты записи находятся в одном тактовом домене
    2. Может работать с произвольным количеством и типом портов чтения
  2. LUT RAM (распределенная оперативная память): использует хранилище LUT в качестве оперативной памяти.
    1. Поддерживается на большинстве ПЛИС (за исключением ice40).
    2. Обычно имеет один порт синхронной записи, один или несколько портов асинхронного чтения
    3. Маленький
    4. Никогда не будет использоваться для ПЗУ (переход на обычные LUT всегда лучше).
  3. Блочная RAM: выделенные ячейки памяти
    1. Поддерживаются практически все ПЛИС
    2. Поддерживает только синхронное чтение
    3. Два порта с отдельными тактовыми сигналами
    4. Обычно поддерживается настоящий двухпортовый режим (за исключением ice40, который поддерживает только SDP).
    5. Обычно поддерживаются асимметричные памяти и возможность записи на каждый байт
    6. Размер в несколько килобайт
  4. Огромная RAM:
    1. Поддерживается только для нескольких устройств:
      1. Некоторые устройства Xilinx UltraScale (UltraRAM)
        1. Двухпортовая, оба с взаимоисключающими синхронными чтением и записью
        2. Однотактовая
        3. Исходные данные ячеек памяти должны быть равны 0
      2. Некоторые устройства ice40 (SPRAM)
        1. Однапортовая с взаимоисключающими синхронными чтениями и записями
        2. Не поддерживает исходные данные
      3. Nexus (большой объем оперативной памяти)
        1. Двухпортовая, оба с взаимоисключающими синхронными чтением и записью
        2. Однотактовая
    2. Не будет автоматически выбираться кодом вывода памяти, требуется явный выбор через атрибут ram_style

В целом, процесс автоматического выбора может работать примерно так:

Этот процесс можно переопределить, приписав к памяти атрибут ram_style:

Ошибкой будет, если это переопределение не может быть реализовано для данного устройства.

Для совместимости с другим программным обеспечением принимается также множество альтернативных написаний атрибута.

3.1.4.5.2. Исходные данные

Большинство FPGA-устройств поддерживают инициализацию всех видов памяти значениями, задаваемыми пользователем. Если явная инициализация не используется, исходные значение памяти не определено. Исходные данные могут быть предоставлены операторами initial, записывающими ячейки памяти с помощью системных задач $readmemh или $readmemb.

3.1.4.5.3. Порт записи с разрешающим байтом

...
Копировать
reg [31 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable[0])
                mem[write_addr][7:0] <= write_data[7:0];
        if (write_enable[1])
                mem[write_addr][15:8] <= write_data[15:8];
        if (write_enable[2])
                mem[write_addr][23:16] <= write_data[23:16];
        if (write_enable[3])
                mem[write_addr][31:24] <= write_data[31:24];
        if (read_enable)
                read_data <= mem[read_addr];
end

3.1.4.6. Паттерны простой двухпортовой памяти (SDP)

3.1.4.6.1. Асинхронное чтение SDP

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];
always @(posedge clk)
        if (write_enable)
                mem[write_addr] <= write_data;
assign read_data = mem[read_addr];

3.1.4.6.2. Синхронный SDP с пересечением тактовых доменов

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge write_clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;
end

always @(posedge read_clk) begin
        if (read_enable)
                read_data <= mem[read_addr];
end

3.1.4.6.3. Приоритетное синхронное чтение SDP

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;
        if (read_enable)
                read_data <= mem[read_addr];
end

3.1.4.6.4. Синхронный SDP с неопределенным поведением с приоритетной операцией чтения

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;

        if (read_enable) begin
                read_data <= mem[read_addr];

        if (write_enable && read_addr == write_addr)
                // this if block
                read_data <= 'x;
        end
end
...
Копировать
(* no_rw_check *)
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;

        if (read_enable)
                read_data <= mem[read_addr];
end

3.1.4.6.5. Синхронный SDP с приоритетной операцией записи

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;

        if (read_enable) begin
                read_data <= mem[read_addr];
                if (write_enable && read_addr == write_addr)
                        read_data <= write_data;
        end
end

3.1.4.6.6. Синхронный SDP с приоритетной операцией записи (альтернативный паттерн)

...
Копировать
reg [ADDR_WIDTH - 1 : 0] read_addr_reg;
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;
        read_addr_reg <= read_addr;
end

assign read_data = mem[read_addr_reg];

3.1.4.7. Паттерны Однопортовой RAM

3.1.4.7.1. Однопортовая RAM с асинхронным чтением

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];
always @(posedge clk)
        if (write_enable)
                mem[addr] <= write_data;
assign read_data = mem[addr];

3.1.4.7.2. Синхронная однопортовая RAM с взаимоисключающим чтением/записью

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[addr] <= write_data;
        else if (read_enable)
                read_data <= mem[addr];
end

3.1.4.7.3. Синхронная однопортовая RAM с приоритетной функцией чтения

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[addr] <= write_data;
        if (read_enable)
                read_data <= mem[addr];
end

3.1.4.7.4. Синхронная однопортовая RAM с приоритетной функцией записи

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[addr] <= write_data;
        if (read_enable)
                if (write_enable)
                        read_data <= write_data;
                else
                        read_data <= mem[addr];
end

3.1.4.7.5. Порт синхронного чтения с исходными значениями

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];
reg [DATA_WIDTH - 1 : 0] read_data;
initial read_data = 'h1234;

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;
        if (read_enable)
                read_data <= mem[read_addr];
end

3.1.4.8. Паттерны сброса регистров чтения

Сбросы можно комбинировать с любыми другими поддерживаемыми паттернами (за исключением того, что синхронный и асинхронный сбросы не могут использоваться в одном порту чтения). Если используется блочная RAM и выбранный сброс (синхронный или асинхронный) используется, но не поддерживается текущим устройством, в него будет вставлена небольшая схема эмуляции.

3.1.4.8.1. Синхронный сброс, где сброс приоритетнее разрешающего

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;

        if (read_reset)
                read_data <= 'h1234;
        else if (read_enable)
                read_data <= mem[read_addr];
end

3.1.4.8.2. Синхронный сброс, где разрешающий приоритетнее сброса

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;
        if (read_enable)
                if (read_reset)
                        read_data <= 'h1234;
                else
                        read_data <= mem[read_addr];
end

3.1.4.8.3. Порт синхронного чтения с асинхронным сбросом

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;
end

always @(posedge clk, posedge read_reset) begin
        if (read_reset)
                read_data <= 'h1234;
        else if (read_enable)
                read_data <= mem[read_addr];
end

3.1.4.9. Асимметричные модели памяти

Построение асимметричной памяти (памяти с портами чтения/записи разной ширины):

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

3.1.4.9.1. Широкий синхронный порт чтения

...
Копировать
reg [7:0] mem [0:255];
wire [7:0] write_addr;
wire [5:0] read_addr;
wire [7:0] write_data;
reg [31:0] read_data;

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;
        if (read_enable) begin
                read_data[7:0] <= mem[{read_addr, 2'b00}];
                read_data[15:8] <= mem[{read_addr, 2'b01}];
                read_data[23:16] <= mem[{read_addr, 2'b10}];
                read_data[31:24] <= mem[{read_addr, 2'b11}];
        end
end

3.1.4.9.2. Широкий асинхронный порт чтения

...
Копировать
reg [7:0] mem [0:511];
wire [8:0] write_addr;
wire [5:0] read_addr;
wire [7:0] write_data;
wire [63:0] read_data;

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;
end

assign read_data[7:0] = mem[{read_addr, 3'b000}];
assign read_data[15:8] = mem[{read_addr, 3'b001}];
assign read_data[23:16] = mem[{read_addr, 3'b010}];
assign read_data[31:24] = mem[{read_addr, 3'b011}];
assign read_data[39:32] = mem[{read_addr, 3'b100}];
assign read_data[47:40] = mem[{read_addr, 3'b101}];
assign read_data[55:48] = mem[{read_addr, 3'b110}];
assign read_data[63:56] = mem[{read_addr, 3'b111}];

3.1.4.9.3. Широкий порт записи

...
Копировать
reg [7:0] mem [0:255];
wire [5:0] write_addr;
wire [7:0] read_addr;
wire [31:0] write_data;
reg [7:0] read_data;

always @(posedge clk) begin
        if (write_enable[0])
                mem[{write_addr, 2'b00}] <= write_data[7:0];
        if (write_enable[1])
                mem[{write_addr, 2'b01}] <= write_data[15:8];
        if (write_enable[2])
                mem[{write_addr, 2'b10}] <= write_data[23:16];
        if (write_enable[3])
                mem[{write_addr, 2'b11}] <= write_data[31:24];
        if (read_enable)
                read_data <= mem[read_addr];
end

3.1.4.10. Двухпортовые модели (TDP)

3.1.4.10.1. TDP с разными тактовыми сигналами с чтение/запись

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk_a) begin
        if (write_enable_a)
                mem[addr_a] <= write_data_a;
        else if (read_enable_a)
                read_data_a <= mem[addr_a];
end

always @(posedge clk_b) begin
        if (write_enable_b)
                mem[addr_b] <= write_data_b;
        else if (read_enable_b)
                read_data_b <= mem[addr_b];
end

3.1.4.10.2. TDP с одним тактовым сигналом с приоритетной операцией чтением

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable_a)
                mem[addr_a] <= write_data_a;
        if (read_enable_a)
                read_data_a <= mem[addr_a];
end

always @(posedge clk) begin
        if (write_enable_b)
                mem[addr_b] <= write_data_b;
        if (read_enable_b)
                read_data_b <= mem[addr_b];
end

3.1.4.10.3. TDP с несколькими портами чтения

...
Копировать
reg [31:0] mem [0:31];

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] <= write_data;
end

assign read_data_a = mem[read_addr_a];
assign read_data_b = mem[read_addr_b];
assign read_data_c = mem[read_addr_c];

3.1.4.11. Паттерны поддерживаются только в Verific

Следующие паттерны поддерживаются только при считывании конструкции с помощью Verific frontend.

3.1.4.12. Синхронный SDP с приоритетной операцией записи с помощью блокирующих назначения(присвоения)

Используйте sdp_wf для совместимости с фронтендом Yosys Verilog.
...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr] = write_data;

        if (read_enable)
                read_data <= mem[read_addr];
end

3.1.4.13. Асимметричная память за счет выбора частей

Для совместимости с Frontend Yosys Verilog создайте широкие порты из нешироких портов (см. wide_sr).
...
Копировать
reg [31:0] mem [2**ADDR_WIDTH - 1 : 0];

wire [1:0] byte_lane;
wire [7:0] write_data;

always @(posedge clk) begin
        if (write_enable)
                mem[write_addr][byte_lane * 8 +: 8] <= write_data;

        if (read_enable)
                read_data <= mem[read_addr];
end

3.1.4.14. Нежелательные паттерны

3.1.4.14.1. Асинхронная запись

...
Копировать
reg [DATA_WIDTH - 1 : 0] mem [2**ADDR_WIDTH - 1 : 0];

always @* begin
        if (write_enable)
                mem[write_addr] = write_data;
end

assign read_data = mem[read_addr];

3.1.5. Команды оптимизации

Yosys использует ряд оптимизаций для получения более качественных и чистых результатов. В этой главе описываются эти оптимизации.

3.1.5.1. Макрокоманда opt

В Yosys команда opt выполняет ряд простых оптимизаций. К ним относится удаление неиспользуемых сигналов и ячеек и упрощение константных выражений. Рекомендуется запускать эту команду после каждого основного шага в сценарии синтеза. Эта макрокоманда вызывает следующие команды opt_*:

Листинг 3.14: Команды, вызываемые opt
...
Копировать
opt_expr
opt_merge -nomux

do
    opt_muxtree
    opt_reduce
    opt_merge
    opt_share  (-full only)
    opt_dff  (except when called with -noff)
    opt_clean
    opt_expr
while <changed design>

3.1.5.2. Упрощение и простая перезапись константных выражений — opt_expr

Этот команда выполняет упрощение константных выражений на внутренних комбинационных ячейках, описанных в библиотеке внутренних ячеек(глава 4.2.4.). Это означает, что ячейка со всеми постоянными входами заменяется постоянным значением, управляемым этой ячейкой. В некоторых случаях эта команда может также оптимизировать ячейки с некоторыми постоянными входами.

Таблица 3.1: Правила упрощение константных выражений для ячеек $_AND_, используемые в opt_expr.

A-InputB-InputReplacement
any00
0any0
111
X/ZX/ZX
1X/ZX
X/Z1X
anyX/Z0
X/Zany0
a1a
1bb

В табл. 3.1 приведены правила замены, используемые для оптимизации вентилей $_AND_. Первые три правила реализуют очевидные правила упрощения константных выражений. Обратите внимание, что «any» может включать динамические значения, вычисляемые другими частями схемы. Следующие три строки распространяют состояния undef (X). Это единственные три случая, когда разрешено распространять undef в соответствии с п. 5.1.10 стандарта IEEE 1364-2005.

Следующие две строки принимают значение 0 для состояний undef. Эти два правила используются только в том случае, если в текущем модуле невозможны другие подстановки. Если другие подстановки возможны, они выполняются первыми, в надежде, что ‘any’ изменится на значение undef или 1 и, следовательно, выход может быть установлен в undef.

Последние две строки просто заменяют вентили $_AND_ с одним входом constant-1 на буфер.

Кроме этого базового упрощения, передача opt_expr может заменить ячейки $eq и $ne шириной в 1 бит буферами или без вентилей, если один из входов является постоянным. Проверки равенства также могут быть уменьшены в размере, если есть лишние биты в аргументах (т.е. битов, которые постоянны на обоих входах). Это может, например, привести к тому, что константа шириной 32 бита, например 255, будет уменьшена до 8-битного значения 8’11111111, если сравниваемый сигнал только 8-битный, как в модуле addr_gen после opt_expr; clean в Synthesis starter.

Передача opt_expr очень консервативна в отношении оптимизации ячеек $mux, так как эти ячейки часто используются для моделирования деревьев решений, и разрушение этих деревьев может помешать другим оптимизациям.

Листинг 3.15: пример verilog для демонстрации opt_expr

...
Копировать
module uut(
    input a,
    output y, z
);  
    assign y = a == a;
    assign z = a != a;
endmodule
Рис. 3.1: До и после opt_expr
Рис. 3.1: До и после opt_expr

3.1.5.3. Слияние одинаковых ячеек — opt_merge

Этот команда выполняет тривиальное разделение ресурсов. Это означает, что данный команда определяет ячейки с одинаковыми входами и заменяет их одним экземпляром ячейки.

Опция -nomux может быть использована для отключения разделения ресурсов для ячеек мультиплексора ($mux и $pmux). Это может быть полезно, так как предотвращает объединение деревьев мультиплексоров, что может помешать opt_muxtree определить возможные оптимизации.

Листинг 3.16: пример verilog для демонстрации opt_merge

...
Копировать
module uut(
    input  [3:0] a, b,
    output [3:0] y, z
);
    assign y = a + b;
    assign z = b + a;
endmodule
До и после opt_merge
Рис. 3.2: До и после opt_merge

3.1.5.4. Удаление не активных ветвей из дерева мультиплексоров — opt_muxtree

Этот команда оптимизирует деревья ячеек мультиплексора, анализируя входы выбора. Рассмотрим следующий простой пример:

Листинг 3.17: пример verilog для демонстрации opt_muxtree

...
Копировать
module uut(
    input a, b, c, d,
    output y
);  
    assign y = a ? (a ? b : c) : d;
endmodule
yosys3-3 До и после opt_muxtree
Рис. 3.3: До и после opt_muxtree

Выход никогда не может быть c, так как для этого нужно, чтобы a было 1 для внешнего мультиплексора и 0 для внутреннего мультиплексора. Процедура opt_muxtree обнаруживает это противоречие и заменяет внутренний мультиплексор на a константой 1.

3.1.5.5. Упрощение больших MUX и AND/OR вентилей — opt_reduce

Это простая операция оптимизации, которая выявляет и объединяет одинаковые входные биты в ячейках $reduce_and и $reduce_or. Он также сортирует входные биты, чтобы облегчить определение совместно используемых ячеек $reduce_and и $reduce_or в других командах.

Этот команда также выявляет и объединяет идентичные входы в ячейки мультиплексора. В этом случае новый общий бит выбора управляется с помощью ячейки $reduce_or, которая объединяет исходные биты выбора.

Наконец, эта команда объединяет деревья ячеек $reduce_and и деревья ячеек $reduce_or в одну большую ячейку $reduce_and или $reduce_or.

Эти три простые оптимизации выполняются в цикле до тех пор, пока не будет получен стабильный результат.

3.1.5.6. Объединение взаимоисключающих ячеек с общими входами — opt_share

Этот команда определяет взаимоисключающие клетки одного типа, которые:

что позволяет объединить ячейки и перевести мультиплексор с мультиплексирования своего выхода на мультиплексирование не совместно использующих входной сигнал.

Листинг 3.18: пример verilog для демонстрации opt_share

...
Копировать
module uut(
    input  [15:0] a, b,
    input         sel,
    output [15:0] res,
);
    assign res = {sel ? a + b : a - b};
endmodule
yosys3-4 До и после opt_share
Рис. 3.4: До и после opt_share

При выполнении opt в полном объеме исходный $mux (обозначенный $3) оптимизируется opt_expr.

3.1.5.7. Выполнение оптимизаций DFF — opt_dff

Этот команда определяет однобитные триггеры d-типа $(_DFF_, $dff и $adff) с постоянным входом данных и заменяет их постоянным источником. Он также может объединить мультиплексоры разрешения тактовых импульсов и синхронного сброса, удалив неиспользуемые управляющие входы.

Если вызвать с параметров -nodffe и -nosdff, эта команда используется для подготовки дизайна к работе с FSM.

3.1.5.8. Удаление неиспользуемых ячеек и проводов — opt_clean

Эта команда определяет неиспользуемые сигналы и ячейки и удаляет их из проекта. Он также создает атрибут \ unused_bits для проводов с неиспользуемыми битами. Этот атрибут может быть использован для отладки или другими командами оптимизации.

3.1.5.9. Когда использовать opt или clean

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

Рекомендуется вызывать opt перед такими дорогостоящими командами, как sat или freduce, поскольку возможный выигрыш в этих случаях гораздо выше, чем возможный проигрыш.

Команда clean, которая является псевдонимом opt_clean с меньшей детализацией сообщения отладки, с другой стороны, очень быстрая, и многие команды оставляют беспорядок (болтающиеся сигнальные провода и т. д.). Например, большинство команд не удаляют никаких проводов или ячеек. Они просто меняют соединения и зависят от последующего вызова clean, чтобы избавиться от теперь уже неиспользуемых объектов. Поэтому в каждом скрипте синтеза не помешает иногда использовать ;;, который сам по себе является псевдонимом для clean, например
...
Копировать
hierarchy; proc; opt; memory; opt_expr;; fsm;;

3.1.5.10. Другие оптимизации

3.1.6. Технологическое размещение

В предыдущих главах описывалось, как HDL-код преобразуется в RTL-схему. Нетлист RTL по-прежнему основан на абстрактных типах coarse-grain ячеек, таких как сумматоры произвольной ширины и даже умножители. В этой главе рассказывается о том, как RTL-схема преобразуется в функционально эквивалентную схему с использованием типов ячеек, доступных в архитектуре устройства.

Технологическое размещение часто выполняется в два этапа. На первом этапе RTL ячейки размещаются с помощью внутренней библиотеки однобитных ячеек (см. раздел Gates). На втором этапе этот нетлист внутренних типов вентилей преобразуется в нетлист вентилей из библиотеки технологии устройства.

Если в архитектуре устройства предусмотрены coarse-grain ячейки (например, блочные память или АЛУ), их необходимо размещать непосредственно в нетлист RTL, поскольку информация о coarse-grain структуре проекта теряется при ее размещении на типы вентилей с битовой шириной.

3.1.6.1. Замена ячеек

Простейшей формой технологического размещения является замена ячеек, выполняемая командой techmap. Эта команда, когда ей предоставляется файл Verilog, реализующий типы ячеек RTL с помощью более простых ячеек, просто заменяет ячейки RTL на предоставленную реализацию.

Если файл не предоставлен, techmap использует встроенный файл, который сопоставляет типы ячеек Yosys RTL с внутренней библиотекой вентилей, используемой Yosys. Любопытный читатель может найти этот файл в файле astechlibs/common/techmap.v в дереве исходниках Yosys.

В techmap были добавлены дополнительные возможности для условного отображения ячеек. Это может быть полезно, например, если архитектура устройства поддерживает аппаратное обеспечение множителей для одних битов, но не для других.

В обычном потоке синтеза сначала используется передача techmap для прямого сопоставления некоторых ячеек RTL с ячейками coarse-grain, предоставляемыми архитектурой устройства (если таковые имеются), а затем используется techmap со встроенным файлом по умолчанию для сопоставления оставшихся ячеек RTL с логикой вентилей.

3.1.6.2. Замена подсхем

Иногда архитектура устройства предоставляет ячейки, более мощные, чем RTL-ячейки, используемые Yosys. Например, ячейка в архитектуре устройства, которая может вычислять абсолютную разность двух чисел, не соответствует ни одному типу RTL-ячеек, а только их комбинациям.

Для таких случаев Yosys предоставляет функцию извлечения, которая может сопоставить заданный набор модулей с конструкцией и определить части конструкции, которые идентичны (т.е. изоморфные подсхемы) любому из заданных модулей. Эти совпадающие подсхемы затем заменяются экземплярами заданных модулей.

В процессе извлечения также можно найти основные вариации заданных модулей, например, заменить местами входы в коммутативных типах ячеек.

Кроме того, команда извлечения имеет ограниченную поддержку поиска частых подсхем, т. е. процесса поиска повторяющихся подсхем в конструкции. Это имеет несколько применений, включая проектирование новых coarse-grain архитектур.

Тяжелая алгоритмическая работа, выполняемая при команде извлечения (решение проблемы изоморфной подсхемы и поиск частых подсхем), выполняется с помощью библиотеки SubCircuit, которая также может использоваться отдельно, без Yosys (см. SubCircuit).

3.1.6.3. Технологическое размещение на уровне вентилей

На уровне вентилей архитектура устройства обычно описывается «файлом Liberty». Формат файла Liberty — это промышленный стандарт, который можно использовать для описания поведения и других свойств стандартных библиотечных ячеек.

Сопоставление проекта, использующего внутреннюю библиотеку вентилей Yosys (например, в результате сопоставления с этим представлением с помощью передачи techmap), выполняется в два этапа.

Сначала регистровые ячейки должны быть размещены на регистры, доступные в архитектурах устройства. Архитектура устройства может не предоставлять все варианты триггеров D-типа с положительным и отрицательным фронтом тактового импульса, высокоактивным и низкоактивным асинхронными установкой и/или сбросом и т. д. Поэтому процесс размещения регистров может добавить в проект дополнительные инверторы, и поэтому важно сначала отобразить ячейки регистров.

Сопоставление регистровых ячеек может быть выполнено с помощью команды dfflibmap. Эта команда принимает в качестве аргумента файл Liberty (с помощью опции -liberty) и использует только регистровые ячейки из файла Liberty.

Во-вторых, комбинационная логика должна быть сопоставлена с архитектурой устройства. Это делается с помощью внешней программы ABC через команду abc с использованием параметра -liberty. Обратите внимание, что в этом случае используются только комбинаторные ячейки из библиотеки ячеек.

Иногда файлы Liberty содержат коммерческие секреты (например, конфиденциальную информацию о времени), которыми нельзя свободно делиться. Это усложняет такие процессы, как сообщение об ошибках в соответствующих инструментах. Когда информация в файле Liberty, используемом Yosys и ABC, не являются частью конфиденциальной информации, то для удаления конфиденциальной информации из файла Liberty можно использовать дополнительный инструмент yosys-filterlib.

3.1.7. Команда extract

Примеры кода можно найти в docs/source/code_examples/macc.

...
Копировать
read_verilog macc_simple_test.v
hierarchy -check -top test;;
yosys3-5 до extract
Рис. 3.5: до extract
...
Копировать
extract -constports -map macc_simple_xmap.v;;
yosys3-6 после extract
Рис. 3.6: после extract
Листинг 3.19: macc_simple_test.v
...
Копировать
module test(a, b, c, d, y);
    input [15:0] a, b;
    input [31:0] c, d;
    output [31:0] y;
    assign y = a * b + c + d;
endmodule
Листинг 3.20: macc_simple_xmap.v
...
Копировать
module macc_16_16_32(a, b, c, y);
    input [15:0] a, b;
    input [31:0] c;
    output [31:0] y;
    assign y = a*b + c;
endmodule

Листинг 3.21: macc_simple_test_01.v

...
Копировать
module test(a, b, c, d, x, y);
    input [15:0] a, b, c, d;
    input [31:0] x;
    output [31:0] y;
    assign y = a*b + c*d + x;
endmodule
Листинг 3.22: macc_simple_test_02.v
...
Копировать
module test(a, b, c, d, x, y);
    input [15:0] a, b, c, d;
    input [31:0] x;
    output [31:0] y;
    assign y = a*b + (c*d + x);
endmodule
Листинг 3.22: macc_simple_test_02.v

3.1.7.1. Метод «wrap-extract-unwrap»

Часто coarse-grain элемент имеет постоянную битовую ширину, но может использоваться для реализации операций с меньшей битовой шириной. Например, 18×25-битный умножитель может быть использован для реализации 16×20-битного умножения.

Способом размещения таких элементов при coarse-grain синтезе является метод wrap-extract-unwrap:

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

Теперь все операции кодируются с использованием той же ширины битов, что и в coarse-grain элементе. Команда extract может быть использована для замены схем на ячейки архитектуры устройства.

Оставшуюся ячейку-обертку можно развернуть с помощью techmap.

3.1.7.2. Пример: DSP48_MACC

В этом разделе приведен пример, показывающий, как разместить MACC-операции произвольного размера на MACC-ячейки с 18×25-битным умножителем и 48-битным сумматором (например, ячейки Xilinx DSP48).

Предварительная обработка: macc_xilinx_swap_map.v

Убедитесь, что A — меньший из портов на всех множителях.

Листинг 3.23: macc_xilinx_swap_map.v
...
Копировать
(* techmap_celltype = "$mul" *)
module mul_swap_ports (A, B, Y);

parameter A_SIGNED = 0;
parameter B_SIGNED = 0;
parameter A_WIDTH = 1;
parameter B_WIDTH = 1;
parameter Y_WIDTH = 1;

input [A_WIDTH-1:0] A;
input [B_WIDTH-1:0] B;
output [Y_WIDTH-1:0] Y;

wire _TECHMAP_FAIL_ = A_WIDTH <= B_WIDTH;

$mul #(
    .A_SIGNED(B_SIGNED),
    .B_SIGNED(A_SIGNED),
    .A_WIDTH(B_WIDTH),
    .B_WIDTH(A_WIDTH),
    .Y_WIDTH(Y_WIDTH)
) _TECHMAP_REPLACE_ (
    .A(B),
    .B(A),
    .Y(Y)
);

endmodule

Обертывание множителей: macc_xilinx_wrap_map.v

Листинг 3.24: macc_xilinx_wrap_map.v
...
Копировать
(* techmap_celltype = "$mul" *)
module mul_wrap (A, B, Y);

parameter A_SIGNED = 0;
parameter B_SIGNED = 0;
parameter A_WIDTH = 1;
parameter B_WIDTH = 1;
parameter Y_WIDTH = 1;

input [A_WIDTH-1:0] A;
input [B_WIDTH-1:0] B;
output [Y_WIDTH-1:0] Y;

wire [17:0] A_18 = A;
wire [24:0] B_25 = B;
wire [47:0] Y_48;
assign Y = Y_48;

wire [1023:0] _TECHMAP_DO_ = "proc; clean";

reg _TECHMAP_FAIL_;
initial begin
    _TECHMAP_FAIL_ <= 0;
    if (A_SIGNED || B_SIGNED)
        _TECHMAP_FAIL_ <= 1;
    if (A_WIDTH < 4 || B_WIDTH < 4)
        _TECHMAP_FAIL_ <= 1;
    if (A_WIDTH > 18 || B_WIDTH > 25)
        _TECHMAP_FAIL_ <= 1;
    if (A_WIDTH*B_WIDTH < 100)
        _TECHMAP_FAIL_ <= 1;
end

$__mul_wrapper #(
    .A_SIGNED(A_SIGNED),
    .B_SIGNED(B_SIGNED),
    .A_WIDTH(A_WIDTH),
    .B_WIDTH(B_WIDTH),
    .Y_WIDTH(Y_WIDTH)
) _TECHMAP_REPLACE_ (
    .A(A_18),
    .B(B_25),
    .Y(Y_48)
);

endmodule

Обертывание сумматоров: macc_xilinx_wrap_map.v

Листинг 3.25: macc_xilinx_wrap_map.v
...
Копировать
(* techmap_celltype = "$add" *)
module add_wrap (A, B, Y);

parameter A_SIGNED = 0;
parameter B_SIGNED = 0;
parameter A_WIDTH = 1;
parameter B_WIDTH = 1;
parameter Y_WIDTH = 1;

input [A_WIDTH-1:0] A;
input [B_WIDTH-1:0] B;
output [Y_WIDTH-1:0] Y;

wire [47:0] A_48 = A;
wire [47:0] B_48 = B;
wire [47:0] Y_48;
assign Y = Y_48;

wire [1023:0] _TECHMAP_DO_ = "proc; clean";

reg _TECHMAP_FAIL_;
initial begin
    _TECHMAP_FAIL_ <= 0;
    if (A_SIGNED || B_SIGNED)
        _TECHMAP_FAIL_ <= 1;
    if (A_WIDTH < 10 && B_WIDTH < 10)
        _TECHMAP_FAIL_ <= 1;
end

$__add_wrapper #(
    .A_SIGNED(A_SIGNED),
    .B_SIGNED(B_SIGNED),
    .A_WIDTH(A_WIDTH),
    .B_WIDTH(B_WIDTH),
    .Y_WIDTH(Y_WIDTH)
) _TECHMAP_REPLACE_ (
    .A(A_48),
    .B(B_48),
    .Y(Y_48)
);

endmodule

Извлечение: macc_xilinx_xmap.v

Листинг 3.26: macc_xilinx_xmap.v
...
Копировать
module DSP48_MACC (a, b, c, y);

input [17:0] a;
input [24:0] b;
input [47:0] c;
output [47:0] y;

assign y = a*b + c;

endmodule

Просто используйте те же команды обертывания на этом модуле, что и на дизайне, чтобы создать шаблон для команды extract.

Разворачивание множителей: macc_xilinx_unwrap_map.v

Листинг 3.27: Модуль $__mul_wrapper в файле macc_xilinx_unwrap_map.v
...
Копировать
module $__mul_wrapper (A, B, Y);

parameter A_SIGNED = 0;
parameter B_SIGNED = 0;
parameter A_WIDTH = 1;
parameter B_WIDTH = 1;
parameter Y_WIDTH = 1;

input [17:0] A;
input [24:0] B;
output [47:0] Y;

wire [A_WIDTH-1:0] A_ORIG = A;
wire [B_WIDTH-1:0] B_ORIG = B;
wire [Y_WIDTH-1:0] Y_ORIG;
assign Y = Y_ORIG;

$mul #(
    .A_SIGNED(A_SIGNED),
    .B_SIGNED(B_SIGNED),
    .A_WIDTH(A_WIDTH),
    .B_WIDTH(B_WIDTH),
    .Y_WIDTH(Y_WIDTH)
) _TECHMAP_REPLACE_ (
    .A(A_ORIG),
    .B(B_ORIG),
    .Y(Y_ORIG)
);

endmodule

Разворачивание сумматоров: macc_xilinx_unwrap_map.v

Листинг 3.28: Модуль $__add_wrapper в файле macc_xilinx_unwrap_map.v
...
Копировать
module $__add_wrapper (A, B, Y);

parameter A_SIGNED = 0;
parameter B_SIGNED = 0;
parameter A_WIDTH = 1;
parameter B_WIDTH = 1;
parameter Y_WIDTH = 1;

input [47:0] A;
input [47:0] B;
output [47:0] Y;

wire [A_WIDTH-1:0] A_ORIG = A;
wire [B_WIDTH-1:0] B_ORIG = B;
wire [Y_WIDTH-1:0] Y_ORIG;
assign Y = Y_ORIG;

$add #(
    .A_SIGNED(A_SIGNED),
    .B_SIGNED(B_SIGNED),
    .A_WIDTH(A_WIDTH),
    .B_WIDTH(B_WIDTH),
    .Y_WIDTH(Y_WIDTH)
) _TECHMAP_REPLACE_ (
    .A(A_ORIG),
    .B(B_ORIG),
    .Y(Y_ORIG)
);

endmodule

Листинг 3.29: test1 из файла macc_xilinx_test.v

...
Копировать
module test1(a, b, c, d, e, f, y);
    input [19:0] a, b, c;
    input [15:0] d, e, f;
    output [41:0] y;
    assign y = a*b + c*d + e*f;
endmodule
Листинг 3.30: test2 из файла macc_xilinx_test.v
...
Копировать
module test2(a, b, c, d, e, f, y);
    input [19:0] a, b, c;
    input [15:0] d, e, f;
    output [41:0] y;
    assign y = a*b + (c*d + e*f);
endmodule
Обертывание в test1:
...
Копировать
techmap -map macc_xilinx_wrap_map.v

connwrappers -unsigned $__mul_wrapper Y Y_WIDTH \
             -unsigned $__add_wrapper Y Y_WIDTH;;
Завершение в test2:
...
Копировать
techmap -map macc_xilinx_wrap_map.v

connwrappers -unsigned $__mul_wrapper Y Y_WIDTH \
             -unsigned $__add_wrapper Y Y_WIDTH;;
Экстракт в тесте1:
...
Копировать
design -push
read_verilog macc_xilinx_xmap.v
techmap -map macc_xilinx_swap_map.v
techmap -map macc_xilinx_wrap_map.v;;
design -save __macc_xilinx_xmap
design -pop

extract -constports -ignore_parameters \
        -map %__macc_xilinx_xmap       \
        -swap $__add_wrapper A,B ;;
Экстракт в тесте2:
...
Копировать
design -push
read_verilog macc_xilinx_xmap.v
techmap -map macc_xilinx_swap_map.v
techmap -map macc_xilinx_wrap_map.v;;
design -save __macc_xilinx_xmap
design -pop

extract -constports -ignore_parameters \
        -map %__macc_xilinx_xmap       \
        -swap $__add_wrapper A,B ;;
Разверните в test2:
...
Копировать
techmap -map macc_xilinx_unwrap_map.v;;

3.1.8. Инструменты ABC

ABC, разработанный в Калифорнийском университете в Беркли, представляет собой набор логических инструментов, используемый для тонкой оптимизации и размещения LUT.

В Yosys есть две разные команды, которые используют этот набор логических инструментов, но используют его по-разному.

Команда abc может использоваться как для размещения ASIC (например, abc -liberty), так и для размещения FPGA (abc -lut), но эта страница посвящена размещению FPGA.

Команда abc9, как правило, обеспечивает более высокое качество размещения благодаря тому, что ему известны комбинационные блоки, тайминги DFF и LUT, что дает ему более глобальный взгляд на проблему размещения.

3.1.8.1. ABC: модель единица задержки , простая и эффективная

В команда abc используется очень упрощенное представление о ПЛИС:

Эта модель известна как «модель единичной задержки», поскольку каждая LUT использует одну единицу задержки.

С этой точки зрения проблема, которую должна решить компания ABC, заключается в поиске размещения сети на LUT, имеющего наименьшую задержку, а затем в оптимизации этого размещения по размеру при сохранении этой задержки.

Такой подход имеет свои преимущества:

Но у такого подхода есть и недостатки:

3.1.8.2. ABC9: обобщенная модель задержки, реалистичная и гибкая

В ABC9 используется более подробная и точная модель ПЛИС:

Эта модель известна как «обобщенная модель задержки», поскольку она была обобщена на произвольные единицы задержки. На самом деле ABC9 неважно, какие единицы вы здесь используете, но в Yosys принято использовать пикосекунды. Обратите внимание на введение box как понятия. Хотя обобщенная модель задержки не требует boxes, они естественно вписываются в нее для представления комбинационных задержек. Даже синхронные задержки, такие как прибытие и установка, могут быть эмулированы с помощью комбинационных ящиков, которые действуют как задержка. Эта возможность распространяется и на white box, когда разводчик может видеть внутренности box и удалять незайдествованные box без выходов, например, сумматоры.

ABC9 находит размещение сети на LUT, которое имеет наименьшую задержку, а затем минимизирует его, чтобы найти наименьшую площадь, но у него гораздо больше информации о сети, с которой можно работать.

В результате ABC9 может удалять ячейки (например, сумматоры), чтобы уменьшить площадь, лучше оптимизировать работу вокруг этих ячеек, а также переставлять входы, чтобы обеспечить критический путь самыми быстрыми входами.

3.1.8.3. Обзор настройки для ABC9

Большая часть конфигурации поступает из атрибутов и блоков specify в имитационных моделях Verilog.

3.1.8.3.1. Specify синтаксис

По условию, все задержки в блоках specify указываются в целых пикосекундах. Файлы, содержащие блоки specify, следует читать с опцией -specify в read_verilog, чтобы они не были пропущены.

3.1.8.3.2. LUTs

LUT должны быть аннотированы атрибутом (* abc9_lut=N *), где N — относительная площадь модели LUT. Например, если архитектура может объединять LUT для получения больших LUT, то объединенные LUT будут иметь все больший N. И наоборот, если архитектура может разделять большие LUT на меньшие LUT, то меньшие LUT будут иметь меньший N.

LUT обычно задаются простыми комбинационными частями от входов LUT до выхода LUT.

3.1.8.3.3. DFFs

DFF должны быть аннотированы атрибутом (* abc9_flop *), однако ABC9 предъявляет к ним ряд особых требований:

Стоит отметить, что в чистом режиме abc9 в ABC9 передаются только время установки и прибытия (в частности, они моделируются как буферы с заданной задержкой). В режиме abc9 -dff в ABC9 передается сам триггер, что позволяет проводить последовательную оптимизацию.

Некоторые производители имеют универсальные модели DFF, которые включают асинхронные установки/сбросы, даже если они не используются. Поэтому для решения этой проблемы существует идиома упрощения: с помощью файла techmap можно обнаружить флопы, у которых есть постоянный источник для этих асинхронных элементов управления, они могут быть отображены в промежуточный, упрощенный триггере, который квалифицируется как (* abc9_flop *), прогоняется через *abc9* , а затем отображается обратно в исходный треггере. Это используется в synthintelalm и synth_quicklogic для PolarPro3.

Обычно DFF имеют ограничения на установку входных сигналов по отношению к тактовому сигналу и время прибытия выходного сигнала Q.

3.1.8.3.4. Boxes

«Box» — это чисто комбинаторная часть жесткой логики. Если логика подвержена воздействию ABC9, то это «white box», в противном случае — «black box». Цепочки переноса лучше всего реализовать как «white box», а DSP — как «black box» (умножители слишком сложны, чтобы с ними можно было легко работать). LUT RAM тоже могут быть реализованы как «white box».

Ячейки — это, пожалуй, самое большое преимущество ABC9 перед ABC: благодаря осведомленности о цепочках переноса и DSP она позволяет избежать оптимизации для путей, которые на самом деле не являются критическими, а более длинные пути приводят к тому, что ABC9 может уменьшить площадь проектирования за счет размещения другой логики в более крупных, но более медленных ячейках.

3.1.9. Размещение с библиотечными ячееками

Хотя большая часть этой документации посвящена использованию Yosys с ПЛИС, также возможно размещение с библиотечными ячееками, которые могут быть использованы при проектировании ASIC. В этом разделе будет рассмотрен краткий пример проекта, доступный в исходном коде Yosys в разделе docs/source/code_examples/intro/. Проект содержит простой сценарий синтеза ASIC (counter.ys), цифровой проект, написанный на языке Verilog (counter.v), и простую библиотеку КМОП-ячеек (mycells.lib). Многие из первых шагов здесь уже описаны более подробно в документе Синтезатор.

Примечание: Скрипт counter.ys содержит команды, используемые для создания изображений в этом документе. Фрагменты кода в этом документе пропускают эти команды, включая номера строк, чтобы читатель мог следить за источником.

3.1.9.1. Простой счетчик

Во-первых, давайте быстро рассмотрим конструкцию.:

Листинг 3.31: counter.v
...
Копировать
module counter (clk, rst, en, count);
    input clk, rst, en;
    output reg [1:0] count;

    always @(posedge clk)
        if (rst)
            count <= 2'd0;
        else if (en)
            count <= count + 2'd1;

endmodule

Это простой счетчик со сигналами сбросом и разрешением. Если сигнал сброса rst имеет высокий уровень, то счетчик сбрасывается на 0. В противном случае, если сигнал разрешения, en, высокий, то регистр подсчета будет увеличиваться на 1 каждом восходящем фронте тактового сигнала clk.

3.1.9.2. Загрузка конструкции

Листинг 3.32: counter.ys — чтение конструкции
...
Копировать
# read design
read_verilog counter.v
hierarchy -check -top counter

Наша схема теперь выглядит следующим образом:

yosys3-7 счетчик после hierarchy
Рис. 3.7: счетчик после hierarchy

3.1.9.3. Coarse-grain представление

Листинг 3.33: counter.ys — высокоуровневый материал

...
Копировать
# the high-level stuff
proc; opt
memory; opt
fsm; opt
yosys3-8 Coarse-grain представление модуля счетчика
Рис. 3.8: Coarse-grain представление модуля счетчика

3.1.9.4. Размещение логических вентилей

Листинг 3.34: counter.ys — размещение на внутренней библиотечной ячееки

...
Копировать
# размещение с внутренней библиотекой ячеек
techmap; opt
yosys3-9 счетчик после techmap
Рис. 3.9: счетчик после techmap

3.1.9.5. Размещение в оборудование

В данном примере мы используем файл Liberty для описания библиотеки ячеек, с которой будет размещена наша внутренняя библиотека ячеек:

Листинг 3.35: mycells.lib
...
Копировать
library(demo) {
  cell(BUF) {
    area: 6;
    pin(A) { direction: input; }
    pin(Y) { direction: output;
              function: "A"; }
  }
  cell(NOT) {
    area: 3;
    pin(A) { direction: input; }
    pin(Y) { direction: output;
              function: "A'"; }
  }
  cell(NAND) {
    area: 4;
    pin(A) { direction: input; }
    pin(B) { direction: input; }
    pin(Y) { direction: output;
             function: "(A*B)'"; }
  }
  cell(NOR) {
    area: 4;
    pin(A) { direction: input; }
    pin(B) { direction: input; }
    pin(Y) { direction: output;
             function: "(A+B)'"; }
  }
  cell(DFF) {
    area: 18;
    ff(IQ, IQN) { clocked_on: C;
                  next_state: D; }
    pin(C) { direction: input;
                 clock: true; }
    pin(D) { direction: input; }
    pin(Q) { direction: output;
              function: "IQ"; }
  }
}

Напомним, что встроенные в Yosys типы логических вентилей — $_NOT_, $_AND_, $_OR_, $_XOR_ и $_MUX_ с набором типов dff-памяти. mycells.lib определяет наши целевые ячейки как BUF, NOT, NAND, NOR и DFF. Сопоставление между ними выполняется с помощью команд dfflibmap и abc следующим образом:

Листинг 3.36: counter.ys — размещение в оборудованием
...
Копировать
dfflibmap -liberty mycells.lib
# размещение логики с mycells.lib
abc -liberty mycells.lib
# отчистка
clean

Окончательная версия нашего модуля счетчика выглядит следующим образом:

Перед выводом в файл verilog с помощью write_verilog , который затем может быть загружен в другой инструмент:

yosys3-10 счетчик после аппаратного отображения ячеек
Рис. 3.10: счетчик после аппаратного отображения ячеек
Листинг 3.37: counter.ys — запись синтезированного дизайна
...
Копировать
# запись синтезированной конструкции
write_verilog synth.v
Главная
Курсы
Вебинары
1. Что такое Yosys
2.1. Установка Yosys
2.2. Синтезатор
2.3. Создание скриптов
3.1. Синтез в деталях
Закрыть