Как стать автором
Обновить

Создаем I2C Master Controller на Verilog. Проверим работу на реальном железе

Уровень сложностиСредний
Время на прочтение22 мин
Количество просмотров6.7K
Всего голосов 53: ↑52 и ↓1+59
Комментарии10

Комментарии 10

Когда вы его подключите к AXI и начнете взаимодействовать из Linux, вы ведь нам расскажете, как написать свой драйвер?

Разумеется. Это будет как раз материал одной из следующих статей :)

Кажется базовый Verilog-кодинг стал одной из моих компетенций, но безусловно есть куда расти.

Например, перейти на SystemVerilog и использовать ='0; для инициализации ВСЕХ reg (в т.ч. led_driver.v и прочее).

Отличное замечание. Спасибо! :)

Драйвер -- это, вообще говоря, либо простая железяка, которая "качает" другую железяку (скажем, драйвер двигателя), либо программа, реализующая логику управления чем-либо. Здесь, думается, корректней было бы говорить о контроллере семисегментника и т.д., а не о драйвере. Но это так, придирки :)

необходимо будет придумать SPI-автомат

А чего его придумывать? Обычный сдвиговый регистр + клоки + CS.

Так не делал же ни разу :)

По моему опыту, i2c на порядок сложнее.

Здравствуйте.

У меня получилось перенести этот дизайн в VIVADO под Kintex XC7K325T.

Я решил это сделать в графическом виде.

Общий вид
Общий вид
Конечный автомат и i2c контроллер
Конечный автомат и i2c контроллер

В процессе возникли сложности с синтезом. Никак не хотели синтезироваться конструкции с тремя состояниями из прошлой статьи.

assign scl_io = (scl_r) ? 1'bz : 1'b0;

У меня от такой записи синтезировались просто OBUF ну и конечно же обе линии (SCL, SDA) "лежали за "земле". Кстати если заменить "z" значение в этом мультиплексоре на "1", то осциллограмма получалась верная, но OBUF не работают на вход.

Мной были использованы OBUFT (https://docs.amd.com/r/en-US/ug953-vivado-7series-libraries/OBUFT) для SCL и IOBUF (https://docs.amd.com/r/en-US/ug953-vivado-7series-libraries/IOBUF) для SDA.

В коде это выглядит вот так:

// assign scl_io = (~scl_r) ? 1'b0 : 1'bz; // избавляемся от мультиплексора

OBUFT #( // включаем OUTPUT в библиотечный элемент 
   .DRIVE(12),     // Specify the output drive strength
   .IOSTANDARD("DEFAULT"), // Specify the output I/O standard
   .SLEW("SLOW")   // Specify the output slew rate
) OBUFT_inst_scl (
   .O(scl_io),     // Buffer output (connect directly to top-level port)
   .I(1'b0),       // Buffer input
   .T(scl_r)       // 3-state enable input
);

Теперь в SCHEMATIC можно наблюдать правильный выход после синтеза:

С SDA чуть-чуть посложнее. IOBUF был включен в TOP-LEVEL файле. Для это из I2C_bit_controller потребовать вывести сигнал наружу:

//---------------------------------------------------
assign T = into_w || sda_r; // здесь Т - это OUTPUT
//---------------------------------------------------

Теперь в TOP-LEVEL файле подключаем библиотечный элемент:

wire T;     // провод для design_wrapper
wire sda_w; // провод sda, по нему пойдут даннные из SLAVE в MASTER через IOBUF

IOBUF IOBUF_inst_sda (
  .O(sda_w),     // Buffer output
  .IO(sda_io),   // Buffer inout port (connect directly to top-level port) // sda_io
  .I(),          // Buffer input
  .T(T)          // 3-state enable input, high=input, low=output // (into_w || sda_r)
);

В design_wrapper включаем правильные сигналы:

...
.scl_io_0(scl_io),
.sda_io_0(sda_w), // sda_io
...

Теперь это будет работать на реальном железе. На моей кастомной плате нет семисегментников, поэтому для вывода данных была использована двухпортовая BRAM память. Один порт в Microblaze, а второй управляется кастомным контроллером из прошлых статей автора. Так для отображения всех данных я буду использовать терминал, данные в который поступают из софт-процессора MICROBLAZE. По линии I2С на этой плате управляется PLL LMK03318 (https://qeeniu.net/product/LMK03318#all). В cnt_0_data (по адресу 0xC000000C) отображается номер регистра, в cnt_1_data (по адресу 0xC0000010) данные, которые мы хотим записать по i2c линии в микросхему, а в cnt_2_data (по адресу 0xC0000014) считанные данные.

После включения питания и сброса (по кнопке RESET) адрес регистра устанавливается в 0xA, а данные, которые необходимо записать в 0xDE.

Нажимаю кнопку чтения.

В регистр записалось значение 0x0A, что по даташиту соответствует адресу микросхемы по-умолчанию.

Установим адрес 0xC

и запишем по этому адресу значение 0xDE в микросхему. Это будет соответствовать отключению функции AUTOSTART LMK03318.

Делаю сброс (кнопкой RESET) и считываю регистры в приложении для Microblaze:

А потом опять 0xDF

Summary. Пользуясь моментом хочу сказать спасибо автору этих статей, я думаю что благодаря Вам проектирование под плис и программирование становится доступным каждому кто захочет в этом разобраться. Комментарий получился объемным, но очень поверхностным. Если у кого-то будут вопросы - не стесняйтесь писать, я постараюсь ответить.

ОГОНЬ! Спасибо вам, что написали такой объемный и большой комментарий :)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий