blob: 0b042b5d1eed619d42f411c26508769ba3fcb756 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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