Перейти к содержанию
    

Конвейер на AXI Stream

Добрый вечер, коллеги. Захотелось мне создать свой модуль конвейерной обработки пакетов по интерфейсу AXI Stream. Имею AXI Stream Slave и Master с сигналами TDATA, TVALID, TREADY, TLAST.  Без сигнала TREADY все просто и понятно, поток идет в одном направление без тормозов и все ок. Как только появляется сигнал обратной связи TREADY, то начинается веселье с торможением конвейера и триггерованием TREADY. На просторах сети в  github нашел код некого Alex'a https://github.com/alexforencich/verilog-axis в модуле axis_register.v и были те заветные буферизированные тормоза конвейера, которые я и поставил на выход своего модуля. Есть некие сомнения в правильности обработки... правильно ли я понимаю, что tvalid я проталкиваю по ступеням конвейера как данные по сигналу s_axis_tready из axis_register.v, а обработку данных веду как по s_axis_tready из axis_register.v, так еще и по tvalid соответствующей ступени конвейера? Может есть у кого пример построения такого конвейера или ссылка на чтиво по данной теме?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Всякое продвижение данных по конвейеру происходит тогда и только тогда, когда и master_valid, и slave_ready стоят в единице:

if (master_valid = '1' and slave_ready = '1') then
   slave_data_in <= master_data_out;
endif;

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

9 hours ago, likeasm said:

Есть некие сомнения в правильности обработки... правильно ли я понимаю, что tvalid я проталкиваю по ступеням конвейера как данные по сигналу s_axis_tready из axis_register.v, а обработку данных веду как по s_axis_tready из axis_register.v, так еще и по tvalid соответствующей ступени конвейера?

Сомнения в чем: в коде axis_register.v, в вашем понимании стандарта или в чем то еще?

9 hours ago, likeasm said:

Может есть у кого пример построения такого конвейера или ссылка на чтиво по данной теме?

ИМХО стандарт на AXI крайне желательно прочитать пару раз, там подробно разобраны эти моменты.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, andrew_b said:

Всякое продвижение данных по конвейеру происходит тогда и только тогда, когда и master_valid, и slave_ready стоят в единице:


if (master_valid == '1' and slave_ready = '1') then
   slave_data_in <= master_data_out;
endif;

 

Сигнал TDATA, TLAST нужно рассматривать как данные, а вот как продвигать сигнал TVALID по конвейеру, я так полагаю по сигналу TREADY от приемника, в данном случае от молуля axis_register.v?

1 hour ago, des00 said:

Сомнения в чем: в коде axis_register.v, в вашем понимании стандарта или в чем то еще?

ИМХО стандарт на AXI крайне желательно прочитать пару раз, там подробно разобраны эти моменты.

Сомнений по модулю нет, по axi тоже вопросов пока нет, вопрос больше по идеологии построения линии конвейера. Я могу модуль axis_register.v поставить после каждой стадии конвейера, либо в конце и тормозить все стадии конвейера по TREADY, и в последней ситуации не понятно, как тащить TVALID? Я предполагаю, что TVALID будет идти вместе с данным по сигналу TREADY от axis_register.v

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

35 minutes ago, likeasm said:

Сомнений по модулю нет, по axi тоже вопросов пока нет, вопрос больше по идеологии построения линии конвейера. Я могу модуль axis_register.v поставить после каждой стадии конвейера, либо в конце и тормозить все стадии конвейера по TREADY, и в последней ситуации не понятно, как тащить TVALID? Я предполагаю, что TVALID будет идти вместе с данным по сигналу TREADY от axis_register.v

