Jump to content
    

Реализация кольцевого буфера

Добрый день!

Написал модуль кольцевого буфера, планирую использовать для временного хранения отсчетов, захваченных с АЦП.

Модуль служит для того, чтобы сохранять предыдущие N-отсчетов с АЦП. Суть работы модуля такая:

1) Данные пишутся в буфер непрерывно, по кругу

2) при возникновении разового импульса на входе catch все данные выдаются на выход (интерфейс AXI-Stream)

3) В момент выдачи данных, запрещается запись в буфер

 

Вроде хорошо работает на частотах до 250 МГц (Virtex-6). Но хотелось бы оптимизировать его, чтобы он работать на чуть большей частоте (300МГц). Может кто-нибудь подскажет, как.

Еще хотелось бы сделать модуль более параметризируемым. Чтобы можно было выбирать тип используемой памяти (блочная или распределенная)

Код модуля:

`timescale 1ns/1ps

module ring_buffer #(
  parameter DATA_WIDTH = 8,
  parameter BUFFER_DEPTH = 32768,
  parameter USE_BLOCK_RAM = 1
) 
(
  input wire clk,          // Тактовый сигнал
  input wire reset,        // Сигнал сброса
  input wire [DATA_WIDTH-1:0] data_in,  // Входные данные Native интерфейса
  input wire catch,         // Флаг начала выдачи данных

  output wire [DATA_WIDTH-1:0] data_out,  // Выходные данные
  output wire valid_out,        // Сигнал валидности выходных данных
  output wire last_out          // Сигнал последнего отсчета выходных данных
);

  
  reg [DATA_WIDTH-1:0] buffer [0:BUFFER_DEPTH-1];  // Кольцевой буфер предыдущих отсчетов
  reg [$clog2(BUFFER_DEPTH)-1:0] read_ptr = 0;      // Указатель на чтение
  reg [$clog2(BUFFER_DEPTH)-1:0] write_ptr = 0;     // Указатель на запись
  reg transfer_active = 1'b0;         // Флаг активной передачи данных

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      // Сброс в начальное состояние при срабатывании сигнала сброса
      //buffer <= 0;
      read_ptr <= 0;
      write_ptr <= 0;
      transfer_active <= 0;
    end else begin
      // Запись в буфер независимо от флага
      if (!transfer_active)
        buffer[write_ptr] <= data_in;
      
      if (!transfer_active) 
        write_ptr <= write_ptr + 1;

      // Установка флага активной передачи данных
      if (catch) begin
        transfer_active <= 1;
        
      end else if (last_out) begin
        transfer_active <= 0;
        
      end

      if (transfer_active) begin
        read_ptr <= read_ptr + 1;
      end
      else begin
        if (catch)
          read_ptr <= write_ptr + 1;
      end
    end
  end

  // Выходные данные
  assign data_out  = buffer[read_ptr];
  assign valid_out = transfer_active;
  assign last_out  = (read_ptr == write_ptr - 1) ? 1'b1 : 1'b0;  // Сигнал последнего отсчета при чтении последнего отсчета из буфера

endmodule

 

Тестбенч:

`timescale 1ns/1ps

module ring_tb ();


  logic       clk     = '0;  // Тактовый сигнал
  logic       reset   = '0;  // Сигнал сброса
  logic [7:0] data_in = '0;  // Входные данные Native интерфейса
  logic       catch   = '0;  // Флаг начала выдачи данных

  logic [7:0] data_out;   // Выходные данные
  logic       valid_out;  // Сигнал валидности выходных данных
  logic       last_out;   // Сигнал последнего отсчета выходных данных

ring #(
  .DATA_WIDTH    (8),
  .BUFFER_DEPTH  (32768),
  .USE_BLOCK_RAM (1)
) 
dut
(
  .clk          (clk),          // Тактовый сигнал
  .reset        (reset),        // Сигнал сброса
  .data_in      (data_in),  // Входные данные Native интерфейса
  .catch        (catch),         // Флаг начала выдачи данных

  .data_out     (data_out),  // Выходные данные
  .valid_out    (valid_out),        // Сигнал валидности выходных данных
  .last_out     (last_out)          // Сигнал последнего отсчета выходных данных
);



  initial begin
    clk = 0;
    forever clk = #5 ~clk;
  end

  initial begin
    reset = 1; #100;
    reset = 0; #100;
  end

  always_ff @(posedge clk) begin : proc_data_in
    data_in <= data_in + 1'b1;
  end

  initial begin
    catch = '0; #500000;
    catch = '1; #10;
    catch = '0; #500000;
    catch = '1; #10;
    catch = '0;
  end

endmodule

 

Share this post


Link to post
Share on other sites

28 минут назад, srf55 сказал:

3) В момент выдачи данных, запрещается запись в буфер

Я не специалист в ПЛИС, но... почему так? Зачем запрещать запись?

Share this post


Link to post
Share on other sites

34 минуты назад, Arlleex сказал:

Я не специалист в ПЛИС, но... почему так? Зачем запрещать запись?

