| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Filename: axivcamera |
| // |
| // Project: WB2AXIPSP: bus bridges and other odds and ends |
| // |
| // Purpose: Reads a video frame from a source and writes the result to |
| // {{{ |
| // memory. |
| // }}} |
| // Registers: |
| // {{{ |
| // 0: FBUF_CONTROL and status |
| // bit 0: START(1)/STOP(0) |
| // Command the core to start by writing a '1' to this bit. It |
| // will then remain busy/active until either you tell it to halt, |
| // or an error occurrs. |
| // bit 1: BUSY |
| // bit 2: ERR |
| // If the core receives a bus error, it assumes it has been |
| // inappropriately set up, sets this error bit and then halts. |
| // It will not start again until this bit is cleared. Only a |
| // write to the control register with the ERR bit set will clear |
| // it. (Don't do this unless you know what caused it to halt ...) |
| // bit 3: DIRTY |
| // If you update core parameters while it is running, the busy |
| // bit will be set. This bit is an indication that the current |
| // configuration doesn't necessarily match the one you are reading |
| // out. To clear DIRTY, deactivate the core, wait for it to be |
| // no longer busy, and then start it again. This will also start |
| // it under the new configuration so the two match. |
| // bits 15-8: FRAMES |
| // Indicates the number of frames you want to copy. Set this to |
| // 0 to continuously copy, or a finite number to grab only that |
| // many frames. |
| // |
| // As a feature, this isn't perhaps the most useful, since every |
| // frame will be written to the same location. A more useful |
| // approach would be to increase the desired number of lines to |
| // (NLINES * NFRAMES), and then to either leave this number at zero |
| // or set it to one. |
| // |
| // 2: FBUF_LINESTEP |
| // Controls the distance from one line to the next. This is the |
| // value added to the address of the beginning of the line to get |
| // to the beginning of the next line. This should nominally be |
| // equal to the number of bytes per line, although it doesn't |
| // need to be. |
| // |
| // Any attempt to set this value to zero will simply copy the |
| // number of data bytes per line (rounded down to the neareset |
| // word) into this value. This is a recommended approach to |
| // setting this value. |
| // |
| // 4: FBUF_LINEBYTES |
| // This is the number of data bytes necessary to capture all of |
| // the video data in a line. This value must be more than zero |
| // in order to activate the core. |
| // |
| // At present, this core can only handle a number of bytes aligned |
| // with the word size of the bus. To convert from bytes to words, |
| // round any fractional part upwards (i.e. ceil()). The core |
| // will naturally round down internally. |
| // |
| // 6: FBUF_NUMLINES |
| // The number of lines of active video data in a frame. This |
| // number must be greater than zero in order to activate and |
| // operate the core. |
| // |
| // 8: FBUF_ADDRESS |
| // The is the first address of video data in memory. Each frame |
| // will start reading from this address. |
| // |
| // 12: (reserved for the upper FBUF_ADDRESS) |
| // |
| // KNOWN ISSUES: |
| // |
| // Does not support interlacing (yet). (Interlacing is not on my "todo" |
| // list, so it might take a while to get said support if you need it.) |
| // |
| // Does not support unaligned addressing. The frame buffer address, and |
| // the line step, must all be word aligned. While nothing is stopping |
| // me from supporting unaligned addressing, it was such a pain to do the |
| // last time that I might just hold off until I have a paying customer |
| // who wants it. |
| // |
| // Line bytes that include a fraction of a word are rounded down and not |
| // up. |
| // |
| // }}} |
| // |
| // Creator: Dan Gisselquist, Ph.D. |
| // Gisselquist Technology, LLC |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Copyright (C) 2020, Gisselquist Technology, LLC |
| // {{{ |
| // |
| // This file is part of the WB2AXIP project. |
| // |
| // The WB2AXIP project contains free software and gateware, licensed under the |
| // Apache License, Version 2.0 (the "License"). You may not use this project, |
| // or this file, except in compliance with the License. You may obtain a copy |
| // of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations |
| // under the License. |
| // |
| // }}} |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // |
| `default_nettype none |
| // |
| module axivcamera #( |
| // {{{ |
| parameter C_AXI_ADDR_WIDTH = 32, |
| parameter C_AXI_DATA_WIDTH = 32, |
| parameter C_AXI_ID_WIDTH = 1, |
| // |
| // We support five 32-bit AXI-lite registers, requiring 5-bits |
| // of AXI-lite addressing |
| localparam C_AXIL_ADDR_WIDTH = 4, |
| localparam C_AXIL_DATA_WIDTH = 32, |
| // |
| // The bottom ADDRLSB bits of any AXI address are subword bits |
| localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3, |
| localparam AXILLSB = $clog2(C_AXIL_DATA_WIDTH)-3, |
| // |
| // OPT_LGMAXBURST |
| parameter OPT_LGMAXBURST = 8, |
| // |
| parameter [0:0] DEF_ACTIVE_ON_RESET = 0, |
| parameter [15:0] DEF_LINES_PER_FRAME = 1024, |
| parameter [16-ADDRLSB-1:0] DEF_WORDS_PER_LINE = (1280 * 32)/C_AXI_DATA_WIDTH, |
| // |
| // DEF_FRAMEADDR: the default AXI address of the frame buffer |
| // containing video memory. Unless OPT_UNALIGNED is set, this |
| // should be aligned so that DEF_FRAMEADDR[ADDRLSB-1:0] == 0. |
| parameter [C_AXI_ADDR_WIDTH-1:0] DEF_FRAMEADDR = 0, |
| // |
| // The (log-based two of the) size of the FIFO in words. |
| // I like to set this to the size of two AXI bursts, so that |
| // while one is being read out the second can be read in. Can |
| // also be set larger if desired. |
| parameter OPT_LGFIFO = OPT_LGMAXBURST+1, |
| localparam LGFIFO = (OPT_LGFIFO < OPT_LGMAXBURST+1) |
| ? OPT_LGMAXBURST+1 : OPT_LGFIFO, |
| // |
| // AXI_ID is the ID we will use for all of our AXI transactions |
| parameter AXI_ID = 0, |
| // |
| // OPT_IGNORE_HLAST |
| parameter [0:0] OPT_IGNORE_HLAST = 0, |
| // |
| // OPT_TUSER_IS_SOF. Xilinx and I chose different encodings. |
| // I encode TLAST == VLAST, and TUSER == HLAST (optional). |
| // Xilinx likes TLAST == HLAST and TUSER == SOF (start of frame) |
| // Set OPT_TUSER_IS_SOF to use Xilinx's encoding |
| parameter [0:0] OPT_TUSER_IS_SOF = 0 |
| // }}} |
| ) ( |
| // {{{ |
| input wire S_AXI_ACLK, |
| input wire S_AXI_ARESETN, |
| // |
| // The incoming video stream data/pixel interface |
| // {{{ |
| input wire S_AXIS_TVALID, |
| output wire S_AXIS_TREADY, |
| input wire [C_AXI_DATA_WIDTH-1:0] S_AXIS_TDATA, |
| input wire /* VLAST */ S_AXIS_TLAST, |
| input wire /* HLAST */ S_AXIS_TUSER, |
| // }}} |
| // |
| // The control interface |
| // {{{ |
| input wire S_AXIL_AWVALID, |
| output wire S_AXIL_AWREADY, |
| input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_AWADDR, |
| input wire [2:0] S_AXIL_AWPROT, |
| // |
| input wire S_AXIL_WVALID, |
| output wire S_AXIL_WREADY, |
| input wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_WDATA, |
| input wire [C_AXIL_DATA_WIDTH/8-1:0] S_AXIL_WSTRB, |
| // |
| output wire S_AXIL_BVALID, |
| input wire S_AXIL_BREADY, |
| output wire [1:0] S_AXIL_BRESP, |
| // |
| input wire S_AXIL_ARVALID, |
| output wire S_AXIL_ARREADY, |
| input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_ARADDR, |
| input wire [2:0] S_AXIL_ARPROT, |
| // |
| output wire S_AXIL_RVALID, |
| input wire S_AXIL_RREADY, |
| output wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_RDATA, |
| output wire [1:0] S_AXIL_RRESP, |
| // }}} |
| // |
| |
| // |
| // The AXI (full) write-only interface |
| // {{{ |
| output wire M_AXI_AWVALID, |
| input wire M_AXI_AWREADY, |
| output wire [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, |
| output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, |
| output wire [7:0] M_AXI_AWLEN, |
| output wire [2:0] M_AXI_AWSIZE, |
| output wire [1:0] M_AXI_AWBURST, |
| output wire M_AXI_AWLOCK, |
| output wire [3:0] M_AXI_AWCACHE, |
| output wire [2:0] M_AXI_AWPROT, |
| output wire [3:0] M_AXI_AWQOS, |
| // |
| output wire M_AXI_WVALID, |
| input wire M_AXI_WREADY, |
| output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, |
| output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, |
| output wire M_AXI_WLAST, |
| // |
| input wire M_AXI_BVALID, |
| output wire M_AXI_BREADY, |
| input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, |
| input wire [1:0] M_AXI_BRESP |
| // }}} |
| // }}} |
| ); |
| |
| // Core logic implementation |
| // {{{ |
| // Local parameter declarations |
| // {{{ |
| localparam [1:0] FBUF_CONTROL = 2'b00, |
| FBUF_FRAMEINFO = 2'b01, |
| FBUF_ADDRLO = 2'b10, |
| FBUF_ADDRHI = 2'b11; |
| |
| localparam CBIT_ACTIVE = 0, |
| CBIT_BUSY = 1, |
| CBIT_ERR = 2, |
| // CBIT_DIRTY = 3, |
| CBIT_LOST_SYNC = 4; |
| |
| localparam TMPLGMAXBURST=(LGFIFO-1 > OPT_LGMAXBURST) |
| ? OPT_LGMAXBURST : LGFIFO-1; |
| |
| localparam LGMAXBURST = (TMPLGMAXBURST+ADDRLSB > 12) |
| ? (12-ADDRLSB) : TMPLGMAXBURST; |
| |
| localparam [ADDRLSB-1:0] LSBZEROS = 0; |
| // }}} |
| |
| wire i_clk = S_AXI_ACLK; |
| wire i_reset = !S_AXI_ARESETN; |
| |
| // Signal declarations |
| // {{{ |
| reg soft_reset, r_err, r_stopped, lost_sync; |
| reg cfg_active, cfg_zero_length, |
| cfg_continuous; |
| reg [C_AXI_ADDR_WIDTH-1:0] cfg_frame_addr; |
| reg [15:0] cfg_frame_lines, cfg_line_step; |
| reg [16-ADDRLSB-1:0] cfg_line_words; |
| |
| // FIFO signals |
| wire reset_fifo, write_to_fifo, |
| read_from_fifo; |
| wire [C_AXI_DATA_WIDTH-1:0] write_data, fifo_data; |
| wire [LGFIFO:0] fifo_fill; |
| wire fifo_full, fifo_empty, |
| fifo_vlast, fifo_hlast; |
| |
| wire awskd_valid, axil_write_ready; |
| wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] awskd_addr; |
| // |
| wire wskd_valid; |
| wire [C_AXIL_DATA_WIDTH-1:0] wskd_data; |
| wire [C_AXIL_DATA_WIDTH/8-1:0] wskd_strb; |
| reg axil_bvalid; |
| // |
| wire arskd_valid, axil_read_ready; |
| wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] arskd_addr; |
| reg [C_AXIL_DATA_WIDTH-1:0] axil_read_data; |
| reg axil_read_valid; |
| reg [C_AXIL_DATA_WIDTH-1:0] w_status_word; |
| reg [2*C_AXIL_DATA_WIDTH-1:0] wide_address, new_wideaddr; |
| wire [C_AXIL_DATA_WIDTH-1:0] new_cmdaddrlo, new_cmdaddrhi; |
| reg [C_AXIL_DATA_WIDTH-1:0] wide_config; |
| wire [C_AXIL_DATA_WIDTH-1:0] new_config; |
| |
| reg axi_awvalid, axi_wvalid, axi_wlast, |
| phantom_start, start_burst, |
| aw_none_outstanding; |
| reg [C_AXI_ADDR_WIDTH-1:0] axi_awaddr; |
| reg [7:0] axi_awlen, next_awlen; |
| reg [8:0] wr_pending; |
| reg [15:0] aw_bursts_outstanding; |
| // |
| // reg vlast; |
| // reg [15:0] r_frame_lines, r_line_step; |
| reg [16-ADDRLSB-1:0] next_line_words; |
| |
| reg req_hlast, req_vlast, req_newline; |
| reg [15:0] req_nlines; |
| reg [7:0] req_nframes; |
| reg [16-ADDRLSB-1:0] req_line_words; |
| reg [C_AXI_ADDR_WIDTH:0] req_addr, req_line_addr, next_line_addr; |
| // |
| reg wr_hlast, wr_vlast; |
| reg [15:0] wr_lines; |
| reg [16-ADDRLSB-1:0] wr_line_beats; |
| // wire no_fifo__available; |
| // |
| reg [LGMAXBURST-1:0] till_boundary; |
| reg [LGFIFO:0] fifo_data_available; |
| reg fifo_bursts_available; |
| // }}} |
| |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // AXI-lite signaling |
| // |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // This is mostly the skidbuffer logic, and handling of the VALID |
| // and READY signals for the AXI-lite control logic in the next |
| // section. |
| // {{{ |
| |
| // |
| // Write signaling |
| // |
| // {{{ |
| |
| skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB)) |
| axilawskid(// |
| .i_clk(S_AXI_ACLK), .i_reset(i_reset), |
| .i_valid(S_AXIL_AWVALID), .o_ready(S_AXIL_AWREADY), |
| .i_data(S_AXIL_AWADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), |
| .o_valid(awskd_valid), .i_ready(axil_write_ready), |
| .o_data(awskd_addr)); |
| |
| skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_DATA_WIDTH+C_AXIL_DATA_WIDTH/8)) |
| axilwskid(// |
| .i_clk(S_AXI_ACLK), .i_reset(i_reset), |
| .i_valid(S_AXIL_WVALID), .o_ready(S_AXIL_WREADY), |
| .i_data({ S_AXIL_WDATA, S_AXIL_WSTRB }), |
| .o_valid(wskd_valid), .i_ready(axil_write_ready), |
| .o_data({ wskd_data, wskd_strb })); |
| |
| assign axil_write_ready = awskd_valid && wskd_valid |
| && (!S_AXIL_BVALID || S_AXIL_BREADY); |
| |
| initial axil_bvalid = 0; |
| always @(posedge i_clk) |
| if (i_reset) |
| axil_bvalid <= 0; |
| else if (axil_write_ready) |
| axil_bvalid <= 1; |
| else if (S_AXIL_BREADY) |
| axil_bvalid <= 0; |
| |
| assign S_AXIL_BVALID = axil_bvalid; |
| assign S_AXIL_BRESP = 2'b00; |
| // }}} |
| |
| // |
| // Read signaling |
| // |
| // {{{ |
| skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB)) |
| axilarskid(// |
| .i_clk(S_AXI_ACLK), .i_reset(i_reset), |
| .i_valid(S_AXIL_ARVALID), .o_ready(S_AXIL_ARREADY), |
| .i_data(S_AXIL_ARADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), |
| .o_valid(arskd_valid), .i_ready(axil_read_ready), |
| .o_data(arskd_addr)); |
| |
| assign axil_read_ready = arskd_valid |
| && (!axil_read_valid || S_AXIL_RREADY); |
| |
| initial axil_read_valid = 1'b0; |
| always @(posedge i_clk) |
| if (i_reset) |
| axil_read_valid <= 1'b0; |
| else if (axil_read_ready) |
| axil_read_valid <= 1'b1; |
| else if (S_AXIL_RREADY) |
| axil_read_valid <= 1'b0; |
| |
| assign S_AXIL_RVALID = axil_read_valid; |
| assign S_AXIL_RDATA = axil_read_data; |
| assign S_AXIL_RRESP = 2'b00; |
| // }}} |
| |
| // }}} |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // AXI-lite controlled logic |
| // {{{ |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // |
| |
| // |
| // soft_reset, r_err |
| // {{{ |
| initial soft_reset = 1; |
| always @(posedge i_clk) |
| if (i_reset) |
| begin |
| soft_reset <= 1; |
| r_err <= 0; |
| end else if (r_stopped) |
| begin |
| |
| if (axil_write_ready && awskd_addr == FBUF_CONTROL |
| && wskd_strb[0] && wskd_data[CBIT_ERR]) |
| begin |
| r_err <= cfg_zero_length; |
| soft_reset <= 0; |
| end |
| |
| if (!r_err) |
| soft_reset <= 0; |
| |
| end else // if (!soft_reset) |
| begin |
| // Halt on any bus error. We'll require user intervention |
| // to start back up again |
| if ((M_AXI_BVALID && M_AXI_BREADY && M_AXI_BRESP[1]) |
| ||(req_addr[C_AXI_ADDR_WIDTH])) |
| begin |
| soft_reset <= 1; |
| r_err <= 1; |
| end |
| |
| // Halt on any user request |
| if (!cfg_active) |
| soft_reset <= 1; |
| end |
| // }}} |
| |
| // wide_* and new_* write setup registers |
| // {{{ |
| always @(*) |
| begin |
| wide_address = 0; |
| wide_address[C_AXI_ADDR_WIDTH-1:0] = cfg_frame_addr; |
| wide_address[ADDRLSB-1:0] = 0; |
| |
| wide_config = { cfg_frame_lines, cfg_line_words, |
| {(ADDRLSB){1'b0}} }; |
| end |
| |
| assign new_cmdaddrlo = apply_wstrb( |
| wide_address[C_AXIL_DATA_WIDTH-1:0], |
| wskd_data, wskd_strb); |
| |
| generate if (C_AXI_ADDR_WIDTH > 32) |
| begin |
| |
| assign new_cmdaddrhi = apply_wstrb( |
| wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], |
| wskd_data, wskd_strb); |
| |
| end else begin |
| |
| assign new_cmdaddrhi = 0; |
| |
| end endgenerate |
| |
| |
| wire [C_AXIL_DATA_WIDTH-1:0] new_control; |
| assign new_control = apply_wstrb(w_status_word, wskd_data, wskd_strb); |
| assign new_config = apply_wstrb(wide_config, wskd_data, wskd_strb); |
| |
| always @(*) |
| begin |
| new_wideaddr = wide_address; |
| |
| if (awskd_addr == FBUF_ADDRLO) |
| new_wideaddr[C_AXIL_DATA_WIDTH-1:0] = new_cmdaddrlo; |
| if (awskd_addr == FBUF_ADDRHI) |
| new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH] = new_cmdaddrhi; |
| new_wideaddr[ADDRLSB-1:0] = 0; |
| new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH] = 0; |
| end |
| // }}} |
| |
| // Configuration registers (Write) |
| // {{{ |
| initial cfg_active = 0; |
| initial cfg_continuous = 0; |
| initial cfg_frame_addr = DEF_FRAMEADDR; |
| initial cfg_frame_addr[ADDRLSB-1:0] = 0; |
| initial cfg_line_words = DEF_WORDS_PER_LINE; |
| initial cfg_frame_lines = DEF_LINES_PER_FRAME; |
| initial cfg_zero_length = (DEF_WORDS_PER_LINE == 0) |
| ||(DEF_LINES_PER_FRAME == 0); |
| initial cfg_line_step = { DEF_WORDS_PER_LINE, {(ADDRLSB){1'b0}} }; |
| always @(posedge i_clk) |
| if (i_reset) |
| begin |
| cfg_active <= DEF_ACTIVE_ON_RESET; |
| cfg_frame_addr <= DEF_FRAMEADDR; |
| cfg_line_words <= DEF_WORDS_PER_LINE; |
| cfg_line_step <= { DEF_WORDS_PER_LINE, {(ADDRLSB){1'b0}} }; |
| cfg_frame_lines <= DEF_LINES_PER_FRAME; |
| cfg_zero_length <= (DEF_WORDS_PER_LINE==0) |
| ||(DEF_LINES_PER_FRAME == 0); |
| req_nframes <= 0; |
| cfg_continuous <= 0; |
| end else begin |
| if (phantom_start && req_vlast && req_hlast && req_nframes > 0) |
| req_nframes <= req_nframes - 1; |
| |
| if (axil_write_ready) |
| case(awskd_addr) |
| FBUF_CONTROL: begin |
| if (wskd_strb[0]) |
| cfg_active <= (cfg_active || r_stopped) |
| && wskd_data[CBIT_ACTIVE] |
| && (!r_err || wskd_data[CBIT_ERR]) |
| && (!cfg_zero_length); |
| |
| if (!cfg_active && r_stopped && wskd_strb[1]) |
| begin |
| req_nframes <= wskd_data[15:8]; |
| cfg_continuous <= (wskd_data[15:8] == 0); |
| end |
| |
| if (!cfg_active && r_stopped) |
| begin |
| if (new_control[31:16] == 0) |
| begin |
| cfg_line_step <= 0; |
| cfg_line_step[16-1:ADDRLSB] <= cfg_line_words; |
| end else |
| cfg_line_step <= new_control[31:16]; |
| end end |
| FBUF_FRAMEINFO: |
| if (!cfg_active && r_stopped) |
| begin |
| { cfg_frame_lines, cfg_line_words } |
| <= new_config[C_AXIL_DATA_WIDTH-1:ADDRLSB]; |
| cfg_zero_length <= (new_config[31:16] == 0) |
| ||(new_config[15:ADDRLSB] == 0); |
| end |
| FBUF_ADDRLO, FBUF_ADDRHI: if (!cfg_active && r_stopped) |
| cfg_frame_addr <= new_wideaddr[C_AXI_ADDR_WIDTH-1:0]; |
| default: begin end |
| endcase |
| |
| if (M_AXI_BREADY && M_AXI_BVALID && M_AXI_BRESP[1]) |
| cfg_active <= 0; |
| if (req_addr[C_AXI_ADDR_WIDTH]) |
| cfg_active <= 0; |
| if (phantom_start && req_vlast && req_hlast && req_nframes <= 1 |
| && !cfg_continuous) |
| cfg_active <= 0; |
| |
| cfg_line_step[ADDRLSB-1:0] <= 0; |
| cfg_frame_addr[ADDRLSB-1:0] <= 0; |
| end |
| // }}} |
| |
| // AXI-Lite read register data |
| // {{{ |
| always @(*) |
| begin |
| w_status_word = 0; |
| w_status_word[31:16] = cfg_line_step; |
| w_status_word[15:8] = req_nframes; |
| w_status_word[CBIT_LOST_SYNC] = lost_sync; |
| // w_status_word[CBIT_DIRTY] = cfg_dirty; |
| w_status_word[CBIT_ERR] = r_err; |
| w_status_word[CBIT_BUSY] = !soft_reset; |
| w_status_word[CBIT_ACTIVE] = cfg_active || (!soft_reset || !r_stopped); |
| end |
| |
| always @(posedge i_clk) |
| if (!axil_read_valid || S_AXIL_RREADY) |
| begin |
| axil_read_data <= 0; |
| case(arskd_addr) |
| FBUF_CONTROL: axil_read_data <= w_status_word; |
| FBUF_FRAMEINFO: axil_read_data <= { cfg_frame_lines, |
| cfg_line_words, {(ADDRLSB){1'b0}} }; |
| FBUF_ADDRLO: axil_read_data <= wide_address[C_AXIL_DATA_WIDTH-1:0]; |
| FBUF_ADDRHI: axil_read_data <= wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; |
| default axil_read_data <= 0; |
| endcase |
| end |
| // }}} |
| |
| // apply_wstrb function for applying wstrbs to register words |
| // {{{ |
| function [C_AXIL_DATA_WIDTH-1:0] apply_wstrb; |
| input [C_AXIL_DATA_WIDTH-1:0] prior_data; |
| input [C_AXIL_DATA_WIDTH-1:0] new_data; |
| input [C_AXIL_DATA_WIDTH/8-1:0] wstrb; |
| |
| integer k; |
| for(k=0; k<C_AXIL_DATA_WIDTH/8; k=k+1) |
| begin |
| apply_wstrb[k*8 +: 8] |
| = wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8]; |
| end |
| endfunction |
| // }}} |
| |
| // }}} |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // The data FIFO section |
| // {{{ |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // |
| |
| reg S_AXIS_SOF, S_AXIS_HLAST, S_AXIS_VLAST; |
| generate if (OPT_TUSER_IS_SOF) |
| begin : XILINX_SOF_LOCK |
| reg [15:0] axis_line; |
| reg axis_last_line; |
| |
| always @(*) |
| S_AXIS_HLAST = S_AXIS_TLAST; |
| |
| always @(*) |
| S_AXIS_SOF = S_AXIS_TUSER; |
| |
| // Generate S_AXIS_VLAST from S_AXIS_SOF |
| always @(posedge S_AXI_ACLK) |
| if (!S_AXI_ARESETN) |
| axis_line <= 0; |
| else if (S_AXIS_TVALID && S_AXIS_TREADY) |
| begin |
| if (S_AXIS_SOF) |
| axis_line <= 0; |
| else if (S_AXIS_HLAST) |
| axis_line <= axis_line + 1; |
| end |
| |
| always @(posedge S_AXI_ACLK) |
| axis_last_line <= (axis_line+1 >= cfg_frame_lines); |
| |
| always @(*) |
| S_AXIS_VLAST = axis_last_line && S_AXIS_HLAST; |
| |
| end else begin : VLAST_LOCK |
| |
| always @(*) |
| S_AXIS_VLAST = S_AXIS_TLAST; |
| |
| always @(*) |
| S_AXIS_HLAST = S_AXIS_TUSER; |
| |
| /* |
| initial S_AXIS_SOF = 0; |
| always @(posedge S_AXI_ACLK) |
| if (!S_AXI_ARESETN) |
| S_AXIS_SOF <= 1'b0; |
| else if (S_AXIS_TVALID && S_AXIS_TREADY) |
| S_AXIS_SOF <= S_AXIS_VLAST; |
| */ |
| always @(*) |
| S_AXIS_SOF = 1'b0; |
| |
| // Verilator lint_off UNUSED |
| wire unused_sof; |
| assign unused_sof = &{ 1'b0, S_AXIS_SOF }; |
| // Verilator lint_on UNUSED |
| end endgenerate |
| |
| // Read/write signaling, lost_sync detection |
| // {{{ |
| assign reset_fifo = (!cfg_active || r_stopped || lost_sync); |
| |
| assign write_to_fifo = S_AXIS_TVALID && !lost_sync && !fifo_full; |
| assign write_data = S_AXIS_TDATA; |
| assign read_from_fifo = (M_AXI_WVALID && M_AXI_WREADY); |
| assign S_AXIS_TREADY = 1'b1; |
| |
| initial lost_sync = 1; |
| always @(posedge S_AXI_ACLK) |
| begin |
| if (!S_AXI_ARESETN || !cfg_active || r_stopped) |
| lost_sync <= 0; |
| else if (M_AXI_WVALID && M_AXI_WREADY |
| &&((!OPT_IGNORE_HLAST&&(wr_hlast != fifo_hlast)) |
| || (!wr_hlast && fifo_vlast) |
| || (wr_vlast && wr_hlast && !fifo_vlast))) |
| // Here is where we might possibly notice we've lost sync |
| lost_sync <= 1; |
| |
| // lost_sync <= 1'b0; |
| end |
| // }}} |
| |
| generate if (LGFIFO > 0) |
| begin : GEN_SPACE_AVAILBLE |
| // Here's where we'll put the actual outgoing FIFO |
| // {{{ |
| reg [LGFIFO:0] next_data_available; |
| always @(*) |
| begin |
| next_data_available = fifo_data_available; |
| // Verilator lint_off WIDTH |
| if (phantom_start) |
| next_data_available = fifo_data_available - (M_AXI_AWLEN + 1); |
| // Verilator lint_on WIDTH |
| if (write_to_fifo) |
| next_data_available = next_data_available + 1; |
| end |
| |
| initial fifo_data_available = 0; |
| initial fifo_bursts_available = 0; |
| always @(posedge i_clk) |
| if (reset_fifo) |
| begin |
| fifo_data_available <= 0; |
| fifo_bursts_available <= 0; |
| end else if (phantom_start || write_to_fifo) |
| begin |
| fifo_data_available <= next_data_available; |
| fifo_bursts_available <= |next_data_available[LGFIFO:LGMAXBURST]; |
| end |
| |
| sfifo #(.BW(C_AXI_DATA_WIDTH+2), .LGFLEN(LGFIFO)) |
| sfifo(i_clk, reset_fifo, |
| write_to_fifo, { S_AXIS_VLAST, |
| (!OPT_IGNORE_HLAST && S_AXIS_HLAST),write_data}, |
| fifo_full, fifo_fill, |
| read_from_fifo, { fifo_vlast, fifo_hlast, |
| fifo_data }, fifo_empty); |
| // }}} |
| end else begin : NO_FIFO |
| // {{{ |
| // |
| // This isn't verified or tested. I'm not sure I'd expect this |
| // to work at all. |
| assign fifo_full = M_AXI_WVALID && !M_AXI_WREADY; |
| assign fifo_fill = 0; |
| assign fifo_empty = !S_AXIS_TVALID; |
| assign fifo_vlast = S_AXIS_VLAST; |
| assign fifo_hlast = (!OPT_IGNORE_HLAST && S_AXIS_HLAST); |
| |
| assign fifo_data = write_data; |
| // }}} |
| end endgenerate |
| |
| // }}} |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // Outoing frame address counting |
| // {{{ |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // |
| reg w_frame_needs_alignment, frame_needs_alignment, |
| line_needs_alignment, |
| line_multiple_bursts, req_needs_alignment, req_multiple_bursts; |
| |
| // frame_needs_alignment |
| // {{{ |
| always @(*) |
| begin |
| w_frame_needs_alignment = 0; |
| if (|cfg_line_words[15-ADDRLSB:LGMAXBURST]) |
| w_frame_needs_alignment = 1; |
| if (~cfg_frame_addr[ADDRLSB +: LGMAXBURST] |
| < cfg_line_words[LGMAXBURST-1:0]) |
| w_frame_needs_alignment = 1; |
| if (cfg_frame_addr[ADDRLSB +: LGMAXBURST] == 0) |
| w_frame_needs_alignment = 0; |
| end |
| |
| always @(posedge i_clk) |
| if (!cfg_active && r_stopped) |
| frame_needs_alignment <= w_frame_needs_alignment; |
| // }}} |
| |
| // line_needs_alignment |
| // {{{ |
| always @(posedge i_clk) |
| if (r_stopped) |
| line_multiple_bursts <= (cfg_line_words >= (1<<LGMAXBURST)); |
| |
| always @(*) |
| if (!cfg_active || req_vlast) |
| next_line_addr = { 1'b0, cfg_frame_addr }; |
| else |
| next_line_addr = req_line_addr+ { {(C_AXI_ADDR_WIDTH-16){1'b0}}, cfg_line_step }; |
| |
| always @(posedge i_clk) |
| if (req_newline) |
| begin |
| line_needs_alignment <= 0; |
| if (|next_line_addr[ADDRLSB +: LGMAXBURST]) |
| begin |
| if (|cfg_line_words[15-ADDRLSB:LGMAXBURST]) |
| line_needs_alignment <= 1; |
| if (~next_line_addr[ADDRLSB +: LGMAXBURST] |
| < cfg_line_words[LGMAXBURST-1:0]) |
| line_needs_alignment <= 1; |
| end |
| end |
| // }}} |
| |
| // req_addr, req_line_addr, req_line_words |
| // {{{ |
| always @(*) |
| // Verilator lint_off WIDTH |
| next_line_words = req_line_words - (M_AXI_AWLEN+1); |
| // Verilator lint_on WIDTH |
| |
| initial req_addr = 0; |
| initial req_line_addr = 0; |
| always @(posedge i_clk) |
| if (i_reset || r_stopped) |
| begin |
| req_addr <= { 1'b0, cfg_frame_addr }; |
| req_line_addr <= { 1'b0, cfg_frame_addr }; |
| req_line_words <= cfg_line_words; |
| req_newline <= 1; |
| req_multiple_bursts <= (cfg_line_words >= (1<<LGMAXBURST)); |
| req_needs_alignment <= w_frame_needs_alignment; |
| end else if (phantom_start) |
| begin |
| req_newline <= req_hlast; |
| req_needs_alignment <= 0; |
| if (req_hlast && req_vlast) |
| begin |
| req_addr <= { 1'b0, cfg_frame_addr }; |
| req_line_addr <= { 1'b0, cfg_frame_addr }; |
| req_line_words <= cfg_line_words; |
| req_multiple_bursts <= line_multiple_bursts; |
| |
| req_needs_alignment <= frame_needs_alignment; |
| end else if (req_hlast) |
| begin |
| // verilator lint_off WIDTH |
| req_addr <= req_line_addr + cfg_line_step; |
| req_line_addr <= req_line_addr + cfg_line_step; |
| // verilator lint_on WIDTH |
| req_line_words <= cfg_line_words; |
| req_needs_alignment <= line_needs_alignment; |
| req_multiple_bursts <= line_multiple_bursts; |
| end else begin |
| req_addr <= req_addr + (1<<(LGMAXBURST+ADDRLSB)); |
| req_line_words <= next_line_words; |
| req_multiple_bursts <= |next_line_words[15-ADDRLSB:LGMAXBURST]; |
| |
| req_addr[LGMAXBURST+ADDRLSB-1:0] <= 0; |
| end |
| end else |
| req_newline <= 0; |
| // }}} |
| |
| // req_nlines, req_vlast |
| // {{{ |
| always @(posedge i_clk) |
| if (i_reset || r_stopped) |
| begin |
| req_nlines <= cfg_frame_lines-1; |
| req_vlast <= (cfg_frame_lines <= 1); |
| end else if (phantom_start && req_hlast) |
| begin |
| if (req_vlast) |
| begin |
| req_nlines <= cfg_frame_lines-1; |
| req_vlast <= (cfg_frame_lines <= 1); |
| end else begin |
| req_nlines <= req_nlines - 1; |
| req_vlast <= (req_nlines <= 1); |
| end |
| end |
| // }}} |
| |
| // }}} |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // Outgoing sync counting |
| // {{{ |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // |
| |
| // wr_line_beats, wr_hlast |
| // {{{ |
| always @(posedge i_clk) |
| if (i_reset || r_stopped) |
| begin |
| wr_line_beats <= cfg_line_words-1; |
| wr_hlast <= (cfg_line_words == 1); |
| end else if (M_AXI_WVALID && M_AXI_WREADY) |
| begin |
| if (wr_hlast) |
| begin |
| wr_line_beats <= cfg_line_words - 1; |
| wr_hlast <= (cfg_line_words <= 1); |
| end else begin |
| wr_line_beats <= wr_line_beats - 1; |
| wr_hlast <= (wr_line_beats <= 1); |
| end |
| end |
| // }}} |
| |
| // wr_lines, wr_vlast |
| // {{{ |
| always @(posedge i_clk) |
| if (i_reset || r_stopped) |
| begin |
| wr_lines <= cfg_frame_lines-1; |
| wr_vlast <= (cfg_frame_lines == 1); |
| end else if (M_AXI_WVALID && M_AXI_WREADY && wr_hlast) |
| begin |
| if (wr_vlast) |
| begin |
| wr_lines <= cfg_frame_lines - 1; |
| wr_vlast <= (cfg_frame_lines <= 1); |
| end else begin |
| wr_lines <= wr_lines - 1; |
| wr_vlast <= (wr_lines <= 1); |
| end |
| end |
| // }}} |
| |
| // }}} |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // The outgoing AXI (full) protocol section |
| // {{{ |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // |
| |
| // Some counters to keep track of our state |
| // {{{ |
| |
| // aw_bursts_outstanding |
| // {{{ |
| // Count the number of bursts outstanding--these are the number of |
| // AWVALIDs that have been accepted, but for which the BVALID |
| // has not (yet) been returned. |
| initial aw_none_outstanding = 1; |
| initial aw_bursts_outstanding = 0; |
| always @(posedge i_clk) |
| if (i_reset) |
| begin |
| aw_bursts_outstanding <= 0; |
| aw_none_outstanding <= 1; |
| end else case ({ phantom_start, M_AXI_BVALID && M_AXI_BREADY }) |
| 2'b01: begin |
| aw_bursts_outstanding <= aw_bursts_outstanding - 1; |
| aw_none_outstanding <= (aw_bursts_outstanding == 1); |
| end |
| 2'b10: begin |
| aw_bursts_outstanding <= aw_bursts_outstanding + 1; |
| aw_none_outstanding <= 0; |
| end |
| default: begin end |
| endcase |
| // }}} |
| |
| // r_stopped |
| // {{{ |
| // Following an error or drop of cfg_active, soft_reset will go high. |
| // We then come to a stop once everything becomes inactive |
| initial r_stopped = 1; |
| always @(posedge i_clk) |
| if (i_reset) |
| r_stopped <= 1; |
| else if (r_stopped) |
| begin |
| // Synchronize on the last pixel of an incoming frame |
| if (cfg_active && S_AXIS_TVALID && S_AXIS_VLAST) |
| r_stopped <= soft_reset; |
| end else if ((soft_reset || lost_sync) && aw_none_outstanding |
| && !M_AXI_AWVALID && !M_AXI_WVALID) |
| r_stopped <= 1; |
| // }}} |
| |
| // }}} |
| |
| // |
| // start_burst |
| // {{{ |
| always @(*) |
| begin |
| start_burst = 1; |
| if (LGFIFO > 0 && !fifo_bursts_available) |
| begin |
| if (!req_multiple_bursts && fifo_data_available[7:0] |
| < req_line_words[7:0]) |
| start_burst = 0; |
| if (req_multiple_bursts && !fifo_bursts_available) |
| start_burst = 0; |
| end |
| |
| if (phantom_start || req_newline || lost_sync) |
| start_burst = 0; |
| |
| // Can't start a new burst if the outgoing channel is still |
| // stalled. |
| if (M_AXI_AWVALID && !M_AXI_AWREADY) |
| start_burst = 0; |
| if (M_AXI_WVALID && (!M_AXI_WREADY || !M_AXI_WLAST)) |
| start_burst = 0; |
| |
| // Don't let our aw_bursts_outstanding counter overflow |
| if (aw_bursts_outstanding[15]) |
| start_burst = 0; |
| |
| // Don't wrap around memory |
| if (req_addr[C_AXI_ADDR_WIDTH]) |
| start_burst = 0; |
| |
| // If the user wants us to stop, then stop |
| if (soft_reset || !cfg_active || r_stopped) |
| start_burst = 0; |
| end |
| // }}} |
| |
| // AWLEN |
| // {{{ |
| always @(*) |
| till_boundary = ~req_addr[ADDRLSB +: LGMAXBURST]; |
| |
| always @(*) |
| begin |
| if (req_needs_alignment) |
| next_awlen = till_boundary; |
| else if (req_multiple_bursts) |
| next_awlen = (1<<LGMAXBURST)-1; |
| else |
| next_awlen = req_line_words[7:0]-1; |
| end |
| |
| always @(posedge i_clk) |
| if (!M_AXI_AWVALID || M_AXI_AWREADY) |
| axi_awlen <= next_awlen; |
| // }}} |
| |
| // wr_pending |
| // {{{ |
| initial wr_pending = 0; |
| always @(posedge i_clk) |
| begin |
| if (M_AXI_WVALID && M_AXI_WREADY) |
| wr_pending <= wr_pending - 1; |
| if (start_burst) |
| wr_pending <= next_awlen + 1; |
| |
| if (!S_AXI_ARESETN) |
| wr_pending <= 0; |
| end |
| // }}} |
| |
| // req_hlast |
| // {{{ |
| always @(posedge i_clk) |
| // Verilator lint_off WIDTH |
| if (r_stopped) |
| req_hlast <= (cfg_frame_addr[ADDRLSB +: LGMAXBURST] |
| + cfg_line_words <= (1<<LGMAXBURST)); |
| else if (phantom_start) |
| begin |
| if (req_hlast && req_vlast) |
| req_hlast <= (cfg_frame_addr[ADDRLSB +: LGMAXBURST] |
| + cfg_line_words <= (1<<LGMAXBURST)); |
| else if (req_hlast) |
| req_hlast <= (next_line_addr[ADDRLSB +: LGMAXBURST] |
| + cfg_line_words <= (1<<LGMAXBURST)); |
| else |
| req_hlast <= (req_line_words - (M_AXI_AWLEN+1) |
| <= (1<<LGMAXBURST)); |
| // Verilator lint_on WIDTH |
| end |
| // }}} |
| |
| // phantom_start |
| // {{{ |
| initial phantom_start = 0; |
| always @(posedge i_clk) |
| if (i_reset) |
| phantom_start <= 0; |
| else |
| phantom_start <= start_burst; |
| // }}} |
| |
| // AWVALID |
| // {{{ |
| initial axi_awvalid = 0; |
| always @(posedge i_clk) |
| if (i_reset) |
| axi_awvalid <= 0; |
| else if (!M_AXI_AWVALID || M_AXI_AWREADY) |
| axi_awvalid <= start_burst; |
| // }}} |
| |
| // ARADDR |
| // {{{ |
| initial axi_awaddr = 0; |
| always @(posedge i_clk) |
| begin |
| if (start_burst) |
| axi_awaddr <= req_addr[C_AXI_ADDR_WIDTH-1:0]; |
| |
| axi_awaddr[ADDRLSB-1:0] <= 0; |
| end |
| // }}} |
| |
| // WVALID |
| // {{{ |
| initial axi_wvalid = 0; |
| always @(posedge i_clk) |
| if (i_reset) |
| axi_wvalid <= 0; |
| else if (!M_AXI_WVALID || M_AXI_WREADY) |
| axi_wvalid <= start_burst || (M_AXI_WVALID && !M_AXI_WLAST); |
| // }}} |
| |
| // WLAST |
| // {{{ |
| always @(posedge i_clk) |
| begin |
| if (M_AXI_WVALID && M_AXI_WREADY) |
| axi_wlast <= (wr_pending <= 2); |
| |
| if (start_burst) |
| axi_wlast <= (next_awlen == 0); |
| end |
| // }}} |
| |
| |
| // Set the constant M_AXI_* signals |
| // {{{ |
| assign M_AXI_AWVALID= axi_awvalid; |
| assign M_AXI_AWID = AXI_ID; |
| assign M_AXI_AWADDR = axi_awaddr; |
| assign M_AXI_AWLEN = axi_awlen; |
| // Verilator lint_off WIDTH |
| assign M_AXI_AWSIZE = $clog2(C_AXI_DATA_WIDTH)-3; |
| // Verilator lint_on WIDTH |
| assign M_AXI_AWBURST= 2'b01; // INCR |
| assign M_AXI_AWLOCK = 0; |
| assign M_AXI_AWCACHE= 0; |
| assign M_AXI_AWPROT = 0; |
| assign M_AXI_AWQOS = 0; |
| // |
| assign M_AXI_WVALID = axi_wvalid; |
| assign M_AXI_WLAST = axi_wlast; |
| assign M_AXI_WDATA = fifo_data; |
| assign M_AXI_WSTRB = -1; |
| // |
| assign M_AXI_BREADY = 1; |
| // }}} |
| |
| // End AXI protocol section |
| // }}} |
| |
| // Verilator lint_off UNUSED |
| // {{{ |
| wire unused; |
| assign unused = &{ 1'b0, S_AXIL_AWPROT, S_AXIL_ARPROT, M_AXI_BID, |
| M_AXI_BRESP[0], fifo_empty, |
| S_AXIL_AWADDR[AXILLSB-1:0], S_AXIL_ARADDR[AXILLSB-1:0], |
| new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH], |
| new_control, new_config, fifo_fill, next_line_addr, |
| fifo_vlast, fifo_hlast |
| }; |
| // Verilator lint_on UNUSED |
| // }}} |
| // }}} |
| //////////////////////////////////////////////////////////////////////////////// |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Formal properties |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| //////////////////////////////////////////////////////////////////////////////// |
| `ifdef FORMAL |
| // {{{ |
| |
| // The formal properties for this core are maintained elsewhere |
| |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // Contract checks |
| // {{{ |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // |
| |
| // The formal proof for this core doesn't (yet) include a contract |
| // check. |
| |
| // }}} |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // Simplifying (careless) assumptions |
| // {{{ |
| //////////////////////////////////////////////////////////////////////// |
| // |
| // |
| |
| // If the FIFO gets reset, then we really don't care about what |
| // gets written to memory. However, it is possible that a value |
| // on the output might get changed and so violate our protocol checker. |
| // (We don't care.) So let's just assume it never happens, and check |
| // everything else instead. |
| always @(*) |
| if (M_AXI_WVALID) |
| assume(!lost_sync && cfg_active); |
| // }}} |
| // }}} |
| `endif |
| endmodule |