Документация Engee

Работа с параметрами сигналов для пользовательских шаблонов

Страница в процессе разработки.

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

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

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

С помощью встраивания $(sprint(dump, input(1))) в шаблон можно узнать, например, все содержимое всех полей первого входного сигнала.

Структура signal

Структура signal описывает сигнал в пользовательских шаблонах и предоставляет доступ ко всем необходимым параметрам через поле ty. Эти сигналы представляются как элементы массивов ins (входные порты) и outs (выходные порты), либо как отдельные порты, например input(1) или output(1). Доступны следующие поля структуры signal:

  • expr::String — выражение константы или настраиваемого параметра, если сигнал является таковым;

  • full_qual_name::String — полное квалифицированное имя сигнала;

  • name::String — читаемое (красивое) имя сигнала;

  • ieas::String — имя атомарной подсистемы, наиболее близкой к выбранному блоку;

  • indirect::Bool — флаг косвенной ссылки на сигнал;

  • rows::UInt64 — количество строк в сигнале;

  • cols:: UInt64 — количество столбцов в сигнале;

  • ty::typ — тип сигнала (см. ниже);

  • sample_time::Float64 — частота дискретизации сигнала.

Поле ty

Поле ty используется в структуре signal и предоставляет информацию о типе данных сигнала. Поле ty имеет следующие ключевые параметры для описания типа данных:

  • bits::Int — разрядность (ширина) данных;

  • fractional_bits::Int — количество дробных битов;

  • is_unsigned::Bool — флаг, определяющий, является ли тип беззнаковым;

  • is_floating::Bool — флаг, определяющий, является ли тип числом с плавающей точкой;

  • is_complex::Bool — флаг, определяющий, является ли тип комплексным.

Тогда пример использования через порты, включающий signal и ty будет выглядеть так:

/* Получение информации о типе данных и размерах первого входного сигнала */
if $(ins[1].ty.is_floating) {
    // Логика для чисел с плавающей точкой
}

if $(ins[1].rows) > 1 {
    // Логика для многомерного массива
}

В примере показывается применение структур signal и ty для получения информации о входном сигнале, представляемом как ins[1]. Рассмотрим основные моменты:

  1. Доступ к информации о типе данных через ty:

    if $(ins[1].ty.is_floating) {
        // Логика для чисел с плавающей точкой
    }
    • Поле ty содержит характеристики типа данных сигнала.

    • В данном случае используется свойство is_floating, которое возвращает флаг (true или false), указывающий, является ли сигнал числом с плавающей точкой.

    • Условие проверяет, относится ли сигнал к типу float. Если да (логика выполняется), то эта логика — специфичная для обработки чисел с плавающей точкой.

  2. Доступ к размерности сигнала через rows:

    if $(ins[1].rows) > 1 {
        // Логика для многомерного массива
    }
    • Свойство rows возвращает количество строк в сигнале.

    • Здесь проверяется, является ли сигнал многомерным массивом (количество строк больше 1). Если условие выполняется, то применяется логика для обработки многомерных сигналов.

В примере ins[1] представляет входной сигнал, который является экземпляром структуры signal. Доступ к информации о сигнале осуществляется через его свойства (rows, cols, ty и другие). Например, rows и cols дают размеры сигнала, а поле ty предоставляет детализированную информацию о типе данных сигнала (например, разрядность, наличие дробной части, знаковость).


Важно помнить, что массивы ins и outs представляют входные и выходные порты соответственно. В случае если блок автономно генерирует сигналы (например, блоки Синусоидальная функция или Константа), то массивы могут быть не нужны. Вместо этого применяются встроенные функции или параметры:

//! BlockType = :Sin
//! TargetLang = :C

//! @Step
$(output_datatype_name(1)) $(output(1)) = sin($(get_baserate()) * $(model_substep(1)));

Применение в шаблонах

В шаблоне ниже реализуется блок Оператор сравнения для Verilog генерации. Поскольку генерация Verilog-кода проходит с использованием Chisel, то шаблоны также пишутся на Chisel (с использованием Julia метакода).

Пример шаблона для блока Оператор сравнения
//! BlockType = :RelationalOperator
//! TargetLang = :Chisel

/*! #----------------------------------------------------# */
//! @Definitions
val $(output(1)) = Wire($(show_chisel_type(output(1))))
/*! #----------------------------------------------------# */
/*! @Step
function patch_op(op)
    op == "==" ? "===" : op == "~=" ? "=/=" : op
end
function get_idx(len, blen)
    if len == 1
        return ""
    elseif len == blen
        return "(i)"
    else
        return "(i % $(len))"
    end
end
function maybe_zext(sig, sig2)
    if sig.ty.is_unsigned && !sig2.ty.is_unsigned
        return ".zext"
    end
    ""
end
function impl(sig1, sig2, op, out)
    dim0 = dim(out)
    dim1 = dim(sig1)
    dim2 = dim(sig2)
    blen = lcm(dim1, dim2) # broadcasted length
    if blen == 1
        loop_decl = ""
    else
        loop_decl = "for (i <- 0 until $(blen)) "
    end
    "$(loop_decl)$(output(1))$(get_idx(dim0, blen)) := \
        $(sig1)$(get_idx(dim1, blen))$(maybe_zext(sig1, sig2)) \
         $(patch_op(op)) \
        $(sig2)$(get_idx(dim2, blen))$(maybe_zext(sig2, sig1))"
end
*/
$(impl(input(1), input(2), param.Operator, output(1)))
/*! #----------------------------------------------------# */