Сейчас подумал, что можно обеспечить, чтобы адрес write_ptr был на 1 больше, чем адрес read_ptr. Тогда никаких коллизий не будет происходить. Спасибо!

Только тогда придется сделать еще один счетчик(считать до значения глубины FIFO), т.к не получится останавливать выдачу данных на условии: (read_ptr == write_ptr - 1)

Edited by srf55

Share this post


Link to post
Share on other sites

я в свое время написал аналогичный модуль 

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;

entity delay_line is
generic(
  W                 : integer := 8;    -- data width
  L                 : integer := 1200);  -- delay length, shall be > 3
port(
  i_clk             : in  std_logic;
  i_sync_reset      : in  std_logic;
  i_data            : in  std_logic_vector(W-1 downto 0);
  o_data            : out std_logic_vector(W-1 downto 0));
end delay_line;

architecture rtl of delay_line is

type t_ram is array (L-2 downto 0) of std_logic_vector(W-1 downto 0);
signal m_ram : t_ram;

signal r_addr_wr         : integer range 0 to L-2;
signal r_addr_rd         : integer range 0 to L-2;
signal r_enable_read     : std_logic;

begin

p_write : process (i_clk)
begin
  if rising_edge(i_clk) then
    if(i_sync_reset='1') then
      r_addr_wr      <= 0;
      r_enable_read  <= '0';
    else
      m_ram(r_addr_wr) <= i_data;
      if(r_addr_wr<L-2) then
        r_addr_wr      <= r_addr_wr + 1;
      else
        r_addr_wr      <= 0;
        r_enable_read  <= '1';       -- enable reading section
      end if;
    end if;
  end if;
end process p_write;

p_read : process (i_clk)
begin
  if rising_edge(i_clk) then
    if(i_sync_reset='1') then
      r_addr_rd      <= 0;
    else
      if(r_enable_read='1') then
        o_data         <= m_ram(r_addr_rd) ; -- additional delay
        if(r_addr_rd<L-2) then
          r_addr_rd      <= r_addr_rd + 1;
        else
          r_addr_rd      <= 0;
        end if;
      end if;
    end if;
  end if;
end process p_read;


end rtl;

тоже по кольцу пишет, но может обеспечить задержку...

PS все предложенное варианты реализации фифо...

 

Вам предложу рассмотреть реализацию счетчика на базе конвеера из более мелких счетчиков, должно повысить тактовую частоту...

Share this post


Link to post
Share on other sites

1 hour ago, Maverick_ said:

Вам предложу рассмотреть реализацию счетчика на базе конвеера из более мелких счетчиков, должно повысить тактовую частоту...

Xilinx быстрые счётчики реализует на DSP48.

 

2 hours ago, srf55 said:

Вроде хорошо работает на частотах до 250 МГц (Virtex-6). Но хотелось бы оптимизировать его, чтобы он работать на чуть большей частоте (300МГц). Может кто-нибудь подскажет, как.

Выделить каждый регистр в отдельный always\модуль и посмотреть максимально возможную частоту его работы.

 

2 hours ago, srf55 said:

Еще хотелось бы сделать модуль более параметризируемым. Чтобы можно было выбирать тип используемой памяти (блочная или распределенная)

На распределённой скорость будет ниже, но если хочется - попробуйте generate.

 

Share this post


Link to post
Share on other sites

1 час назад, Maverick_ сказал:

Вам предложу рассмотреть реализацию счетчика на базе конвеера из более мелких счетчиков, должно повысить тактовую частоту...

Спасибо!

Основываясь на мысли Arlleex сделал ring.v (прекратил запрещать запись во время выдачи данных)

Добавил дополнительные регистры (ring_pipe.v). Но счетчики, на более мелкие (как посоветовал Maverick_) пока что дробить не стал. Т.к Fmax возрос до 370 МГц и этого хватает. Потом ради интереса может займусь.

сделал ring_tb.sv - для проверки того, что модули работают идентично

 

Share this post


Link to post
Share on other sites

Прежде чем  беспокоить DSP или зазря переписывать always  есть смысл  посмотреть логи   и понять что же является узким место в плане времянки.  

Из  приведенного  куска кода   вопрос может вызывать только 

assign last_out  = (read_ptr == write_ptr - 1) ? 1'b1 : 1'b0;

Так как сравнении идет с не с регистром, а с разностью - и это может влиять на времянку.  

 

Share this post


Link to post
Share on other sites

2 hours ago, srf55 said:

интерфейс AXI-Stream

Разве? Где сигнал ready? А вот когда он появится, тогда

1 hour ago, srf55 said:

обеспечить, чтобы адрес write_ptr был на 1 больше, чем адрес read_ptr

не получится.

 

2 hours ago, srf55 said:

Чтобы можно было выбирать тип используемой памяти (блочная или распределенная)

Через строковый параметр

parameter RAM_TYPE = "block"

...


localparam L_RAM_TYPE   = (RAM_TYPE == "block") ? "block" : "distributed";


(* RAM_STYLE = L_RAM_TYPE *)
reg [DATA_WIDTH-1:0] buffer [0:BUFFER_DEPTH-1];  // Кольцевой буфер предыдущих отсчетов

 

