Работа с параметрами сигналов для пользовательских шаблонов
Страница в процессе разработки. |
В данной статье описывается подход к получению подробной информации о сигналах с помощью пользовательских шаблонов. Рекомендуется ознакомиться со статьей по пользовательским шаблонам генерации кода прежде, чем изучать текущую. |
Генерируя код на основе пользовательских шаблонов, можно узнать полезную информацию о сигналах, такую как размер, тип данных, частота выборки и другие данные.
Далее рассмотрим ключевые структуры, поля и их применение в пользовательских шаблонах для получения информации о сигналах.
С помощью встраивания $(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]
. Рассмотрим основные моменты:
-
Доступ к информации о типе данных через
ty
:if $(ins[1].ty.is_floating) { // Логика для чисел с плавающей точкой }
-
Поле
ty
содержит характеристики типа данных сигнала. -
В данном случае используется свойство
is_floating
, которое возвращает флаг (true
илиfalse
), указывающий, является ли сигнал числом с плавающей точкой. -
Условие проверяет, относится ли сигнал к типу
float
. Если да (логика выполняется), то эта логика — специфичная для обработки чисел с плавающей точкой.
-
-
Доступ к размерности сигнала через
rows
:if $(ins[1].rows) > 1 { // Логика для многомерного массива }
-
Свойство
rows
возвращает количество строк в сигнале. -
Здесь проверяется, является ли сигнал многомерным массивом (количество строк больше 1). Если условие выполняется, то применяется логика для обработки многомерных сигналов.
-
В примере ins[1]
представляет входной сигнал, который является экземпляром структуры signal
. Доступ к информации о сигнале осуществляется через его свойства (rows
, cols
, ty
и другие). Например, rows
и cols
дают размеры сигнала, а поле ty
предоставляет детализированную информацию о типе данных сигнала (например, разрядность, наличие дробной части, знаковость).
Важно помнить, что массивы
|
Применение в шаблонах
В шаблоне ниже реализуется блок Оператор сравнения для 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-кода по умолчанию при нажатии кнопки «Сгенерировать код» ![]() ![]() |