diff --git a/rtl/axis_cobs_decode.v b/rtl/axis_cobs_decode.v new file mode 100644 index 000000000..b95639329 --- /dev/null +++ b/rtl/axis_cobs_decode.v @@ -0,0 +1,328 @@ +/* + +Copyright (c) 2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * AXI4-Stream consistent overhead byte stuffing (COBS) decoder + */ +module axis_cobs_decode +( + input wire clk, + input wire rst, + + /* + * AXI input + */ + input wire [7:0] input_axis_tdata, + input wire input_axis_tvalid, + output wire input_axis_tready, + input wire input_axis_tlast, + input wire input_axis_tuser, + + /* + * AXI output + */ + output wire [7:0] output_axis_tdata, + output wire output_axis_tvalid, + input wire output_axis_tready, + output wire output_axis_tlast, + output wire output_axis_tuser +); + +// state register +localparam [1:0] + STATE_IDLE = 2'd0, + STATE_SEGMENT = 2'd1, + STATE_NEXT_SEGMENT = 2'd2; + +reg [1:0] state_reg = STATE_IDLE, state_next; + +reg [7:0] count_reg = 8'd0, count_next; +reg suppress_zero_reg = 1'b0, suppress_zero_next; + +reg [7:0] temp_tdata_reg = 8'd0, temp_tdata_next; +reg temp_tvalid_reg = 1'b0, temp_tvalid_next; + +// internal datapath +reg [7:0] output_axis_tdata_int; +reg output_axis_tvalid_int; +reg output_axis_tready_int_reg = 1'b0; +reg output_axis_tlast_int; +reg output_axis_tuser_int; +wire output_axis_tready_int_early; + +reg input_axis_tready_reg = 1'b0, input_axis_tready_next; + +assign input_axis_tready = input_axis_tready_reg; + +always @* begin + state_next = STATE_IDLE; + + count_next = count_reg; + suppress_zero_next = suppress_zero_reg; + + temp_tdata_next = temp_tdata_reg; + temp_tvalid_next = temp_tvalid_reg; + + output_axis_tdata_int = 8'd0; + output_axis_tvalid_int = 1'b0; + output_axis_tlast_int = 1'b0; + output_axis_tuser_int = 1'b0; + + input_axis_tready_next = 1'b0; + + case (state_reg) + STATE_IDLE: begin + // idle state + input_axis_tready_next = output_axis_tready_int_early | ~temp_tvalid_reg; + + // output final word + output_axis_tdata_int = temp_tdata_reg; + output_axis_tvalid_int = temp_tvalid_reg; + output_axis_tlast_int = temp_tvalid_reg; + temp_tvalid_next = temp_tvalid_reg & ~output_axis_tready_int_reg; + + if (input_axis_tready & input_axis_tvalid) begin + // valid input data + // skip any leading zeros + if (input_axis_tdata != 8'd0) begin + // store count value and zero suppress + count_next = input_axis_tdata-1; + suppress_zero_next = (input_axis_tdata == 8'd255); + input_axis_tready_next = output_axis_tready_int_early; + if (input_axis_tdata == 8'd1) begin + // next byte will be count value + state_next = STATE_NEXT_SEGMENT; + end else begin + // next byte will be data + state_next = STATE_SEGMENT; + end + end else begin + state_next = STATE_IDLE; + end + end else begin + state_next = STATE_IDLE; + end + end + STATE_SEGMENT: begin + // receive segment + input_axis_tready_next = output_axis_tready_int_early; + + if (input_axis_tready & input_axis_tvalid) begin + // valid input data + // store in temp register + temp_tdata_next = input_axis_tdata; + temp_tvalid_next = 1'b1; + // move temp to output + output_axis_tdata_int = temp_tdata_reg; + output_axis_tvalid_int = temp_tvalid_reg; + // decrement count + count_next = count_reg - 1; + if (input_axis_tdata == 8'd0) begin + // got a zero byte in a frame - mark it as an error and re-sync + temp_tvalid_next = 1'b0; + output_axis_tvalid_int = 1'b1; + output_axis_tuser_int = 1'b1; + output_axis_tlast_int = 1'b1; + input_axis_tready_next = 1'b1; + state_next = STATE_IDLE; + end else if (input_axis_tlast) begin + // end of frame + if (count_reg == 8'd1 && ~input_axis_tuser) begin + // end of frame indication at correct time, go to idle to output final byte + state_next = STATE_IDLE; + end else begin + // end of frame indication at invalid time or tuser assert, so mark as an error and re-sync + temp_tvalid_next = 1'b0; + output_axis_tvalid_int = 1'b1; + output_axis_tuser_int = 1'b1; + output_axis_tlast_int = 1'b1; + input_axis_tready_next = 1'b1; + state_next = STATE_IDLE; + end + end else if (count_reg == 8'd1) begin + // next byte will be count value + state_next = STATE_NEXT_SEGMENT; + end else begin + // next byte will be data + state_next = STATE_SEGMENT; + end + end else begin + state_next = STATE_SEGMENT; + end + end + STATE_NEXT_SEGMENT: begin + // next segment + input_axis_tready_next = output_axis_tready_int_early; + + if (input_axis_tready & input_axis_tvalid) begin + // valid input data + // store zero in temp if not suppressed + temp_tdata_next = 8'd0; + temp_tvalid_next = ~suppress_zero_reg; + // move temp to output + output_axis_tdata_int = temp_tdata_reg; + output_axis_tvalid_int = temp_tvalid_reg; + if (input_axis_tdata == 8'd0) begin + // got a zero byte delineating the end of the frame, so mark as such and re-sync + temp_tvalid_next = 1'b0; + output_axis_tuser_int = input_axis_tuser; + output_axis_tlast_int = 1'b1; + input_axis_tready_next = 1'b1; + state_next = STATE_IDLE; + end else if (input_axis_tlast) begin + if (input_axis_tdata == 8'd1 && ~input_axis_tuser) begin + // end of frame indication at correct time, go to idle to output final byte + state_next = STATE_IDLE; + end else begin + // end of frame indication at invalid time or tuser assert, so mark as an error and re-sync + temp_tvalid_next = 1'b0; + output_axis_tvalid_int = 1'b1; + output_axis_tuser_int = 1'b1; + output_axis_tlast_int = 1'b1; + input_axis_tready_next = 1'b1; + state_next = STATE_IDLE; + end + end else begin + // otherwise, store count value and zero suppress + count_next = input_axis_tdata-1; + suppress_zero_next = (input_axis_tdata == 8'd255); + input_axis_tready_next = output_axis_tready_int_early; + if (input_axis_tdata == 8'd1) begin + // next byte will be count value + state_next = STATE_NEXT_SEGMENT; + end else begin + // next byte will be data + state_next = STATE_SEGMENT; + end + end + end else begin + state_next = STATE_NEXT_SEGMENT; + end + end + endcase +end + +always @(posedge clk) begin + if (rst) begin + state_reg <= STATE_IDLE; + temp_tvalid_reg <= 1'b0; + input_axis_tready_reg <= 1'b0; + end else begin + state_reg <= state_next; + temp_tvalid_reg <= temp_tvalid_next; + input_axis_tready_reg <= input_axis_tready_next; + end + + temp_tdata_reg <= temp_tdata_next; + + count_reg <= count_next; + suppress_zero_reg <= suppress_zero_next; +end + +// output datapath logic +reg [7:0] output_axis_tdata_reg = 8'd0; +reg output_axis_tvalid_reg = 1'b0, output_axis_tvalid_next; +reg output_axis_tlast_reg = 1'b0; +reg output_axis_tuser_reg = 1'b0; + +reg [7:0] temp_axis_tdata_reg = 8'd0; +reg temp_axis_tvalid_reg = 1'b0, temp_axis_tvalid_next; +reg temp_axis_tlast_reg = 1'b0; +reg temp_axis_tuser_reg = 1'b0; + +// datapath control +reg store_axis_int_to_output; +reg store_axis_int_to_temp; +reg store_axis_temp_to_output; + +assign output_axis_tdata = output_axis_tdata_reg; +assign output_axis_tvalid = output_axis_tvalid_reg; +assign output_axis_tlast = output_axis_tlast_reg; +assign output_axis_tuser = output_axis_tuser_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +assign output_axis_tready_int_early = output_axis_tready | (~temp_axis_tvalid_reg & (~output_axis_tvalid_reg | ~output_axis_tvalid_int)); + +always @* begin + // transfer sink ready state to source + output_axis_tvalid_next = output_axis_tvalid_reg; + temp_axis_tvalid_next = temp_axis_tvalid_reg; + + store_axis_int_to_output = 1'b0; + store_axis_int_to_temp = 1'b0; + store_axis_temp_to_output = 1'b0; + + if (output_axis_tready_int_reg) begin + // input is ready + if (output_axis_tready | ~output_axis_tvalid_reg) begin + // output is ready or currently not valid, transfer data to output + output_axis_tvalid_next = output_axis_tvalid_int; + store_axis_int_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_axis_tvalid_next = output_axis_tvalid_int; + store_axis_int_to_temp = 1'b1; + end + end else if (output_axis_tready) begin + // input is not ready, but output is ready + output_axis_tvalid_next = temp_axis_tvalid_reg; + temp_axis_tvalid_next = 1'b0; + store_axis_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + if (rst) begin + output_axis_tvalid_reg <= 1'b0; + output_axis_tready_int_reg <= 1'b0; + temp_axis_tvalid_reg <= 1'b0; + end else begin + output_axis_tvalid_reg <= output_axis_tvalid_next; + output_axis_tready_int_reg <= output_axis_tready_int_early; + temp_axis_tvalid_reg <= temp_axis_tvalid_next; + end + + // datapath + if (store_axis_int_to_output) begin + output_axis_tdata_reg <= output_axis_tdata_int; + output_axis_tlast_reg <= output_axis_tlast_int; + output_axis_tuser_reg <= output_axis_tuser_int; + end else if (store_axis_temp_to_output) begin + output_axis_tdata_reg <= temp_axis_tdata_reg; + output_axis_tlast_reg <= temp_axis_tlast_reg; + output_axis_tuser_reg <= temp_axis_tuser_reg; + end + + if (store_axis_int_to_temp) begin + temp_axis_tdata_reg <= output_axis_tdata_int; + temp_axis_tlast_reg <= output_axis_tlast_int; + temp_axis_tuser_reg <= output_axis_tuser_int; + end +end + +endmodule diff --git a/rtl/axis_cobs_encode.v b/rtl/axis_cobs_encode.v new file mode 100644 index 000000000..390e72a80 --- /dev/null +++ b/rtl/axis_cobs_encode.v @@ -0,0 +1,473 @@ +/* + +Copyright (c) 2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * AXI4-Stream consistent overhead byte stuffing (COBS) encoder + */ +module axis_cobs_encode # +( + // append zero for in band framing + parameter APPEND_ZERO = 1 +) +( + input wire clk, + input wire rst, + + /* + * AXI input + */ + input wire [7:0] input_axis_tdata, + input wire input_axis_tvalid, + output wire input_axis_tready, + input wire input_axis_tlast, + input wire input_axis_tuser, + + /* + * AXI output + */ + output wire [7:0] output_axis_tdata, + output wire output_axis_tvalid, + input wire output_axis_tready, + output wire output_axis_tlast, + output wire output_axis_tuser +); + +// state register +localparam [1:0] + INPUT_STATE_IDLE = 2'd0, + INPUT_STATE_SEGMENT = 2'd1, + INPUT_STATE_FINAL_ZERO = 2'd2, + INPUT_STATE_APPEND_ZERO = 2'd3; + +reg [1:0] input_state_reg = INPUT_STATE_IDLE, input_state_next; + +localparam [0:0] + OUTPUT_STATE_IDLE = 1'd0, + OUTPUT_STATE_SEGMENT = 1'd1; + +reg [0:0] output_state_reg = OUTPUT_STATE_IDLE, output_state_next; + +reg [7:0] input_count_reg = 8'd0, input_count_next; +reg [7:0] output_count_reg = 8'd0, output_count_next; +reg fail_frame_reg = 1'b0, fail_frame_next; + +// internal datapath +reg [7:0] output_axis_tdata_int; +reg output_axis_tvalid_int; +reg output_axis_tready_int_reg = 1'b0; +reg output_axis_tlast_int; +reg output_axis_tuser_int; +wire output_axis_tready_int_early; + +reg input_axis_tready_mask; + +assign input_axis_tready = code_fifo_in_tready & data_fifo_in_tready & input_axis_tready_mask; + +reg [7:0] code_fifo_in_tdata; +reg code_fifo_in_tvalid; +reg code_fifo_in_tlast; +reg code_fifo_in_tuser; +wire code_fifo_in_tready; + +wire [7:0] code_fifo_out_tdata; +wire code_fifo_out_tvalid; +wire code_fifo_out_tlast; +wire code_fifo_out_tuser; +reg code_fifo_out_tready; + +axis_fifo #( + .ADDR_WIDTH(8), + .DATA_WIDTH(8) +) +code_fifo_inst ( + .clk(clk), + .rst(rst), + // AXI input + .input_axis_tdata(code_fifo_in_tdata), + .input_axis_tvalid(code_fifo_in_tvalid), + .input_axis_tready(code_fifo_in_tready), + .input_axis_tlast(code_fifo_in_tlast), + .input_axis_tuser(code_fifo_in_tuser), + // AXI output + .output_axis_tdata(code_fifo_out_tdata), + .output_axis_tvalid(code_fifo_out_tvalid), + .output_axis_tready(code_fifo_out_tready), + .output_axis_tlast(code_fifo_out_tlast), + .output_axis_tuser(code_fifo_out_tuser) +); + +reg [7:0] data_fifo_in_tdata; +reg data_fifo_in_tvalid; +reg data_fifo_in_tlast; +wire data_fifo_in_tready; + +wire [7:0] data_fifo_out_tdata; +wire data_fifo_out_tvalid; +wire data_fifo_out_tlast; +reg data_fifo_out_tready; + +axis_fifo #( + .ADDR_WIDTH(8), + .DATA_WIDTH(8) +) +data_fifo_inst ( + .clk(clk), + .rst(rst), + // AXI input + .input_axis_tdata(data_fifo_in_tdata), + .input_axis_tvalid(data_fifo_in_tvalid), + .input_axis_tready(data_fifo_in_tready), + .input_axis_tlast(data_fifo_in_tlast), + .input_axis_tuser(1'b0), + // AXI output + .output_axis_tdata(data_fifo_out_tdata), + .output_axis_tvalid(data_fifo_out_tvalid), + .output_axis_tready(data_fifo_out_tready), + .output_axis_tlast(data_fifo_out_tlast), + .output_axis_tuser() +); + +always @* begin + input_state_next = INPUT_STATE_IDLE; + + input_count_next = input_count_reg; + + fail_frame_next = fail_frame_reg; + + input_axis_tready_mask = 1'b0; + + code_fifo_in_tdata = 8'd0; + code_fifo_in_tvalid = 1'b0; + code_fifo_in_tlast = 1'b0; + code_fifo_in_tuser = 1'b0; + + data_fifo_in_tdata = input_axis_tdata; + data_fifo_in_tvalid = 1'b0; + data_fifo_in_tlast = 1'b0; + + case (input_state_reg) + INPUT_STATE_IDLE: begin + // idle state + input_axis_tready_mask = 1'b1; + fail_frame_next = 1'b0; + + if (input_axis_tready & input_axis_tvalid) begin + // valid input data + + if (input_axis_tdata == 8'd0 || (input_axis_tlast & input_axis_tuser)) begin + // got a zero or propagated error, so store a zero code + code_fifo_in_tdata = 8'd1; + code_fifo_in_tvalid = 1'b1; + if (input_axis_tlast) begin + // last byte, so close out the frame + fail_frame_next = input_axis_tuser; + input_state_next = INPUT_STATE_FINAL_ZERO; + end else begin + // return to idle to await next segment + input_state_next = INPUT_STATE_IDLE; + end + end else begin + // got something other than a zero, so store it and init the segment counter + input_count_next = 8'd2; + data_fifo_in_tdata = input_axis_tdata; + data_fifo_in_tvalid = 1'b1; + if (input_axis_tlast) begin + // last byte, so store the code and close out the frame + code_fifo_in_tdata = 8'd2; + code_fifo_in_tvalid = 1'b1; + if (APPEND_ZERO) begin + // zero frame mode, need to add a zero code to end the frame + input_state_next = INPUT_STATE_APPEND_ZERO; + end else begin + // normal frame mode, close out the frame + data_fifo_in_tlast = 1'b1; + input_state_next = INPUT_STATE_IDLE; + end + end else begin + // await more segment data + input_state_next = INPUT_STATE_SEGMENT; + end + end + end else begin + input_state_next = INPUT_STATE_IDLE; + end + end + INPUT_STATE_SEGMENT: begin + // encode segment + input_axis_tready_mask = 1'b1; + fail_frame_next = 1'b0; + + if (input_axis_tready & input_axis_tvalid) begin + // valid input data + + if (input_axis_tdata == 8'd0 || (input_axis_tlast & input_axis_tuser)) begin + // got a zero or propagated error, so store the code + code_fifo_in_tdata = input_count_reg; + code_fifo_in_tvalid = 1'b1; + if (input_axis_tlast) begin + // last byte, so close out the frame + fail_frame_next = input_axis_tuser; + input_state_next = INPUT_STATE_FINAL_ZERO; + end else begin + // return to idle to await next segment + input_state_next = INPUT_STATE_IDLE; + end + end else begin + // got something other than a zero, so store it and increment the segment counter + input_count_next = input_count_reg+1; + data_fifo_in_tdata = input_axis_tdata; + data_fifo_in_tvalid = 1'b1; + if (input_count_reg == 8'd254) begin + // 254 bytes in frame, so dump and reset counter + code_fifo_in_tdata = input_count_reg+1; + code_fifo_in_tvalid = 1'b1; + input_count_next = 8'd1; + end + if (input_axis_tlast) begin + // last byte, so store the code and close out the frame + code_fifo_in_tdata = input_count_reg+1; + code_fifo_in_tvalid = 1'b1; + if (APPEND_ZERO) begin + // zero frame mode, need to add a zero code to end the frame + input_state_next = INPUT_STATE_APPEND_ZERO; + end else begin + // normal frame mode, close out the frame + data_fifo_in_tlast = 1'b1; + input_state_next = INPUT_STATE_IDLE; + end + end else begin + // await more segment data + input_state_next = INPUT_STATE_SEGMENT; + end + end + end else begin + input_state_next = INPUT_STATE_SEGMENT; + end + end + INPUT_STATE_FINAL_ZERO: begin + // final zero code required + input_axis_tready_mask = 1'b0; + + if (code_fifo_in_tready) begin + // push a zero code and close out frame + if (fail_frame_reg) begin + code_fifo_in_tdata = 8'd2; + code_fifo_in_tuser = 1'b1; + end else begin + code_fifo_in_tdata = 8'd1; + end + code_fifo_in_tvalid = 1'b1; + if (APPEND_ZERO) begin + // zero frame mode, need to add a zero code to end the frame + input_state_next = INPUT_STATE_APPEND_ZERO; + end else begin + // normal frame mode, close out the frame + code_fifo_in_tlast = 1'b1; + fail_frame_next = 1'b0; + input_state_next = INPUT_STATE_IDLE; + end + end else begin + input_state_next = INPUT_STATE_FINAL_ZERO; + end + end + INPUT_STATE_APPEND_ZERO: begin + // append zero for zero framing + input_axis_tready_mask = 1'b0; + + if (code_fifo_in_tready) begin + // push frame termination code and close out frame + code_fifo_in_tdata = 8'd0; + code_fifo_in_tlast = 1'b1; + code_fifo_in_tuser = fail_frame_reg; + code_fifo_in_tvalid = 1'b1; + fail_frame_next = 1'b0; + input_state_next = INPUT_STATE_IDLE; + end else begin + input_state_next = INPUT_STATE_APPEND_ZERO; + end + end + endcase +end + +always @* begin + output_state_next = OUTPUT_STATE_IDLE; + + output_count_next = output_count_reg; + + output_axis_tdata_int = 8'd0; + output_axis_tvalid_int = 1'b0; + output_axis_tlast_int = 1'b0; + output_axis_tuser_int = 1'b0; + + code_fifo_out_tready = 1'b0; + + data_fifo_out_tready = 1'b0; + + case (output_state_reg) + OUTPUT_STATE_IDLE: begin + // idle state + + if (output_axis_tready_int_reg & code_fifo_out_tvalid) begin + // transfer out code byte and load counter + output_axis_tdata_int = code_fifo_out_tdata; + output_axis_tlast_int = code_fifo_out_tlast; + output_axis_tuser_int = code_fifo_out_tuser & code_fifo_out_tlast; + output_count_next = code_fifo_out_tdata-1; + output_axis_tvalid_int = 1'b1; + code_fifo_out_tready = 1'b1; + if (code_fifo_out_tdata == 8'd0 || code_fifo_out_tdata == 8'd1 || code_fifo_out_tuser) begin + // frame termination and zero codes will be followed by codes + output_state_next = OUTPUT_STATE_IDLE; + end else begin + // transfer out data + output_state_next = OUTPUT_STATE_SEGMENT; + end + end else begin + output_state_next = OUTPUT_STATE_IDLE; + end + end + OUTPUT_STATE_SEGMENT: begin + // segment output + + if (output_axis_tready_int_reg & data_fifo_out_tvalid) begin + // transfer out data byte and decrement counter + output_axis_tdata_int = data_fifo_out_tdata; + output_axis_tlast_int = data_fifo_out_tlast; + output_count_next = output_count_reg - 1; + output_axis_tvalid_int = 1'b1; + data_fifo_out_tready = 1'b1; + if (output_count_reg == 1'b1) begin + // done with segment, get a code byte next + output_state_next = OUTPUT_STATE_IDLE; + end else begin + // more data to transfer + output_state_next = OUTPUT_STATE_SEGMENT; + end + end else begin + output_state_next = OUTPUT_STATE_SEGMENT; + end + end + endcase +end + +always @(posedge clk) begin + if (rst) begin + input_state_reg <= INPUT_STATE_IDLE; + output_state_reg <= OUTPUT_STATE_IDLE; + end else begin + input_state_reg <= input_state_next; + output_state_reg <= output_state_next; + end + + input_count_reg <= input_count_next; + output_count_reg <= output_count_next; + fail_frame_reg <= fail_frame_next; +end + +// output datapath logic +reg [7:0] output_axis_tdata_reg = 8'd0; +reg output_axis_tvalid_reg = 1'b0, output_axis_tvalid_next; +reg output_axis_tlast_reg = 1'b0; +reg output_axis_tuser_reg = 1'b0; + +reg [7:0] temp_axis_tdata_reg = 8'd0; +reg temp_axis_tvalid_reg = 1'b0, temp_axis_tvalid_next; +reg temp_axis_tlast_reg = 1'b0; +reg temp_axis_tuser_reg = 1'b0; + +// datapath control +reg store_axis_int_to_output; +reg store_axis_int_to_temp; +reg store_axis_temp_to_output; + +assign output_axis_tdata = output_axis_tdata_reg; +assign output_axis_tvalid = output_axis_tvalid_reg; +assign output_axis_tlast = output_axis_tlast_reg; +assign output_axis_tuser = output_axis_tuser_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +assign output_axis_tready_int_early = output_axis_tready | (~temp_axis_tvalid_reg & (~output_axis_tvalid_reg | ~output_axis_tvalid_int)); + +always @* begin + // transfer sink ready state to source + output_axis_tvalid_next = output_axis_tvalid_reg; + temp_axis_tvalid_next = temp_axis_tvalid_reg; + + store_axis_int_to_output = 1'b0; + store_axis_int_to_temp = 1'b0; + store_axis_temp_to_output = 1'b0; + + if (output_axis_tready_int_reg) begin + // input is ready + if (output_axis_tready | ~output_axis_tvalid_reg) begin + // output is ready or currently not valid, transfer data to output + output_axis_tvalid_next = output_axis_tvalid_int; + store_axis_int_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_axis_tvalid_next = output_axis_tvalid_int; + store_axis_int_to_temp = 1'b1; + end + end else if (output_axis_tready) begin + // input is not ready, but output is ready + output_axis_tvalid_next = temp_axis_tvalid_reg; + temp_axis_tvalid_next = 1'b0; + store_axis_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + if (rst) begin + output_axis_tvalid_reg <= 1'b0; + output_axis_tready_int_reg <= 1'b0; + temp_axis_tvalid_reg <= 1'b0; + end else begin + output_axis_tvalid_reg <= output_axis_tvalid_next; + output_axis_tready_int_reg <= output_axis_tready_int_early; + temp_axis_tvalid_reg <= temp_axis_tvalid_next; + end + + // datapath + if (store_axis_int_to_output) begin + output_axis_tdata_reg <= output_axis_tdata_int; + output_axis_tlast_reg <= output_axis_tlast_int; + output_axis_tuser_reg <= output_axis_tuser_int; + end else if (store_axis_temp_to_output) begin + output_axis_tdata_reg <= temp_axis_tdata_reg; + output_axis_tlast_reg <= temp_axis_tlast_reg; + output_axis_tuser_reg <= temp_axis_tuser_reg; + end + + if (store_axis_int_to_temp) begin + temp_axis_tdata_reg <= output_axis_tdata_int; + temp_axis_tlast_reg <= output_axis_tlast_int; + temp_axis_tuser_reg <= output_axis_tuser_int; + end +end + +endmodule diff --git a/tb/test_axis_cobs_decode.py b/tb/test_axis_cobs_decode.py new file mode 100755 index 000000000..d8ef20f44 --- /dev/null +++ b/tb/test_axis_cobs_decode.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from myhdl import * +import os + +try: + from queue import Queue +except ImportError: + from Queue import Queue + +import axis_ep + +module = 'axis_cobs_decode' +testbench = 'test_axis_cobs_decode' + +srcs = [] + +srcs.append("../rtl/%s.v" % module) +srcs.append("%s.v" % testbench) + +src = ' '.join(srcs) + +build_cmd = "iverilog -o %s.vvp %s" % (testbench, src) + +def dut_axis_cobs_decode(clk, + rst, + current_test, + input_axis_tdata, + input_axis_tvalid, + input_axis_tready, + input_axis_tlast, + input_axis_tuser, + output_axis_tdata, + output_axis_tvalid, + output_axis_tready, + output_axis_tlast, + output_axis_tuser): + + if os.system(build_cmd): + raise Exception("Error running build command") + return Cosimulation("vvp -m myhdl %s.vvp -lxt2" % testbench, + clk=clk, + rst=rst, + current_test=current_test, + input_axis_tdata=input_axis_tdata, + input_axis_tvalid=input_axis_tvalid, + input_axis_tready=input_axis_tready, + input_axis_tlast=input_axis_tlast, + input_axis_tuser=input_axis_tuser, + output_axis_tdata=output_axis_tdata, + output_axis_tvalid=output_axis_tvalid, + output_axis_tready=output_axis_tready, + output_axis_tlast=output_axis_tlast, + output_axis_tuser=output_axis_tuser) + +def cobs_encode(block): + block = bytearray(block) + enc = bytearray() + + seg = bytearray() + code = 1 + + new_data = True + + for b in block: + if b == 0: + enc.append(code) + enc.extend(seg) + code = 1 + seg = bytearray() + new_data = True + else: + code += 1 + seg.append(b) + new_data = True + if code == 255: + enc.append(code) + enc.extend(seg) + code = 1 + seg = bytearray() + new_data = False + + if new_data: + enc.append(code) + enc.extend(seg) + + return bytes(enc) + +def cobs_decode(block): + block = bytearray(block) + dec = bytearray() + + it = iter(bytearray(block)) + code = 0 + + i = 0 + + if 0 in block: + return None + + while i < len(block): + code = block[i] + i += 1 + if i+code-1 > len(block): + return None + for k in range(code-1): + dec.append(block[i]) + i += 1 + if code < 255 and i < len(block): + dec.append(0) + + return bytes(dec) + +def prbs31(state = 0x7fffffff): + while True: + for i in range(8): + if bool(state & 0x08000000) ^ bool(state & 0x40000000): + state = ((state & 0x3fffffff) << 1) | 1 + else: + state = (state & 0x3fffffff) << 1 + yield state & 0xff + +def bench(): + + # Parameters + + + # Inputs + clk = Signal(bool(0)) + rst = Signal(bool(0)) + current_test = Signal(intbv(0)[8:]) + + input_axis_tdata = Signal(intbv(0)[8:]) + input_axis_tvalid = Signal(bool(0)) + input_axis_tlast = Signal(bool(0)) + input_axis_tuser = Signal(bool(0)) + output_axis_tready = Signal(bool(0)) + + # Outputs + input_axis_tready = Signal(bool(0)) + output_axis_tdata = Signal(intbv(0)[8:]) + output_axis_tvalid = Signal(bool(0)) + output_axis_tlast = Signal(bool(0)) + output_axis_tuser = Signal(bool(0)) + + # sources and sinks + source_queue = Queue() + source_pause = Signal(bool(0)) + sink_queue = Queue() + sink_pause = Signal(bool(0)) + + source = axis_ep.AXIStreamSource(clk, + rst, + tdata=input_axis_tdata, + tvalid=input_axis_tvalid, + tready=input_axis_tready, + tlast=input_axis_tlast, + tuser=input_axis_tuser, + fifo=source_queue, + pause=source_pause, + name='source') + + sink = axis_ep.AXIStreamSink(clk, + rst, + tdata=output_axis_tdata, + tvalid=output_axis_tvalid, + tready=output_axis_tready, + tlast=output_axis_tlast, + tuser=output_axis_tuser, + fifo=sink_queue, + pause=sink_pause, + name='sink') + + # DUT + dut = dut_axis_cobs_decode(clk, + rst, + current_test, + input_axis_tdata, + input_axis_tvalid, + input_axis_tready, + input_axis_tlast, + input_axis_tuser, + output_axis_tdata, + output_axis_tvalid, + output_axis_tready, + output_axis_tlast, + output_axis_tuser) + + @always(delay(4)) + def clkgen(): + clk.next = not clk + + def wait_normal(): + i = 4 + while i > 0: + i = max(0, i-1) + if input_axis_tvalid or output_axis_tvalid or not source_queue.empty(): + i = 4 + yield clk.posedge + + def wait_pause_source(): + i = 2 + while i > 0: + i = max(0, i-1) + if input_axis_tvalid or output_axis_tvalid or not source_queue.empty(): + i = 2 + source_pause.next = True + yield clk.posedge + yield clk.posedge + yield clk.posedge + source_pause.next = False + yield clk.posedge + + def wait_pause_sink(): + i = 2 + while i > 0: + i = max(0, i-1) + if input_axis_tvalid or output_axis_tvalid or not source_queue.empty(): + i = 2 + sink_pause.next = True + yield clk.posedge + yield clk.posedge + yield clk.posedge + sink_pause.next = False + yield clk.posedge + + @instance + def check(): + yield delay(100) + yield clk.posedge + rst.next = 1 + yield clk.posedge + rst.next = 0 + yield clk.posedge + yield delay(100) + yield clk.posedge + + # testbench stimulus + + for payload_len in list(range(1,33))+list(range(252,261))+[512,1024]: + gen = prbs31() + for block in [bytearray([0]*payload_len), + bytearray([k%255+1 for k in range(payload_len)]), + b'\x00'+bytearray([k%255+1 for k in range(payload_len)])+b'\x00', + bytearray([next(gen) for i in range(payload_len)])]: + + yield clk.posedge + print("test 1: test packet, length %d" % payload_len) + current_test.next = 1 + + enc = cobs_encode(block) + + test_frame = axis_ep.AXIStreamFrame(enc) + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + yield clk.posedge + print("test 2: test packet, length %d, zero frame" % payload_len) + current_test.next = 2 + + enc = cobs_encode(block) + + test_frame = axis_ep.AXIStreamFrame(enc+b'\x00') + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + yield clk.posedge + print("test 3: back-to-back packets, length %d" % payload_len) + current_test.next = 3 + + test_frame2 = axis_ep.AXIStreamFrame(enc) + test_frame1 = axis_ep.AXIStreamFrame(enc) + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame1) + source_queue.put(test_frame2) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == block + assert not rx_frame.user[-1] + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + yield clk.posedge + print("test 4: back-to-back packets, length %d, zero frame" % payload_len) + current_test.next = 4 + + test_frame = axis_ep.AXIStreamFrame(enc+b'\x00'+enc+b'\x00') + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == block + assert not rx_frame.user[-1] + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + yield clk.posedge + print("test 5: tuser assert and bad frame, length %d" % payload_len) + current_test.next = 5 + + test_frame1 = axis_ep.AXIStreamFrame(enc) + test_frame2 = axis_ep.AXIStreamFrame(enc+b'\x02') + test_frame3 = axis_ep.AXIStreamFrame(enc) + + test_frame1.user = 1 + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame1) + source_queue.put(test_frame2) + source_queue.put(test_frame3) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert rx_frame.user[-1] + + rx_frame = sink_queue.get(False) + + assert rx_frame.user[-1] + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + yield clk.posedge + print("test 6: tuser assert and bad frame, length %d, zero frame" % payload_len) + current_test.next = 6 + + test_frame1 = axis_ep.AXIStreamFrame(enc+b'\x00') + test_frame2 = axis_ep.AXIStreamFrame(enc+b'\x02\x00') + test_frame3 = axis_ep.AXIStreamFrame(enc+b'\x00') + + test_frame1.user = 1 + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame1) + source_queue.put(test_frame2) + source_queue.put(test_frame3) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert rx_frame.user[-1] + + rx_frame = sink_queue.get(False) + + assert rx_frame.user[-1] + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + raise StopSimulation + + return dut, source, sink, clkgen, check + +def test_bench(): + sim = Simulation(bench()) + sim.run() + +if __name__ == '__main__': + print("Running test...") + test_bench() diff --git a/tb/test_axis_cobs_decode.v b/tb/test_axis_cobs_decode.v new file mode 100644 index 000000000..07e20affa --- /dev/null +++ b/tb/test_axis_cobs_decode.v @@ -0,0 +1,91 @@ +/* + +Copyright (c) 2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * Testbench for axis_cobs_decode + */ +module test_axis_cobs_decode; + +// Parameters + +// Inputs +reg clk = 0; +reg rst = 0; +reg [7:0] current_test = 0; + +reg [7:0] input_axis_tdata = 0; +reg input_axis_tvalid = 0; +reg input_axis_tlast = 0; +reg input_axis_tuser = 0; +reg output_axis_tready = 0; + +// Outputs +wire input_axis_tready; +wire [7:0] output_axis_tdata; +wire output_axis_tvalid; +wire output_axis_tlast; +wire output_axis_tuser; + +initial begin + // myhdl integration + $from_myhdl(clk, + rst, + current_test, + input_axis_tdata, + input_axis_tvalid, + input_axis_tlast, + input_axis_tuser, + output_axis_tready); + $to_myhdl(input_axis_tready, + output_axis_tdata, + output_axis_tvalid, + output_axis_tlast, + output_axis_tuser); + + // dump file + $dumpfile("test_axis_cobs_decode.lxt"); + $dumpvars(0, test_axis_cobs_decode); +end + +axis_cobs_decode +UUT ( + .clk(clk), + .rst(rst), + .input_axis_tdata(input_axis_tdata), + .input_axis_tvalid(input_axis_tvalid), + .input_axis_tready(input_axis_tready), + .input_axis_tlast(input_axis_tlast), + .input_axis_tuser(input_axis_tuser), + .output_axis_tdata(output_axis_tdata), + .output_axis_tvalid(output_axis_tvalid), + .output_axis_tready(output_axis_tready), + .output_axis_tlast(output_axis_tlast), + .output_axis_tuser(output_axis_tuser) +); + +endmodule diff --git a/tb/test_axis_cobs_encode.py b/tb/test_axis_cobs_encode.py new file mode 100755 index 000000000..6422de2a9 --- /dev/null +++ b/tb/test_axis_cobs_encode.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from myhdl import * +import os + +try: + from queue import Queue +except ImportError: + from Queue import Queue + +import axis_ep + +module = 'axis_cobs_encode' +testbench = 'test_axis_cobs_encode' + +srcs = [] + +srcs.append("../rtl/%s.v" % module) +srcs.append("../rtl/axis_fifo.v") +srcs.append("%s.v" % testbench) + +src = ' '.join(srcs) + +build_cmd = "iverilog -o %s.vvp %s" % (testbench, src) + +def dut_axis_cobs_encode(clk, + rst, + current_test, + input_axis_tdata, + input_axis_tvalid, + input_axis_tready, + input_axis_tlast, + input_axis_tuser, + output_axis_tdata, + output_axis_tvalid, + output_axis_tready, + output_axis_tlast, + output_axis_tuser): + + if os.system(build_cmd): + raise Exception("Error running build command") + return Cosimulation("vvp -m myhdl %s.vvp -lxt2" % testbench, + clk=clk, + rst=rst, + current_test=current_test, + input_axis_tdata=input_axis_tdata, + input_axis_tvalid=input_axis_tvalid, + input_axis_tready=input_axis_tready, + input_axis_tlast=input_axis_tlast, + input_axis_tuser=input_axis_tuser, + output_axis_tdata=output_axis_tdata, + output_axis_tvalid=output_axis_tvalid, + output_axis_tready=output_axis_tready, + output_axis_tlast=output_axis_tlast, + output_axis_tuser=output_axis_tuser) + +def cobs_encode(block): + block = bytearray(block) + enc = bytearray() + + seg = bytearray() + code = 1 + + new_data = True + + for b in block: + if b == 0: + enc.append(code) + enc.extend(seg) + code = 1 + seg = bytearray() + new_data = True + else: + code += 1 + seg.append(b) + new_data = True + if code == 255: + enc.append(code) + enc.extend(seg) + code = 1 + seg = bytearray() + new_data = False + + if new_data: + enc.append(code) + enc.extend(seg) + + return bytes(enc) + +def cobs_decode(block): + block = bytearray(block) + dec = bytearray() + + it = iter(bytearray(block)) + code = 0 + + i = 0 + + if 0 in block: + return None + + while i < len(block): + code = block[i] + i += 1 + if i+code-1 > len(block): + return None + for k in range(code-1): + dec.append(block[i]) + i += 1 + if code < 255 and i < len(block): + dec.append(0) + + return bytes(dec) + +def prbs31(state = 0x7fffffff): + while True: + for i in range(8): + if bool(state & 0x08000000) ^ bool(state & 0x40000000): + state = ((state & 0x3fffffff) << 1) | 1 + else: + state = (state & 0x3fffffff) << 1 + yield state & 0xff + +def bench(): + + # Parameters + APPEND_ZERO = 0 + + # Inputs + clk = Signal(bool(0)) + rst = Signal(bool(0)) + current_test = Signal(intbv(0)[8:]) + + input_axis_tdata = Signal(intbv(0)[8:]) + input_axis_tvalid = Signal(bool(0)) + input_axis_tlast = Signal(bool(0)) + input_axis_tuser = Signal(bool(0)) + output_axis_tready = Signal(bool(0)) + + # Outputs + input_axis_tready = Signal(bool(0)) + output_axis_tdata = Signal(intbv(0)[8:]) + output_axis_tvalid = Signal(bool(0)) + output_axis_tlast = Signal(bool(0)) + output_axis_tuser = Signal(bool(0)) + + # sources and sinks + source_queue = Queue() + source_pause = Signal(bool(0)) + sink_queue = Queue() + sink_pause = Signal(bool(0)) + + source = axis_ep.AXIStreamSource(clk, + rst, + tdata=input_axis_tdata, + tvalid=input_axis_tvalid, + tready=input_axis_tready, + tlast=input_axis_tlast, + tuser=input_axis_tuser, + fifo=source_queue, + pause=source_pause, + name='source') + + sink = axis_ep.AXIStreamSink(clk, + rst, + tdata=output_axis_tdata, + tvalid=output_axis_tvalid, + tready=output_axis_tready, + tlast=output_axis_tlast, + tuser=output_axis_tuser, + fifo=sink_queue, + pause=sink_pause, + name='sink') + + # DUT + dut = dut_axis_cobs_encode(clk, + rst, + current_test, + input_axis_tdata, + input_axis_tvalid, + input_axis_tready, + input_axis_tlast, + input_axis_tuser, + output_axis_tdata, + output_axis_tvalid, + output_axis_tready, + output_axis_tlast, + output_axis_tuser) + + @always(delay(4)) + def clkgen(): + clk.next = not clk + + def wait_normal(): + i = 4 + while i > 0: + i = max(0, i-1) + if input_axis_tvalid or output_axis_tvalid or not source_queue.empty(): + i = 4 + yield clk.posedge + + def wait_pause_source(): + i = 2 + while i > 0: + i = max(0, i-1) + if input_axis_tvalid or output_axis_tvalid or not source_queue.empty(): + i = 2 + source_pause.next = True + yield clk.posedge + yield clk.posedge + yield clk.posedge + source_pause.next = False + yield clk.posedge + + def wait_pause_sink(): + i = 2 + while i > 0: + i = max(0, i-1) + if input_axis_tvalid or output_axis_tvalid or not source_queue.empty(): + i = 2 + sink_pause.next = True + yield clk.posedge + yield clk.posedge + yield clk.posedge + sink_pause.next = False + yield clk.posedge + + @instance + def check(): + yield delay(100) + yield clk.posedge + rst.next = 1 + yield clk.posedge + rst.next = 0 + yield clk.posedge + yield delay(100) + yield clk.posedge + + # testbench stimulus + + for payload_len in list(range(1,33))+list(range(252,261))+[512,1024]: + gen = prbs31() + for block in [bytearray([0]*payload_len), + bytearray([k%255+1 for k in range(payload_len)]), + b'\x00'+bytearray([k%255+1 for k in range(payload_len)])+b'\x00', + bytearray([next(gen) for i in range(payload_len)])]: + yield clk.posedge + print("test 1: test packet, length %d" % payload_len) + current_test.next = 1 + + enc = cobs_encode(block) + + test_frame = axis_ep.AXIStreamFrame(block) + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == enc + assert cobs_decode(rx_frame.data) == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + yield clk.posedge + print("test 2: back-to-back packets, length %d" % payload_len) + current_test.next = 2 + + test_frame1 = axis_ep.AXIStreamFrame(block) + test_frame2 = axis_ep.AXIStreamFrame(block) + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame1) + source_queue.put(test_frame2) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == enc + assert cobs_decode(rx_frame.data) == block + assert not rx_frame.user[-1] + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == enc + assert cobs_decode(rx_frame.data) == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + yield clk.posedge + print("test 3: tuser assert, length %d" % payload_len) + current_test.next = 3 + + test_frame1 = axis_ep.AXIStreamFrame(block) + test_frame2 = axis_ep.AXIStreamFrame(block) + + test_frame1.user = 1 + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame1) + source_queue.put(test_frame2) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert cobs_decode(rx_frame.data) == None + assert rx_frame.user[-1] + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == enc + assert cobs_decode(rx_frame.data) == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + raise StopSimulation + + return dut, source, sink, clkgen, check + +def test_bench(): + sim = Simulation(bench()) + sim.run() + +if __name__ == '__main__': + print("Running test...") + test_bench() diff --git a/tb/test_axis_cobs_encode.v b/tb/test_axis_cobs_encode.v new file mode 100644 index 000000000..9e40ca835 --- /dev/null +++ b/tb/test_axis_cobs_encode.v @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * Testbench for axis_cobs_encode + */ +module test_axis_cobs_encode; + +// Parameters +parameter APPEND_ZERO = 0; + +// Inputs +reg clk = 0; +reg rst = 0; +reg [7:0] current_test = 0; + +reg [7:0] input_axis_tdata = 0; +reg input_axis_tvalid = 0; +reg input_axis_tlast = 0; +reg input_axis_tuser = 0; +reg output_axis_tready = 0; + +// Outputs +wire input_axis_tready; +wire [7:0] output_axis_tdata; +wire output_axis_tvalid; +wire output_axis_tlast; +wire output_axis_tuser; + +initial begin + // myhdl integration + $from_myhdl(clk, + rst, + current_test, + input_axis_tdata, + input_axis_tvalid, + input_axis_tlast, + input_axis_tuser, + output_axis_tready); + $to_myhdl(input_axis_tready, + output_axis_tdata, + output_axis_tvalid, + output_axis_tlast, + output_axis_tuser); + + // dump file + $dumpfile("test_axis_cobs_encode.lxt"); + $dumpvars(0, test_axis_cobs_encode); +end + +axis_cobs_encode #( + .APPEND_ZERO(APPEND_ZERO) +) +UUT ( + .clk(clk), + .rst(rst), + .input_axis_tdata(input_axis_tdata), + .input_axis_tvalid(input_axis_tvalid), + .input_axis_tready(input_axis_tready), + .input_axis_tlast(input_axis_tlast), + .input_axis_tuser(input_axis_tuser), + .output_axis_tdata(output_axis_tdata), + .output_axis_tvalid(output_axis_tvalid), + .output_axis_tready(output_axis_tready), + .output_axis_tlast(output_axis_tlast), + .output_axis_tuser(output_axis_tuser) +); + +endmodule diff --git a/tb/test_axis_cobs_encode_zero_frame.py b/tb/test_axis_cobs_encode_zero_frame.py new file mode 100755 index 000000000..b4fe9e8bc --- /dev/null +++ b/tb/test_axis_cobs_encode_zero_frame.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from myhdl import * +import os + +try: + from queue import Queue +except ImportError: + from Queue import Queue + +import axis_ep + +module = 'axis_cobs_encode' +testbench = 'test_axis_cobs_encode_zero_frame' + +srcs = [] + +srcs.append("../rtl/%s.v" % module) +srcs.append("../rtl/axis_fifo.v") +srcs.append("%s.v" % testbench) + +src = ' '.join(srcs) + +build_cmd = "iverilog -o %s.vvp %s" % (testbench, src) + +def dut_axis_cobs_encode(clk, + rst, + current_test, + input_axis_tdata, + input_axis_tvalid, + input_axis_tready, + input_axis_tlast, + input_axis_tuser, + output_axis_tdata, + output_axis_tvalid, + output_axis_tready, + output_axis_tlast, + output_axis_tuser): + + if os.system(build_cmd): + raise Exception("Error running build command") + return Cosimulation("vvp -m myhdl %s.vvp -lxt2" % testbench, + clk=clk, + rst=rst, + current_test=current_test, + input_axis_tdata=input_axis_tdata, + input_axis_tvalid=input_axis_tvalid, + input_axis_tready=input_axis_tready, + input_axis_tlast=input_axis_tlast, + input_axis_tuser=input_axis_tuser, + output_axis_tdata=output_axis_tdata, + output_axis_tvalid=output_axis_tvalid, + output_axis_tready=output_axis_tready, + output_axis_tlast=output_axis_tlast, + output_axis_tuser=output_axis_tuser) + +def cobs_encode(block): + block = bytearray(block) + enc = bytearray() + + seg = bytearray() + code = 1 + + new_data = True + + for b in block: + if b == 0: + enc.append(code) + enc.extend(seg) + code = 1 + seg = bytearray() + new_data = True + else: + code += 1 + seg.append(b) + new_data = True + if code == 255: + enc.append(code) + enc.extend(seg) + code = 1 + seg = bytearray() + new_data = False + + if new_data: + enc.append(code) + enc.extend(seg) + + return bytes(enc) + +def cobs_decode(block): + block = bytearray(block) + dec = bytearray() + + it = iter(bytearray(block)) + code = 0 + + i = 0 + + if 0 in block: + return None + + while i < len(block): + code = block[i] + i += 1 + if i+code-1 > len(block): + return None + for k in range(code-1): + dec.append(block[i]) + i += 1 + if code < 255 and i < len(block): + dec.append(0) + + return bytes(dec) + +def prbs31(state = 0x7fffffff): + while True: + for i in range(8): + if bool(state & 0x08000000) ^ bool(state & 0x40000000): + state = ((state & 0x3fffffff) << 1) | 1 + else: + state = (state & 0x3fffffff) << 1 + yield state & 0xff + +def bench(): + + # Parameters + APPEND_ZERO = 1 + + # Inputs + clk = Signal(bool(0)) + rst = Signal(bool(0)) + current_test = Signal(intbv(0)[8:]) + + input_axis_tdata = Signal(intbv(0)[8:]) + input_axis_tvalid = Signal(bool(0)) + input_axis_tlast = Signal(bool(0)) + input_axis_tuser = Signal(bool(0)) + output_axis_tready = Signal(bool(0)) + + # Outputs + input_axis_tready = Signal(bool(0)) + output_axis_tdata = Signal(intbv(0)[8:]) + output_axis_tvalid = Signal(bool(0)) + output_axis_tlast = Signal(bool(0)) + output_axis_tuser = Signal(bool(0)) + + # sources and sinks + source_queue = Queue() + source_pause = Signal(bool(0)) + sink_queue = Queue() + sink_pause = Signal(bool(0)) + + source = axis_ep.AXIStreamSource(clk, + rst, + tdata=input_axis_tdata, + tvalid=input_axis_tvalid, + tready=input_axis_tready, + tlast=input_axis_tlast, + tuser=input_axis_tuser, + fifo=source_queue, + pause=source_pause, + name='source') + + sink = axis_ep.AXIStreamSink(clk, + rst, + tdata=output_axis_tdata, + tvalid=output_axis_tvalid, + tready=output_axis_tready, + tlast=output_axis_tlast, + tuser=output_axis_tuser, + fifo=sink_queue, + pause=sink_pause, + name='sink') + + # DUT + dut = dut_axis_cobs_encode(clk, + rst, + current_test, + input_axis_tdata, + input_axis_tvalid, + input_axis_tready, + input_axis_tlast, + input_axis_tuser, + output_axis_tdata, + output_axis_tvalid, + output_axis_tready, + output_axis_tlast, + output_axis_tuser) + + @always(delay(4)) + def clkgen(): + clk.next = not clk + + def wait_normal(): + i = 4 + while i > 0: + i = max(0, i-1) + if input_axis_tvalid or output_axis_tvalid or not source_queue.empty(): + i = 4 + yield clk.posedge + + def wait_pause_source(): + i = 2 + while i > 0: + i = max(0, i-1) + if input_axis_tvalid or output_axis_tvalid or not source_queue.empty(): + i = 2 + source_pause.next = True + yield clk.posedge + yield clk.posedge + yield clk.posedge + source_pause.next = False + yield clk.posedge + + def wait_pause_sink(): + i = 2 + while i > 0: + i = max(0, i-1) + if input_axis_tvalid or output_axis_tvalid or not source_queue.empty(): + i = 2 + sink_pause.next = True + yield clk.posedge + yield clk.posedge + yield clk.posedge + sink_pause.next = False + yield clk.posedge + + @instance + def check(): + yield delay(100) + yield clk.posedge + rst.next = 1 + yield clk.posedge + rst.next = 0 + yield clk.posedge + yield delay(100) + yield clk.posedge + + # testbench stimulus + + for payload_len in list(range(1,33))+list(range(252,261))+[512,1024]: + gen = prbs31() + for block in [bytearray([0]*payload_len), + bytearray([k%255+1 for k in range(payload_len)]), + b'\x00'+bytearray([k%255+1 for k in range(payload_len)])+b'\x00', + bytearray([next(gen) for i in range(payload_len)])]: + yield clk.posedge + print("test 1: test packet, length %d" % payload_len) + current_test.next = 1 + + enc = cobs_encode(block) + + test_frame = axis_ep.AXIStreamFrame(block) + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == enc+b'\x00' + assert cobs_decode(rx_frame.data[:-1]) == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + yield clk.posedge + print("test 2: back-to-back packets, length %d" % payload_len) + current_test.next = 2 + + test_frame1 = axis_ep.AXIStreamFrame(block) + test_frame2 = axis_ep.AXIStreamFrame(block) + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame1) + source_queue.put(test_frame2) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == enc+b'\x00' + assert cobs_decode(rx_frame.data[:-1]) == block + assert not rx_frame.user[-1] + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == enc+b'\x00' + assert cobs_decode(rx_frame.data[:-1]) == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + yield clk.posedge + print("test 3: tuser assert, length %d" % payload_len) + current_test.next = 3 + + test_frame1 = axis_ep.AXIStreamFrame(block) + test_frame2 = axis_ep.AXIStreamFrame(block) + + test_frame1.user = 1 + + for wait in wait_normal, wait_pause_source, wait_pause_sink: + source_queue.put(test_frame1) + source_queue.put(test_frame2) + yield clk.posedge + yield clk.posedge + + yield wait() + + yield clk.posedge + yield clk.posedge + + rx_frame = sink_queue.get(False) + + assert cobs_decode(rx_frame.data[:-1]) == None + assert rx_frame.user[-1] + + rx_frame = sink_queue.get(False) + + assert cobs_decode(enc) == block + assert rx_frame.data == enc+b'\x00' + assert cobs_decode(rx_frame.data[:-1]) == block + assert not rx_frame.user[-1] + + assert sink_queue.empty() + + yield delay(100) + + raise StopSimulation + + return dut, source, sink, clkgen, check + +def test_bench(): + sim = Simulation(bench()) + sim.run() + +if __name__ == '__main__': + print("Running test...") + test_bench() diff --git a/tb/test_axis_cobs_encode_zero_frame.v b/tb/test_axis_cobs_encode_zero_frame.v new file mode 100644 index 000000000..14226bc52 --- /dev/null +++ b/tb/test_axis_cobs_encode_zero_frame.v @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * Testbench for axis_cobs_encode + */ +module test_axis_cobs_encode_zero_frame; + +// Parameters +parameter APPEND_ZERO = 1; + +// Inputs +reg clk = 0; +reg rst = 0; +reg [7:0] current_test = 0; + +reg [7:0] input_axis_tdata = 0; +reg input_axis_tvalid = 0; +reg input_axis_tlast = 0; +reg input_axis_tuser = 0; +reg output_axis_tready = 0; + +// Outputs +wire input_axis_tready; +wire [7:0] output_axis_tdata; +wire output_axis_tvalid; +wire output_axis_tlast; +wire output_axis_tuser; + +initial begin + // myhdl integration + $from_myhdl(clk, + rst, + current_test, + input_axis_tdata, + input_axis_tvalid, + input_axis_tlast, + input_axis_tuser, + output_axis_tready); + $to_myhdl(input_axis_tready, + output_axis_tdata, + output_axis_tvalid, + output_axis_tlast, + output_axis_tuser); + + // dump file + $dumpfile("test_axis_cobs_encode_zero_frame.lxt"); + $dumpvars(0, test_axis_cobs_encode_zero_frame); +end + +axis_cobs_encode #( + .APPEND_ZERO(APPEND_ZERO) +) +UUT ( + .clk(clk), + .rst(rst), + .input_axis_tdata(input_axis_tdata), + .input_axis_tvalid(input_axis_tvalid), + .input_axis_tready(input_axis_tready), + .input_axis_tlast(input_axis_tlast), + .input_axis_tuser(input_axis_tuser), + .output_axis_tdata(output_axis_tdata), + .output_axis_tvalid(output_axis_tvalid), + .output_axis_tready(output_axis_tready), + .output_axis_tlast(output_axis_tlast), + .output_axis_tuser(output_axis_tuser) +); + +endmodule