Разберем основные части кода:

  • В первых двух строках указываем, что пишем шаблон для блока Оператор сравнения, и что шаблон этот предназначен для Chisel-кода;

  • Видим, что в секцию @Definitions пишется объявление выходного сигнала. Wire обозначает синтезируемый hardware-сигнал указанного типа. Chisel тип сигнала можно получить с помощью встроенной вспомогательной функции show_chisel_type, которая по параметрам сигнала или типа возвращает строку, представляющую искомый Chisel тип. Например, если sig.ty.fractional_bits == 0, то функция вернет SInt($(sig.ty.bits).W) или UInt($(sig.ty.bits).W), в зависимости от знаковости типа. Полное определение функции:

    function show_chisel_type(x::typ) :: String
      if x.bits == 1 && x.fractional_bits == 0
        out = "Bool()"
      elseif x.fractional_bits == 0
        out = (x.is_unsigned ? "U" : "S") * "Int($(x.bits).W)"
      else
        out = "FixedPoint($(x.bits).W,$(x.fractional_bits).BP)"
      end
      out
    end
    function show_chisel_type(x::signal) :: String
      base_type = show_chisel_type(x.ty)
      len = x.rows * x.cols
      len == 1 ? base_type : "Vec($(len), $(base_type))"
    end
  • Ради удобства чтения секции визуально отделены между собой комментариями, не генерирующими никакого полезного Julia или Chisel кода;

  • Далее начинаются определения вспомогательных функций, которые будут использованы на этапе генерации основного кода, выполняющего присваивание блоку нужного значения. Как видно по предпоследней строке шаблона, в буфер (секцию) @Step в сгенерированный Chisel файл попадет результат работы функции impl с указанными аргументами. impl использует следующие функции:

    • patch_op — исправляет строковое представление оператора из блока Relational Operator в соответствующий оператор в Chisel

    • get_idx — используется для единообразного доступа к скалярным и к векторным сигналам. В случае скаляра мы не генерируем никаких дополнительных индексов, а в случае вектора — генерируем обращение к нужному элементу вектора (i). Также функция обрабатывает случай broadcasting’а сигналов.

    • maybe_zext — выполняет расширение нулём, если это необходимо

    • Встроенная функция dim возвращает длину сигнала, т.е. s.rows * s.cols. Альтернативно можно было воспользоваться функцией input_len.

      Напоминаем, что выражение $(some_signal), использованное внутри строки или внутри Chisel-кода, вернет/напечатает строку, представляющую имя сигнала (или значение константы, если сигнал это константа).
  • Итого, функция impl сначала вычисляет длину выходного сигнала (наименьшее общее кратное длин входных сигналов), затем, если необходимо, генерирует цикл, в котором будет происходить присваивание элементам нужного значения, а затем присваивает (:=) выходному сигналу результат сравнения входных сигналов, используя выбранный оператор сравнения. Входные сигналы при необходимости расширяются по длине или по знаку.

Пример сгенерированного кода
//> using scala "2.13.14"
//> using dep "org.chipsalliance::chisel:6.7.0"
//> using plugin "org.chipsalliance:::chisel-plugin:6.7.0"
//> using options "-unchecked" "-deprecation" "-feature" "-language:reflectiveCalls" "-Xcheckinit" "-Xfatal-warnings" "-Wdead-code"

import chisel3._
import circt.stage.ChiselStage
import fixedpoint._
import fixedpoint.shadow.{Mux}

class SimpleCompare extends Module {
  val io = IO(new Bundle{
    val In1 = Input(FixedPoint(16.W,2.BP))
    val In2 = Input(FixedPoint(16.W,3.BP))
    val Out1 = Output(Bool())
  })
  val RelationalOperator = Wire(Bool())

  RelationalOperator := io.In1 <= io.In2

  io.Out1 := RelationalOperator
}


object SimpleCompareDriver extends App {
  ChiselStage.emitSystemVerilogFile(
    new SimpleCompare,
    firtoolOpts = Array("--disable-all-randomization", "--strip-debug-info",
                        "--lowering-options=disallowLocalVariables"))
}

Далее преобразуется в Verilog:

module SimpleCompare(
input         clock,
              reset,
input  [15:0] io_In1,
              io_In2,
output        io_Out1
);

  assign io_Out1 = $signed({io_In1, 1'h0}) <= $signed({io_In2[15], io_In2});

endmodule
Приведенный выше шаблон встроен в библиотеку шаблонов по умолчанию. Например, если не указан пользовательский шаблон для блока Оператор сравнения, то в случае генерации Chisel/Verilog-кода по умолчанию при нажатии кнопки «Сгенерировать код» verilog icon будет использован шаблон выше. Если не устраивает поведение шаблона, то можно сообщить об ошибке или предложить улучшение через кнопку обратной связи feedback icon, или вовсе переопределить шаблон.