////////////////////////////////////////////////////////////////////////////////
//
// Filename:	rtl/axi2axi3.v
// {{{
// Project:	WB2AXIPSP: bus bridges and other odds and ends
//
// Purpose:	Bridge from an AXI4 slave to an AXI3 master
//
//	The goal here is to not lose bus resolution, capacity or capability,
//	while bridging from AXI4 to AXI3.  The biggest problem with such
//	a bridge is that we'll need to break large requests (AxLEN>15) up
//	into smaller packets.  After that, everything should work as normal
//	with only minor modifications for AxCACHE.
//
// Opportunity:
//	The cost of this core is very much determined by the number of ID's
//	supported.  It should be possible, with little extra cost or effort,
//	to reduce the ID space herein.  This should be a topic for future
//	exploration.
//
// Status:
//	This core is an unverified first draft.  It has past neither formal
//	nor simulation testing.  Therefore, it is almost certain to have bugs
//	within it.  Use it at your own risk.
//
// Creator:	Dan Gisselquist, Ph.D.
//		Gisselquist Technology, LLC
//
////////////////////////////////////////////////////////////////////////////////
// }}}
// Copyright (C) 2019-2025, 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	axi2axi3 #(
		// {{{
		parameter	C_AXI_ID_WIDTH = 1,
		parameter	C_AXI_ADDR_WIDTH = 32,
		parameter	C_AXI_DATA_WIDTH = 32
		// }}}
	) (
		// {{{
		input	wire				S_AXI_ACLK,
		input	wire				S_AXI_ARESETN,
		//
		// The AXI4 incoming/slave interface
		input	reg				S_AXI_AWVALID,
		output	wire				S_AXI_AWREADY,
		input	reg	[C_AXI_ID_WIDTH-1:0]	S_AXI_AWID,
		input	reg	[C_AXI_ADDR_WIDTH-1:0]	S_AXI_AWADDR,
		input	reg	[7:0]			S_AXI_AWLEN,
		input	reg	[2:0]			S_AXI_AWSIZE,
		input	reg	[1:0]			S_AXI_AWBURST,
		input	reg				S_AXI_AWLOCK,
		input	reg	[3:0]			S_AXI_AWCACHE,
		input	reg	[2:0]			S_AXI_AWPROT,
		input	reg	[3:0]			S_AXI_AWQOS,
		//
		//
		input	wire				S_AXI_WVALID,
		output	wire				S_AXI_WREADY,
		input	wire	[C_AXI_DATA_WIDTH-1:0]	S_AXI_WDATA,
		input	wire [C_AXI_DATA_WIDTH/8-1:0]	S_AXI_WSTRB,
		input	wire				S_AXI_WLAST,
		//
		//
		output	wire				S_AXI_BVALID,
		input	wire				S_AXI_BREADY,
		output	wire	[C_AXI_ID_WIDTH-1:0]	S_AXI_BID,
		output	wire	[1:0]			S_AXI_BRESP,
		//
		//
		input	wire				S_AXI_ARVALID,
		output	wire				S_AXI_ARREADY,
		input	wire	[C_AXI_ID_WIDTH-1:0]	S_AXI_ARID,
		input	wire	[C_AXI_ADDR_WIDTH-1:0]	S_AXI_ARADDR,
		input	wire	[7:0]			S_AXI_ARLEN,
		input	wire	[2:0]			S_AXI_ARSIZE,
		input	wire	[1:0]			S_AXI_ARBURST,
		input	wire				S_AXI_ARLOCK,
		input	wire	[3:0]			S_AXI_ARCACHE,
		input	wire	[2:0]			S_AXI_ARPROT,
		input	wire	[3:0]			S_AXI_ARQOS,
		//
		output	wire				S_AXI_RVALID,
		input	wire				S_AXI_RREADY,
		output	wire	[C_AXI_ID_WIDTH-1:0]	S_AXI_RID,
		output	wire	[C_AXI_DATA_WIDTH-1:0]	S_AXI_RDATA,
		output	wire				S_AXI_RLAST,
		output	wire	[1:0]			S_AXI_RRESP,
		//
		//
		// The AXI3 Master (outgoing) 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	[3:0]			M_AXI_AWLEN,
		output	wire	[2:0]			M_AXI_AWSIZE,
		output	wire	[1:0]			M_AXI_AWBURST,
		output	wire	[1:0]			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_ID_WIDTH-1:0]	M_AXI_WID,
		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,
		//
		//
		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	[3:0]			M_AXI_ARLEN,
		output	wire	[2:0]			M_AXI_ARSIZE,
		output	wire	[1:0]			M_AXI_ARBURST,
		output	wire	[1:0]			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_ID_WIDTH-1:0]	M_AXI_RID,
		input	wire	[C_AXI_DATA_WIDTH-1:0]	M_AXI_RDATA,
		input	wire				M_AXI_RLAST,
		input	wire	[1:0]			M_AXI_RRESP
		// }}}
	);

	//
	localparam		ADDRLSB= $clog2(C_AXI_DATA_WIDTH)-3;
	localparam	[0:0]	OPT_LOWPOWER = 1'b0;
	localparam		LGWFIFO = 4;
	localparam		NID = (1<<C_AXI_ID_WIDTH);
	parameter		LGFIFO = 8;

	genvar	ID;

	////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////
	//
	// WRITE SIDE
	// {{{
	////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////

	////////////////////////////////////////////////////////////////////////
	//
	// Write address channel
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//
	reg	[7:0]			r_awlen;
	reg	[3:0]			next_wlen;
	wire				awskd_valid;
	reg				awskd_ready;
	wire	[C_AXI_ID_WIDTH-1:0]	awskd_id;
	wire	[C_AXI_ADDR_WIDTH-1:0]	awskd_addr;
	wire	[7:0]			awskd_len;
	wire	[2:0]			awskd_size;
	wire	[1:0]			awskd_burst;
	wire				awskd_lock;
	wire	[3:0]			awskd_cache;
	wire	[2:0]			awskd_prot;
	wire	[3:0]			awskd_qos;
	reg				axi_awvalid;
	reg	[C_AXI_ID_WIDTH-1:0]	axi_awid;
	reg	[C_AXI_ADDR_WIDTH-1:0]	axi_awaddr;
	reg	[3:0]			axi_awlen;
	reg	[2:0]			axi_awsize;
	reg	[1:0]			axi_awburst;
	reg	[1:0]			axi_awlock;
	reg	[3:0]			axi_awcache;
	reg	[2:0]			axi_awprot;
	reg	[3:0]			axi_awqos;

	skidbuffer #(
		.DW(C_AXI_ADDR_WIDTH + C_AXI_ID_WIDTH + 8 + 3 + 2 + 1+4+3+4),
		.OPT_OUTREG(1'b0)
	) awskid (
		.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN),
		.i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY),
			.i_data({ S_AXI_AWID, S_AXI_AWADDR,  S_AXI_AWLEN,
				S_AXI_AWSIZE, S_AXI_AWBURST, S_AXI_AWLOCK,
				S_AXI_AWCACHE,  S_AXI_AWPROT,  S_AXI_AWQOS }),
		.o_valid(awskd_valid), .i_ready(awskd_ready),
			.o_data({ awskd_id,  awskd_addr,  awskd_len,
				awskd_size,  awskd_burst, awskd_lock,
				awskd_cache, awskd_prot,  awskd_qos })
	);

	always @(*)
	begin
		awskd_ready = 1;
		if (r_awlen > 0)
			awskd_ready = 0;
		if (M_AXI_AWVALID && M_AXI_AWREADY)
			awskd_ready = 0;
		if (|wfifo_fill[LGWFIFO:LGWFIFO-1])
			awskd_ready = 0;
	end

	initial	axi_awvalid = 1'b0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		axi_awvalid <= 0;
	else if (awskd_valid || r_awlen > 0)
		axi_awvalid <= 1;
	else if (M_AXI_AWREADY)
		axi_awvalid <= 0;

	initial	r_awlen = 0;
	initial	axi_awlen = 0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
	begin
		r_awlen <= 0;
		axi_awlen <= 0;
	end else if (!M_AXI_AWVALID || M_AXI_AWREADY)
	begin
		if (r_awlen > 15)
		begin
			axi_awlen <= 4'd15;
			r_awlen <= r_awlen - 8'd16;
		end else if (r_awlen > 0)
		begin
			axi_awlen[3:0] <= r_awlen[3:0] - 1;
			r_awlen <= 0;
		end else if (awskd_valid)
		begin
			if (awskd_len > 15)
			begin
				r_awlen <= awskd_len + 1'b1 - 8'd16;
				axi_awlen <= 4'd15;
			end else begin
				r_awlen <= 0;
				axi_awlen <= awskd_len[3:0];
			end
		end else begin
			r_awlen <= 0;
			axi_awlen <= 0;
		end
	end


	always @(posedge S_AXI_ACLK)
	if (awskd_valid && awskd_ready)
		axi_awaddr <= awskd_addr;
	else if (M_AXI_AWVALID && M_AXI_AWREADY)
	begin
		// Verilator lint_off WIDTH
		axi_awaddr <= axi_awaddr + ((M_AXI_AWLEN + 1) << M_AXI_AWSIZE);
		// Verilator lint_on  WIDTH

		case(M_AXI_AWSIZE)
		3'b000: begin end
		3'b001: axi_awaddr[  0] <= 0;
		3'b010: axi_awaddr[1:0] <= 0;
		3'b011: axi_awaddr[2:0] <= 0;
		3'b100: axi_awaddr[3:0] <= 0;
		3'b101: axi_awaddr[4:0] <= 0;
		3'b110: axi_awaddr[5:0] <= 0;
		3'b111: axi_awaddr[6:0] <= 0;
		endcase
	end


	initial	M_AXI_AWSIZE = ADDRLSB[2:0];
	always @(posedge S_AXI_ACLK)
	begin
		if (awskd_valid && awskd_ready)
		begin
			axi_awid     <= awskd_id;
			axi_awsize   <= awskd_size;
			axi_awburst  <= awskd_burst;
			axi_awlock[0]<=awskd_lock;
			axi_awcache  <= awskd_cache;
			axi_awprot   <= awskd_prot;
			axi_awqos    <= awskd_qos;
		end

		if (C_AXI_DATA_WIDTH < 16)
			axi_awsize <= 3'b000;
		else if (C_AXI_DATA_WIDTH < 32)
			axi_awsize[2:1] <= 2'b00;
		else if (C_AXI_DATA_WIDTH < 128)
			axi_awsize[2] <= 1'b0;

		axi_awlock[1] <= 1'b0;
	end

	assign	M_AXI_AWVALID = axi_awvalid;
	assign	M_AXI_AWID    = axi_awid;
	assign	M_AXI_AWADDR  = axi_awaddr;
	assign	M_AXI_AWLEN   = axi_awlen;
	assign	M_AXI_AWSIZE  = axi_awsize;
	assign	M_AXI_AWBURST = axi_awburst;
	assign	M_AXI_AWLOCK  = axi_awlock;
	assign	M_AXI_AWCACHE = axi_awcache;
	assign	M_AXI_AWPROT  = axi_awprot;
	assign	M_AXI_AWQOS   = axi_awqos;
	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Write data channel
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//
	wire				wskd_valid;
	reg				wskd_ready, write_idle;
	wire	[C_AXI_DATA_WIDTH-1:0]	wskd_data;
	wire [C_AXI_DATA_WIDTH/8-1:0]	wskd_strb;
	wire 				wskd_last;
	reg	[4:0]			r_wlen;
	wire	[C_AXI_ID_WIDTH-1:0]	wfifo_id,   raw_wfifo_id;
	wire	[3:0]			wfifo_wlen, raw_wfifo_wlen;
	reg				next_wlast, r_wlast;
	wire				raw_wfifo_last;
	wire				wfifo_empty, wfifo_full;
	wire	[LGWFIFO:0]		wfifo_fill;
	reg				axi_wvalid;
	reg	[C_AXI_DATA_WIDTH-1:0]	axi_wdata;
	reg [C_AXI_DATA_WIDTH/8-1:0]	axi_wstrb;
	reg	[C_AXI_ID_WIDTH-1:0]	axi_wid;
	reg				axi_wlast;

	skidbuffer #(
		.DW(C_AXI_DATA_WIDTH + (C_AXI_DATA_WIDTH/8) + 1),
		.OPT_OUTREG(1'b0)
	) wskid (
		.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN),
		.i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY),
			.i_data({ S_AXI_WDATA, S_AXI_WSTRB,  S_AXI_WLAST }),
		.o_valid(wskd_valid), .i_ready(wskd_ready),
			.o_data({ wskd_data,  wskd_strb,  wskd_last })
	);

	always @(*)
		wskd_ready = !M_AXI_WVALID || M_AXI_WREADY;

	initial	axi_wvalid = 0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		axi_wvalid <= 0;
	else if (wskd_valid)
		axi_wvalid <= 1;
	else if (M_AXI_WREADY)
		axi_wvalid <= 0;

	initial	write_idle = 0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		write_idle <= 1;
	else if (M_AXI_WVALID && M_AXI_WREADY && M_AXI_WLAST && !wskd_valid)
		write_idle <= 1;
	else if (wskd_valid && wskd_ready)
		write_idle <= 0;

	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN && OPT_LOWPOWER)
	begin
		axi_wdata <= 0;
		axi_wstrb <= 0;
	end else if (!M_AXI_WVALID || M_AXI_WREADY)
	begin
		axi_wdata <= wskd_data;
		axi_wstrb <= wskd_strb;

		if (OPT_LOWPOWER && !wskd_valid)
		begin
			axi_wdata <= 0;
			axi_wstrb <= 0;
		end
	end

	always @(*)
	begin
		if (r_awlen[7:4] != 0)
			next_wlen = 4'd15;
		else if (r_awlen[3:0] != 0)
			next_wlen = r_awlen[3:0];
		else if (awskd_len[7:4] != 0)
			next_wlen = 4'd15;
		else
			next_wlen = awskd_len[3:0];

		if (r_awlen != 0)
			next_wlast = (r_awlen[7:4] == 0);
		else
			next_wlast = (awskd_len[7:4] == 0);
	end

	sfifo #(.BW(C_AXI_ID_WIDTH+4+1), .LGFLEN(LGWFIFO))
	wfifo(S_AXI_ACLK, !S_AXI_ARESETN,
		(!M_AXI_AWVALID || M_AXI_AWREADY)&&(awskd_valid||(r_awlen > 0))
			&& !write_idle,
		// The length of the next burst
		{ ((r_awlen != 0) ? M_AXI_AWID : awskd_id),
						next_wlen, next_wlast },
		wfifo_full, wfifo_fill,
		//
		(!M_AXI_WVALID || M_AXI_WREADY),
		{ raw_wfifo_id, raw_wfifo_wlen, raw_wfifo_last },
		wfifo_empty);

	assign	wfifo_id   = (wfifo_empty) ? awskd_id  : raw_wfifo_id;
	assign	wfifo_wlen = (wfifo_empty) ? next_wlen : raw_wfifo_wlen;

	initial	r_wlen = 0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN && OPT_LOWPOWER)
		r_wlen <= 0;
	else if (!M_AXI_WVALID || M_AXI_WREADY)
	begin
		if (r_wlen > 1)
			r_wlen <= r_wlen - 1;
		else if (!wfifo_empty)
			r_wlen <= raw_wfifo_wlen + 1;
		else if (awskd_valid && awskd_ready)
			r_wlen <= next_wlen + 1;
	end

	initial	r_wlast = 0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN && OPT_LOWPOWER)
		r_wlast <= 0;
	else if (!M_AXI_WVALID || M_AXI_WREADY)
	begin
		if (r_wlen > 0)
			r_wlast <= (r_wlen <= 1);
		else
			r_wlast <= raw_wfifo_last;
	end

	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN && OPT_LOWPOWER)
	begin
		axi_wid   <= 0;
		axi_wlast <= 0;
	end else if (!M_AXI_WVALID || M_AXI_WREADY)
	begin
		axi_wid   <= wfifo_id;
		axi_wlast <= r_wlast;

		if (OPT_LOWPOWER && !wskd_valid)
		begin
			axi_wid   <= 0;
			axi_wlast <= 0;
		end
	end

	assign	M_AXI_WVALID = axi_wvalid;
	assign	M_AXI_WDATA  = axi_wdata;
	assign	M_AXI_WSTRB  = axi_wstrb;
	assign	M_AXI_WID    = axi_wid;
	assign	M_AXI_WLAST  = axi_wlast;
	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Write return channel
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//
	wire				bskd_valid;
	reg				bskd_ready;
	wire	[C_AXI_ID_WIDTH-1:0]	bskd_id;
	wire	[1:0]			bskd_resp;
	reg	[1:0]			bburst_err	[0:NID-1];
	reg	[NID-1:0]		wbfifo_valid;
	reg	[NID-1:0]		wbfifo_last;
	reg				axi_bvalid;
	reg	[C_AXI_ID_WIDTH-1:0]	axi_bid;
	reg	[1:0]			axi_bresp;

	skidbuffer #(.DW(C_AXI_ID_WIDTH + 2), .OPT_OUTREG(1'b0)
	) bskid (
		.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN),
		.i_valid(M_AXI_BVALID), .o_ready(M_AXI_BREADY),
			.i_data({ M_AXI_BID, M_AXI_BRESP }),
		.o_valid(bskd_valid), .i_ready(bskd_ready),
			.o_data({ bskd_id, bskd_resp })
	);

	generate for(ID=0; ID < NID; ID = ID + 1)
	begin : WRITE_BURST_TRACKING
		wire			wbfifo_empty, wbfifo_full;
		wire	[LGFIFO:0]	wbfifo_fill;

		initial	wbfifo_valid[ID] = 0;
		always @(posedge S_AXI_ACLK)
		if (!S_AXI_ARESETN)
			wbfifo_valid[ID] <= 0;
		else if (!wbfifo_valid[ID] && !wbfifo_empty)
			wbfifo_valid[ID] <= 1;
		else if (bskd_valid && bskd_ready && bskd_id==ID)
			wbfifo_valid[ID] <= !wbfifo_empty;

		sfifo #(.BW(1), .LGFLEN(LGFIFO))
		wbfifo(S_AXI_ACLK, !S_AXI_ARESETN,
			(M_AXI_WVALID && M_AXI_WREADY && M_AXI_WLAST)
				&& (M_AXI_WID == ID),
			(r_wlen == 0) ? 1'b1 : 1'b0,
			wbfifo_full, wbfifo_fill,
			//
			(!wbfifo_valid[ID] || (M_AXI_BVALID && M_AXI_BREADY
					&& M_AXI_BID == ID)),
			wbfifo_last[ID], wbfifo_empty
		);

		// Verilator lint_off UNUSED
		wire	unused;
		assign	unused = &{ 1'b0, wbfifo_full, wbfifo_fill };
		// Verilator lint_on  UNUSED

	end endgenerate

	always @(*)
		bskd_ready = (!S_AXI_BVALID || S_AXI_BREADY);

	initial	axi_bvalid = 1'b0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		axi_bvalid <= 1'b0;
	else if (bskd_valid && bskd_ready)
		axi_bvalid <= wbfifo_last[bskd_id];
	else if (M_AXI_BREADY)
		axi_bvalid <= 1'b0;

	generate for(ID=0; ID < NID; ID = ID + 1)
	begin : WRITE_ERR_BY_ID

		initial	bburst_err[ID] = 2'b0;
		always @(posedge S_AXI_ACLK)
		if (!S_AXI_ARESETN)
			bburst_err[ID] <= 2'b0;
		else if (S_AXI_BVALID && S_AXI_BREADY && S_AXI_BID == ID
				&& wbfifo_last[ID])
			bburst_err[ID] <= 2'b0;
		else if (bskd_valid && bskd_id == ID)
			bburst_err[ID] <= bburst_err[ID] | bskd_resp;

	end endgenerate

	initial	axi_bid   = 0;
	initial	axi_bresp = 0;
	always @(posedge S_AXI_ACLK)
	if (OPT_LOWPOWER && !S_AXI_ARESETN)
	begin
		axi_bid   <= 0;
		axi_bresp <= 0;
	end else if (!S_AXI_BVALID || S_AXI_BREADY)
	begin
		axi_bid   <= bskd_id;
		axi_bresp <= bskd_resp | bburst_err[bskd_id];

		if (OPT_LOWPOWER && !bskd_valid)
		begin
			axi_bid   <= 0;
			axi_bresp <= 0;
		end
	end

	assign	S_AXI_BVALID = axi_bvalid;
	assign	S_AXI_BID    = axi_bid;
	assign	S_AXI_BRESP  = axi_bresp;
	// }}}
	// }}}
	////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////
	//
	// READ SIDE
	// {{{
	////////////////////////////////////////////////////////////////////////
	////////////////////////////////////////////////////////////////////////

	////////////////////////////////////////////////////////////////////////
	//
	// Read address channel
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//
	reg	[7:0]			r_arlen;
	wire				arskd_valid;
	reg				arskd_ready;
	wire	[C_AXI_ID_WIDTH-1:0]	arskd_id;
	wire	[C_AXI_ADDR_WIDTH-1:0]	arskd_addr;
	wire	[7:0]			arskd_len;
	wire	[2:0]			arskd_size;
	wire	[1:0]			arskd_burst;
	wire				arskd_lock;
	wire	[3:0]			arskd_cache;
	wire	[2:0]			arskd_prot;
	wire	[3:0]			arskd_qos;
	reg				axi_arvalid;
	reg	[C_AXI_ID_WIDTH-1:0]	axi_arid;
	reg	[C_AXI_ADDR_WIDTH-1:0]	axi_araddr;
	reg	[3:0]			axi_arlen;
	reg	[2:0]			axi_arsize;
	reg	[1:0]			axi_arburst;
	reg	[1:0]			axi_arlock;
	reg	[3:0]			axi_arcache;
	reg	[2:0]			axi_arprot;
	reg	[3:0]			axi_arqos;

	skidbuffer #(
		.DW(C_AXI_ADDR_WIDTH + C_AXI_ID_WIDTH + 8 + 3 + 2 + 1+4+3+4),
		.OPT_OUTREG(1'b0)
	) arskid (
		.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN),
		.i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY),
			.i_data({ S_AXI_ARID, S_AXI_ARADDR,  S_AXI_ARLEN,
				S_AXI_ARSIZE, S_AXI_ARBURST, S_AXI_ARLOCK,
				S_AXI_ARCACHE,  S_AXI_ARPROT,  S_AXI_ARQOS }),
		.o_valid(arskd_valid), .i_ready(arskd_ready),
			.o_data({ arskd_id,  arskd_addr,  arskd_len,
				arskd_size,  arskd_burst, arskd_lock,
				arskd_cache, arskd_prot,  arskd_qos })
	);

	always @(*)
	begin
		arskd_ready = 1;
		if (r_arlen > 0)
			arskd_ready = 0;
		if (M_AXI_ARVALID && M_AXI_ARREADY)
			arskd_ready = 0;
	end

	initial	axi_arvalid = 1'b0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		axi_arvalid <= 1'b0;
	else if (r_arlen > 0)
		axi_arvalid <= 1'b1;
	else if (arskd_ready)
		axi_arvalid <= 1'b1;
	else if (M_AXI_ARREADY)
		axi_arvalid <= 1'b0;

	initial	r_arlen = 0;
	initial	M_AXI_ARLEN = 0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
	begin
		r_arlen   <= 0;
		axi_arlen <= 0;
	end else if (!M_AXI_ARVALID || M_AXI_ARREADY)
	begin
		if (r_arlen[7:4] != 0)
		begin
			axi_arlen <= 4'd15;
			r_arlen <= r_arlen - 16;
		end else if (r_arlen[3:0] != 0)
		begin
			axi_arlen <= r_arlen[3:0] - 1;
			r_arlen <= 0;
		end else if (arskd_valid)
		begin
			if (arskd_len[7:4] != 0)
			begin
				r_arlen <= arskd_len + 1 - 16;
				axi_arlen <= 4'd15;
			end else begin
				r_arlen <= 0;
				axi_arlen <= arskd_len[3:0];
			end
		end else begin
			r_arlen <= 0;
			axi_arlen <= 0;
		end
	end


	always @(posedge S_AXI_ACLK)
	if (arskd_valid && arskd_ready)
		axi_araddr <= arskd_addr;
	else if (M_AXI_ARVALID && M_AXI_ARREADY)
	begin
		// Verilator lint_off WIDTH
		axi_araddr <= axi_araddr + ((M_AXI_ARLEN + 1) << M_AXI_ARSIZE);
		// Verilator lint_on  WIDTH

		case(M_AXI_AWSIZE)
		3'b000: begin end
		3'b001: axi_araddr[  0] <= 0;
		3'b010: axi_araddr[1:0] <= 0;
		3'b011: axi_araddr[2:0] <= 0;
		3'b100: axi_araddr[3:0] <= 0;
		3'b101: axi_araddr[4:0] <= 0;
		3'b110: axi_araddr[5:0] <= 0;
		3'b111: axi_araddr[6:0] <= 0;
		endcase
	end

	initial	axi_arsize = ADDRLSB[2:0];
	always @(posedge S_AXI_ACLK)
	begin
		if (arskd_valid && arskd_ready)
		begin
			axi_arid      <= arskd_id;
			axi_arsize    <= arskd_size;
			axi_arburst   <= arskd_burst;
			axi_arlock[0] <= arskd_lock;
			axi_arcache   <= arskd_cache;
			axi_arprot    <= arskd_prot;
			axi_arqos     <= arskd_qos;
		end

		// Propagate constants, to help the optimizer out out a touch
		axi_arlock[1] <= 1'b0;

		if (C_AXI_DATA_WIDTH < 16)
			axi_arsize <= 3'b000;
		else if (C_AXI_DATA_WIDTH < 32)
			axi_arsize[2:1] <= 2'b00;
		else if (C_AXI_DATA_WIDTH < 128)
			axi_arsize[2] <= 1'b0;
	end

	generate for(ID=0; ID < NID; ID = ID + 1)
	begin : READ_BURST_TRACKING
		wire			rbfifo_empty, rbfifo_full;
		wire	[LGFIFO:0]	rbfifo_fill;

		initial	rbfifo_valid[ID] = 0;
		always @(posedge S_AXI_ACLK)
		if (!S_AXI_ARESETN)
			rbfifo_valid[ID] <= 0;
		else if (!rbfifo_valid[ID] && !rbfifo_empty)
			rbfifo_valid[ID] <= 1;
		else if (rskd_valid && rskd_ready && rskd_last && rskd_id==ID)
			rbfifo_valid[ID] <= !rbfifo_empty;

		sfifo #(.BW(1), .LGFLEN(LGFIFO))
		rbfifo(S_AXI_ACLK, !S_AXI_ARESETN,
			(!M_AXI_ARVALID || M_AXI_ARREADY)
				&& (((r_arlen>0)&&(M_AXI_ARID==ID))
					|| (arskd_valid && arskd_id == ID)),
			((arskd_valid && arskd_ready && arskd_len <= 15)
				||(r_arlen <= 15)) ? 1'b1 : 1'b0,
			rbfifo_full, rbfifo_fill,
			//
			(!rbfifo_valid[ID] || (M_AXI_RVALID && M_AXI_RREADY
					&& M_AXI_RID == ID && M_AXI_RLAST)),
			rid_last[ID], rbfifo_empty
		);

		// Verilator lint_off UNUSED
		wire	unused;
		assign	unused = &{ 1'b0, rbfifo_full, rbfifo_fill };
		// Verilator lint_on  UNUSED
	end endgenerate

	assign	M_AXI_ARVALID= axi_arvalid;
	assign	M_AXI_ARID   = axi_arid;
	assign	M_AXI_ARADDR = axi_araddr;
	assign	M_AXI_ARLEN  = axi_arlen;
	assign	M_AXI_ARSIZE = axi_arsize;
	assign	M_AXI_ARBURST= axi_arburst;
	assign	M_AXI_ARLOCK = axi_arlock;
	assign	M_AXI_ARCACHE= axi_arcache;
	assign	M_AXI_ARPROT = axi_arprot;
	assign	M_AXI_ARQOS  = axi_arqos;
	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Read data channel
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//
	wire				rskd_valid;
	reg				rskd_ready;
	wire	[C_AXI_ID_WIDTH-1:0]	rskd_id;
	wire	[C_AXI_DATA_WIDTH-1:0]	rskd_data;
	wire				rskd_last;
	wire	[1:0]			rskd_resp;
	reg	[1:0]			rburst_err	[0:NID-1];
	reg	[NID-1:0]		rbfifo_valid;
	reg	[NID-1:0]		rid_last;
	reg				axi_rvalid;
	reg	[C_AXI_ID_WIDTH-1:0]	axi_rid;
	reg	[C_AXI_DATA_WIDTH-1:0]	axi_rdata;
	reg				axi_rlast;
	reg	[1:0]			axi_rresp;

	skidbuffer #(
		.DW(C_AXI_DATA_WIDTH + C_AXI_ID_WIDTH + 3),
		.OPT_OUTREG(1'b0)
	) rskid (
		.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN),
		.i_valid(M_AXI_RVALID), .o_ready(M_AXI_RREADY),
			.i_data({ M_AXI_RID, M_AXI_RDATA,  M_AXI_RLAST,
				M_AXI_RRESP }),
		.o_valid(rskd_valid), .i_ready(rskd_ready),
			.o_data({ rskd_id,  rskd_data,  rskd_last, rskd_resp })
	);

	always @(*)
		rskd_ready = !S_AXI_RVALID || S_AXI_RREADY;

	initial	axi_rvalid = 1'b0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		axi_rvalid <= 1'b0;
	else if (rskd_valid && rskd_ready)
		axi_rvalid <= 1'b1;
	else if (S_AXI_RREADY)
		axi_rvalid <= 1'b0;

	generate for(ID=0; ID < NID; ID = ID + 1)
	begin : READ_ERR_BY_ID

		initial	rburst_err[ID] = 2'b0;
		always @(posedge S_AXI_ACLK)
		if (!S_AXI_ARESETN)
			rburst_err[ID] <= 2'b0;
		else if (S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST
				&& S_AXI_RID == ID)
			rburst_err[ID] <= 2'b0;
		else if (rskd_valid && rskd_id == ID)
			rburst_err[ID] <= rburst_err[ID] | rskd_resp;

	end endgenerate

	always @(posedge S_AXI_ACLK)
	if (!S_AXI_RVALID || S_AXI_RREADY)
	begin
		axi_rid   <= rskd_id;
		axi_rdata <= rskd_data;
		axi_rresp <= rskd_resp | rburst_err[rskd_id];
		axi_rlast <= rid_last[rskd_id] && rskd_last;
	end

	assign	S_AXI_RVALID= axi_rvalid;
	assign	S_AXI_RID   = axi_rid;
	assign	S_AXI_RDATA = axi_rdata;
	assign	S_AXI_RRESP = axi_rresp;
	assign	S_AXI_RLAST = axi_rlast;
	// }}}
	// }}}
	// Make Verilator happy
	// {{{
	// Verilator lint_off UNUSED
	wire	unused;
	assign	unused = &{ 1'b0, wfifo_fill[LGWFIFO-2:0], wskd_last,
			wfifo_wlen, wfifo_full };
	// Verilator lint_on UNUSED
	// }}}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// Formal property section
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
`ifdef	FORMAL
//
// This design has not been formally verified.
//
`endif
endmodule