тогда что у вы назваете конвейером? Сколько видел AXIS блоков, это функционально законченные модули. Если back-pressure/handshake отсутствует, там всегда 1ца, только valid передается по цепочке или вообще на все заходит, используясь как clkena. При этом в доке делается оговорка, что есть отход от AXI спецификации. Если же требуется back-pressure/handshake, то он реализуется на памяти/фифо, с таким расчетом, чтобы торможение на выходе, привело к торможению входа и записи к фифо без переполнения. Т.е. акси кухня вешается снаружи вашего вычислительного модуля.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 21.04.2021 13:32:39
// Design Name: 
// Module Name: axis_tlast_generator
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module axis_pipe_line
#( 
	////////////////////////////////////////////////////////////////////////////
	//------------------------------- Inputs -----------------------------------
    // Width of AXI stream interfaces in bits
    parameter TDATA_WIDTH = 16,
    // Propagate tkeep signal
    parameter TKEEP_ENABLE = (TDATA_WIDTH>8),
    // tkeep signal width (words per cycle)
    parameter TKEEP_WIDTH = (TDATA_WIDTH/8), 
    // Propagate tid signal       
    parameter TID_ENABLE = 0,
    // tid signal width
    parameter TID_WIDTH = 8,
    // Propagate tdest signal
    parameter TDEST_ENABLE = 0,
    // tdest signal width
    parameter TDEST_WIDTH = 8,
    // Propagate tuser signal 
    parameter TUSER_ENABLE = 1,
    // tuser signal width
    parameter TUSER_WIDTH = 1,              
    // Propagate tlast signal  
    parameter TLAST_ENABLE = 1,
    // Output register type
    // 0 to bypass, 1 for simple buffer, 2 for skid buffer 
    parameter OUT_REG_TYPE = 2

)
(
	//////////////////////////////////////////////////////////////////////////////////////
	//------------------------------- System signals -------------------------------------
    input aclk,
    input aresetn,
	//////////////////////////////////////////////////////////////////////////////////////
	//------------------- (Slave) AXIS Payload Interface ------------------------
    input  wire [TDATA_WIDTH-1:0]  s_axis_tdata,
    input  wire [TKEEP_WIDTH-1:0]  s_axis_tkeep,
    input  wire                   s_axis_tvalid,
    output wire                   s_axis_tready,
    input  wire                   s_axis_tlast,
    input  wire [TID_WIDTH-1:0]    s_axis_tid,
    input  wire [TDEST_ENABLE-1:0]  s_axis_tdest,
    input  wire [TUSER_WIDTH-1:0]  s_axis_tuser,
    //////////////////////////////////////////////////////////////////////////////////////
	//------------------- (Slave) AXIS Control Interface ------------------------


	//////////////////////////////////////////////////////////////////////////////////////
	//------------------- (Master) AXIS Payload Interface -----------------------
    output wire [TDATA_WIDTH-1:0]  m_axis_tdata,
    output wire [TKEEP_WIDTH-1:0]  m_axis_tkeep,
    output wire                   m_axis_tvalid,
    input  wire                   m_axis_tready,
    output wire                   m_axis_tlast,
    output wire [TID_WIDTH-1:0]    m_axis_tid,
    output wire [TDEST_ENABLE-1:0]  m_axis_tdest,
    output wire [TUSER_WIDTH-1:0]  m_axis_tuser
);





