19:26 Функциональные языки в разработке аппаратуры! | |
Простой способ моделировать сигналы — представить их списками значений в каждый момент времени. Если один сигнал равен другому со смещением на один квант во времени, мы просто добавляем в начала списка 0 delay s = 0:s >>или так: Можно создать свой тип для сигналов — это эффективнее, безопаснее и правильнее, но для простоты мы пока ограничимся использованием простых списков. data Signal v = S v (Signal v) delay v s = S v s Если требуется точное моделирование времени работы, то сигнал можно представить списком пар (интервал времени, значение сигнала) и предусмотреть представление неустановившихся значений. RS-триггер представляет из себя два NOR-узла, соединенные взаимно-рекурсивно. У этой системы есть два стабильных состояния, в которых на выходе одного NOR единица, а другого — ноль. Подавая единицу на второй вход одного из NOR-узлов можно переключать состояния. nor '_' '_' = '~' nor _ _ = '_' rs r s = (q, nq) where q = '_' : zipWith nor r nq nq = '_' : zipWith nor q s main = let r = "~_______" s = "___~~___" (q,nq) = rs r s in do print r print s print q print nq Такой подход позволяет сравнительно легко моделировать на уровне RTL достаточно сложные схемы. Тактовый сигнал явно не присутствует, но подразумевается везде, где это необходимо. Регистры можно моделировать с помощью задержки или явно предусмотрев состояние в параметрах и возвращаемом значении кода узла. macD r x y = acc where prods = zipWith (*) x y sums = zipWith (+) acc prods acc = 0 : zipWith (\r v -> if r == 1 then 0 else v) r sums
macS r x y = scanl macA 0 $ zip3 r x y where macA acc (r,x,y) = if r == 1 then 0 else acc+x*y Здесь описаны две эквивалентные модели операции MAC (умножение со сложением) с аккумулятором. macD — с использованием рекурсивного сигнала с задержкой, macS — с использованием явно описанного состояния. Если подмножество Haskell так хорошо моделирует синхронную аппаратуру, то почему бы из него не синтезировать HDL? Clash В качестве примера я хочу рассмотреть Clash, так как он умеет компилировать и в VHDL, и в SystemVerilog, и в старый добрый Verilog (который меня привлекает тем, что используется не только в микроэлектронике :-)). topEntity :: Signal (Bool, Bool) -> Signal (Bool, Bool) topEntity s = fmap (\(s1,s2) -> (s1 .&. s2, s1 `xor` s2)) s
fmap превращает функцию от пары логических величин в функцию от сигнала. Для работы с состоянием можно использовать автоматы Мура и Мили. Рассмотрим делитель частоты, сначала с помощью автомата Мура. data DIV3S = S0 | S1 | S2 div3st S0 _ = S1 div3st S1 _ = S2 div3st S2 _ = S0
div3out S2 = True div3out _ = False topEntity :: Signal Bool -> Signal Bool topEntity = moore div3st div3out S0 data — это конструкция Haskell описывающая тип данных. Библиотечная функция moore создает узел по двум этим функциям и начальному состоянию. То же самое с автоматом Мили: data DIV3S = S0 | S1 | S2 div3 S0 _ = (S1, False) div3 S1 _ = (S2, False) div3 S2 _ = (S0, True) topEntity :: Signal Bool -> Signal Bool topEntity = mealy div3 S0 В Clash вместо списков используются вектора фиксированного размера и большинство библиотечных функций переопределено на работу с ними. Добраться до стандартных списковых функций можно добавив в файл (или выполнив в REPL) строчку import qualified Data.List as L
После этого можно использовать функции, явно указав префикс «L.». Например *DIV3Mealy L> L.scanl (+) 0 [1,2,3,4] [0,1,3,6,10] С векторами работают большинство привычных списковых функций. *DIV3Mealy L> scanl (+) 0 (1 :> 2 :> 3 :> 4 :> Nil) <0,1,3,6,10> *DIV3Mealy L> scanl (+) 0 $(v [1,2,3,4]) <0,1,3,6,10> Но там много тонкостей, за подробностями стоит обратиться к документации. Перспективы Haskell очень мощный язык, и его возможно использовать для разработки DSL, например для разработки программного интерфейса устройства (с генерацией, кроме HDL, еще и через Ivory драйверов и эмуляторов для систем виртуализации), или описания архитектуры и микроархитектуры (с генерацией LLVM backend, оптимизирующий для данной микроархитектуры).
| |
|
Всего комментариев: 0 | |