Как использовать BRAM, если он не нужен модулю?
Я работаю над (на первый взгляд) простым проектом в качестве учебного упражнения: подключение дисплея PMOD 96x64 на базе SSD1331 через iCEstick (FPGA Lattice iCE40HX-1k) к ПК, чтобы я мог отправить некоторое изображение в кодировке RGB565 через USB для показа на сказал дисплей.
Дело в том, что дисплей SSD1331 требует процедуры инициализации, чтобы просто перейти в состояние "чистый черный экран". В контроллере дисплея должно быть около 20 команд; длина варьируется от 1 до 5 байтов, всего 44 байта.
До сих пор я написал Verilog pwr_on
модуль с FSM для переключения команд в PMOD в правильной последовательности; Значения для команд определяются как localparam
, Все отлично работает но всегда есть но. Я полагал, что все эти командные константы хранятся в LUT (я не определяю какие-либо блоки оперативной памяти, и куда они еще пойдут, не так ли?), И только с 1280 LUT, доступными в iCE40HX1k, используется около сотни из них для процедуры инициализации, которая требует около 150 мсек и не требуется, пока следующий сброс не станет пустой тратой.
Теперь я вижу следующие способы решения этой проблемы:
- Не реализовывайте последовательность инициализации в FPGA вообще; вместо этого отправьте эти команды через USB.
Просто, но не так интересно; В конце концов, я пытаюсь изучить программирование на ПЛИС, а не драйверы для Linux. - Извлекать выгоду
SB_WARMBOOT
и мультиконфигурация.
В iCE40HX может храниться до 4 конфигураций в EEPROM;SB_WARMBOOT
примитив позволяет прыгать между ними по желанию. Я мог бы запрограммировать процедуру инициализации в конфигурации 0, и как только она закончилась, перейдите к конфигурации 1 с поддержкой USB, таким образом, имея чистый лист. Однако мне нужно удерживать как минимум 3 вывода PMOD дисплея (pmod_enable, vcc_enable и pmod_rstn) на высоком уровне при переходе между конфигурациями. Я не могу найти никаких средств для этого; если кто-нибудь знает, пожалуйста, пришлите мне в правильном направлении. - Сохраняйте данные команд в BRAM.
HX1K имеет 16 блоков RAM4K (каждый хранит 4096 битов), поэтому даже один из них должен обеспечить достаточно места для 44 байтов командных данных без затрат ценных LUT.
Вариант 3 выглядит достаточно просто. Тем не менее, будучи скандалом о моих ресурсах, я бы хотел, чтобы этот блок RAM4K был доступен для других задач после завершения init. Теперь мне кажется, что синтезатор Verilog (я использую yosys) полностью забывает о том, что когда pwr_on
модуль тянет done
высокий провод, ячейка BRAM, к которой она была присоединена, может быть повторно использована при выводе другой логики.
Одно из решений, которое приходит на ум, - это выделить этот блок BRAM в отдельный модуль, заполнить его данными, необходимыми для init, и подключить его к pwr_on
модуль, затем подключите его к другим модулям по мере необходимости. Тем не менее, этот подход выглядит некрасиво по нескольким причинам, поэтому возникает вопрос: есть ли уловка, которую я пропускаю? Как я могу использовать один блок BRAM в, скажем, SB_RAM512x8
Конфигурация для одного модуля, а затем использовать его как SB_RAM256x16
для другого?
1 ответ
Мультиплексируйте адрес чтения в EBR, используемый для данных конфигурации PMOD
EBR Ice40, насколько мне известно, не может изменить WRITE_MODE
а также READ_MODE
во время бега (поправьте меня, если я ошибаюсь). Следовательно, я бы посоветовал создать экземпляр EBR в конфигурации, которую вы хотите использовать после запуска PMOD. Содержимое EBR должно включать данные конфигурации для PMOD, указанные обычным способом черезINIT_0
через INIT_F
.
Адрес чтения для EBR должен быть мультиплексором адреса от FSM, управляющего инициированием PMOD, и адресом, который будет использоваться после инициации, это будет стоить всего около 8 LUT.
Для аналогичного проекта (управление OLED-дисплеем SSD1351 с помощью ICEStick) я написал последовательность инициализации как "проводное ПЗУ" с большим оператором case() следующим образом:
module SSD1351InitROM(
input wire [5:0] address,
output reg [8:0] data
);
always @(*) begin
case(address)
0: data=9'h0_02; // Reset low during 0.5s
1: data=9'h0_01; // Reset high during 0.5s
2: data=9'h0_fd; 3: data=9'h1_12; // Unlock driver
4: data=9'h0_fd; 5: data=9'h1_b1; // unlock commands
6: data=9'h0_ae; // display off
7: data=9'h0_a4; // display mode off
8: data=9'h0_15; 9: data=9'h1_00; 10: data=9'h1_7f; // column address
11: data=9'h0_75; 12: data=9'h1_00; 13: data=9'h1_7f; // row address
14: data=9'h0_b3; 15: data=9'h1_f1; // front clock divider (see section 8.5 of manual)
16: data=9'h0_ca; 17: data=9'h1_7f; // multiplex
18: data=9'h0_a0; 19: data=9'h1_74; // remap,data format,increment
20: data=9'h0_a1; 21: data=9'h1_00; // display start line
22: data=9'h0_a2; 23: data=9'h1_00; // display offset
24: data=9'h0_ab; 25: data=9'h1_01; // VDD regulator ON
26: data=9'h0_b4; 27: data=9'h1_a0; 28: data=9'h1_b5; 29: data=9'h1_55; // segment voltage ref pins
30: data=9'h0_c1; 31: data=9'h1_c8; 32: data=9'h1_80; 33: data=9'h1_c0; // contrast current for colors A,B,C
34: data=9'h0_c7; 35: data=9'h1_0f; // master contrast current
36: data=9'h0_b1; 37: data=9'h1_32; // length of segments 1 and 2 waveforms
38: data=9'h0_b2; 39: data=9'h1_a4; 40: data=9'h1_00; 41: data=9'h1_00; // display enhancement
42: data=9'h0_bb; 43: data=9'h1_17; // first pre-charge voltage phase 2
44: data=9'h0_b6; 45: data=9'h1_01; // second pre-charge period (see table 9-1 of manual)
46: data=9'h0_be; 47: data=9'h1_05; // Vcomh voltage
48: data=9'h0_a6; // display on // a6 = normal, a7 = inverse, a5 = all on
49: data=9'h0_af; // display mode on
default: data=9'h0_00; // end of program
endcase
end
endmodule
Я также был обеспокоен тем, что съедает слишком много LUT только для последовательности инициализации, но это остается разумным, вот использование устройства для всего проекта, которое отображает небольшую анимацию на SSD1351:
Info: Device utilisation:
Info: ICESTORM_LC: 246/ 1280 19%
Info: ICESTORM_RAM: 0/ 16 0%
Info: SB_IO: 11/ 112 9%
Info: SB_GB: 6/ 8 75%
Info: ICESTORM_PLL: 1/ 1 100%
Info: SB_WARMBOOT: 0/ 1 0%
Я предполагаю, что это оставляет достаточно ресурсов для UART, которые вам понадобятся для декодирования данных изображения с USB (обычно около 100 LUT, я бы сказал). Я использую один из swapforth / J1: https://github.com/jamesbowman/swapforth/blob/master/j1a/icestorm/uart.v(легко понять и не жаждать LUT).
Полные исходники моего проекта (и других) доступны на моей странице github: https://github.com/BrunoLevy/learn-fpga/
Отказ от ответственности: я новичок в VERILOG, мой стиль, вероятно, далек от совершенства...
Я использую Xilinx, но различия между основными строительными блоками в FPGA невелики.
Я быстро выполнил поиск "Lattice BRAM" и обнаружил, что память Lattice, как и в Xilinx, имеет два порта. Это означает, что вы можете получить доступ к памяти из двух мест. Вы должны проверить, есть ли у вашего устройства эта опция.
Если это так, решение состоит в том, чтобы создать экземпляр двухпортовой памяти и использовать ее сначала в качестве ПЗУ для инициализации дисплея. Затем используйте другой порт, чтобы использовать BRAM в качестве обычной памяти. Большим преимуществом является то, что вся логика для доступа к двум портам уже на кремнии, поэтому вам не нужно использовать какую-либо программируемую логику для этого.
Помните, что только переконфигурация устройства восстановит содержимое. Обычного сброса не будет.
Остается проблема инициализации содержимого оперативной памяти при запуске. Я знаю, что это можно сделать в Xilinx, поэтому вам нужно поискать эквивалентную заметку о приложении Lattice.