Как использовать 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 мсек и не требуется, пока следующий сброс не станет пустой тратой.

Теперь я вижу следующие способы решения этой проблемы:

  1. Не реализовывайте последовательность инициализации в FPGA вообще; вместо этого отправьте эти команды через USB.
    Просто, но не так интересно; В конце концов, я пытаюсь изучить программирование на ПЛИС, а не драйверы для Linux.
  2. Извлекать выгоду SB_WARMBOOT и мультиконфигурация.
    В iCE40HX может храниться до 4 конфигураций в EEPROM; SB_WARMBOOT примитив позволяет прыгать между ними по желанию. Я мог бы запрограммировать процедуру инициализации в конфигурации 0, и как только она закончилась, перейдите к конфигурации 1 с поддержкой USB, таким образом, имея чистый лист. Однако мне нужно удерживать как минимум 3 вывода PMOD дисплея (pmod_enable, vcc_enable и pmod_rstn) на высоком уровне при переходе между конфигурациями. Я не могу найти никаких средств для этого; если кто-нибудь знает, пожалуйста, пришлите мне в правильном направлении.
  3. Сохраняйте данные команд в 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.

Другие вопросы по тегам