diff options
Diffstat (limited to 'rtl/wb2axip/axivcamera.v')
| -rw-r--r-- | rtl/wb2axip/axivcamera.v | 1219 |
1 files changed, 1219 insertions, 0 deletions
diff --git a/rtl/wb2axip/axivcamera.v b/rtl/wb2axip/axivcamera.v new file mode 100644 index 0000000..d5de9ad --- /dev/null +++ b/rtl/wb2axip/axivcamera.v @@ -0,0 +1,1219 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// 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-2024, 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; + // }}} + + 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 : GEN_LARGE_AW + + assign new_cmdaddrhi = apply_wstrb( + wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], + wskd_data, wskd_strb); + + end else begin : SINGLE_WORD_AW + + 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 + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + wire 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; + + assign S_AXIS_HLAST = S_AXIS_TLAST; + assign 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); + + assign S_AXIS_VLAST = axis_last_line && S_AXIS_HLAST; + + end else begin : VLAST_LOCK + + assign S_AXIS_VLAST = S_AXIS_TLAST; + + assign 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; + */ + assign 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 + // }}} + + // Make Verilator happy + // {{{ + // 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 |
