На асинхронной логике можно сделать очень много, и особенно на FPGA

от автора

В естественной среде (в реальном мире), на асинхронной логике построено всё, и даже жизнь. Пытаться повторить последнее на FPGA разумеется можно только в виде игры, но и её пока что не будет. Хотя публикация имеет место быть, теперь с удвоенным объёмом материала, так как мне нельзя писать чаще раза в неделю, то я решил вторую публикацию включить сюда, материал которой мной планировался в статье «Асинхронные триггеры: цена стиля, и не каждая логика решения раскрывает суть решаемой ей задачи.» Всё это — мой небольшой опыт работы с FPGA, который свидетельствует в пользу того, что материал распространённый ранее в сети, по части асинхронной логики, не то что не точный — а не соответствует естественному порядку вещей в действительности, это было‑бы можно объяснять специфичностью FPGA, но никакой такой специфичности на самом деле нет, не было, и вряд‑ли когда будет, поэтому пусть тот материал остаётся сам по себе, и рассматривать его далее, как минимум тут, не стоит. Итак поехали

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

Тут есть небольшой лайфхак и маленькое рассекречивание небольшой технической хитрости разработчика (я нигде не сказал что коммерческой, дело в том, что документация Gowin достаточно минималистична — и это правильно, поэтому прочитать в ней почему сделано так, а не эдак — не стоит пытаться ). Кроме того, как выяснилось касательно Verilog — стиль описания имеет свою цену, правда это выяснилось после понимания небольшой технической хитрости разработчиков. И хоть я только осваиваю эту новую для меня область — пониманию подобных это никак не мешает, надеюсь, что кому-то этот мой опыт поможет понять некоторые изучаемые им аспекты цифровой схемотехники — тут главное думать и вникать, и это будет приносить свои плоды.

Приведу код обоих асинхронных триггеров и изображения синтезируемой схемы от него. Первый триггер, который исправно работал при входящем сигнале Z на вход S (setap), был мной собран из самых разных сведений в сети, от тем на разных форумах до журналов по электроннике, так как в книгах и учебниках (самоучителях), лабораторных работах и прочих публикациях от учёного и учащегося люда — я ничего не нашёл. Вот его код

module Trs (input S, R, output reg Q); always @(R, S)   begin      if(R)         Q  <= 1'b0;      else if(S)          Q  <= 1'b1;   end endmodule

. Здесь применяется стиль описания поведения и процедурный блок always (терминология взята из книги «Основы языка проектирования цифровой схемотехники» В.В.Соловьёв ). Если значение выходного порта назначается внутри процедурного блока always, то он должен иметь тип reg, даже если описывается комбинационная схема — это взято практически прямо из книги. Ещё в языке имеется процедурный блок initial, но мне он пока не понадобился, хотя пробовал получить асинхронный триггер и с его помощью. Так вот синтезируемая схема данного кода выглядит так

Вид громоздкий и ничего хорошего не обещающий, но рабочий — это факт, даже когда на входе S отсутствует сигнал — Z(некоторые называют это плавающим сигналом, в контексте описания принципа работы электронных ключей), но всё-же правильнее наверное так

Z — «состояние высокого сопротивления», то есть отсутствие сигнала.

. И в силу того, что мне хотелось чего-то более компактного и простого (а простота у меня прямо ассоциируется с надёжностью), пришлось открыть документацию, и обнаружить там примитив защёлки DLC, на вид более аккуратной и простой, тем более что она имеет асинхронный сброс, а раз так — остальное прикрутить уже не долго. И вроде запускалась, но схема лаунчера из прошлой публикации не работала, так как тогда тестбенчи я ещё не начал осваивать, то обнаружил путём разных комбинаций что полученный триггер на её основе сбрасывает своё состояние при отсутствии сигнала, это для меня было неожиданностью — то что отсутствие сигнала далее даёт ошибку по схеме я знал, но чтобы вот так — внутри модуля памяти, хоть и самодельной — это было новостью, почему — всё просто: в программе симулятора мной используемого этих примитивов нет, и для тестов я использовал триггер RS, а сбоить начинает при отсутствии сигнала далеко не всё, и в частности логические элементы. Но тогда я этому значения не придал, и счёл логические элементы в схеме выше — банальной проверкой условия, хоть и понимал что она особо не требуется, но счёл это издержками синтеза и логики компилятора. В силу этого начал упорствовать с электронными ключами, понимая что они то меньше весят, да и работают всяко побыстрее. Но не тут-то было, крутил вертел, и с пониманием того, что по весу затрачиваемых ресурсов у меня меньшего ничего не выходит — пришло и понимание того, что это у разработчиков вовсе не проверка условия, а заполнение пробела отсутствия сигнала, которое не противоречит условию. В общем они для стабилизации сигнала воспользовались логическими элементами так, чтобы они не противоречили условию срабатывания и работы экземпляра.

Тогда я взял и сделал так-же, но с защёлкой DLC, в структурном стиле описания (он мне очень нравится):

module Trs (input S, R,   output Q); not n1(R1,R); and a1(G,S,R1); DLC dlc1 (Q,G,G,R); endmodule

, то-есть это

И тогда всё стало ровно и проект работал. А так как у меня на ноутбуке Ubuntu, то разумеется тестбенчей я не могу производить так вот запросто, среда работает, с Open FPGA Loader можно прошить плату, но тестбенчи запросто — нет, то мной для этих целей три месяца назад был приобретён миниПК с предустановленной Windows, там да — всё можно.

И оные показали что действительно более простое надёжнее, так как сигнал стабильнее. Естественно, что сигнал прослеживается на графике и в файле только там, где он попадает в такт сигнала выборки, но это ведь всего тестбенч, и как минимум — всё сопрягается, вопреки моим ожиданиям на страшилки в сети — что это невероятно сложно.

Подключил осцилятор по этим сведениям, и выставил делитель в 100, если кому интересно то под спойлером, и ещё увеличил в счётчике число триггеров до 30 и отслеживаю w16, чтобы время подачи сигнала нуля примерно было равно времени подачи сигнала единицы. Оба триггера примерно одинаковы в быстродействии, но структурный — стабильнее. Всё под спойлером

Скрытый текст

триггеров в счётчике 30 и слежу за сигналом посреди цепи

Скрытый текст

//Copyright (C)2014-2024 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: IP file
//Tool Version: V1.9.9.03 Education (64-bit)
//Part Number: GW1NR-LV9QN88PC6/I5
//Device: GW1NR-9
//Device Version: C
//Created Time: Sat Nov 2 21:03:31 2024

module Gowin_OSC (oscout);

output oscout;

OSC osc_inst (
.OSCOUT(oscout)
);

defparam osc_inst.FREQ_DIV = 100;
defparam osc_inst.DEVICE = «GW1NR-9C»;

endmodule //Gowin_OSC

module Trs
(input S, R,
output Q);
not n1(R1,R);
and a1(G,S,R1);
DLC dlc1 (Q,G,G,R);
endmodule

//module Trs
//(input S, R, output reg Q);
//always @(R, S)
// begin
// if(R)
// Q <= 1’b0;
// else if(S)
// Q <= 1’b1;
// end
//endmodule

module pprobe (input sys_clk, output led); // 6 LEDS pin);
wire w30;
wire w0;
Trs trs1(w30,w0,w0);
not n0(led, w0);
Trs trs2(led,w0,w2);
Trs trs3(w2,w0,w3);
Trs trs4(w3,w0,w4);
Trs trs5(w4,w0,w5);
Trs trs6(w5,w0,w6);
Trs trs7(w6,w0,w7);
Trs trs8(w7,w0,w8);
Trs trs9(w8,w0,w9);
Trs trs10(w9,w0,w10);
Trs trs11(w10,w0,w11);
Trs trs12(w11,w0,w12);
Trs trs13(w12,w0,w13);
Trs trs14(w13,w0,w14);
Trs trs15(w14,w0,w15);
Trs trs16(w15,w0,w16);
Trs trs17(w16,w0,w17);
Trs trs18(w17,w0,w18);
Trs trs19(w18,w0,w19);
Trs trs20(w19,w0,w20);
Trs trs21(w20,w0,w21);
Trs trs22(w21,w0,w22);
Trs trs23(w22,w0,w23);
Trs trs24(w23,w0,w24);
Trs trs25(w24,w0,w25);
Trs trs26(w25,w0,w26);
Trs trs27(w26,w0,w27);
Trs trs28(w27,w0,w28);
Trs trs29(w28,w0,w29);
Trs trs30(w29,w0,w30);

Gowin_OSC(oscout);
endmodule

