![](https://habrastorage.org/webt/fu/yo/23/fuyo230rbfhla_li8ryzzngttlw.jpeg)
Нам предстоит сделать предпоследний шаг в практическом освоении работы с UDB. Сегодня мы будем вести разработку не при помощи автоматизированного UDB Editor, а в полуручном режиме, с использованием Datapath Config Tool. Очень хорошим подспорьем в освоении этого инструмента является документ AN82156 — PSoC 3, PSoC 4, and PSoC 5LP – Designing PSoC Creator Components with UDB Datapaths. Собственно, я сам учился по нему.
Возможно, при прочтении наших переводов документации на UDB, кто-то пробовал воспроизвести знания оттуда на практике и заметил, что в UDB Editor доступна не вся функциональность, описанная в публикациях. Это связано с тем, что разработчики не стали помещать в UDB Editor некоторые особо громоздкие механизмы. Авторы AN82156 утверждают, что через UDB Editor не получится сделать следующие вещи:
- организовать параллельный ввод и вывод данных;
- организовать динамическое управление FIFO;
- реализовать инверсию тактового сигнала FIFO;
- реализовать функцию CRC;
- реализовать функцию PRS;
- реализовать выбор входящего переноса;
- реализовать динамический входящий перенос.
От себя я добавлю, что не нашёл, как реализовать в UDB Editor перестановку нибблов.
Если эти функции необходимы в проекте, потребуется создать собственный код на языке Verilog. Я специально использовал слово «создать», а не «написать». Знать этот язык программирования достаточно на уровне чтения. В смысле, надо понимать, какая конструкция для чего нужна. А уметь писать с нуля — всегда полезно, но для изложенного в этой статье данный навык не является обязательным.
В качестве решаемой задачи, я выбрал полусинтетический случай. В общем и целом, я решил вывести какие-нибудь данные в параллельный порт, а в частности, из того, что лежит под рукой, параллельный порт есть у текстового ЖК-дисплея. Я вытащил его три года назад из 3D-принтера MZ3D, когда пересаживал последнего на STM32. Поэтому случай и полусинтетический: сегодня такие индикаторы обычно имеют вход I2C, и подключение через ворох проводов в реальной жизни им не нужно. Однако параллельные порты у современных ЖКД тоже имеются, так что использовать их для повторения эксперимента сможет каждый.
Рассмотрим схему включения дисплея, взятую с сайта reprap.org (это было нелегко, мой провайдер блокирует данный сайт, как и ряд других технических, мотивируя это тем, что тот живёт на одном IP с кем-то заблокированным).
![](https://habrastorage.org/webt/0x/th/ry/0xthryzxadx_9guzmhdyhrxnzty.png)
Прекрасная схема! Во-первых, мне не надо думать насчёт чтения: данные в ЖКД могут только писаться (линия R/W заземлена и недоступна на разъёме). Во-вторых, данные идут в 4х-битном формате, значит, мы сможем не только отработать параллельный вывод, но и проверить работу функции перестановки нибблов.
Создание проекта
Итак, запускаем PSoC Creator и выбираем File->New->Project:
![](https://habrastorage.org/webt/pv/ma/tv/pvmatvfviveyo4fa822osy45p2c.png)
Дальше я выбираю свою макетную плату:
![](https://habrastorage.org/webt/dv/st/hi/dvsthib82j6ynrdxc0sk-5j20nk.png)
Далее — пустая схема:
![](https://habrastorage.org/webt/oi/fx/vv/oifxvvsf0qye7j-mdyoxmbfux4q.png)
Проект назову LCDTest2:
![](https://habrastorage.org/webt/qs/3f/rl/qs3frlgxrnzqzqrzzfryptvjfqq.png)
Теперь, как и раньше, идём на вкладку Components:
![](https://habrastorage.org/webt/wq/nb/bo/wqnbboubijieyovk7akrat6piik.png)
И, выбрав проект, нажимаем правую кнопку «мыши», после чего выбираем Add Component Item.
![](https://habrastorage.org/webt/hi/fu/hv/hifuhvearnltnfz2dvmhwtzgw4o.png)
И вот здесь надо выбрать Symbol Wizard. Имя дадим… Ну, скажем, LCD4bit.
![](https://habrastorage.org/webt/59/f0/g3/59f0g3lya8ado4kcxgj6lcsxggk.png)
Я назначил символу следующие порты:
![](https://habrastorage.org/webt/6a/jr/uo/6ajruo1io2jusjunhameiumtb20.png)
clk — это вход тактовой частоты. Порты с префиксом LCD — это стандартные порты ЖК-дисплея. hungry – выходы, сообщающие блоку DMA, что в FIFO имеется свободное место, идея рассматривалась в статье про управление RGB светодиодами. Нажимаем OK, получаем символ.
![](https://habrastorage.org/webt/ze/fq/5z/zefq5z65eq0i4mq21y2d3jip7t4.png)
Теперь на основе этого символа следует сгенерить Verilog шаблон. Нажимаем в окрестностях символа правую кнопку «мыши» и в контекстном меню выбираем Generate Verilog.
![](https://habrastorage.org/webt/h9/k7/oo/h9k7oogp_wqsdfugdg-8bdr_hru.png)
У нас получился шаблон, показанный на рисунке ниже (в текстовом виде он пока не имеет никакого смысла):
![](https://habrastorage.org/webt/ax/t9/k-/axt9k-s-zfi2kfd7qdz9pe-zlqq.png)
Нам создали модуль и какие-то участки. Но пока что не создали Datapath. Чтобы добавить его, идём в дерево проекта, выбираем файл LCD4bit.v, нажимаем правую кнопку «мыши» и в появившемся контекстном меню выбираем Datapath Config Tool:
![](https://habrastorage.org/webt/z2/1g/5w/z21g5wom4_-yq_nmcjlhvb3irg0.png)
Перед нами открывается окно, которое я пока покажу лишь частично:
![](https://habrastorage.org/webt/m0/fz/9i/m0fz9idl6xt5rimcittxpme2iea.png)
Прошу любить и жаловать, редактор Datapath. В нём имеются все биты, которые описывались в переводе фирменной документации. Но этих битов так много, что в первые дни я на него смотрел, но боялся что-либо предпринять. Посмотрю-посмотрю и выйду. И только через некоторое время, привыкнув, начал пытаться что-то делать. Собственно, поэтому я и привёл только часть окна. Зачем пугать всех раньше времени? Пока же нам надо просто создать Datapath, поэтому мы выбираем пункт меню Edit->New Datapath:
![](https://habrastorage.org/webt/-n/dx/vj/-ndxvjk_wrpdjucuq0_c3jcqfai.png)
Какой вариант выбрать в появившемся диалоге?
![](https://habrastorage.org/webt/wf/ma/kf/wfmakfepho5l4h4nxffpua7wgok.png)
Вопрос чуть более серьёзен, чем кажется. Давайте я даже следующий абзац выделю, чтобы никто не попадался (я сам попался, а потом видел вопросы в сети от попавшихся, причём им никто не отвечал толком, а ответ есть в AN82156, надо только читать не по диагонали, так как там это написано короткой незаметной фразой).
Если планируется работать с параллельными данными, то выбирать надо обязательно вариант CY_PSOC3_DP. Ни один другой вариант не будет содержать портов для подключения параллельных данных.Итак. Пусть экземпляр у нас зовут LCD_DP:
![](https://habrastorage.org/webt/tf/a3/hs/tfa3hsi-mu2ljvyeukwmprwlm_4.png)
Нажимаем OK и пока что закрываем Datapath Config Tool, согласившись на сохранение результата. Мы вернёмся сюда позже.
Наш Verilog код расширился. Теперь в нём есть Datapath. Начало у него совершенно нечитаемое. Это не страшно, его настраивает Datapath Config Tool.
![](https://habrastorage.org/webt/tz/d9/ur/tzd9urfa66hsfic2w9gsn6tkysu.png)
А конец описания Datapath будем править мы. Наш участок выглядит так
(с этого места имеет смысл приводить всё в текстовом виде).
)) LCD_DP(
/* input */ .reset(1'b0),
/* input */ .clk(1'b0),
/* input [02:00] */ .cs_addr(3'b0),
/* input */ .route_si(1'b0),
/* input */ .route_ci(1'b0),
/* input */ .f0_load(1'b0),
/* input */ .f1_load(1'b0),
/* input */ .d0_load(1'b0),
/* input */ .d1_load(1'b0),
/* output */ .ce0(),
/* output */ .cl0(),
/* output */ .z0(),
/* output */ .ff0(),
/* output */ .ce1(),
/* output */ .cl1(),
/* output */ .z1(),
/* output */ .ff1(),
/* output */ .ov_msb(),
/* output */ .co_msb(),
/* output */ .cmsb(),
/* output */ .so(),
/* output */ .f0_bus_stat(),
/* output */ .f0_blk_stat(),
/* output */ .f1_bus_stat(),
/* output */ .f1_blk_stat(),
/* input */ .ci(1'b0), // Carry in from previous stage
/* output */ .co(), // Carry out to next stage
/* input */ .sir(1'b0), // Shift in from right side
/* output */ .sor(), // Shift out to right side
/* input */ .sil(1'b0), // Shift in from left side
/* output */ .sol(), // Shift out to left side
/* input */ .msbi(1'b0), // MSB chain in
/* output */ .msbo(), // MSB chain out
/* input [01:00] */ .cei(2'b0), // Compare equal in from prev stage
/* output [01:00] */ .ceo(), // Compare equal out to next stage
/* input [01:00] */ .cli(2'b0), // Compare less than in from prv stage
/* output [01:00] */ .clo(), // Compare less than out to next stage
/* input [01:00] */ .zi(2'b0), // Zero detect in from previous stage
/* output [01:00] */ .zo(), // Zero detect out to next stage
/* input [01:00] */ .fi(2'b0), // 0xFF detect in from previous stage
/* output [01:00] */ .fo(), // 0xFF detect out to next stage
/* input [01:00] */ .capi(2'b0), // Software capture from previous stage
/* output [01:00] */ .capo(), // Software capture to next stage
/* input */ .cfbi(1'b0), // CRC Feedback in from previous stage
/* output */ .cfbo(), // CRC Feedback out to next stage
/* input [07:00] */ .pi(8'b0), // Parallel data port
/* output [07:00] */ .po() // Parallel data port
);
Страшно? Сейчас разберёмся, что к чему — перестанет быть страшно. На самом деле, в этом тексте имеется три чётко выраженных группы. Давайте вспоминать перевод документации. Как выглядел Datapath на рисунке? Я сразу отмечу на рисунке места, к которым относятся группы «1», «2» и «3».
![](https://habrastorage.org/webt/ji/xe/5x/jixe5x_fxqpx6kgybrjmakagpw4.png)
Собственно, первая группа портов в verilog коде — это входы. Сравните имена на выходе мультиплексора входов («1» на рисунке) и имена сигналов в коде.
Сейчас все входы занулены. Мы должны будем подключить тактовый вход и сможем пробросить до шести входных линий, как это делали в UDB Editor. Вот эти входы:
/* input */ .reset(1'b0),
/* input */ .clk(1'b0),
/* input [02:00] */ .cs_addr(3'b0),
/* input */ .route_si(1'b0),
/* input */ .route_ci(1'b0),
/* input */ .f0_load(1'b0),
/* input */ .f1_load(1'b0),
/* input */ .d0_load(1'b0),
/* input */ .d1_load(1'b0),
Вторая группа — выходы. Имена в коде также совпадают с именами входов выходного мультиплексора «2»:
/* output */ .ce0(),
/* output */ .cl0(),
/* output */ .z0(),
/* output */ .ff0(),
/* output */ .ce1(),
/* output */ .cl1(),
/* output */ .z1(),
/* output */ .ff1(),
/* output */ .ov_msb(),
/* output */ .co_msb(),
/* output */ .cmsb(),
/* output */ .so(),
/* output */ .f0_bus_stat(),
/* output */ .f0_blk_stat(),
/* output */ .f1_bus_stat(),
/* output */ .f1_blk_stat(),
Третья группа есть только у данного вида Datapath (у остальных она отсутствует, поэтому отсутствуют и параллельные данные). Это внутренние сигналы Datapath, через которые можно самостоятельно производить объединение в цепочки либо иные полезные действия. Имена в коде также совпадают с именами внутренних сигналов, разбросанных по рисунку. Мы через один из них (последний в списке, его зовут po) будем выводить параллельные данные напрямую на ножки микросхемы.
/* input */ .ci(1'b0), // Carry in from previous stage
/* output */ .co(), // Carry out to next stage
/* input */ .sir(1'b0), // Shift in from right side
/* output */ .sor(), // Shift out to right side
/* input */ .sil(1'b0), // Shift in from left side
/* output */ .sol(), // Shift out to left side
/* input */ .msbi(1'b0), // MSB chain in
/* output */ .msbo(), // MSB chain out
/* input [01:00] */ .cei(2'b0), // Compare equal in from prev stage
/* output [01:00] */ .ceo(), // Compare equal out to next stage
/* input [01:00] */ .cli(2'b0), // Compare less than in from prv stage
/* output [01:00] */ .clo(), // Compare less than out to next stage
/* input [01:00] */ .zi(2'b0), // Zero detect in from previous stage
/* output [01:00] */ .zo(), // Zero detect out to next stage
/* input [01:00] */ .fi(2'b0), // 0xFF detect in from previous stage
/* output [01:00] */ .fo(), // 0xFF detect out to next stage
/* input [01:00] */ .capi(2'b0), // Software capture from previous stage
/* output [01:00] */ .capo(), // Software capture to next stage
/* input */ .cfbi(1'b0), // CRC Feedback in from previous stage
/* output */ .cfbo(), // CRC Feedback out to next stage
/* input [07:00] */ .pi(8'b0), // Parallel data port
/* output [07:00] */ .po() // Parallel data port
);
Итак. По мере работы нам придётся подключить часть этих входов и выходов к собственным сущностям, а остальные — просто оставить в том виде, в каком нам их создали.
Использование UDB Editor как справочника
И вот у нас получилась заготовка, мы знаем, куда и что нам предстоит вписать. Осталось понять, что именно мы туда будем вписывать. Так получилось, что я пользуюсь языком Verilog не каждый день, поэтому в общих чертах всё помню, а писать с нуля для меня это всегда является стрессовой ситуацией. Когда проект уже идёт — оно всё вспоминается, но если через пару месяцев простоя начать что-то с чистого листа, разумеется, я уже не помню деталей синтаксиса данного конкретного языка. Поэтому я предлагаю попросить среду разработки помочь нам.
UDB Editor для самоконтроля строит Verilog код. Воспользуемся тем фактом, что компоненты, которые не задействованы в основной схеме, не компилируются, поэтому мы вполне можем создать вспомогательный компонент в UDB Editor, и он не будет попадать в выходной код. Мы нарисуем там автомат, мы произведём черновую настройку входов и выходов Datapath, а потом просто перенесём автоматически созданный текст в наш verilog модуль и творчески всё доработаем. Это же гораздо проще, чем вспоминать детали синтаксиса Verilog и писать всё с нуля (хотя, кто постоянно пользуется Verilog-ом, тому, разумеется, будет проще написать именно с нуля: творческая доработка, как мы скоро увидим, хоть и проста, но требует времени).
Итак, начинаем делать вспомогательный компонент. Привычным движением руки добавляем новый элемент в проект:
![](https://habrastorage.org/webt/to/d2/fg/tod2fgx1opcdho2edizges-vr3q.png)
Это будет документ UDB, назовём его UDBhelper:
![](https://habrastorage.org/webt/j3/wt/iu/j3wtiuj2htdr-ct4f7m_3z497oi.png)
Пора подумать об автомате, который мы разместим на созданном листе. Для этого надо рассмотреть, какую временную диаграмму мы должны сформировать с его помощью:
![](https://habrastorage.org/webt/rh/zj/da/rhzjdasl8ioia8n8okjuuaieft4.png)
![](https://habrastorage.org/webt/dy/3a/0z/dy3a0zmkacoa4tvgaugebovdhqy.png)
Итак. Сначала следует установить сигнал RS (так как R/W припаян к нулю аппаратно). Далее следует выждать tAS, после чего поднять сигнал E и установить данные (установка данных относительно положительного фронта E не лимитирована). Данные должны находиться на шине не менее, чем tDSW, после чего следует уронить сигнал E. Данные должны оставаться на шине ещё как минимум tDHW, а RS — как минимум tAH.
RS — это флаг «команда или данные». Если RS равен нулю, то пишется команда, если единице — данные.
Предлагаю через FIFO0 посылать команды, а через FIFO1 — данные. В рамках текущей задачи это ничему не противоречит. Тогда предложенный мною конечный автомат будет иметь следующий вид:
![](https://habrastorage.org/webt/jn/6y/6j/jn6y6jnzllwbwgmslak7nif1xmu.png)
В состоянии Idle автомат находится, пока ни в одном из FIFO нет данных. Если появились данные в FIFO0, он идёт в состояние LoadF0, где в будущем будет принимать данные из FIFO0 в A0.
Пока передаются команды, данные слать не стоит. Поэтому условие приёма данных будет ниже по приоритету, чем условие приёма команд.
![](https://habrastorage.org/webt/og/gn/fl/oggnfl3tcjcxa8pzu64qcj3yzpa.png)
Данные принимаются в A1 в состоянии LoadF1 (из FIFO1 они могут попасть только в регистр A1 и не могут попасть в регистр A0), а затем копируются из A1 в A0 в состоянии A1toA0.
Каким бы путём мы ни шли к месту схождения стрелок, у нас есть данные в A0. Они уже выведены в параллельный порт. Взводим E (в состоянии E_UP1), роняем E (в состоянии E_DOWN1). Далее у нас будет состояние для обмена нибблов (SWAP), после чего E снова поднимается (E_UP2). На этом я исчерпал восемь состояний, которые можно закодировать тремя битами. А мы помним, что у ОЗУ динамической конфигурации Datapath всего три адресных входа. Можно было бы применить некоторые хитрости, но статья и так получается большая. Поэтому просто второй раз E будем ронять в состоянии Idle. Тогда нам вполне хватит восьми состояний.
На лист также положим Datapath и назначим его входы и выходы уже привычным по предыдущим статьям способом. Вот входы:
![](https://habrastorage.org/webt/ct/fn/0j/ctfn0jvexwfycfrdovbnatwv7ze.png)
Вот выходы:
![](https://habrastorage.org/webt/ct/mj/gd/ctmjgdzfvx-mz8qnzwbp6undlau.png)
Ничего нового, всё уже описывалось в предыдущих статьях цикла. Итак, у нас получилась заготовка, на основе которой мы сможем сделать что-то своё. Правда, чтобы убедиться, что всё собирается, нам надо вывести нашу систему на верхний уровень проекта, иначе никакие ошибки не будут находиться. А при первичных опытах без ошибок не получится. Поэтому сделаем ещё одно вспомогательное действие.
Описание, как делается схема, выходит за рамки описания работы с UDB. Я просто покажу, какая схема получилась у меня. Блок DMA только один: при посылке команд в ЖКД надо выдерживать большие паузы, поэтому всё равно это проще делать программно. Для других применений просто можно поставить второй блок DMA по аналогии, задействовав сигнал hungry0.
![](https://habrastorage.org/webt/mz/zd/g8/mzzdg80tinx_ffjjigxypmehj0w.png)
Чтобы точно уложиться во временные рамки, я выбрал тактовую частоту, равную одному мегагерцу. Можно было бы взять частоту и повыше, но данные передаются по длинным проводам в условиях высоких помех, поэтому время на установку данных до и после строба лучше взять с запасом. Если кто-то будет повторять мои опыты на той же макетной плате — не используйте порт P3.2: на плате к этой ножке припаян конденсатор. Я полчаса убил, пока выявил, почему у меня не формируется импульс E, который я сначала туда подключил. Перекинул на P3.1 — всё сразу заработало. У меня шина данных идёт на P3.7-P3.4, RS идёт на P3.3, поэтому E исходно шла на P3.2…
Ну вот. Теперь, если попробовать скомпилировать проект, получим вполне предсказуемые ошибки
![](https://habrastorage.org/webt/az/tm/fb/aztmfbvzsl52ghanjovqpva5_7o.png)
Значит, система пытается собирать что-то. Но ей пока нечего собрать. Приступаем к копированию кода. Для этого в UDB Editor переключаемся на вкладку Verilog (эта вкладка расположена под окном с листом UDB Editor):
![](https://habrastorage.org/webt/kg/tk/fy/kgtkfyn1tssdt4-cjydawzl7vnw.png)
Что там знакомое? В самом конце текста есть тело автомата. Давайте начнём перенос с него.
Также разместим его под Datapath:
/* ==================== State Machine: SM ==================== */
always @ (posedge clock)
begin : Idle_state_logic
case(SM)
Idle :
begin
if (( !F0empty ) == 1'b1)
begin
SM <= LoadF0 ;
end
else if (( !F1empty ) == 1'b1)
begin
SM <= LoadF1 ;
end
end
LoadF0 :
begin
if (( 1'b1 ) == 1'b1)
begin
SM <= E_Up1 ;
end
end
E_Up1 :
begin
if (( 1'b1 ) == 1'b1)
begin
SM <= E_Down1 ;
end
end
E_Down1 :
begin
if (( 1'b1 ) == 1'b1)
begin
SM <= SWAP ;
end
end
SWAP :
begin
if (( 1'b1 ) == 1'b1)
begin
SM <= E_UP2 ;
end
end
E_UP2 :
begin
if (( 1'b1 ) == 1'b1)
begin
SM <= Idle ;
end
end
LoadF1 :
begin
if (( 1'b1 ) == 1'b1)
begin
SM <= A1toA0 ;
end
end
A1toA0 :
begin
if (( 1'b1 ) == 1'b1)
begin
SM <= E_Up1 ;
end
end
default :
begin
SM <= Idle;
end
endcase
end
Сверху для этого кода имеются объявления (имена для состояний, цепи для Datapath, регистр, кодирующий состояние автомата). Переносим их в соответствующий
участок нашего кода:
/* ==================== Wire and Register Declarations ==================== */
localparam [2:0] Idle = 3'b000;
localparam [2:0] LoadF0 = 3'b001;
localparam [2:0] LoadF1 = 3'b010;
localparam [2:0] E_Up1 = 3'b100;
localparam [2:0] A1toA0 = 3'b011;
localparam [2:0] E_Down1 = 3'b101;
localparam [2:0] SWAP = 3'b110;
localparam [2:0] E_UP2 = 3'b111;
wire hungry0;
wire F0empty;
wire hungry1;
wire F1empty;
wire Datapath_1_d0_load;
wire Datapath_1_d1_load;
wire Datapath_1_f0_load;
wire Datapath_1_f1_load;
wire Datapath_1_route_si;
wire Datapath_1_route_ci;
wire [2:0] Datapath_1_select;
reg [2:0] SM;
Ну, и
участок связывания сигналов переносим:
/* ==================== Assignment of Combinatorial Variables ==================== */
assign Datapath_1_d0_load = (1'b0);
assign Datapath_1_d1_load = (1'b0);
assign Datapath_1_f0_load = (1'b0);
assign Datapath_1_f1_load = (1'b0);
assign Datapath_1_route_si = (1'b0);
assign Datapath_1_route_ci = (1'b0);
assign Datapath_1_select[0] = (SM[0]);
assign Datapath_1_select[1] = (SM[1]);
assign Datapath_1_select[2] = (SM[2]);
Пришла пора подключить Datapath. Код, перенесённый из UDB Editor, хорош для машинной правки, но не очень — для ручной. Там создаются цепи, которые одним концом подключаются ко входам Datapath, а другим — к константам. Но в коде, созданном Datapath Configuration Tool (который делает всё для ручной работы), все входы уже подключены к нулевым константам напрямую. Так что я подключу только те линии, которые не являются константами, а всё, что относится к пробросу констант вырежу из перенесённого текста. Подключение получилось таким (цветом выделены места, которые я отредактировал относительно автоматически созданного в Datapath Configuration Tool):
![](https://habrastorage.org/webt/-3/gk/fd/-3gkfd3bhnnc7wygbr6wpqoscyo.png)
То же самое текстом:
)) LCD_DP(
/* input */ .reset(1'b0),
/* input */ .clk(clk),
/* input [02:00] */ .cs_addr(SM),
/* input */ .route_si(1'b0),
/* input */ .route_ci(1'b0),
/* input */ .f0_load(1'b0),
/* input */ .f1_load(1'b0),
/* input */ .d0_load(1'b0),
/* input */ .d1_load(1'b0),
/* output */ .ce0(),
/* output */ .cl0(),
/* output */ .z0(),
/* output */ .ff0(),
/* output */ .ce1(),
/* output */ .cl1(),
/* output */ .z1(),
/* output */ .ff1(),
/* output */ .ov_msb(),
/* output */ .co_msb(),
/* output */ .cmsb(),
/* output */ .so(),
/* output */ .f0_bus_stat(hungry0),
/* output */ .f0_blk_stat(F0empty),
/* output */ .f1_bus_stat(hungry1),
/* output */ .f1_blk_stat(F1empty),
С параллельными данными чуть сложнее. У Datapath порт восьмибитный, а наружу надо вывести только четыре из них. Поэтому заводим вспомогательную цепь и подключаем на выход только её половину:
wire [7:0] tempBus;
assign LCD_D = tempBus[7:4];
И подключаем всё так:
![](https://habrastorage.org/webt/km/aq/_a/kmaq_abi2kkprp406kne413j5u0.png)
То же самое текстом:
/* input [07:00] */ .pi(8'b0), // Parallel data port
/* output [07:00] */ .po( tempBus) // Parallel data port
);
Пробуем собрать (Shift+F6 либо через пункт меню Build->Generate Application). Получаем ошибку:
![](https://habrastorage.org/webt/kw/ox/as/kwoxasreij4-snkxyvc63iqozxq.png)
У нас есть порты hungry0 и hungry1 (появились при создании компонента), а также одноимённые цепи (появились при перетягивании из образца). Просто удалим эти цепи (оставив порты). И где-то просочился сигнал clock, а у нас эта цепь называется clk.
После удаления всех лишних цепей (тех, которые исходно пробрасывали нулевые константы на входы Datapath, а также hungry0 и hungry1), получаем такой код начала нашего файла:
// Your code goes here
/* ==================== Wire and Register Declarations ==================== */
localparam [2:0] Idle = 3'b000;
localparam [2:0] LoadF0 = 3'b001;
localparam [2:0] LoadF1 = 3'b010;
localparam [2:0] E_Up1 = 3'b100;
localparam [2:0] A1toA0 = 3'b011;
localparam [2:0] E_Down1 = 3'b101;
localparam [2:0] SWAP = 3'b110;
localparam [2:0] E_UP2 = 3'b111;
wire F0empty;
wire F1empty;
reg [2:0] SM;
/* ==================== Assignment of Combinatorial Variables ==================== */
wire [7:0] tempBus;
assign LCD_D = tempBus[7:4];
А при замене clock на clk в теле автомата я заодно выкину все строки, которые хороши при автогенерации, но при ручной правке только создают путаницу (все сравнения, дающие безусловный результат TRUE и прочее). В частности, в примере ниже можно вычеркнуть около половины строк (а некоторые begin/end — по желанию, иногда они понадобятся, ведь мы будем добавлять действия, их я выделил цветом):
![](https://habrastorage.org/webt/9s/e9/t1/9se9t15zkc1hux1qrpga3jbh7cm.png)
После причёсывания по приведённому выше принципу (и замены clock на clk) остаётся такое тело
(оно стало короче, а значит — легче читается):
always @ (posedge clk)
begin : Idle_state_logic
case(SM)
Idle :
begin
if (( !F0empty ) == 1'b1)
begin
SM <= LoadF0 ;
end
else if (( !F1empty ) == 1'b1)
begin
SM <= LoadF1 ;
end
end
LoadF0 :
begin
SM <= E_Up1 ;
end
E_Up1 :
begin
SM <= E_Down1 ;
end
E_Down1 :
begin
SM <= SWAP ;
end
SWAP :
begin
SM <= E_UP2 ;
end
E_UP2 :
begin
SM <= Idle ;
end
LoadF1 :
begin
SM <= A1toA0 ;
end
A1toA0 :
begin
SM <= E_Up1 ;
end
default :
begin
SM <= Idle;
end
endcase
end
Теперь нам при компиляции говорят, что цепи LCD_E и LCD_RS не подключены.
Собственно, это правда:
![](https://habrastorage.org/webt/cz/da/h7/czdah7nfkzmou2mr8-omzs_-hby.png)
Пришла пора добавить конечному автомату действий. Заменим объявления соответствующих неподключённым цепям портов на reg, так как мы будем писать в них в теле автомата (таков синтаксис языка Verilog, если пишем — данные должны защёлкнуться, для этого нужен триггер, а его даёт ключевое слово reg):
![](https://habrastorage.org/webt/l5/vq/uj/l5vqujn7yirpf4dkorxzevy9ij4.png)
То же самое текстом:
module LCD4bit (
output hungry0,
output hungry1,
output [3:0] LCD_D,
output reg LCD_E,
output reg LCD_RS,
input clk
);
И наполним автомат действиями. Логику я уже проговаривал выше, когда рассматривал граф переходов автомата, поэтому покажу только результат:
![](https://habrastorage.org/webt/ij/t1/5i/ijt15itzdfznooyvlgz0sjlk5t4.png)
То же самое текстом:
always @ (posedge clk)
begin : Idle_state_logic
case(SM)
Idle :
begin
LCD_E <= 0;
if (( !F0empty ) == 1'b1)
begin
SM <= LoadF0 ;
LCD_RS <= 0;
end
else if (( !F1empty ) == 1'b1)
begin
SM <= LoadF1 ;
LCD_RS <= 1;
end
end
LoadF0 :
begin
SM <= E_Up1 ;
end
E_Up1 :
begin
SM <= E_Down1 ;
LCD_E <= 1'b1;
end
E_Down1 :
begin
SM <= SWAP ;
LCD_E <= 1'b0;
end
SWAP :
begin
SM <= E_UP2 ;
end
E_UP2 :
begin
SM <= Idle ;
LCD_E <= 1;
end
LoadF1 :
begin
SM <= A1toA0 ;
end
A1toA0 :
begin
SM <= E_Up1 ;
end
default :
begin
SM <= Idle;
end
endcase
end
С этого момента проект начинает собираться. Но работать он пока не будет. Пока что я лихо проговаривал: «В этом состоянии у нас загрузится регистр из FIFO», «В этом скопируется A1 в A0», «В этом переставятся нибблы». В общем, говорил я много, а действий пока не было. Пришла пора их выполнить. Смотрим, как у нас закодировались состояния:
localparam [2:0] Idle = 3'b000;
localparam [2:0] LoadF0 = 3'b001;
localparam [2:0] LoadF1 = 3'b010;
localparam [2:0] E_Up1 = 3'b100;
localparam [2:0] A1toA0 = 3'b011;
localparam [2:0] E_Down1 = 3'b101;
localparam [2:0] SWAP = 3'b110;
localparam [2:0] E_UP2 = 3'b111;
Вновь открываем Datapath Configuration Tool:
![](https://habrastorage.org/webt/eh/oh/sz/ehohszogw7vzyiup9cjumf4b3ii.png)
И начинаем править строки CFGRAM. При правке следует держать перед глазами схему Datapath, а именно:
![](https://habrastorage.org/webt/cz/xr/vu/czxrvuaul7vkqilrqinrgzpu1fy.png)
Красными рамками на рисунке ниже (и стрелками на рисунке выше) я выделил исправленные участки (и путь прохождения данных) для состояния LoadF0 (код 001, то есть, Reg1). Комментарии я также вписывал вручную. В A0 должно попасть содержимое F0.
![](https://habrastorage.org/webt/4z/zv/kh/4zzvkhchg55gnp-39oag_d-bkt8.png)
Зелёными рамками и стрелками я обозначил настройки и путь для состояния LoadF1 (код 010 — Reg2).
Синими рамками и стрелками я обозначил настройки и путь для состояния A1toA0 (код 011 — Reg3).
Фиолетовыми рамками и стрелками я обозначил настройки и путь для состояния SWAP (код 110 — Reg6).
Наконец, оранжевыми стрелками показан путь параллельных данных. А действий для них не совершается никаких. Они всегда выходят из SRCA. У нас почти всегда в качестве SRCA выбран A0: данные выходят из A0. Вот для перенаправления входных данных пришлось бы выполнять массу вспомогательных действий, но мы же не принимаем никаких данных, так что здесь нам эти действия не нужны, а все желающие найдут их перечень в AN82156. Также нам не требуется править никаких статических настроек Datapath, поэтому закрываем Datapath Config Tool.
Всё. Задуманная аппаратура завершена. Приступаем к разработке кода на Си. Для этого идём на вкладку Source и правим файл main.c.
![](https://habrastorage.org/webt/fp/ia/7s/fpia7s4zyzaxy8mzmqxktib-obw.png)
Штатная инициализация ЖКД и вывод символов «ABC» выглядят так (напомню, команды уходят в FIFO0, между командами документация требует вставлять паузы, а данные уходят в FIFO1, про паузы между данными я ничего не нашёл):
volatile uint8_t* pFIFO0 = (uint8_t*) LCD4bit_1_LCD_DP__F0_REG;
volatile uint8_t* pFIFO1 = (uint8_t*) LCD4bit_1_LCD_DP__F1_REG;
pFIFO0[0] = 0x33;
CyDelay (5);
pFIFO0[0] = 0x33;
CyDelay (100);
pFIFO0[0] = 0x33;
CyDelay (5);
pFIFO0[0] = 0x20;
CyDelay (5);
pFIFO0[0] = 0x0C; // включили экран
CyDelay (50);
pFIFO0[0] = 0x01; // Очистили экран
CyDelay (50);
pFIFO1[0] = 'A';
pFIFO1[0] = 'B';
pFIFO1[0] = 'C';
Что такое? Почему на экране только первый символ?
![](https://habrastorage.org/webt/3g/tq/sr/3gtqsrr050bqbhaqi2ke9g1pg0w.png)
А если между выводом данных добавить задержки — всё хорошо:
![](https://habrastorage.org/webt/3b/he/2a/3bhe2a1_ioou2gktkqfzydumrne.png)
Осциллографу для такой работы не хватит каналов. Проверяем работу на логическом анализаторе. Процесс записи данных выглядит следующим образом.
![](https://habrastorage.org/webt/yr/y_/_j/yry__jhgasihbzmmrrr2cnsylaq.png)
Все данные — на месте (три пары посылок). Время на установку и защёлкивание данных выделяется в достаточном объёме. В общем, с точки зрения временных диаграмм — всё сделано верно. Научная задача решена, желаемые временные диаграммы формируются. Вот инженерная — нет. Виной всему — медлительность процессора, установленного в ЖКД. Между байтами надо добавить задержки.
Задержки мы будем формировать при помощи семибитного счётчика, заодно потренируемся добавлять его в такую систему. Пусть мы будем находиться в состоянии Idle не менее, чем некое заданное время, а семибитный счётчик будет нам это время отмерять. И вновь мы будем не писать, а создавать код. Поэтому снова идём на вспомогательный компонент UDB Editor и добавляем на лист счётчик, настроив его параметры следующим образом:
![](https://habrastorage.org/webt/gq/8c/qw/gq8cqwl0liu7mv9gpgm8-wngom0.png)
Этот счётчик будет работать всегда (Enable присвоено значение 1). Но загружаться он будет, когда автомат находится в состоянии E_UP2 (после которого мы сразу провалимся в состояние Idle). Линия Count7_1_tc взведётся в 1, когда счётчик досчитает до нуля, что мы сделаем дополнительным условием выхода из состояния Idle. На рисунке также вписано значение периода, но в Verilog коде мы его не найдём. Его придётся вписывать в код на Си. Но сначала переносим автоматически сгенерированный Verilog код, переключившись на вкладку Verilog. В первую очередь, счётчик следует подключить (этот код мы видим в начале файла и перенесём тоже в начало):
`define CY_BLK_DIR "$CYPRESS_DIR\..\psoc\content\CyComponentLibrary\CyComponentLibrary.cylib\Count7_v1_0"
`include "$CYPRESS_DIR\..\psoc\content\CyComponentLibrary\CyComponentLibrary.cylib\Count7_v1_0\Count7_v1_0.v"
Как выполняется творческая доработка линий и констант уже описывалось, поэтому я просто покажу результат. Вот добавленные в итоге цепи и присвоения (остальные задавали константы, поэтому я их выкинул):
wire Count7_1_load;
wire Count7_1_tc;
assign Count7_1_load = (SM==E_UP2);
А вот сам счётчик, помещаемый в конце файла. Все константы присваиваются портам прямо в этом объявлении:
Count7_v1_0 Count7_1 (
.en(1'b1),
.load(Count7_1_load),
.clock(clk),
.reset(1'b0),
.cnt(),
.tc(Count7_1_tc));
defparam Count7_1.EnableSignal = 1;
defparam Count7_1.LoadSignal = 1;
Чтобы этот счётчик включился в работу, автомату добавляем дополнительное условие для выхода из состояния Idle:
![](https://habrastorage.org/webt/pc/it/pd/pcitpdmqenlvq8pblarf3uh5h84.png)
То же самое текстом:
case(SM)
Idle :
begin
LCD_E <= 0;
if (( !F0empty ) == 1'b1)
begin
SM <= LoadF0 ;
LCD_RS <= 0;
end
else if (( !F1empty &Count7_1_tc ) == 1'b1)
begin
SM <= LoadF1 ;
LCD_RS <= 1;
end
end
API для добавленного таким образом счётчика не создаётся, поэтому в функцию main добавляем две волшебных строки, которые я сформировал по образу и подобию увиденного в API от прошлых проектов (первая строка задаёт загружаемое значение счёта, тот самый Load, вторая запускает счётчик):
*((uint8_t*)LCD4bit_1_Count7_1_Counter7__PERIOD_REG) = 0x20;
*((uint8_t*)LCD4bit_1_Count7_1_Counter7__CONTROL_AUX_CTL_REG) |= 0x20; // Start
Анализатор показывает, что в доработанном случае задержки налицо:
![](https://habrastorage.org/webt/ui/ey/ax/uieyax5yt3f0kpkhyyoikiishmq.png)
На ЖКД также есть все три символа.
Но программный вывод символов в реальной жизни неприемлем. Если просто добавлять их в FIFO, произойдёт переполнение. Ждать опорожнения FIFO — это значит создавать большие задержки для процессорного ядра. Процессор работает на частоте 72 МГц, а данные выводятся за 7-8 тактов на частоте 1 МГц. Поэтому в реальной жизни текст надо выводить средствами DMA. Именно здесь нам пригодится принцип «Запустил и забыл». Все задержки для временной диаграммы нам сформирует UDB, а готовность FIFO к приёму данных нам будет определять контроллер DMA. От процессорного ядра требуется только сформировать строку в памяти и настроить DMA, после чего оно может заниматься другими задачами, совершенно не заботясь о выводе в ЖКД.
Добавим такой код:
static const char line[] = "This is a line";
/* Defines for DMA_D */
#define DMA_D_BYTES_PER_BURST 1
#define DMA_D_REQUEST_PER_BURST 1
/* Variable declarations for DMA_D */
/* Move these variable declarations to the top of the function */
uint8 DMA_D_Chan;
uint8 DMA_D_TD[1];
/* DMA Configuration for DMA_D */
DMA_D_Chan = DMA_D_DmaInitialize(DMA_D_BYTES_PER_BURST, DMA_D_REQUEST_PER_BURST,
HI16(line), HI16(LCD4bit_1_LCD_DP__F1_REG));
DMA_D_TD[0] = CyDmaTdAllocate();
CyDmaTdSetConfiguration(DMA_D_TD[0], sizeof(line)-1, CY_DMA_DISABLE_TD, CY_DMA_TD_INC_SRC_ADR);
CyDmaTdSetAddress(DMA_D_TD[0], LO16((uint32)line), LO16((uint32)LCD4bit_1_LCD_DP__F1_REG));
CyDmaChSetInitialTd(DMA_D_Chan, DMA_D_TD[0]);
CyDmaChEnable(DMA_D_Chan, 1);
На экране имеем:
![](https://habrastorage.org/webt/i7/ny/vi/i7nyvipk7tg8w_jdflryw2s39uk.png)
Заключение
На полусинтетическом, но близком к реальным задачам, примере мы освоили механизм разработки кода для UDB при помощи альтернативного механизма — Datapath Config Tool. Этот механизм, в отличие от UDB Editor, даёт доступ абсолютно ко всем возможностям управления UDB, но работа с ним сложнее, чем с UDB Editor. Тем не менее предложенный автором статьи метод позволяет не писать код с нуля, а просто создавать его, опираясь на вспомогательный код, созданный всё тем же UDB Editor.
Получившийся при написании статьи тестовый проект можно взять тут.
Комментарии (3)
Cobolorum
06.03.2019 20:26Была у Atmel серия микроконтроллеров AT49K так это было FPGA + 8-bit AVR Microcontroller.
Такая была милотоа. Хочешь аппаратный реверсивный счетчик на 42-бита а пожалуйста, хочешь UART на 16-бит тоже пожалуйста (правда чуть сложнее). Хочешь контроллер на циклическую шину и это можно. Я рядышком стоит такой стандартный AVR c обычной периферией. Т.е. такой микро Zynq.Почему убили серию не понятно.
VBKesha
Ох и за мудрено же все тут, но дев кит куплен обратного пути нет :)
PS. А у вас нет в планах перевода по аналоговой части этих чипов?
EasyLy Автор
Это уже — высший пилотаж. Для многих применений подойдёт и простой UDB Editor. По крайней мере, все реальные разработки, которые приходилось делать — сделаны в нём. Просто не хотелось бросать изучение на полпути, а раз изучил — почему бы не выложить результаты?
Ещё одна причина, по которой эта статья сделана — очень многие чужие вещи написаны именно через Datapath Config Tool. И их надо уметь читать, если есть желание учиться на чужом опыте. Следующая статья (финальная в цикле) задумана именно про это. А как её писать, если не показано, что за инструмент используется?
Но в целом — UDB Editor накрывает довольно широкий диапазон задач. Так что бояться не надо.
Планов по переводам или авторскому описанию аналоговой части пока нет. Не было нужды в неё играть пока что. Присматриваюсь к программированию DFB (Digital Filter Block), но фирменных примеров практически нет. Освоение идёт очень тяжело. А хочется сделать аппаратный PID регулятор для 3D принтера на нём. Вернее, три (два для экструдеров и один для стола). Не уверен, хватит ли ресурсов, но присматриваюсь к этой части, связанной с аналоговым сигналом.