Why does my rptr signal in vhdl move forward even tho it shouldn't?

57 Views Asked by At

Hi i have this block of code for a ring buffer

library ieee;
use ieee.std_logic_1164.all;
 
entity ring_buffer is
  generic (
    RAM_WIDTH : natural;
    RAM_DEPTH : natural
  );
  port (
    clk : in std_logic;
    rst : in std_logic;
 
    -- Write port
    wr_en : in std_logic;
    wr_data : in std_logic_vector(RAM_WIDTH - 1 downto 0);
 
    -- Read port
    rd_en : in std_logic;
    rd_valid : out std_logic;
    rd_data : out std_logic_vector(RAM_WIDTH - 1 downto 0);
 
    -- Flags
    empty : out std_logic;
    empty_next : out std_logic;
    full : out std_logic;
    full_next : out std_logic;
 
    -- The number of elements in the FIFO
    fill_count : out integer range RAM_DEPTH - 1 downto 0
  );
end ring_buffer;
 
architecture rtl of ring_buffer is
 
  type ram_type is array (0 to RAM_DEPTH - 1) of
    std_logic_vector(wr_data'range);
  signal ram : ram_type;
 
  subtype index_type is integer range ram_type'range;
  signal head : index_type;
  signal tail : index_type;
 
  signal empty_i : std_logic;
  signal full_i : std_logic;
  signal fill_count_i : integer range RAM_DEPTH - 1 downto 0;
 
  -- Increment and wrap
  procedure incr(signal index : inout index_type) is
  begin
    if index = index_type'high then
      index <= index_type'low;
    else
      index <= index + 1;
    end if;
  end procedure;
 
begin
 
  -- Copy internal signals to output
  empty <= empty_i;
  full <= full_i;
  fill_count <= fill_count_i;
 
  -- Set the flags
  empty_i <= '1' when fill_count_i = 0 else '0';
  empty_next <= '1' when fill_count_i <= 1 else '0';
  full_i <= '1' when fill_count_i >= RAM_DEPTH - 1 else '0';
  full_next <= '1' when fill_count_i >= RAM_DEPTH - 2 else '0';
 
  -- Update the head pointer in write
  PROC_HEAD : process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        head <= 0;
      else
 
        if wr_en = '1' and full_i = '0' then
          incr(head);
        end if;
 
      end if;
    end if;
  end process;
 
  -- Update the tail pointer on read and pulse valid
  PROC_TAIL : process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        tail <= 0;
        rd_valid <= '0';
      else
        rd_valid <= '0';
 
        if rd_en = '1' and empty_i = '0' then
          incr(tail);
          rd_valid <= '1';
        end if;
 
      end if;
    end if;
  end process;
 
  -- Write to and read from the RAM
  PROC_RAM : process(clk)
  begin
    if rising_edge(clk) then
      ram(head) <= wr_data;
      rd_data <= ram(tail);
    end if;
  end process;
 
  -- Update the fill count
  PROC_COUNT : process(head, tail)
  begin
    if head < tail then
      fill_count_i <= head - tail + RAM_DEPTH;
    else
      fill_count_i <= head - tail;
    end if;
  end process;
 
end architecture;

I have no idea why it does that... but when I init it and write to it with a frequency 25MHz it owerites the first read bit for some reason so instead of stopping at the 511 address in memory (that being RAM_DEPTH in my case) it goes to another one up so that it goes to 0 i tried changing it so that the if statement in PROC_HEAD has full_next = '1' instead of full_i and it still did the same thing... thank you for helping

1

There are 1 best solutions below

0
On

Adding a testbench to provide a minimal, complete, and verifiable example:

library ieee;
use ieee.std_logic_1164.all;

entity ringbuffer_tb is
end entity;

architecture foo of ringbuffer_tb is
    constant RAM_WIDTH:     natural := 4;
    constant RAM_DEPTH:     natural := 8;
    component ring_buffer is
        generic (
            RAM_WIDTH : natural;
            RAM_DEPTH : natural
        );
        port (
            clk:        in  std_logic;
            rst:        in  std_logic;
            wr_en:      in  std_logic;
            wr_data:    in  std_logic_vector(RAM_WIDTH - 1 downto 0);
            rd_en:      in  std_logic;
            rd_valid:   out std_logic;
            rd_data:    out std_logic_vector(RAM_WIDTH - 1 downto 0);
            empty:      out std_logic;
            empty_next: out std_logic;
            full:       out std_logic;
            full_next:  out std_logic;
            fill_count: out integer range RAM_DEPTH - 1 downto 0
        );
    end component;
    signal clk:         std_logic := '0';
    signal rst:         std_logic;
    signal wr_en:       std_logic;
    signal wr_data:     std_logic_vector (RAM_WIDTH - 1 downto 0);
    signal rd_en:       std_logic;
    signal rd_valid:    std_logic;
    signal rd_data:     std_logic_vector (RAM_WIDTH - 1 downto 0);
    signal empty:       std_logic;
    signal empty_next:  std_logic;
    signal full:        std_logic;
    signal full_next:   std_logic;
    signal fill_count:  integer range RAM_DEPTH - 1 downto 0;
    signal finished:    boolean;
begin
    CLOCK:
    process
    begin
        if not finished then
            clk <= not clk;
            wait for 20 ns;  -- 25 MHz
        else
            wait;
        end if;
    end process;
DUT:
    ring_buffer
        generic map (
            RAM_WIDTH => RAM_WIDTH,
            RAM_DEPTH => RAM_DEPTH
        )
        port map (
            clk => clk,
            rst => rst,
            wr_en => wr_en,
            wr_data => wr_data,
            rd_en => rd_en,
            rd_valid => rd_valid,
            rd_data => rd_data,
            empty => empty,
            empty_next => empty_next,
            full => full,
            full_next => full_next,
            fill_count => fill_count
        );
STIMULI:
    process
        use ieee.numeric_std.all;
    begin
        wr_en <= '0';
        rd_en <= '0';
        -- RESET:
        rst <= '1';
        wait until falling_edge(clk);  -- center baud sampled rising edge;
        rst <= '0';
        -- STOP WHEN FULL TEST:
        wr_en <= '1';
        for i in 0 to RAM_DEPTH - 1 loop
            wr_data <= 
                std_logic_vector (to_unsigned(RAM_DEPTH - 1 - i, RAM_WIDTH));
                wait until falling_edge(clk);
        end loop;
        -- ATTEMPT ONE MORE WRITE:
        wr_data <= "0101";
        wait until falling_edge(clk);
        wr_en <= '0';
        wait until falling_edge(clk);
        finished <= TRUE;
        wait;
    end process;
end architecture;

verifies the problem, your FIFO writes when full:

ringbuffer_tb_fail.jpg

There are two issues with the ringbuffer. Writes to RAM are not conditional on the FIFO not being full. This can be fixed:

  -- Write to and read from the RAM
  PROC_RAM : process(clk)
  begin
    if rising_edge(clk) then
        if wr_en = '1' and full_i = '0' then  -- CHANGED ADDED write condition
            ram(head) <= wr_data;
        end if;
      rd_data <= ram(tail);
    end if;
  end process;

The other problem is that the full (full_i) and full_next signals are one clock early, it'd prevent writing to the last location. That can be fixed by making fill_count_i synchronous:

  -- Update the fill count
  PROC_COUNT : -- process(head, tail)  -- CHANGED
  process (clk)
  begin
        if rising_edge (clk) then  -- CHANGED ADDED, make fill count synchronous
          if head < tail then
              fill_count_i <= head - tail + RAM_DEPTH;
          else
              fill_count_i <= head - tail;
          end if;
      end if;
  end process;

While there may be other as yet untested issues with your ringbuffer these changes make writes stop when the FIFO is full:

ringbuffer_tb_fixed.jpg

The testcases can be expanded and/or adapted for other RAM_WIDTH and RAM_DEPTH values. You could also have separate read and write stimuli processes as long as they don't write to the same signals. This would allow read and write operations to overlap.