Сначала тестится триггер описанный в структурном стиле описания

Текстовый файл не прилагаю. Затем синтезированный средой и описанный в стиле описания поведения.

Быстродействие у триггеров, если верить тестбенчу такому — примерно одинаковое, за 125 тактов сигнала выборки (а это уже через делитель тактовый часты — осцилятор) у них примерно по 37 срабатываний вроде, ну у того, что в структурном я насчитал 37, а в втором 39, но стабильность другая. Да и вообще мне что-то кажется сомнительной эта затея с тестбенчами, это в общем осваивать ещё надо. Ну а пока так вот.

Ну и далее в коде конечно я применяю свой (структурный) асинхронный триггер. Создаётся впечатление, что полученный триггер самостоятельно — работает стабильнее, хоть и кажется что медленнее. Сбрасывается защёлка своя конечно медленнее. Если посчитать количество срабатываний этого асинхронного счётчика за 64 такта выборки, то соотношение будет таким: синтезированный тригер из кода 11 раз , самостоятельно всего 6 (так как я объдинил единицы что рядом). Это факт, но требующий более детальной проверки с другой выборкой тактируемого сигнала. Я всё-же отдал бы предпочтение стабильности.

Далее всё-то же, что и было в публикации под её основным заголовком.

Лаунчер будет работать с любым из триггеров, просто мне DLATCHRS внешне кажется сильно наворочанным, поэтому всё-же наверное буду использовать асинхронный триггер на базе примитива DLC, в целях экономии средств FPGA в своих проектах. Причина этого успеха заключается в том, что логический элемент AND, при подаче даже сигнала нуля на один из своих входов — уже выдаёт на выходе 0, и при входящем сигнале Z (отсутствие сигнала) на втором входе. Скорее всего стабилизировали сигнал в своём триггере исключая состояние Z и разработчики, чем следовали каким-то предположениям о противорчечащих сигналах на входах R и S, так как сигнал сброса игнорирует остальные.

Собственно лаунчер нужен совсем не много — всего лишь для запуска демки конвейера кэша команд, называемого мной теперь так — «Адресный конвейер, или линия автономной дискретной активации ячеек и линия управления дискретностью (асинхронной логики)», но отсутствие опыта подстёгивало желание срочно его приобрести, с учётом того, что без него дальше будет и вовсе трудно, а тем более работа в целом противоречит опыту пользователей и специалистов (некоторые выдавали себя именно за них, причём авторитетных), ну если не опыту — то сведениям ими охотно распространяемым. Поэтому даже на столь незначительной схеме я поупирался, и моё упорство было вознаграждено — все четыре диода зажигаются, что было свидетельством получения стабильной схемы, даже при наличии в ней сигнала Z. Все свои схемы я проверяю и отлаживаю в симуляторе Logisim — это удбная программа и её симулятор достаточно точен и имеет необходимый инструментарий. Задача лаунчера — зажечь диоды именно поочерёдно, потому что, чтобы привести конвейер в действие в демоварианте (собственно тоже активно создаётся только из‑за отсутствия опыта, ну и ранее расбросанных другими сведений), нужно последовательно произвести ряд действий, их хоть для глаз все диоды загораются одновременно — на самом деле это просто поисходит очень быстро.

Итак лаунчер, изначально хотел реализовать каждое звено на двух триггерах, но не смог быстро создать схему генерирующую сигнал повторного запуска цикла управляющих сигналов, которые приходят из блока SetChain (выбран и выделен во вьювере красным контуром, в отличии от остальных элементов схемы) и необходимы для работы активаторов activate. Каждый активатор выдаёт последовательно на, соответствующий действию, контакт сигнал, и потом убирает его, при этом он отправляет на SetChain сигнал повтороный активации, после чего цикл повторяется.

Сам активатор (в схеме выше их четыре справа, перед выходящими пинами лаунчера) во вьювере Gowin EDA выглядит так (открывается просто двойным кликом мыши по элементу, возвращаемся выбрав пункт «Pop » в выпадающем списке при нажатии на свободное пространство ПКМ). *Кстати — при каждой правке кода нужно сохранять изменения, иначе компилятор их просто не увидит при запуске компиляции.

