likeasm 0 10 июня, 2021 Опубликовано 10 июня, 2021 · Жалоба Добрый вечер, коллеги. Захотелось мне создать свой модуль конвейерной обработки пакетов по интерфейсу 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 соответствующей ступени конвейера? Может есть у кого пример построения такого конвейера или ссылка на чтиво по данной теме? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
andrew_b 14 11 июня, 2021 Опубликовано 11 июня, 2021 · Жалоба Всякое продвижение данных по конвейеру происходит тогда и только тогда, когда и master_valid, и slave_ready стоят в единице: if (master_valid = '1' and slave_ready = '1') then slave_data_in <= master_data_out; endif; Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
des00 25 11 июня, 2021 Опубликовано 11 июня, 2021 · Жалоба 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 крайне желательно прочитать пару раз, там подробно разобраны эти моменты. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
likeasm 0 11 июня, 2021 Опубликовано 11 июня, 2021 · Жалоба 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 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
des00 25 11 июня, 2021 Опубликовано 11 июня, 2021 · Жалоба 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 не позволяет достичь высокой тактовой. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
likeasm 0 15 июня, 2021 Опубликовано 15 июня, 2021 · Жалоба `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 во всех стадиях или все триггеры сбрасывать? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
attaboy 0 15 июня, 2021 Опубликовано 15 июня, 2021 · Жалоба По-моему самый простой вариант - промоделировать блок, сразу все станет понятно. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
des00 25 15 июня, 2021 Опубликовано 15 июня, 2021 · Жалоба 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 во всех стадиях или все триггеры сбрасывать? сбросить только все запросы. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Inanity 1 15 июня, 2021 Опубликовано 15 июня, 2021 · Жалоба Есть решение по-лучше, чем каждый раз проектировать конвейер на 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 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
likeasm 0 16 июня, 2021 Опубликовано 16 июня, 2021 · Жалоба Вы TREADY не триггеруете или это в другом модуле? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Inanity 1 16 июня, 2021 Опубликовано 16 июня, 2021 · Жалоба 8 hours ago, likeasm said: Вы TREADY не триггеруете или это в другом модуле? Да, как видите, tready не зарегистрирован, хотя всё остальное на AXIS выходит с триггеров. Я очень хотел зарегистрировать все выходы, но красиво сделать это не получалось. С другой стороны, всегда можно поставить AXIS register slice и не усложнять этот модуль, т.к. так или иначе в попытке зарегистрировать всё и вся придётся в этом модуле в том или ином виде сделать всё тот же AXIS register slice. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться