Double-Buffered FPGA LED Framebuffer With Block RAM

An LED scanner reads pixels continuously while a CPU, DMA engine, or animation core writes the next image. If both sides touch the displayed image, the panel can show half of one frame and half of another. A double buffer fixes tearing: one memory bank is scanned while the other is updated, and ownership changes only at a frame boundary.

The architecture

VHDL-2008 reference implementation

This article uses synthesizable VHDL-2008. The complete tested VHDL bundle is linked below; adapt clock constraints, I/O standards, and timing parameters to the target board and panel.

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity hub75_row_scanner is
  generic (COLUMNS : positive := 64; ROW_GROUPS : positive := 16);
  port (
    clk       : in  std_logic;
    rst       : in  std_logic;
    dwell_done: in  std_logic;
    pixel_bits: in  std_logic_vector(5 downto 0);
    hub_clk   : out std_logic;
    lat       : out std_logic;
    oe_n      : out std_logic;
    row_addr  : out natural range 0 to ROW_GROUPS - 1;
    rgb       : out std_logic_vector(5 downto 0)
  );
end entity;

architecture rtl of hub75_row_scanner is
  type state_t is (blank, shift_low, shift_high, latch, set_row, display);
  signal state : state_t := blank;
  signal col   : natural range 0 to COLUMNS - 1 := 0;
  signal row   : natural range 0 to ROW_GROUPS - 1 := 0;
begin
  process (clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        state <= blank; col <= 0; row <= 0;
      else
        case state is
          when blank      => col <= 0; state <= shift_low;
          when shift_low  => state <= shift_high;
          when shift_high => if col = COLUMNS - 1 then state <= latch; else col <= col + 1; state <= shift_low; end if;
          when latch      => state <= set_row;
          when set_row    => state <= display;
          when display    => if dwell_done = '1' then if row = ROW_GROUPS - 1 then row <= 0; else row <= row + 1; end if; state <= blank; end if;
        end case;
      end if;
    end if;
  end process;

  hub_clk  <= '1' when state = shift_high else '0';
  lat      <= '1' when state = latch else '0';
  oe_n     <= '0' when state = display else '1';
  row_addr <= row;
  rgb      <= pixel_bits when state = shift_low else (others => '0');
end architecture;

Download the VHDL-2008 FPGA LED controller examples

  • Front bank: read by the real-time display scanner.
  • Back bank: written by the producer.
  • Swap request: says the back bank contains a complete frame.
  • Frame boundary: the only place the scanner changes banks.

The producer must not write the bank currently being scanned. The scanner must not switch until the last row and last bitplane have completed.

Infer two synchronous memories

This example writes the bank opposite front_bank. If the clocks are unrelated, do not pass front_bank directly into write logic without a synchronization and ownership protocol. A safer system acknowledges each swap back to the producer before accepting more writes.

Swap only on a complete frame

A toggle acknowledgement is easier to cross between clock domains than a one-cycle pulse. The producer holds the back bank stable after requesting a swap and waits until it sees the acknowledgement toggle.

Account for memory latency

Block RAM normally has a synchronous read. Request pixel N one clock before its bits are needed, and pipeline the column counter, plane index, and row metadata alongside the data. An off-by-one memory pipeline often appears as a one-column horizontal shift.

Capacity example

A 64×32 RGB framebuffer at 24 bits per pixel contains 49,152 bits. Double buffering requires 98,304 bits before padding and parity. Compare that requirement with the block-RAM geometry of the target FPGA, not only its headline memory total.

Official reference

AMD UG901 RAM HDL Coding Techniques documents portable coding patterns for distributed and block-memory inference.


FPGA LED controller tutorial series

This article is part of the FPGA LED Controller Tutorials learning path. Continue with PWM vs. BCM, the HUB75 timing calculator, or VHDL-2008 verification.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *