diff options
Diffstat (limited to 'rtl/wb2axip/axivdisplay.v')
| -rw-r--r-- | rtl/wb2axip/axivdisplay.v | 1438 |
1 files changed, 1438 insertions, 0 deletions
diff --git a/rtl/wb2axip/axivdisplay.v b/rtl/wb2axip/axivdisplay.v new file mode 100644 index 0000000..aac6c62 --- /dev/null +++ b/rtl/wb2axip/axivdisplay.v @@ -0,0 +1,1438 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axivdisplay +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Reads from memory for the purposes of serving a video frame +// {{{ +// buffer. The result of this core is an AXI stream having the +// word size of the memory interface in the first place. TLAST is set +// based upon the end of the frame, and TUSER based upon the end of the +// line. An option exists to use Xilinx's encoding instead where TLAST +// is the end of the line and TUSER is the first pixel word of the frame. +// +// This core is in many ways similar to the AXI MM2S core, but with some +// key differences: The AXI MM2S core generates a single transfer. This +// core generates a repeating set of transfers--one per line, repeated +// per frame. +// +// To insure high bandwidth, data is first copied into a FIFO of twice +// the width of the longest burst. +// +// }}} +// 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. +// +// 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 into 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. +// +// 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) +// +// BUGS: +// 1. Memory reads are currently allowed to wrap around the end of memory. +// I'm not (yet) sure if this is a good thing or a bad thing. I mean, its +// a great thing for small dedicated memories designated for this purpose +// alone, but perhaps a bad thing if the memory space is shared with +// peripherals as well as a CPU. +// +// 2. I'd still like to add an option to handle memory addresses that +// aren't aligned. Until that is done, the means of interacting with the +// core will change from one implementation to another. +// +// 3. If the configuration changes mid-write, but without de-activating +// the core and waiting for it to come to idle, the synchronization flags +// might out-of-sync with the pixels. To resolve, deactivate the core and +// let it come to whenever changing configuration parameters. +// }}} +// +// 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 axivdisplay #( + // {{{ + 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_UNALIGNED: Allow unaligned accesses, address requests + // and sizes which may or may not match the underlying data + // width. If set, the core will quietly align these requests. + // parameter [0:0] OPT_UNALIGNED = 1'b0, + // + // 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 LGFIFO = OPT_LGMAXBURST+1, + // + // AXI_ID is the ID we will use for all of our AXI transactions + parameter AXI_ID = 0, + // + // OPT_TUSER_IS_SOF. Xilinx and I chose different stream + // encodings. I encode TLAST== VLAST and + // TUSER == (optional) HLAST. Xilinx chose 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 video stream interface + // {{{ + output wire M_AXIS_TVALID, + input wire M_AXIS_TREADY, + output wire [C_AXI_DATA_WIDTH-1:0] M_AXIS_TDATA, + output wire /* VLAST or HLAST */ M_AXIS_TLAST, + output wire /* HLAST or SOF */ M_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) read interface + // {{{ + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [7:0] M_AXI_ARLEN, + output wire [2:0] M_AXI_ARSIZE, + output wire [1:0] M_AXI_ARBURST, + output wire M_AXI_ARLOCK, + output wire [3:0] M_AXI_ARCACHE, + output wire [2:0] M_AXI_ARPROT, + output wire [3:0] M_AXI_ARQOS, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire M_AXI_RLAST, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [1:0] M_AXI_RRESP + // }}} + // }}} + ); + + // 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; + + 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; + reg cfg_active, cfg_zero_length, cfg_dirty; + 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; + wire [LGFIFO:0] fifo_fill; + wire fifo_full, fifo_empty; + + 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 partial_burst_requested; + // reg [LGMAXBURST-1:0] addralign; + // reg [LGFIFO:0] rd_uncommitted; + // reg [LGMAXBURST:0] initial_burstlen; + // reg [LGLENWA-1:0] rd_reads_remaining; + // reg rd_none_remaining, + // rd_last_remaining; + + // wire realign_last_valid; + + reg axi_arvalid, lag_start, phantom_start, start_burst, + ar_none_outstanding; + reg [C_AXI_ADDR_WIDTH-1:0] axi_araddr; + reg [LGMAXBURST:0] max_burst; + reg [7:0] axi_arlen; + reg [15:0] ar_bursts_outstanding; + // + wire vlast, hlast; + reg [15:0] r_frame_lines, r_line_step; + reg [16-ADDRLSB-1:0] r_line_words; + + reg req_hlast, req_vlast; + reg [15:0] req_nlines; + reg [16-ADDRLSB-1:0] req_line_words; + reg [C_AXI_ADDR_WIDTH:0] req_addr, req_line_addr; + // + reg rd_hlast, rd_vlast; + reg [15:0] rd_lines; + reg [16-ADDRLSB-1:0] rd_line_beats; + wire no_fifo_space_available; + // + reg [LGMAXBURST-1:0] till_boundary; + reg [LGFIFO:0] fifo_space_available; + + +`ifdef FORMAL + reg [C_AXI_ADDR_WIDTH:0] f_rd_line_addr, f_rd_addr; +`endif + + wire M_AXIS_VLAST, M_AXIS_HLAST; + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // 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 (soft_reset) + begin + + if ((axil_write_ready && awskd_addr == FBUF_CONTROL) + && wskd_strb[0] && wskd_data[CBIT_ERR]) + r_err <= cfg_zero_length; + + if (cfg_active && !r_err && r_stopped) + 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_RREADY && M_AXI_RVALID && M_AXI_RRESP[1]) + 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 : GEN_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_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); + 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); + cfg_dirty <= 0; + end else begin + if (r_stopped) + cfg_dirty <= 0; + if (cfg_active && req_hlast && req_vlast && phantom_start) + cfg_dirty <= 0; + if (M_AXI_RREADY && M_AXI_RVALID && M_AXI_RRESP[1]) + cfg_active <= 0; + + if (axil_write_ready) + case(awskd_addr) + FBUF_CONTROL: begin + if (wskd_strb[0]) + cfg_active <= wskd_data[CBIT_ACTIVE] + && (!r_err || wskd_data[CBIT_ERR]) + && (!cfg_zero_length); + + if (new_control[31:16] == 0) + begin + cfg_line_step <= 0; + cfg_line_step[16-1:ADDRLSB] <= cfg_line_words; + // Verilator lint_off WIDTH + cfg_dirty <= (cfg_line_step + != (cfg_line_words << ADDRLSB)); + // Verilator lint_on WIDTH + end else begin + cfg_line_step <= new_control[31:16]; + cfg_dirty <= (cfg_line_step != new_control[31:16]); + end end + FBUF_FRAMEINFO: begin + { cfg_frame_lines, cfg_line_words } + <= new_config[C_AXI_DATA_WIDTH-1:ADDRLSB]; + cfg_zero_length <= (new_config[31:16] == 0) + ||(new_config[15:ADDRLSB] == 0); + if ((new_config[31:16] == 0) + ||(new_config[15:ADDRLSB] == 0)) + cfg_active <= 0; + cfg_dirty <= 1; + end + FBUF_ADDRLO, FBUF_ADDRHI: begin + cfg_frame_addr <= new_wideaddr[C_AXI_ADDR_WIDTH-1:0]; + cfg_dirty <= 1; + end + default: begin end + endcase + end + // }}} + + // AXI-Lite read register data + // {{{ + always @(*) + begin + w_status_word = 0; + w_status_word[31:16] = cfg_line_step; + 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 + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + // {{{ + assign reset_fifo = r_stopped; + + assign write_to_fifo = M_AXI_RVALID; + assign write_data = M_AXI_RDATA; + // assign realign_last_valid = 0; + assign vlast = rd_vlast; + assign hlast = rd_hlast; + assign M_AXI_RREADY = !fifo_full; + // }}} + + generate if (LGFIFO > 0) + begin : GEN_SPACE_AVAILBLE + // Here's where we'll put the actual outgoing FIFO + // {{{ + initial fifo_space_available = (1<<LGFIFO); + always @(posedge i_clk) + if (reset_fifo) + fifo_space_available <= (1<<LGFIFO); + else case({phantom_start, read_from_fifo && !fifo_empty }) + 2'b00: begin end + // Verilator lint_off WIDTH + 2'b10: fifo_space_available <= fifo_space_available - (M_AXI_ARLEN+1); + 2'b11: fifo_space_available <= fifo_space_available - (M_AXI_ARLEN); + // Verilator lint_on WIDTH + 2'b01: fifo_space_available <= fifo_space_available + 1; + endcase + + assign M_AXIS_TVALID = !fifo_empty; + assign read_from_fifo = M_AXIS_TVALID && M_AXIS_TREADY; + + sfifo #(.BW(C_AXI_DATA_WIDTH+2), .LGFLEN(LGFIFO)) + sfifo(i_clk, reset_fifo, + write_to_fifo, { vlast && hlast, hlast, write_data }, + fifo_full, fifo_fill, + read_from_fifo, { M_AXIS_VLAST, M_AXIS_HLAST, + M_AXIS_TDATA }, fifo_empty); + + assign no_fifo_space_available = (fifo_space_available < (1<<LGMAXBURST)); + // }}} + end else begin : NO_FIFO + // {{{ + assign fifo_full = !M_AXIS_TREADY; + assign fifo_fill = 0; + assign fifo_empty = !M_AXIS_TVALID; + assign M_AXIS_TVALID = write_to_fifo; + + assign M_AXIS_VLAST = vlast && hlast; + assign M_AXIS_HLAST = hlast; + assign M_AXIS_TDATA = M_AXI_RDATA; + + assign no_fifo_space_available = (ar_bursts_outstanding >= 3); + // }}} + end endgenerate + // }}} + + // Switch between TLAST/TUSER encodings if necessary + // {{{ + generate if (OPT_TUSER_IS_SOF) + begin : XILINX_ENCODING + + reg SOF; + + initial SOF = 1'b1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || r_stopped) + SOF <= 1'b1; + else if (M_AXIS_TVALID && M_AXIS_TREADY) + SOF <= M_AXIS_VLAST; + + assign M_AXIS_TLAST = M_AXIS_HLAST; + assign M_AXIS_TUSER = SOF; + + end else begin : VLAST_EQUALS_TLAST + + assign M_AXIS_TLAST = M_AXIS_VLAST; + assign M_AXIS_TUSER = M_AXIS_HLAST; + end endgenerate + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Outoing frame address counting + // + //////////////////////////////////////////////////////////////////////// + // + // + // {{{ + always @(posedge i_clk) + if (i_reset || r_stopped || (phantom_start && req_hlast && req_vlast)) + begin + r_frame_lines <= cfg_frame_lines; + r_line_words <= cfg_line_words; + r_line_step <= { {(ADDRLSB){1'b0}}, cfg_line_step[15:ADDRLSB] }; + end + + 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; + end else if (phantom_start) + begin + 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; + end else if (req_hlast) + begin + // verilator lint_off WIDTH + req_addr <= req_line_addr + + (r_line_step << M_AXI_ARSIZE); + req_line_addr <= req_line_addr + + (r_line_step << M_AXI_ARSIZE); + // verilator lint_on WIDTH + req_line_words <= r_line_words; + end else begin + // verilator lint_off WIDTH + req_addr <= req_addr + (1<<(LGMAXBURST+ADDRLSB)); + req_line_words <= req_line_words - (M_AXI_ARLEN+1); + // verilator lint_on WIDTH + + req_addr[LGMAXBURST+ADDRLSB-1:0] <= 0; + end + end + + 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 +`ifdef FORMAL + always @(*) + if (!r_stopped) + begin + assert(req_vlast == (req_nlines == 0)); + assert(req_addr >= { 1'b0, cfg_frame_addr }); + assert(req_line_addr >= { 1'b0, cfg_frame_addr }); + assert(req_line_addr <= req_addr); + end + + always @(*) + if (cfg_active) + begin + assert(cfg_frame_lines != 0); + assert(cfg_line_words != 0); + end + + always @(*) + if (!r_stopped) + begin + assert(r_frame_lines != 0); + assert(r_line_words != 0); + end +`endif + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Incoming frame address counting + // + //////////////////////////////////////////////////////////////////////// + // + // + // {{{ + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + rd_line_beats <= cfg_line_words-1; + rd_hlast <= (cfg_line_words == 1); + end else if (M_AXI_RVALID && M_AXI_RREADY) + begin + if (rd_hlast) + begin + rd_line_beats <= r_line_words - 1; + rd_hlast <= (r_line_words <= 1); + end else begin + rd_line_beats <= rd_line_beats - 1; + rd_hlast <= (rd_line_beats <= 1); + end + end + + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + rd_lines <= cfg_frame_lines-1; + rd_vlast <= (cfg_frame_lines == 1); + end else if (M_AXI_RVALID && M_AXI_RREADY && rd_hlast) + begin + if (vlast) + begin + rd_lines <= r_frame_lines - 1; + rd_vlast <= (r_frame_lines <= 1); + end else begin + rd_lines <= rd_lines - 1; + rd_vlast <= (rd_lines <= 1); + end + end + +`ifdef FORMAL + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + f_rd_addr <= { 1'b0, cfg_frame_addr }; + f_rd_line_addr <= { 1'b0, cfg_frame_addr }; + end else if (M_AXI_RVALID && M_AXI_RREADY) + begin + if (rd_vlast && rd_hlast) + begin + f_rd_addr <= cfg_frame_addr; + f_rd_line_addr <= cfg_frame_addr; + end else if (rd_hlast) + begin + f_rd_addr <= f_rd_line_addr + (r_line_step << M_AXI_ARSIZE); + f_rd_line_addr <= f_rd_line_addr + (r_line_step << M_AXI_ARSIZE); + end else begin + f_rd_addr <= f_rd_addr + (1<<ADDRLSB); + end + end +`endif + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The incoming AXI (full) protocol section + // + //////////////////////////////////////////////////////////////////////// + // + // + // {{{ + + // Some counters to keep track of our state + // {{{ + + // ar_bursts_outstanding + // {{{ + // Count the number of bursts outstanding--these are the number of + // ARVALIDs that have been accepted, but for which the RVALID && RLAST + // has not (yet) been returned. + initial ar_none_outstanding = 1; + initial ar_bursts_outstanding = 0; + always @(posedge i_clk) + if (i_reset) + begin + ar_bursts_outstanding <= 0; + ar_none_outstanding <= 1; + end else case ({ phantom_start, + M_AXI_RVALID && M_AXI_RREADY && M_AXI_RLAST }) + 2'b01: begin + ar_bursts_outstanding <= ar_bursts_outstanding - 1; + ar_none_outstanding <= (ar_bursts_outstanding == 1); + end + 2'b10: begin + ar_bursts_outstanding <= ar_bursts_outstanding + 1; + ar_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) + r_stopped <= soft_reset || !cfg_active; + else if (soft_reset && ar_none_outstanding && !M_AXI_ARVALID) + r_stopped <= 1; + // }}} + + // }}} + + // + // start_burst + // {{{ + always @(*) + begin + start_burst = 1; + if (no_fifo_space_available) + start_burst = 0; + if (phantom_start || lag_start) + // Insist on a minimum of two clocks between burst + // starts, so we can get our lengths right + start_burst = 0; + + // Can't start a new burst if the outgoing channel is still + // stalled. + if (M_AXI_ARVALID && !M_AXI_ARREADY) + start_burst = 0; + + // If the user wants us to stop, then stop + if (!cfg_active || soft_reset) + start_burst = 0; + end + // }}} + + // ARLEN + // {{{ + // Coming in, req_addr and req_line_words can be trusted + // lag_start will be true to reflect this + always @(posedge i_clk) + if (lag_start) + begin + if (req_line_words >= (1<<LGMAXBURST)) + max_burst <= (1<<LGMAXBURST); + else + // Verilator lint_off WIDTH + max_burst <= req_line_words; + // Verilator lint_on WIDTH + end + + always @(*) + till_boundary = ~req_addr[ADDRLSB +: LGMAXBURST]; + + always @(posedge i_clk) + if (!M_AXI_ARVALID || M_AXI_ARREADY) + begin + // Verilator lint_off WIDTH + if (till_boundary > 0 && max_burst <= till_boundary) + axi_arlen <= max_burst-1; + // Verilator lint_on WIDTH + else + axi_arlen <= till_boundary; + end + // }}} + + // req_hlast + // {{{ + always @(posedge i_clk) + if (lag_start || start_burst) + begin + req_hlast <= 1; + // Verilator lint_off WIDTH + if (req_line_words > till_boundary+1) + req_hlast <= 0; + if (req_line_words > max_burst) + req_hlast <= 0; + // Verilator lint_on WIDTH + end + +`ifdef FORMAL + always @(*) + if (phantom_start) + begin + if (req_hlast) + begin + assert(axi_arlen+1 == req_line_words); + end else + assert(axi_arlen+1 < req_line_words); + end else if (!soft_reset && !r_stopped && !lag_start) + begin + if (max_burst != req_line_words) + begin + assert(!req_hlast); + end else if (!req_hlast && !M_AXI_ARVALID) + assert(axi_arlen < max_burst); + + assert(max_burst > 0); + if (req_line_words > (1<<LGMAXBURST)) + begin + assert(max_burst == (1<<LGMAXBURST)); + end else + assert(max_burst == req_line_words); + end +`endif + // }}} + + // phantom_start + // {{{ + initial phantom_start = 0; + always @(posedge i_clk) + if (i_reset) + phantom_start <= 0; + else + phantom_start <= start_burst; + + initial lag_start = 1; + always @(posedge i_clk) + if (i_reset || r_stopped) + lag_start <= 1; + else + lag_start <= phantom_start; + // }}} + + // ARVALID + // {{{ + initial axi_arvalid = 0; + always @(posedge i_clk) + if (i_reset) + axi_arvalid <= 0; + else if (!M_AXI_ARVALID || M_AXI_ARREADY) + axi_arvalid <= start_burst; + // }}} + + // ARADDR + // {{{ + initial axi_araddr = 0; + always @(posedge i_clk) + begin + if (start_burst) + axi_araddr <= req_addr[C_AXI_ADDR_WIDTH-1:0]; + + axi_araddr[ADDRLSB-1:0] <= 0; + end + // }}} + + // Set the constant M_AXI_* signals + // {{{ + assign M_AXI_ARVALID= axi_arvalid; + assign M_AXI_ARID = AXI_ID; + assign M_AXI_ARADDR = axi_araddr; + assign M_AXI_ARLEN = axi_arlen; + // Verilator lint_off WIDTH + assign M_AXI_ARSIZE = $clog2(C_AXI_DATA_WIDTH)-3; + // Verilator lint_on WIDTH + assign M_AXI_ARBURST= 2'b01; // INCR + assign M_AXI_ARLOCK = 0; + assign M_AXI_ARCACHE= 0; + assign M_AXI_ARPROT = 0; + assign M_AXI_ARQOS = 0; + // }}} + + // 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_RID, + M_AXI_RRESP[0], fifo_full, wskd_strb[2:0], + 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 }; + // Verilator lint_on UNUSED + // }}} + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // The following are only a subset of the formal properties currently + // being used to verify this module. They are not expected to be + // syntactically accurate. + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + // + // ... + // + + //////////////////////////////////////////////////////////////////////// + // + // Properties of the AXI-stream data interface + // {{{ + // + //////////////////////////////////////////////////////////////////////// + // + // + + // (These are captured by the FIFO within) + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The AXI-lite control interface + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + localparam F_AXIL_LGDEPTH = 4; + wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding, + faxil_wr_outstanding, faxil_awr_outstanding; + + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXIL_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXIL_ADDR_WIDTH), + .F_LGDEPTH(F_AXIL_LGDEPTH), + .F_AXI_MAXWAIT(2), + .F_AXI_MAXDELAY(2), + .F_AXI_MAXRSTALL(3) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXIL_AWVALID), + .i_axi_awready(S_AXIL_AWREADY), + .i_axi_awaddr( S_AXIL_AWADDR), + .i_axi_awprot( S_AXIL_AWPROT), + // + .i_axi_wvalid(S_AXIL_WVALID), + .i_axi_wready(S_AXIL_WREADY), + .i_axi_wdata( S_AXIL_WDATA), + .i_axi_wstrb( S_AXIL_WSTRB), + // + .i_axi_bvalid(S_AXIL_BVALID), + .i_axi_bready(S_AXIL_BREADY), + .i_axi_bresp( S_AXIL_BRESP), + // + .i_axi_arvalid(S_AXIL_ARVALID), + .i_axi_arready(S_AXIL_ARREADY), + .i_axi_araddr( S_AXIL_ARADDR), + .i_axi_arprot( S_AXIL_ARPROT), + // + .i_axi_rvalid(S_AXIL_RVALID), + .i_axi_rready(S_AXIL_RREADY), + .i_axi_rdata( S_AXIL_RDATA), + .i_axi_rresp( S_AXIL_RRESP), + // + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + ); + + always @(*) + begin + assert(faxil_rd_outstanding == (S_AXIL_RVALID ? 1:0) + +(S_AXIL_ARREADY ? 0:1)); + assert(faxil_wr_outstanding == (S_AXIL_BVALID ? 1:0) + +(S_AXIL_WREADY ? 0:1)); + assert(faxil_awr_outstanding== (S_AXIL_BVALID ? 1:0) + +(S_AXIL_AWREADY ? 0:1)); + end + + always @(*) + assert(cfg_zero_length == + ((cfg_line_words == 0)||(cfg_frame_lines == 0))); + + always @(*) + if (cfg_zero_length) + assert(!cfg_active); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The AXI master memory interface + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // + localparam F_AXI_LGDEPTH = 11; // LGLENW-LGMAXBURST+2 ?? + + // + // ... + // + + faxi_master #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + // + // ... + // + // }}} + ) faxi( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // The (unused) write interface + // {{{ + .i_axi_awvalid(1'b0), + .i_axi_awready(1'b0), + .i_axi_awid( AXI_ID), + .i_axi_awaddr( 0), + .i_axi_awlen( 0), + .i_axi_awsize( 0), + .i_axi_awburst(0), + .i_axi_awlock( 0), + .i_axi_awcache(0), + .i_axi_awprot( 0), + .i_axi_awqos( 0), + // + .i_axi_wvalid(0), + .i_axi_wready(0), + .i_axi_wdata( 0), + .i_axi_wstrb( 0), + .i_axi_wlast( 0), + // + .i_axi_bvalid(1'b0), + .i_axi_bready(1'b0), + .i_axi_bid( AXI_ID), + .i_axi_bresp( 2'b00), + // }}} + // The read interface + // {{{ + .i_axi_arvalid(M_AXI_ARVALID), + .i_axi_arready(M_AXI_ARREADY), + .i_axi_arid( M_AXI_ARID), + .i_axi_araddr( M_AXI_ARADDR), + .i_axi_arlen( M_AXI_ARLEN), + .i_axi_arsize( M_AXI_ARSIZE), + .i_axi_arburst(M_AXI_ARBURST), + .i_axi_arlock( M_AXI_ARLOCK), + .i_axi_arcache(M_AXI_ARCACHE), + .i_axi_arprot( M_AXI_ARPROT), + .i_axi_arqos( M_AXI_ARQOS), + // + .i_axi_rvalid(M_AXI_RVALID), + .i_axi_rready(M_AXI_RREADY), + .i_axi_rdata( M_AXI_RDATA), + .i_axi_rlast( M_AXI_RLAST), + .i_axi_rresp( M_AXI_RRESP), + // ... + // }}} + ); + + + // + // ... + // + always @(*) + begin + // ... + + assert(M_AXI_ARBURST == 2'b01); + end + + always @(*) + if (faxi_rd_ckvalid) + begin + assert(!r_stopped); + // + // ... + // + end + + // + // ... + // + + reg [LGMAXBURST-1:0] f_rd_subaddr; + always @(*) + f_rd_subaddr = f_rd_addr[ADDRLSB +: LGMAXBURST]; + + always @(*) + begin + assert(cfg_frame_addr[ADDRLSB-1:0] == 0); + if (!r_stopped) + begin + assert(req_addr[ADDRLSB-1:0] == 0); + assert(req_line_addr[ADDRLSB-1:0] == 0); + end + + if (M_AXI_RVALID) + assert(M_AXI_RLAST == (rd_hlast || (&f_rd_subaddr))); + end + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read request to return address correlations + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + if (M_AXI_RVALID) + begin + if (rd_hlast || (&f_rd_subaddr)) + begin + // ... + assert(M_AXI_RLAST); + end else + // ... + assume(!M_AXI_RLAST); + end + end + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Other formal properties + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Contract checks + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + reg cvr_full_frame, cvr_full_size; + (* anyconst *) reg cvr_hlast_rlast; + + always @(posedge i_clk) + if (r_stopped) + cvr_full_size <= (cfg_line_step >= cfg_line_words) + &&(cfg_line_words > 5) + &&(cfg_frame_lines > 4); + else begin + if ($changed(cfg_line_step)) + cvr_full_size <= 0; + if ($changed(cfg_line_words)) + cvr_full_size <= 0; + if ($changed(cfg_frame_lines)) + cvr_full_size <= 0; + if ($changed(cfg_frame_addr)) + cvr_full_size <= 0; + end + + always @(posedge i_clk) + if (r_stopped || !cvr_full_size) + cvr_full_frame <= 0; + else if (!cvr_full_frame) + cvr_full_frame <= rd_vlast && rd_hlast && M_AXI_RVALID && M_AXI_RREADY; + + always @(*) + cover(!soft_reset); + + always @(*) + cover(start_burst); + + always @(*) + cover(M_AXI_ARVALID && M_AXI_ARREADY); + + always @(*) + cover(M_AXI_RVALID); + + always @(*) + cover(M_AXI_RVALID & M_AXI_RLAST); + + always @(*) + cover(!r_stopped && cvr_full_frame); + + always @(*) + cover(cvr_full_frame && phantom_start && !r_stopped); + + always @(*) + if (cvr_hlast_rlast) + begin + // assume(M_AXI_RLAST == (rd_hlast && M_AXI_RVALID)); + assume(M_AXI_ARREADY && M_AXI_RREADY); + assume(M_AXIS_TREADY); + assume(cfg_frame_addr[12:0] == 0); + assume(cfg_line_step[3:0] == 0); + end + + always @(*) + cover(cvr_hlast_rlast && cvr_full_frame && phantom_start && !r_stopped); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Simplifying (careless) assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(posedge i_clk) + if (!r_stopped || cfg_active) + begin + assume($stable(cfg_frame_addr)); + assume($stable(cfg_frame_lines)); + assume($stable(cfg_line_words)); + assume($stable(cfg_line_step)); + end + + + always @(*) + assume(!f_sequential); + + always @(*) + assume(!f_biglines); + + always @(*) + assume(!req_addr[C_AXI_ADDR_WIDTH]); + + always @(*) + assume(!req_line_addr[C_AXI_ADDR_WIDTH]); + // }}} + // }}} +`endif +endmodule |