Share this post


Link to post
Share on other sites

34 minutes ago, _4afc_ said:

Xilinx быстрые счётчики реализует на DSP48.

 

DSP48 тратить на счетчики на мой взгляд дорогое удовольствие - можно такое делать когда других вариантов нет

Share this post


Link to post
Share on other sites

20 hours ago, Maverick_ said:

DSP48 тратить на счетчики на мой взгляд дорогое удовольствие - можно такое делать когда других вариантов нет

Как-то давным-давно исследовал вопрос "так какой счетчик всё-таки шустрее считает, обычный или на DSP ?". Использовал в качестве базовой архитектуры какой-то из Спартанов, не помню точно какой.

Так вот в итоге получилось что приvерно до 32 бит обычный счётчик уделывает по времянке DSP-шный. Дальше побеждает DSP. В общем, fast carry logic - великая сила, а ведь были времена когда её в ПЛИС не было!

 

Share this post


Link to post
Share on other sites

Спасибо всем за помощь!

Вот что получилось:

ring_buffer.v

ring_buffer_pipe.v

ring_buffer_tb.sv

 

Исправил ошибку, last выставлялся несвоевременно, на такт раньше, чем нужно.

 

В 21.11.2023 в 15:28, andrew_b сказал:
В 21.11.2023 в 12:27, srf55 сказал:

Чтобы можно было выбирать тип используемой памяти (блочная или распределенная)

Через строковый параметр


Добавил возможность выбирать тип используемой памяти (параметр USE_BLOCK_RAM).

 

В 21.11.2023 в 15:26, RobFPGA сказал:

Из  приведенного  куска кода   вопрос может вызывать только 

assign last_out  = (read_ptr == write_ptr - 1) ? 1'b1 : 1'b0;

Так как сравнении идет с не с регистром, а с разностью - и это может влиять на времянку.  

Исправил, теперь в этом месте происходит сравнение с константой 😀

 

В 21.11.2023 в 15:28, andrew_b сказал:
В 21.11.2023 в 12:27, srf55 сказал:

интерфейс AXI-Stream

Разве? Где сигнал ready? А вот когда он появится, тогда

Смотрел стандарт AMBA AXI-Stream и не нашел, что ready (TREADY) - это обязательный сигнал (но мог упустить, и где-то об этом говорится). В этом конкретном случае, на мой взгляд, сигнал TREADY не нужен.

Share this post


Link to post
Share on other sites

37 minutes ago, srf55 said:

В этом конкретном случае, на мой взгляд, сигнал TREADY не нужен.

Вам виднее. Но если вы будете сопрягать свой модуль с каким-то сторонним, у которого выход ready есть? На ваш вход ready всегда можно подать постоянную единицу, так что от него хуже не будет.

Share this post


Link to post
Share on other sites

Может пригодится еще:
Вот пример конвейерезированного счетчика 24 бит, который разбит на два 12-битных:

reg [11:0] cnt_low = 12'd0;
reg [11:0] cnt_high = 12'd0;
reg cnt_low_done = 1'b0;
wire cnt_low_done_buf;

assign cnt_low_done_buf = (cnt_low == 12'hFFE) ? 1'b1 : 1'b0;

always @ (posedge clk)
begin
    if ((cnt_high == MAX_VALUE[23:12])&(cnt_low == MAX_VALUE[11:0])) begin
      cnt_high <= 12'd0;
      cnt_low <= 12'd0;
      cnt_low_done <= 1'b0; end
    else begin
      cnt_low <= cnt_low + 1'b1;
      cnt_low_done <= cnt_low_done_buf;
      if (cnt_low_done) cnt_high <= cnt_high + 1'b1;
    end
end

Если вдруг оказывается, что самое "узкое" по времянке место в проекте это именно большеразрядный счетчик, то данное решение в виде конвейера - поможет однозначно...

Share this post


Link to post
Share on other sites

22 часа назад, dde29 сказал:

Если вдруг оказывается, что самое "узкое" по времянке место в проекте это именно большеразрядный счетчик, то данное решение в виде конвейера - поможет однозначно...

Как оно поможет? Чем это лучше:

logic cnt = 0;

always_ff @(posedge clk) begin
	cnt <= cnt + 1;
end             

?

Ваш вариант будет работать точно не быстрее. В лучшем случае он сведётся к приведённому.

Скоростные многоразрядные счётчики делаются совсем не так.

Share this post


Link to post
Share on other sites

On 3/16/2024 at 10:45 AM, dxp said:

Как оно поможет? Чем это лучше:

logic cnt = 0;

always_ff @(posedge clk) begin
	cnt <= cnt + 1;
end             

?

Ваш вариант будет работать точно не быстрее. В лучшем случае он сведётся к приведённому.

Скоростные многоразрядные счётчики делаются совсем не так.

Может тогда подскажете как?

Но лично мне то, что я привел - помогало увеличить максимальную частоту работы счётчиков, как на Альтере, так и на Ксилинксе...

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...