reg [TDATA_WIDTH-1:0] m_axis_tdata_stage_01_ff  = {TDATA_WIDTH{1'b0}};
reg [TKEEP_WIDTH-1:0] m_axis_tkeep_stage_01_ff  = {TKEEP_WIDTH{1'b0}};
reg                   m_axis_tvalid_stage_01_ff = 1'b0; 
reg                   m_axis_tlast_stage_01_ff  = 1'b0;
reg [TID_WIDTH-1:0]   m_axis_tid_stage_01_ff    = {TID_WIDTH{1'b0}};
reg [TDEST_WIDTH-1:0] m_axis_tdest_stage_01_ff  = {TDEST_WIDTH{1'b0}};
reg [TUSER_WIDTH-1:0] m_axis_tuser_stage_01_ff  = {TUSER_WIDTH{1'b0}};



reg [TDATA_WIDTH-1:0] m_axis_tdata_stage_02_ff  = {TDATA_WIDTH{1'b0}};
reg [TKEEP_WIDTH-1:0] m_axis_tkeep_stage_02_ff  = {TKEEP_WIDTH{1'b0}};
reg                   m_axis_tvalid_stage_02_ff = 1'b0; 
reg                   m_axis_tlast_stage_02_ff  = 1'b0;
reg [TID_WIDTH-1:0]   m_axis_tid_stage_02_ff    = {TID_WIDTH{1'b0}};
reg [TDEST_WIDTH-1:0] m_axis_tdest_stage_02_ff  = {TDEST_WIDTH{1'b0}};
reg [TUSER_WIDTH-1:0] m_axis_tuser_stage_02_ff  = {TUSER_WIDTH{1'b0}};


reg [TDATA_WIDTH-1:0] m_axis_tdata_stage_03_ff  = {TDATA_WIDTH{1'b0}};
reg [TKEEP_WIDTH-1:0] m_axis_tkeep_stage_03_ff  = {TKEEP_WIDTH{1'b0}};
reg                   m_axis_tvalid_stage_03_ff = 1'b0; 
reg                   m_axis_tlast_stage_03_ff  = 1'b0;
reg [TID_WIDTH-1:0]   m_axis_tid_stage_03_ff    = {TID_WIDTH{1'b0}};
reg [TDEST_WIDTH-1:0] m_axis_tdest_stage_03_ff  = {TDEST_WIDTH{1'b0}};
reg [TUSER_WIDTH-1:0] m_axis_tuser_stage_03_ff  = {TUSER_WIDTH{1'b0}};



always @(posedge aclk) begin: proc_s_axis_tlast_rmap_cnt_ff
    if(~aresetn) begin
        m_axis_tvalid_stage_01_ff <= 0;
        m_axis_tvalid_stage_02_ff <= 0;
        m_axis_tvalid_stage_03_ff <= 0;
    end else if(s_axis_tready == 1) begin  
    
        m_axis_tvalid_stage_01_ff  <= s_axis_tvalid;
        m_axis_tvalid_stage_02_ff  <= m_axis_tvalid_stage_01_ff;
        m_axis_tvalid_stage_03_ff  <= m_axis_tvalid_stage_02_ff;

        if(s_axis_tvalid == 1) begin
            m_axis_tdata_stage_01_ff  <= s_axis_tdata;
            m_axis_tkeep_stage_01_ff  <= s_axis_tkeep;
            m_axis_tlast_stage_01_ff  <= s_axis_tlast;
            m_axis_tid_stage_01_ff    <= s_axis_tid;
            m_axis_tdest_stage_01_ff  <= s_axis_tdest;
            m_axis_tuser_stage_01_ff  <= s_axis_tuser;     
        end

        if(m_axis_tvalid_stage_01_ff == 1) begin
            m_axis_tdata_stage_02_ff  <= m_axis_tdata_stage_01_ff;
            m_axis_tkeep_stage_02_ff  <= m_axis_tkeep_stage_01_ff;
            m_axis_tlast_stage_02_ff  <= m_axis_tlast_stage_01_ff;
            m_axis_tid_stage_02_ff    <= m_axis_tid_stage_01_ff;
            m_axis_tdest_stage_02_ff  <= m_axis_tdest_stage_01_ff;
            m_axis_tuser_stage_02_ff  <= m_axis_tuser_stage_01_ff;     
        end

        if(m_axis_tvalid_stage_02_ff == 1) begin
            m_axis_tdata_stage_03_ff  <= m_axis_tdata_stage_02_ff;
            m_axis_tkeep_stage_03_ff  <= m_axis_tkeep_stage_02_ff;
            m_axis_tlast_stage_03_ff  <= m_axis_tlast_stage_02_ff;
            m_axis_tid_stage_03_ff    <= m_axis_tid_stage_02_ff;
            m_axis_tdest_stage_03_ff  <= m_axis_tdest_stage_02_ff;
            m_axis_tuser_stage_03_ff  <= m_axis_tuser_stage_02_ff;     
        end


    end
end


wire [TDATA_WIDTH-1:0]  out_reg_s_axis_tdata;
wire [TKEEP_WIDTH-1:0]  out_reg_s_axis_tkeep;
wire                   out_reg_s_axis_tvalid;
wire                   out_reg_s_axis_tready;
wire                   out_reg_s_axis_tlast;
wire [TID_WIDTH-1:0]    out_reg_s_axis_tid;
wire [TDEST_ENABLE-1:0]  out_reg_s_axis_tdest;
wire [TUSER_WIDTH-1:0]  out_reg_s_axis_tuser;

assign out_reg_s_axis_tdata  = m_axis_tdata_stage_03_ff;
assign out_reg_s_axis_tkeep  = TKEEP_ENABLE ? m_axis_tkeep_stage_03_ff : {TKEEP_WIDTH{1'b1}};
assign out_reg_s_axis_tvalid = m_axis_tvalid_stage_03_ff;
assign out_reg_s_axis_tlast  = TLAST_ENABLE ? m_axis_tlast_stage_03_ff : 1'b1;
assign out_reg_s_axis_tid    = TID_ENABLE   ? m_axis_tid_stage_03_ff   : {TID_WIDTH{1'b0}};
assign out_reg_s_axis_tdest  = TDEST_ENABLE ? m_axis_tdest_stage_03_ff : {TDEST_WIDTH{1'b0}};
assign out_reg_s_axis_tuser  = TUSER_ENABLE ? m_axis_tuser_stage_03_ff : {TUSER_WIDTH{1'b0}};
assign s_axis_tready = out_reg_s_axis_tready;

axis_register 
#(
    // Width of AXI stream interfaces in bits
    .TDATA_WIDTH(TDATA_WIDTH),
    // Propagate tlast signal
    .TLAST_ENABLE(TLAST_ENABLE),
    // Propagate tid signal
    .TID_ENABLE(TID_ENABLE),
    // tid signal width
    .TID_WIDTH(TID_WIDTH),
    // Propagate tdest signal
    .TDEST_ENABLE(TDEST_ENABLE),
    // tdest signal width
    .TDEST_WIDTH(TDEST_WIDTH),
    // Propagate tuser signal
    .TUSER_ENABLE(TUSER_ENABLE),
    // tuser signal width
    .TUSER_WIDTH(TUSER_WIDTH),
    // Register type
    // 0 to bypass, 1 for simple buffer, 2 for skid buffer
    .REG_TYPE(OUT_REG_TYPE)
) axis_register_inst0  
(
    .clk(aclk),
    .rst(aresetn),

    /*
     * AXI Stream input
     */
    .s_axis_tdata(out_reg_s_axis_tdata),
    .s_axis_tkeep(out_reg_s_axis_tkeep),
    .s_axis_tvalid(out_reg_s_axis_tvalid),
    .s_axis_tready(out_reg_s_axis_tready),
    .s_axis_tlast(out_reg_s_axis_tlast),
    .s_axis_tid(out_reg_s_axis_tid),
    .s_axis_tdest(out_reg_s_axis_tdest),
    .s_axis_tuser(out_reg_s_axis_tuser),

    /*
     * AXI Stream output
     */
    .m_axis_tdata(m_axis_tdata),
    .m_axis_tkeep(m_axis_tkeep),
    .m_axis_tvalid(m_axis_tvalid),
    .m_axis_tready(m_axis_tready),
    .m_axis_tlast(m_axis_tlast),
    .m_axis_tid(m_axis_tid),
    .m_axis_tdest(m_axis_tdest),
    .m_axis_tuser(m_axis_tuser)
);

endmodule

Вот в моем представлении конвейер. Данные я пробросил условно, без математических операций. TREADY имею общий для всех стадий конвейера, как глобальный EN. TVALID свой для каждой стадии обработки. Правильно ли я понимаю, TVALID я двигаю по конвейеру только по TREADY? Вопрос по поводу сброса, достаточно будет сбросить только TVALID во всех стадиях или все триггеры сбрасывать?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

По-моему самый простой вариант - промоделировать блок, сразу все станет понятно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, likeasm said:

Вот в моем представлении конвейер. Данные я пробросил условно, без математических операций. TREADY имею общий для всех стадий конвейера, как глобальный EN.

TVALID свой для каждой стадии обработки.

Спасибо, теперь понятно

Quote

Правильно ли я понимаю, TVALID я двигаю по конвейеру только по TREADY?

ИМХО нет, ЕМНП в AXI спецификации указанно что не должно быть зависимости TVALID от TREADY. Инициатором всегда выступает TVALID, а TREADY может быть в любом состоянии. Т.е. даже если задержек у устойства нет TREADY может быть в нуле без TVALID. Более того, например, axi_register.v строка 240-241 if (rst) s_axis_tready_reg <= 1'b0;

Т.е. ваш блок попадет в deadlock состояние после сброса.

Quote

Вопрос по поводу сброса, достаточно будет сбросить только TVALID во всех стадиях или все триггеры сбрасывать?

сбросить только все запросы.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Проектируете конвейер как обычно, без какого-либо хэндшейка вообще, просто input, output и cen. А потом оборачиваете его в обёртку, которая превращает его в AXI4 Stream.

Работает обёртка без "пузырей", т.е. без потери пропускной способности шины.

Есть четыре правила для конвейера, которые нужно соблюдать, они очень простые:

1. Должна быть возможность остановить конвейер в любой момент без каких-либо поломок и потерь данных. Делается это легко, с помощью clock enable:

    always @(posedge clk) begin
        if (cen) begin
            ...
        end
    end

2. Конвейер должен иметь константную задержку (глубину), которая заранее известна. Параметр PIPE_STAGES.

3. Ширина входа и выхода может быть разной, но постоянной. Это параметры PIPE_DATA_IN_WIDTH и PIPE_DATA_OUT_WIDTH.

4. Если нужно пропускать через конвейер без обработки какие-то квалификаторы данных, то используйте tuser, ширина задаётся через PIPE_QUAL_WIDTH. 


Если все правила соблюдаются, то конвейер легко превращается в AXI4 stream с этой обёрткой:

`default_nettype none
`timescale 1ps / 1ps


module axis_pipeliner #
(
    parameter integer PIPE_DATA_IN_WIDTH  = 32,
    parameter integer PIPE_DATA_OUT_WIDTH = 32,
    parameter integer PIPE_QUAL_WIDTH = 4,
    parameter integer PIPE_STAGES = 8
)
(
    input   wire                                axis_aclk,
    input   wire                                axis_aresetn,
    
    input   wire    [PIPE_DATA_IN_WIDTH - 1:0]  s_axis_tdata,
    input   wire    [PIPE_QUAL_WIDTH - 1:0]     s_axis_tuser,
    input   wire                                s_axis_tvalid,
    output  wire                                s_axis_tready,
    input   wire                                s_axis_tlast,
    
    output  wire    [PIPE_DATA_OUT_WIDTH - 1:0] m_axis_tdata,
    output  wire    [PIPE_QUAL_WIDTH - 1:0]     m_axis_tuser,
    output  wire                                m_axis_tvalid,
    input   wire                                m_axis_tready,
    output  wire                                m_axis_tlast,
    
    output  wire                                pipe_cen,
    output  wire    [PIPE_DATA_IN_WIDTH - 1:0]  pipe_in_data,
    input   wire    [PIPE_DATA_OUT_WIDTH - 1:0] pipe_out_data
);
    
/*-------------------------------------------------------------------------------------------------------------------------------------*/
    
    reg     [PIPE_STAGES - 1:0]  tvalid_pipe = {PIPE_STAGES{1'b0}};
    reg     [PIPE_STAGES - 1:0]  tlast_pipe  = {PIPE_STAGES{1'b0}};
    
    
    always @(posedge axis_aclk) begin
        if (~axis_aresetn) begin
            tvalid_pipe <= {PIPE_STAGES{1'b0}};
            tlast_pipe  <= {PIPE_STAGES{1'b0}};
        end else begin
            if (pipe_cen) begin
                tvalid_pipe <= (PIPE_STAGES > 1)? {tvalid_pipe[PIPE_STAGES - 2:0], s_axis_tvalid} : s_axis_tvalid;
                tlast_pipe  <= (PIPE_STAGES > 1)? {tlast_pipe[PIPE_STAGES - 2:0],  s_axis_tlast}  : s_axis_tlast;
            end
        end
    end
    
    
    assign s_axis_tready = s_axis_tvalid & (~tvalid_pipe[PIPE_STAGES - 1] | m_axis_tready);
    assign pipe_cen      = s_axis_tready |  ~tvalid_pipe[PIPE_STAGES - 1] | m_axis_tready;
    assign pipe_in_data  = s_axis_tdata;
    
    assign m_axis_tdata  = pipe_out_data;
    assign m_axis_tvalid = tvalid_pipe[PIPE_STAGES - 1];
    assign m_axis_tlast  = tlast_pipe[PIPE_STAGES - 1];
    
/*-------------------------------------------------------------------------------------------------------------------------------------*/

    genvar i;
    generate
        for (i = 0; i < PIPE_QUAL_WIDTH; i = i + 1) begin: loop
            reg [PIPE_STAGES - 1:0] pipe_gen = {PIPE_STAGES{1'b0}};
            
            always @(posedge axis_aclk) begin
                if (pipe_cen) begin
                    pipe_gen <= (PIPE_STAGES > 1)? {pipe_gen[PIPE_STAGES - 2:0], s_axis_tuser[i]} : s_axis_tuser[i];
                end
            end
            
            assign m_axis_tuser[i] = pipe_gen[PIPE_STAGES - 1];
        end
    endgenerate
    
endmodule

/*-------------------------------------------------------------------------------------------------------------------------------------*/

`default_nettype wire

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

8 hours ago, likeasm said:

Вы TREADY не триггеруете или это в другом модуле?

Да, как видите, tready не зарегистрирован, хотя всё остальное на AXIS выходит с триггеров. Я очень хотел зарегистрировать все выходы, но красиво сделать это не получалось. С другой стороны, всегда можно поставить AXIS register slice и не усложнять этот модуль, т.к. так или иначе в попытке зарегистрировать всё и вся придётся в этом модуле в том или ином виде сделать всё тот же AXIS register slice.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...