一旦模块不需要 BRAM,如何重用它?

How to reuse BRAM once it's not needed by module?

我正在做一个(看似)简单的项目作为学习练习:通过 iCEstick(Lattice iCE40HX-1k FPGA)将基于 SSD1331 的 96x64 PMOD 显示器连接到 PC,这样我就可以通过发送一些 RGB565 编码的图像USB 将显示在所述显示器上。

事实上,SSD1331 显示器需要一个初始化程序才能进入 "clear black screen" 状态。大约有 20 个命令要转移到显示控制器中;长度在 1 到 5 个字节之间变化,总共是 44 个字节。

到目前为止,我用 FSM 编写了 Verilog pwr_on 模块,用于将命令按正确的顺序转移到 PMOD 中;命令的值定义为 localparam。一切正常,但总有一个但是。我认为所有这些命令常量都存储在 LUT 中(我没有推断任何 RAM 块,所以它们会去哪里,对吗?),并且 iCE40HX1k 中只有 1,280 个 LUT 可用,使用大约一百个用于初始化过程大约 150 毫秒,在下一次重置似乎是浪费之前永远不需要。

现在,我可以看到以下方法来处理这个问题:

  1. 根本不要在FPGA中实现初始化序列;相反,通过 USB 发送这些命令。
    简单但没那么有趣;毕竟,我正在尝试学习 FPGA 编程,而不是 Linux 驱动程序。
  2. 利用SB_WARMBOOT和多配置。
    iCE40HX 最多可以有 4 个配置存储在 EEPROM 中; SB_WARMBOOT primitive 基本上可以让你随意在它们之间跳转。我可以在配置 0 中编写 init 过程,一旦它完成,就跳转到支持 USB 的配置 1,从而拥有一个干净的状态。但是,在配置之间转换时,我需要将至少 3 个显示 PMOD 引脚(pmod_enable、vcc_enable 和 pmod_rstn)保持为高电平。我找不到任何方法来做到这一点;如果有人知道请给我正确的方向。
  3. 在 BRAM 中存储命令数据。
    HX1K 有 16 个 RAM4K 块(每个块存储 4096 位),因此即使其中一个块也应该为 44 字节的命令数据提供足够的空间,而无需花费宝贵的 LUT。

选项 3 看起来很简单。然而,由于对我的资源很吝啬,我很乐意在 init 完成后让 RAM4K 块可用于其他任务。现在,在我看来,Verilog 合成器(我正在使用 yosys)完全忘记了这样一个事实,即当 pwr_on 模块将 done 线拉高时,它所连接的 BRAM 单元可以在推断其他逻辑。

想到的一个解决方案是在单独的模块中分配该 BRAM 块,用初始化所需的数据填充它并将其连接到 pwr_on 模块,然后根据需要将其重新连接到其他模块.然而,出于某些原因,这种方法看起来很丑陋,因此问题是:我是否遗漏了一个技巧?我怎么能在一个模块的 SB_RAM512x8 配置中使用一个 BRAM 块,然后将其重新用作另一个模块的 SB_RAM256x16

我使用 Xilinx,但 FPGA 中的基本构建块之间的差异很小。

我快速搜索了 "Lattice BRAM",发现 Lattice 存储器与 Xilinx 一样,是双端口的。这意味着您可以从两个位置访问内存。您应该检查您的设备是否有该选项。

如果是,解决方法是实例化一个双口内存,先作为ROM初始化显示。然后使用另一个端口将 BRAM 用作普通内存。 最大的优势是所有用于两个端口访问的逻辑都已经在硅片上,因此您不必为此使用任何可编程逻辑。

请注意,只有重新配置设备才能恢复内容。正常重置不会。

仍然是启动时初始化RAM内容的问题。我知道这可以在 Xilinx 中完成,所以您必须寻找等效的 Lattice 应用说明。

将读取地址复用到用于 PMOD 配置数据的 EBR

ice40的EBR据我所知不能改变WRITE_MODEREAD_MODE而运行(如果我错了请指正)。因此,我建议在 启动 PMOD 后,在您想要使用的配置中实例化您的 EBR。 EBR 的内容必须包括 PMOD 的配置数据,通过 INIT_0INIT_F.

指定的通常方式

EBR 的读取地址需要是来自控制 PMOD 启动的 FSM 的地址的多路复用器,以及启动后要使用的地址,这将只花费大约 8 个 LUT。

类似的项目(用ICEStick驱动SSD1351 OLED显示器),我把初始化顺序写成"wired ROM",加上一个大的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 的初学者,我的风格可能远非完美...