представление не совсем удобное, но чтобы убедиться в правильности расключения всех проводов — этого вполне хватает, разумеется в относительно сложных схемах я тут‑же сверяюсь с отлаженной схемой в Logisim. Прежде чем сливать сюда весь код (если что плата у меня tangnano9k, и распиновку солью позднее) — хочется показать то, как работает сам активатор. Выходящим пином out1 он активирует то что нужно лаунчеру (в данном тест варианте лаунчер просто записывает в триггеры за его пределами единицу, шаг за шагом, выходя которых подключены к диодам, на данной платке они инверсные поэтому следуют за элементов not). Выход onActivate отправляет сигнал запуска повторного цикла управляющей пары сигналов, а выход outFurther просто пропускает (out) и актуализирует (further) управляющие сигналы далее по цепи активаторов.

Как работает активатор — именно он демонстирует больше всего возможности асинхронной — статической логики (оная интересна тем, что реализовать её можно на любом носителе, мной используется бинарная, но вот тут в симуляции как раз и показано то, что имея всего два типа сигналов и в данном случае два входящих контакта, расширять логику можно на большее число контактов без каких‑либо дешифраторов, может это и не весть какая новость — но тем не менее очень интересно). Третий триггер может и не нужен, но я не стал ломать сильно голову над идеальностью схемы, так как предназначение лаунчера, сейчас, достаточно такое — опосредованное. В анимации симуляции всего два активатора, чтобы хоть как‑то было видны метки возле пинов и элементов, а так можно ставить сколько угодно. Анимация созданна в программе Peek. Файл схемы для Logisim https://disk.yandex.ru/d/ao689KWR5D0FWw

Во вьювере тест модуль выглядит так

Код проекта

Скрытый текст
module Trs (input S, R, output reg Q);//reg //reg Q; always @(R, S)   begin      if(R)         Q  <= 1'b0;      else if(S)          Q  <= 1'b1;   end endmodule  module Chain (input on, off, output out); Trs t1(on,off,out1); Trs t2(out1,off,out2); Trs t3(out2,off,out3); Trs t4(out3,off,out4); Trs t5(out4,off,out5); Trs t6(out5,off,out6); Trs t7(out6,off,out7); Trs t8(out7,off,out8); Trs t9(out8,off,out9); Trs t10(out9,off,out); endmodule  module SetChain (input on, output out,further); wire reset0,reset1; Chain Ch1(on,reset0,out); Chain Ch2(out,further,reset0); Chain Ch3(reset0,reset1,further); Chain Ch4(further,on,reset1); endmodule  module mkick (output led); wire w4; wire w0=0; Trs trs1(w4,w0,wt); not n0(led, wt); Trs trs2(led,w0,w2); Trs trs3(w2,w0,w3); Trs trs4(w3,w0,w4); endmodule  module activate (input out, further, output out1, onActivate,OutFurther); wire wi0=0; wire wi1,onout2,onTrsReset,outTrsReset; bufif1 bf1(on,out,wi1); Trs Tout(on,wi0,onout1); bufif1 bf2(onFurth,onout1,further); Trs TFurth(onFurth,wi0,onWi1); xor xs1(onout2,onout1,onWi1); bufif1 bf3(out1,out,onout2); not n1(wi1, onWi1); bufif1 bf4(OutFurther,out,onWi1); Trs TrsReset(onout2,onTrsReset,outTrsReset); and An1(wA1,outTrsReset,onWi1); not n2(notfurther,further); bufif1 bf5(onActivate,wA1,notfurther); bufif1 bf6(onTrsReset,out,wA1);  endmodule  module Launcher (output Limit,Fills, Unit, Run); wire zero=0; wire out; mkick k1(kick); bufif1 (on,kick,kick); Trs t1(on,out,on1); SetChain Sc1(on1,out,further); activate act1(out,further,Limit,onActivate0,Outfurther1);//activate act1(out,further,Limit,onActivate0,Outfurther1); bufif1 (on,onActivate0,onActivate0);//onActivate0); activate act2(Outfurther1,further,Fills,onActivate1,Outfurther2); bufif1 (on,onActivate1,onActivate1); activate act3(Outfurther2,further,Unit,onActivate2,Outfurther3); bufif1 (on,onActivate2,onActivate2); activate act4(Outfurther3,further,Run,onActivate3,Outfurther4);  endmodule  module Test (output led0, led1, led2, led3); wire wi0 = 0; Launcher La1(on0,on1,on2,on3); Trs t1(on0,wi0,onled0); Trs t2(on1,wi0,onled1); Trs t3(on2,wi0,onled2); Trs t4(on3,wi0,onled3); not n0(led0,onled0); not n1(led1,onled1); not n2(led2,onled2); not n3(led3,onled3);  endmodule 

Распинова для tangnano9k мало чем отличается от распиновке в примере https://wiki.sipeed.com/hardware/en/tang/Tang-Nano-9K/examples/led.html

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

Скрытый текст

Файл схемы для Logisim

В планах так-же поработать над асинхронным АЛУ для процессора. Вообще планируется полностью отличная архитектура вычислительной машины, основной концепцией которой будет асинхронная логика (с перспективой реализации архитектуры на статической), в которой каждый сигнал будет распространяться с естественной для него скоростью, обусловленной скоростью распространения в среде — носителе. Разумеется плюс-минус обусловленность скоростью срабатывания некоторых элементов, но основной упор в схемах будет на то, что те все будут приведены в нужное состояние перед задействованием. Как минимум архитектура мной будет задействована в планируемом проекте сварочного робота, а там — посмотрим, может ещё где. Началось всё с машинного зрения, потом была алгоритмизация и анализ кода — и потом уже пришёл к выводу, что архитектуру лучше полностью делать свою. Делаю по мере возможностей.

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

module Mych(input clk, output wire led0,led1, led2,led3,led4,led5); wire Zero = 0; wire  [0:0]se[0:5]; wire  [0:0]se2[0:5]; wire  [0:0]lim[0:5]; wire  [0:0]Fills[0:5]; wire [0:0]ActivMemo[0:5]; wire Start0,Start, Start1,  Set, Fillsin1, EndFillsin, StartTact1; wire Finish, FinishTact, Unit,InJamp, StartTact,Deactivation, One; wire sens, led; wire limfil,fils; genvar i; generate wire [0:2]OtJam, OutFillsi[0:5], OutStu1[0:5], OutFStu[0:5], OutFStu1[0:5], ORi[0:5], Fini[0:5]; MyCF mycf(Deactivation,StartTact, Set,  ResLim, ResFil, fils, limfil, One, Zero, Unit,InJamp,,,, ,fils,se2[0],Fini[0],ORi[0], OutFillsi[0], OutStu1[0], OutFStu[0], OutFStu1[0], ActivMemo[0]); bufif1 bf0(FinishTact, ActivMemo[0], ActivMemo[0]); bufif1 bf1(Finish, Fini[0], Fini[0]); for (i=0; i<4; i=i+1 ) begin:MyC_generation MyC myc(Deactivation,StartTact, Set,  ResLim, ResFil, fils, limfil, One, Zero, Unit, ORi[i], OutFillsi[i], , OutFStu[i], OutFStu1[i] ,Zero,se2[i+1], Fini[i+1],ORi[i+1], OutFillsi[i+1], , OutFStu[i+1], OutFStu1[i+1], ActivMemo[i+1], se2[i]);//OutStu1[i] bufif1 bf0(FinishTact, ActivMemo[i+1], ActivMemo[i+1]); bufif1 bf1(Finish, Fini[i+1], Fini[i+1]); end MyC myc(Deactivation,StartTact, Set,  ResLim, ResFil, fils,  limfil, One, Zero, Unit, ORi[4], OutFillsi[4], , OutFStu[4], OutFStu1[4] , limfil,se2[5], Fini[5],ORi[5], OutFillsi[5], , OutFStu[5], OutFStu1[5], ActivMemo[5], se2[4]);  endgenerate  Run(clk,FinishTact,Finish,StartRun,Deactivation,StartTact,Set);  not (led0 , ActivMemo [0]); not (led1 , ActivMemo [1]); not (led2 , ActivMemo [2]); not (led3 , ActivMemo [3]); not (led4 , ActivMemo [4]); not (led5 , ActivMemo [5]); endmodule 

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

На этом пока всё, есть куча дел других, поэтому проект демки конвейера, или кэша команд, будет готов примерно через пару недель. Делается всё нормальными темпами — каждый день в будние дни по часу‑два, в выходные примерно так‑же (так как куча других дел), основным тормозом работ было то, что в FPGA я новичок, но это уже постепенно уходит на задний план.


ссылка на оригинал статьи https://habr.com/ru/articles/854562/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *