////////////////////////////////////////////////////////////////////////////////
//
// Filename:	rtl/axisgfsm.v
// {{{
// Project:	WB2AXIPSP: bus bridges and other odds and ends
//
// Purpose:	Scripts an AXI DMA via in-memory tables: reads from the tables,
//		commands the DMA.
//
// Registers:
//
//	0. Control
//		8b	KEY
//			3'b PROT
//			4'b QOS
//		1b	Abort: Either aborting or aborted
//		1b	Err: Ended on an error
//		1b	Busy
//		1b	Interrupt Enable
//		1b	Interrupt Set
//		1b	Start
//	1. Reserved
//	2-3. First table entry address
//		(Current) table entry address on read, if in progress
// (Optional)
//	4-5. Current read address
//	6-7. Current write address
//	1.   Remaining amount to be written (this entry)
//
// Table format:
//	Table entries either consist of five 32-bit words, or three 32-bit
//	words, depending upon the size of the address bus.
//
//	Address bus > 30 bits: five 32-bit words
//	0-1:	64b: { 2'bflags, 62'b SOURCE ADDRESS (bytes) }
//			00: Continue after this to next
//			01: Last item in chain
//			10: Skip this address
//			11: Jump to new address
//	2-3:	64b: { int_en, 1'b0,  DESTINATION ADDRESS (bytes) }
//	4:	32b Transfer length (in bytes)
//
//	Address bus <= 30 bits: three 32-bit words
//	0:	32b: { 2'bflags, 30'b SOURCE ADDRESS (bytes) }
//	1:	32b: { int_en, 1'b0, 30'b DESTINATION ADDRESS (bytes) }
//	2:	32b LENGTH (in bytes)
//
//
// 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
// `define			AXI3
// }}}
module	axisgfsm #(
		// {{{
		parameter	C_AXI_ADDR_WIDTH = 30,
		// Verilator lint_off UNUSED
		parameter	C_AXI_DATA_WIDTH = 32,
		// Verilator lint_on  UNUSED
		localparam DMAC_ADDR_WIDTH = 5,
		localparam DMAC_DATA_WIDTH = 32,
		//
		// The "ABORT_KEY" is a byte that, if written to the control
		// word while the core is running, will cause the data transfer
		// to be aborted.
		parameter	[7:0]			ABORT_KEY  = 8'h6d
		// }}}
	) (
		// {{{
		input	wire	S_AXI_ACLK,
		input	wire	S_AXI_ARESETN,
		//
		// The control interface
		// {{{
		input	wire				i_start, i_abort,
		input	wire	[C_AXI_ADDR_WIDTH-1:0]	i_tbl_addr,
		input	wire	[3:0]			i_qos,
		input	wire	[2:0]			i_prot,
		output	reg				o_busy,
		output	reg				o_done,
		output	reg				o_int,
		output	reg				o_err,
		output	reg	[C_AXI_ADDR_WIDTH-1:0]	o_tbl_addr,
		// }}}
		//
		// The prefetch interface
		// {{{
		output	reg				o_new_pc,
		output	reg				o_pf_clear_cache,
		output	reg				o_pf_ready,
		output	reg	[C_AXI_ADDR_WIDTH-1:0]	o_pf_pc,
		input	wire				i_pf_valid,
		input	wire	[31:0]			i_pf_insn,
		input	wire	[C_AXI_ADDR_WIDTH-1:0]	i_pf_pc,
		input	wire				i_pf_illegal,
		// }}}
		//
		// The DMA submodule's AXI4-lite control interface
		// {{{
		output	reg				o_dmac_wvalid,
		input	wire				i_dmac_wready,
		output	reg [DMAC_ADDR_WIDTH-1:0]	o_dmac_waddr,
		output	reg [DMAC_DATA_WIDTH-1:0]	o_dmac_wdata,
		output	reg [DMAC_DATA_WIDTH/8-1:0]	o_dmac_wstrb,
		input	wire [DMAC_DATA_WIDTH-1:0]	i_dmac_rdata,
		input	wire				i_dma_complete
		// }}}
		// }}}
	);

	// Local parameter definitions
	// {{{

	// Scatter gather state machine states
	// {{{
	// States:
	// - 0. IDLE (nothing going on, waiting for a new program counter)
	//	Enter on completion, reset, or (abort and DMA complete)
	// - 1. READ_LOSRC -> DMA
	// - 2. READ_HISRC -> DMA (Optional)
	//	((Skip || JUMP) ->READ_LOSRC)
	// - 3. READ_LODST -> DMA
	// - 4. READ_HIDST -> DMA (Optional)
	// - 5. READ_LEN   -> DMA
	// - 6. READ_FLAGS -> SETS -> START DMA
	// - 7. Wait on DMA
	//	(Set interrupt if INT complete)
	//	(If last, go to idle, else jump to #1)
	//	(Set LAST on abort)
	//
	localparam [2:0]	SG_IDLE    = 3'h0,
				SG_SRCHALF = 3'h1,
				SG_SRCADDR = 3'h2,
				SG_DSTHALF = 3'h3,
				SG_DSTADDR = 3'h4,
				SG_LENGTH  = 3'h5,
				SG_CONTROL = 3'h6,
				SG_WAIT    = 3'h7;
	// }}}

	// DMA device internal addresses
	// {{{
	// Verilator lint_off UNUSED
	localparam [4:0]	DMA_CONTROL= 5'b00000,
				DMA_UNUSED = 5'b00100,
				DMA_SRCLO  = 5'b01000,
				DMA_SRCHI  = 5'b01100,
				DMA_DSTLO  = 5'b10000,
				DMA_DSTHI  = 5'b10100,
				DMA_LENLO  = 5'b11000,
				DMA_LENHI  = 5'b11100;
	// Verilator lint_on  UNUSED
	// }}}

	localparam [C_AXI_ADDR_WIDTH-1:0] TBL_SIZE
				= (C_AXI_ADDR_WIDTH <= 30) ? (4*3) : (4*5);

	// }}}

	// Register/net declarations
	// {{{
	reg		tbl_last, tbl_int_enable;
	reg	[2:0]	sgstate;
	reg		dma_err, dma_abort, dma_done, dma_busy, dma_starting,
			dma_aborting;
	reg	[59:0]	r_pf_pc;
	reg		dma_op_complete, dma_terminate;

	// }}}

	////////////////////////////////////////////////////////////////////////
	//
	// FSM Control states
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//

	// o_pf_ready
	// {{{
	always @(*)
	begin
		case(sgstate)
		SG_IDLE:	o_pf_ready = 1'b0;
		SG_SRCHALF:	o_pf_ready = (!o_dmac_wvalid || i_dmac_wready);
		SG_SRCADDR:	o_pf_ready = (!o_dmac_wvalid || i_dmac_wready);
		SG_DSTHALF:	o_pf_ready = (!o_dmac_wvalid || i_dmac_wready);
		SG_DSTADDR:	o_pf_ready = (!o_dmac_wvalid || i_dmac_wready);
		SG_LENGTH:	o_pf_ready = (!o_dmac_wvalid || i_dmac_wready);
		SG_CONTROL:	o_pf_ready = 1'b0;
		SG_WAIT:	o_pf_ready = 1'b0;
		endcase

		if (!o_busy || o_pf_clear_cache || o_new_pc)
			o_pf_ready = 0;
	end
	// }}}

	// w_int
	// {{{
	always @(*)
	begin
		o_int = 1'b0;

		if (sgstate == SG_WAIT && i_dma_complete
					&& (tbl_last || tbl_int_enable))
			o_int = 1'b1;
		if (i_pf_valid && o_pf_ready && i_pf_illegal)
			o_int = 1'b1;
	end
	// }}}

	// Returns from the DMA controller (one clock later)
	// {{{
	always @(*)
	begin
		dma_err   = i_dmac_rdata[4];
		dma_abort = i_dmac_rdata[3];
		dma_done  = i_dmac_rdata[1];
		dma_busy  = i_dmac_rdata[0];
	end
	// }}}

	// dma_op_complete
	// {{{
	always @(*)
	if (dma_busy || dma_starting || (o_dmac_wvalid && !i_dmac_wready))
		dma_op_complete = 0;
	else
		dma_op_complete = (!o_dmac_wvalid || !o_dmac_wstrb[0]);
	// }}}

	// dma_terminate
	// {{{
	always @(*)
	begin
		dma_terminate = 0;
		if (i_abort || tbl_last || dma_aborting)
			dma_terminate = 1;
		if (dma_err || i_pf_illegal)
			dma_terminate = 1;
	end
	// }}}

	// The GIANT FSM itself
	// new_pc, pf_clear_cache, dmac_wvalid, dmac_waddr, dmac_wdata,
	// dmac_wstrb, ipc,
	// {{{
	initial	r_pf_pc  = 0;
	initial	sgstate  = SG_IDLE;
	initial	o_new_pc = 1'b0;
	initial	o_busy   = 1'b0;
	initial	o_done   = 1'b0;
	initial	o_err    = 1'b0;
	initial	o_dmac_wvalid = 1'b0;
	always @(posedge S_AXI_ACLK)
	begin
		// Auto-cleared values
		// {{{
		o_new_pc <= 1'b0;
		o_pf_clear_cache <= 1'b0;
		if (i_dmac_wready)
			o_dmac_wvalid <= 1'b0;
		if (!o_dmac_wvalid || i_dmac_wready)
			o_dmac_wstrb <= 4'hf;
		o_done <= 1'b0;
		o_err  <= 1'b0;
		// }}}

		case(sgstate)
		SG_IDLE: // IDLE -- waiting for start
			// {{{
			begin
			r_pf_pc[C_AXI_ADDR_WIDTH-1:0] <= i_tbl_addr;
			if (i_start)
			begin
				o_new_pc <= 1'b1;
				if (C_AXI_ADDR_WIDTH > 30)
					sgstate <= SG_SRCHALF;
				else
					sgstate <= SG_SRCADDR;
				o_busy <= 1'b1;
			end else begin
				o_new_pc <= 1'b0;
				o_pf_clear_cache <= 1'b1;
				o_busy <= 1'b0;
			end end
			// }}}
		SG_SRCHALF: // Get the first source address
			// {{{
			if (i_pf_valid && (!o_dmac_wvalid || i_dmac_wready))
			begin
			o_dmac_wvalid <= 1'b1;
			o_dmac_waddr  <= DMA_SRCLO;
			o_dmac_wdata  <= i_pf_insn;
			sgstate <= SG_SRCADDR;
			// r_pf_pc[31:0] <= i_pf_insn;
			o_tbl_addr <= i_pf_pc;
			end
			// }}}
		SG_SRCADDR: // Second half of the source address
			// {{{
			if (i_pf_valid && o_pf_ready)
			begin
			o_dmac_wvalid <= !i_pf_insn[31];
			o_dmac_waddr  <= (C_AXI_ADDR_WIDTH<=30)
						? DMA_SRCLO : DMA_SRCHI;
			o_dmac_wdata  <= i_pf_insn;
			// r_pf_pc[31:0] <= i_pf_insn;
			if (C_AXI_ADDR_WIDTH <= 30)
			begin
				sgstate <= SG_DSTADDR;
				o_tbl_addr <= i_pf_pc;
				// // r_pf_pc[30-1:0] <= i_pf_insn[30-1:0];
			end else begin
				sgstate <= SG_DSTHALF;
				// r_pf_pc[60-1:30] <= i_pf_insn[30-1:0];
			end

			tbl_last <= 1'b0;
			case(i_pf_insn[31:30])
			2'b00: // Other items still to follow
				tbl_last <= 1'b0;
			2'b01: // Last item in the chain
				tbl_last <= 1'b1;
			2'b10: begin // Skip
				tbl_last <= 1'b0;
				if (C_AXI_ADDR_WIDTH > 30)
					sgstate <= SG_SRCHALF;
				else
					sgstate <= SG_SRCADDR;
				o_new_pc <= 1'b1;
				r_pf_pc[C_AXI_ADDR_WIDTH-1:0]
						<= r_pf_pc[C_AXI_ADDR_WIDTH-1:0] + TBL_SIZE;
				end
			2'b11: begin // Jump
				o_new_pc <= 1'b1;
				// ipc <= // already set from above
				if (C_AXI_ADDR_WIDTH > 30)
					sgstate <= SG_SRCHALF;
				else
					sgstate <= SG_SRCADDR;
				r_pf_pc[C_AXI_ADDR_WIDTH-1:0] <= i_pf_insn[C_AXI_ADDR_WIDTH-1:0];
				end
			endcase
			end
			// }}}
		SG_DSTHALF: // Get the first half of the destination address
			// {{{
			if (i_pf_valid && (!o_dmac_wvalid || i_dmac_wready))
			begin
			o_dmac_wvalid<= 1'b1;
			o_dmac_waddr <= DMA_DSTLO;
			o_dmac_wdata <= i_pf_insn;
			sgstate    <= SG_DSTADDR;
			end
			// }}}
		SG_DSTADDR: // Second half of the destination address
			// {{{
			if (i_pf_valid && (!o_dmac_wvalid || i_dmac_wready))
			begin
			o_dmac_wvalid <= 1'b1;
			o_dmac_waddr  <= (C_AXI_ADDR_WIDTH<=30)
						? DMA_DSTLO : DMA_DSTHI;
			o_dmac_wdata <= { 2'b0, i_pf_insn[29:0] };
			sgstate    <= SG_LENGTH;
			tbl_int_enable <= i_pf_insn[31];
			end
			// }}}
		SG_LENGTH: // The length word
			// {{{
			if (i_pf_valid && (!o_dmac_wvalid || i_dmac_wready))
			begin
			o_dmac_wvalid <= 1'b1;
			o_dmac_waddr  <= DMA_LENLO;
			o_dmac_wdata <= i_pf_insn;
			sgstate    <= SG_CONTROL;
			if (i_pf_insn <= 0)
			begin
				o_dmac_wvalid <= 1'b0;
				if (tbl_last)
				begin
					sgstate    <= SG_IDLE;
					o_new_pc   <= 1'b0;
					o_busy     <= 1'b0;
					o_done     <= 1'b1;
					o_pf_clear_cache <= 1'b1;
				end else begin
					sgstate    <= SG_SRCADDR;
					o_new_pc   <= 1'b1;
				end
			end
			end
			// }}}
		SG_CONTROL: // The control word to get us started
			// {{{
			if (!o_dmac_wvalid || i_dmac_wready)
			begin
			o_dmac_wvalid <= 1'b1;
			o_dmac_waddr  <= DMA_CONTROL;
			o_dmac_wdata <= { 9'h0, i_prot, i_qos,
					11'h0,
					1'h1, // Clear any errors
					1'b1, // Clr abort flag
					1'b1, // Set int enable, int
					1'b1, // Clr any prior interrupt
					1'b1 };
			sgstate    <= SG_WAIT;
			o_tbl_addr <= o_tbl_addr + TBL_SIZE;
			end
			// }}}
		SG_WAIT: // Wait for the DMA transfer to complete
			// {{{
			if (dma_op_complete)
			begin
				// o_int <= int_enable;
				r_pf_pc[C_AXI_ADDR_WIDTH-1:0] <= o_tbl_addr;
				if (dma_terminate)
				begin
					o_pf_clear_cache <= 1'b1;
					sgstate <= SG_IDLE;
					o_busy <= 1'b0;
					o_done <= 1'b1;
					o_err  <= dma_err || dma_abort;
					if (i_pf_illegal)
						r_pf_pc[C_AXI_ADDR_WIDTH-1:0] <= i_pf_pc;
				end else if (C_AXI_ADDR_WIDTH > 30)
					sgstate <= SG_SRCHALF;
				else
					sgstate <= SG_SRCADDR;
			end
			// }}}
		endcase

		if (i_pf_valid && i_pf_illegal
			&& sgstate != SG_CONTROL && sgstate != SG_WAIT
			&& !o_new_pc && !o_pf_clear_cache)
		begin
			// {{{
			sgstate <= SG_IDLE;
			o_pf_clear_cache <= 1'b1;
			o_done <= 1'b1;
			o_busy <= 1'b0;
			o_err  <= 1'b1;
			// }}}
		end

		if (i_abort && (!o_dmac_wvalid || i_dmac_wready))
		begin
			// {{{
			o_dmac_wstrb <= 4'h8;
			o_dmac_wdata[31:24] <= ABORT_KEY;
			o_dmac_wvalid <= o_dmac_wstrb[0] && dma_busy
					&& !dma_err && (sgstate == SG_WAIT);
			// }}}
			if (!dma_busy && (sgstate != SG_WAIT))
			begin
				sgstate <= SG_IDLE;
				o_done  <= 1'b1;
				o_new_pc<= 1'b0;
				o_busy  <= 1'b0;
				o_dmac_wvalid <= 1'b0;
				o_pf_clear_cache  <= 1'b1;
			end
		end

		if (!S_AXI_ARESETN)
		begin
			// {{{
			sgstate <= SG_IDLE;
			o_pf_clear_cache <= 1'b1;
			o_new_pc <= 1'b0;
			o_dmac_wvalid <= 1'b0;
			r_pf_pc <= 0;
			o_busy <= 1'b0;
			o_done <= 1'b0;
			o_err  <= 1'b0;
			// }}}
		end

		r_pf_pc[60-1:C_AXI_ADDR_WIDTH] <= 0;
	end
	// }}}

	// dma_starting
	// {{{
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		dma_starting <= 0;
	else if (sgstate != SG_WAIT)
		dma_starting <= 0;
	else if (!o_dmac_wvalid || o_dmac_waddr != DMA_CONTROL)
		dma_starting <= 0;
	else
		dma_starting <= o_dmac_wdata[0] && o_dmac_wstrb[0];

	always @(*)
		o_pf_pc = { r_pf_pc[C_AXI_ADDR_WIDTH-1:2], 2'b00 };
	// }}}

	// dma_aborting
	// {{{
	initial	dma_aborting = 0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		dma_aborting <= 0;
	else if (sgstate != SG_WAIT)
		dma_aborting <= 0;
	else if (!dma_aborting)
	begin
		if (i_abort)
			dma_aborting <= 1;
	end else if (!dma_busy && !dma_starting && (!o_dmac_wvalid
					|| (i_dmac_wready && !o_dmac_wstrb[0])))
		dma_aborting <= 0;
`ifdef	FORMAL
	always @(*)
	if (S_AXI_ARESETN)
	begin
		if (o_dmac_wvalid && !o_dmac_wstrb[0])
			assert(dma_aborting);
		if (sgstate != SG_WAIT && sgstate != SG_IDLE)
			assert(!dma_aborting);
	end
`endif
	// }}}
	// }}}

	// Make Verilator happy
	// {{{
	// Verilatar lint_off UNUSED
	wire	unused;
	assign	unused = &{ 1'b0, dma_done, i_dmac_rdata[31:5],
				i_dmac_rdata[2],
				r_pf_pc[59:C_AXI_ADDR_WIDTH] };
	// Verilatar lint_on  UNUSED
	// }}}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// Formal properties
// {{{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
`ifdef	FORMAL
	////////////////////////////////////////////////////////////////////////
	//
	////////////////////////////////////////////////////////////////////////
	//
	localparam	F_LGDEPTH = 4;
	// Verilator lint_off UNDRIVEN
	(* anyseq *) reg [TBL_SIZE*8-1:0] f_tblentry;
	(* anyconst *) reg		f_never_abort;
	(* anyseq *)	reg	f_dma_complete;
	// Verilator lint_on  UNDRIVEN
	reg	f_past_valid;
	reg	[C_AXI_ADDR_WIDTH-1 :0]	f_tbladdr, f_pc;
	reg				f_tbl_last, f_tbl_skip, f_tbl_jump,
					f_tbl_int_enable;
	// reg	[C_AXI_ADDR_WIDTH-1:0]	f_tbl_src;
	reg				f_dma_arvalid, f_dma_arready;
			reg	f_dma_busy;



	initial	f_past_valid = 0;
	always @(posedge S_AXI_ACLK)
		f_past_valid <= 1;

	always @(*)
	if (!f_past_valid)
		assume(!S_AXI_ARESETN);

	always @(*)
	if (i_start)
	begin
		assume(!i_abort);
		assume(i_tbl_addr[1:0] == 2'b00);
	end

	////////////////////////////////////////////////////////////////////////
	//
	// Prefetch interface property checks
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//

	// Prefetch properties

	assume property (@(posedge S_AXI_ACLK)
		S_AXI_ARESETN && i_pf_valid && !o_pf_ready
			&& !o_pf_clear_cache && !o_new_pc
		|=> i_pf_valid && $stable({
				i_pf_insn, i_pf_pc, i_pf_illegal }));

	always @(*)
	if (o_new_pc)
		assert(o_pf_pc[1:0] == 2'b00);

	always @(posedge S_AXI_ACLK)
	if (o_new_pc)
		f_pc <= o_pf_pc;
	else if (i_pf_valid && o_pf_ready)
		f_pc <= f_pc + 4;

	always @(*)
	if (i_pf_valid)
		assume(i_pf_pc == f_pc);

	always @(*)
	if (S_AXI_ARESETN && !o_new_pc && !o_pf_clear_cache)
		assert(f_pc[1:0] == 2'b00);

	always @(posedge S_AXI_ACLK)
	if (!f_past_valid || $past(!S_AXI_ARESETN || o_new_pc
				|| o_pf_clear_cache))
	begin
		assume(!i_pf_illegal);
	end else if (!$rose(i_pf_valid))
	begin
		assume(!$rose(i_pf_illegal));
	end else
		assume($stable(i_pf_illegal));
	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// AXI-lite interface property checks
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//
	reg	[F_LGDEPTH-1:0]	fdma_rd_outstanding, fdma_wr_outstanding,
				fdma_awr_outstanding;
	reg			f_dma_bvalid, f_dma_rvalid;

	faxil_master #(
		// {{{
		.C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH),
		.C_AXI_ADDR_WIDTH(DMAC_ADDR_WIDTH),
		.F_OPT_BRESP(1'b1),
		.F_OPT_RRESP(1'b0),
		.F_OPT_NO_RESET(1'b1),
		.F_LGDEPTH(F_LGDEPTH)
		// }}}
	) faxi(
		// {{{
		.i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN),
		//
		.i_axi_awvalid(o_dmac_wvalid),
		.i_axi_awready(i_dmac_wready),
		.i_axi_awaddr(o_dmac_waddr),
		.i_axi_awprot( 3'h0),
		//
		.i_axi_wvalid(o_dmac_wvalid),
		.i_axi_wready(i_dmac_wready),
		.i_axi_wdata( o_dmac_wdata),
		.i_axi_wstrb( o_dmac_wstrb),
		//
		.i_axi_bvalid(f_dma_bvalid),
		.i_axi_bready(1'b1),
		.i_axi_bresp( 2'b00),
		//
		.i_axi_arvalid(f_dma_arvalid),
		.i_axi_arready(f_dma_arready),
		.i_axi_araddr(DMA_CONTROL),
		.i_axi_arprot(3'h0),
		//
		.i_axi_rvalid(f_dma_rvalid),
		.i_axi_rready(1'b1),
		.i_axi_rdata(i_dmac_rdata),
		.i_axi_rresp(2'b00),
		//
		.f_axi_rd_outstanding(fdma_rd_outstanding),
		.f_axi_wr_outstanding(fdma_wr_outstanding),
		.f_axi_awr_outstanding(fdma_awr_outstanding)
		// }}}
	);

	initial	f_dma_arvalid = 1'b0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		f_dma_arvalid <= 1'b0;
	else
		f_dma_arvalid <= 1'b1;

	always @(*)
		f_dma_arready = 1'b1;

	always @(*)
	if (S_AXI_ARESETN)
	begin
		assert(fdma_awr_outstanding == fdma_wr_outstanding);
		assert(fdma_wr_outstanding == (f_dma_bvalid ? 1:0));
		assert(fdma_rd_outstanding <= 1);
	end

	assert property (@(posedge S_AXI_ACLK)
		!S_AXI_ARESETN
		|=> sgstate == SG_IDLE && !o_dmac_wvalid);


	assert property (@(posedge S_AXI_ACLK)
		S_AXI_ARESETN && o_dmac_wvalid && !i_dmac_wready
		|=> o_dmac_wvalid && $stable({
			o_dmac_waddr, o_dmac_wdata, o_dmac_wstrb }));

	assert property (@(posedge S_AXI_ACLK)
		fdma_wr_outstanding == fdma_awr_outstanding);

	assert property (@(posedge S_AXI_ACLK)
		fdma_wr_outstanding == (f_dma_bvalid ? 1:0));

	initial	f_dma_bvalid = 1'b0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		f_dma_bvalid <= 0;
	else if (o_dmac_wvalid && i_dmac_wready)
		f_dma_bvalid <= 1;
	else
		f_dma_bvalid <= 0;

	initial	f_dma_rvalid = 1'b0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		f_dma_rvalid <= 0;
	else if (f_dma_arvalid)
		f_dma_rvalid <= 1;
	else
		f_dma_rvalid <= 0;
	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// DMA busy and read interface properties
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//

	initial	f_dma_busy = 0;
	always @(posedge S_AXI_ACLK)
	if (!S_AXI_ARESETN)
		f_dma_busy <= 0;
	else if (o_dmac_wvalid && i_dmac_wready && o_dmac_waddr == 0
			&& o_dmac_wstrb[0] && o_dmac_wdata[0])
		f_dma_busy <= 1'b1;
	else if (f_dma_busy)
		f_dma_busy <= !f_dma_complete;

	always @(*)
	if (S_AXI_ARESETN)
	begin
		if (sgstate != SG_WAIT)
			assert(!f_dma_busy);
		else if (o_dmac_wvalid && !dma_aborting)
			assert(!f_dma_busy);
	end

	always @(posedge S_AXI_ACLK)
	if (f_past_valid && S_AXI_ARESETN && $past(f_dma_busy))
		assert(!dma_starting);

	always @(posedge S_AXI_ACLK)
	if (f_past_valid)
	begin
		assume(dma_busy == $past(f_dma_busy));
		assume(i_dma_complete == $past(f_dma_complete));
	end

	always @(*)
	if (!f_dma_busy)
		assume(!f_dma_complete);

	assume property (@(posedge S_AXI_ACLK)
		disable iff (!S_AXI_ARESETN)
		!dma_busy ##1 !dma_busy |-> !$rose(dma_err)
	);

	assume property (@(posedge S_AXI_ACLK)
		disable iff (!S_AXI_ARESETN)
		$rose(dma_busy) |=> !dma_err);

	assume property (@(posedge S_AXI_ACLK)
		disable iff (!S_AXI_ARESETN)
		o_dmac_wvalid && i_dmac_wready && o_dmac_wstrb[0]
				&& o_dmac_wdata[4]
		|=> ##1 !dma_err);

	assume property (@(posedge S_AXI_ACLK)
		disable iff (!S_AXI_ARESETN)
		!o_dmac_wvalid || !i_dmac_wready || !o_dmac_wstrb[0]
				|| !o_dmac_wdata[4]
		|=> ##1 !$fell(dma_err));


//	assume property (@(posedge S_AXI_ACLK)
//		dma_busy |=> dma_busy [*0:$]
//		##1 dma_busy && i_dma_complete
//		##1 !dma_busy);

	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// State machine checks
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//


	always @(*)
	if (o_new_pc)
	begin
		if (C_AXI_ADDR_WIDTH <= 30)
		begin
			assert(sgstate == SG_SRCADDR);
		end else
			assert(sgstate == SG_SRCHALF);
	end

	generate if (C_AXI_ADDR_WIDTH > 30)
	begin
		always @(*)
		begin
			f_tbl_last = (f_tblentry[63:62] == 2'b01);
			f_tbl_skip = (f_tblentry[63:62] == 2'b10);
			f_tbl_jump = (f_tblentry[63:62] == 2'b11);
			f_tbl_int_enable = f_tblentry[127];
		end
	end else begin
		always @(*)
		begin
			f_tbl_last = (f_tblentry[31:30] == 2'b01);
			f_tbl_skip = (f_tblentry[31:30] == 2'b10);
			f_tbl_jump = (f_tblentry[31:30] == 2'b11);
			f_tbl_int_enable = f_tblentry[63];
		end
	end endgenerate

	always @(posedge S_AXI_ACLK)
	if (S_AXI_ARESETN && (sgstate != SG_IDLE))
	begin
		if ($stable(sgstate))
		begin
			assume($stable(f_tblentry));
		end else if ((C_AXI_ADDR_WIDTH > 30)&&(sgstate != SG_SRCHALF))
		begin
			assume($stable(f_tblentry));
		end else if ((C_AXI_ADDR_WIDTH <= 30)&&(sgstate != SG_SRCADDR))
			assume($stable(f_tblentry));
	end

	always @(posedge S_AXI_ACLK)
	if (o_new_pc)
		f_tbladdr <= o_pf_pc;
	else if (sgstate == SG_WAIT && dma_op_complete && !dma_terminate)
		f_tbladdr <= f_tbladdr + TBL_SIZE;

	assert property (@(posedge S_AXI_ACLK)
		disable iff (!S_AXI_ARESETN)
		sgstate == SG_IDLE && !i_start
		|=> sgstate == SG_IDLE && o_pf_clear_cache);

	generate if (C_AXI_ADDR_WIDTH <= 30)
	begin : SMALL_ADDRESS_SPACE
		// {{{
		reg	[5:0]	f_dma_seq;

		always @(*)
		begin
			assert(sgstate != SG_SRCHALF);
			assert(sgstate != SG_DSTHALF);
		end

		always @(*)
		if (i_pf_valid)
		case(sgstate)
		// SG_IDLE:	begin end
		// SG_SRCHALF:	begin end
		// SG_DSTHALF:	begin end
		// SG_LENGTH:	begin end
		// SG_CONTROL:	begin end
		// SG_WAIT:	begin end
		SG_SRCADDR: begin
			assume(i_pf_insn == f_tblentry[31:0]
				&&(i_pf_pc == f_tbladdr));
			end
		SG_DSTADDR: begin
			assume(i_pf_insn == f_tblentry[63:32]
				&&(i_pf_pc == f_tbladdr + 4));
			end
		SG_LENGTH: begin
			assume(i_pf_insn == f_tblentry[95:64]
				&&(i_pf_pc == f_tbladdr + 8));
			end
		default: begin end
		endcase

		assert property (@(posedge S_AXI_ACLK)
			disable iff (!S_AXI_ARESETN)
			sgstate == SG_IDLE && i_start
			|=> o_new_pc && !o_pf_clear_cache
				&& sgstate == SG_SRCADDR
				&& r_pf_pc[C_AXI_ADDR_WIDTH-1:0]
					== $past(i_tbl_addr));

		initial	f_dma_seq = 0;
		always @(posedge S_AXI_ACLK)
		if (!S_AXI_ARESETN)
			f_dma_seq <= 0;
		else begin

			// SG_SRCADDR, or entering SG_SRCADDR
			// {{{
			if ((f_dma_seq == 0)&&(i_start))
				f_dma_seq <= 1;

			if (f_dma_seq[0] || o_new_pc)
			begin
				assert(sgstate == SG_SRCADDR);
				assert(!o_dmac_wvalid);
				assert(!dma_busy);
				f_dma_seq <= 1;
				if (i_pf_valid && o_pf_ready)
				begin
					f_dma_seq <= 2;

					if (f_tbl_jump || f_tbl_skip)
						f_dma_seq <= 1;
				end

				if (!o_new_pc)
				begin
					assert(o_pf_pc == f_tbladdr);
					assert(f_pc == o_pf_pc);
				end

				if ($past(sgstate == SG_SRCADDR
					&& i_pf_valid && o_pf_ready))
				begin
					// Jump or skip

					assert(o_new_pc);
					// Check the jump address
					// if ($past(i_pf_insn[31:30] == 2'b11))
					if ($past(f_tbl_jump))
					begin
						assert(o_pf_pc == { $past(i_pf_insn[C_AXI_ADDR_WIDTH-1:2]), 2'b00 });
					end else // Skip
						assert(o_pf_pc == $past(f_tbladdr + TBL_SIZE));
				end
			end
			// }}}

			// SG_DSTADDR
			// {{{
			if (f_dma_seq[1])
			begin
				assert(sgstate == SG_DSTADDR);
				assert(tbl_last == f_tbl_last);
				assert(o_tbl_addr == f_tbladdr);
				if ($rose(f_dma_seq[1]))
					assert(o_dmac_wvalid);
				assert(o_dmac_waddr == DMA_SRCLO);
				assert(o_dmac_wdata == f_tblentry[31:0]);
				assert(&o_dmac_wstrb);
				assert(!dma_busy);
				assert(o_pf_pc == f_tbladdr);
				// assert(f_pc == o_pf_pc + 4);
				f_dma_seq <= 2;
				if (i_pf_valid && o_pf_ready)
					f_dma_seq <= 4;
			end
			// }}}

			// SG_LENGTH
			// {{{
			if (f_dma_seq[2])
			begin
				assert(sgstate == SG_LENGTH);
				assert(tbl_last == f_tbl_last);
				assert(tbl_int_enable == f_tbl_int_enable);
				assert(o_tbl_addr == f_tbladdr);
				if ($rose(f_dma_seq[2]))
					assert(o_dmac_wvalid);
				assert(o_dmac_waddr == DMA_DSTLO);
				assert(o_dmac_wdata == { 2'b00, f_tblentry[61:32] });
				assert(&o_dmac_wstrb);
				assert(!dma_busy);
				assert(o_pf_pc == f_tbladdr);
				// assert(f_pc == o_pf_pc + 8);
				f_dma_seq <= 4;
				if (i_pf_valid && o_pf_ready)
				begin
					if (i_pf_insn == 0)
					begin
						if (tbl_last)
							f_dma_seq <= 0;
						else
							f_dma_seq <= 1;
					end else
						f_dma_seq <= 8;
				end
			end
			// }}}

			// SG_CONTROL
			// {{{
			if (f_dma_seq[3])
			begin
				assert(sgstate == SG_CONTROL);
				assert(tbl_last == f_tbl_last);
				assert(o_tbl_addr == f_tbladdr);
				assert(o_dmac_wvalid);
				assert(o_dmac_waddr == DMA_LENLO);
				assert(o_dmac_wdata == f_tblentry[95:64]);
				assert(&o_dmac_wstrb);
				assert(!dma_busy);
				assert(o_pf_pc == f_tbladdr);
				assert(f_pc == f_tbladdr + TBL_SIZE);
				// assert(f_pc == o_pf_pc);
				f_dma_seq <= 8;
				if (i_dmac_wready)
					f_dma_seq <= 16;
			end
			// }}}

			// SG_WAIT && o_dmac_wvalid
			// {{{
			if (f_dma_seq[4])
			begin
				assert(sgstate == SG_WAIT);
				assert(tbl_last == f_tbl_last);
				assert(o_tbl_addr == f_tbladdr + TBL_SIZE);
				assert(o_dmac_wvalid);
				assert(o_dmac_waddr == DMA_CONTROL);
				assert(o_dmac_wdata[15:0] == 16'h1f);
				assert(&o_dmac_wstrb);
				assert(!dma_busy);
				assert(o_pf_pc == f_tbladdr);
				assert(f_pc == f_tbladdr + TBL_SIZE);
				// assert(f_pc == o_pf_pc);
				f_dma_seq <= 16;
				if (o_dmac_wvalid && i_dmac_wready)
					f_dma_seq <= 32;
			end
			// }}}

			// SG_WAIT && !o_dmac_wvalid
			// {{{
			if (f_dma_seq[5])
			begin
				assert(sgstate == SG_WAIT);
				assert(tbl_last == f_tbl_last);
				assert(o_tbl_addr == f_tbladdr + TBL_SIZE);
				assert(o_dmac_waddr == DMA_CONTROL);
				// assert(o_dmac_wdata == f_tblentry[95:64]);
				if (f_never_abort)
				begin
					assert(&o_dmac_wstrb);
					assert(!o_dmac_wvalid);
				end else
					assert(o_dmac_wstrb == 4'h8
						|| o_dmac_wstrb == 4'hf);
				if (o_dmac_wvalid)
					assert(!o_dmac_wstrb[0]);
				f_dma_seq <= 32;
				assert(o_pf_pc == f_tbladdr);
				assert(f_pc == f_tbladdr + TBL_SIZE);
				// assert(f_pc == o_pf_pc);
				// if (tbl_last || (dma_err && !o_busy)
				//			|| i_pf_illegal)
				if (dma_op_complete)
				begin
					if (dma_terminate)
							// (dma_err && !o_busy))
						f_dma_seq <= 0;
					else
						f_dma_seq <= 1;
				end
			end
			// }}}

			// pf_illegal
			// {{{
			if ((|f_dma_seq[2:0]) && i_pf_illegal)
				f_dma_seq <= 0;
			// }}}

			// i_abort
			if ((|f_dma_seq[3:0]) && i_abort
					&& (!o_dmac_wvalid || i_dmac_wready))
				f_dma_seq <= 0;
		end

		always @(*)
		if (f_dma_seq == 0)
		begin
			assert(sgstate == SG_IDLE);
			assert(!o_new_pc);
			assert(!o_dmac_wvalid);
		end

		always @(posedge S_AXI_ACLK)
		if (!f_past_valid || $past(!S_AXI_ARESETN))
		begin end
		else if (sgstate == 0)
		begin
			assert(o_pf_clear_cache);
			assert(!dma_busy);
		end

		cover property (@(posedge S_AXI_ACLK) S_AXI_ARESETN); // !!!
		cover property (@(posedge S_AXI_ACLK) f_dma_seq == 0 && S_AXI_ARESETN); // !!!
		cover property (@(posedge S_AXI_ACLK) f_dma_seq == 0 && S_AXI_ARESETN && !i_abort); // !!!
		cover property (@(posedge S_AXI_ACLK) f_dma_seq == 0 && S_AXI_ARESETN && !i_abort && i_start); // !!!
		cover property (@(posedge S_AXI_ACLK) f_dma_seq[1]);
		cover property (@(posedge S_AXI_ACLK) f_dma_seq[2]);
		cover property (@(posedge S_AXI_ACLK) f_dma_seq[3]);
		cover property (@(posedge S_AXI_ACLK) f_dma_seq[4]);	// !!!
		cover property (@(posedge S_AXI_ACLK) f_dma_seq[5]);	// !!!

//		cover property (@(posedge S_AXI_ACLK)
//			disable iff (!S_AXI_ARESETN || i_abort || i_pf_illegal
//				|| dma_err)
//			f_dma_seq[0] ##1 1[*0:$] ##1 f_dma_seq[5]
//			##1 f_dma_seq == 0);	// !!!

		cover property (@(posedge S_AXI_ACLK)	// !!!
			f_dma_seq[5] && !tbl_last && f_dma_busy && dma_busy);

		cover property (@(posedge S_AXI_ACLK)	// !!!
			f_dma_seq[5] && tbl_last && f_dma_busy && dma_busy);

		cover property (@(posedge S_AXI_ACLK)
			f_dma_seq[5] ##2 f_dma_seq[0]);	// !!!

		cover property (@(posedge S_AXI_ACLK)
			f_dma_seq[5] && tbl_last && i_dma_complete);	// !!!

		cover property (@(posedge S_AXI_ACLK)
			f_dma_seq[5] && i_dma_complete);	// !!!
		// }}}
	end else begin : BIG_ADDR
	end endgenerate

	always @(*)
		assert(o_busy == (sgstate != SG_IDLE));

	always @(*)
	if (o_busy)
	begin
		assert(!o_done);
		assert(!o_err);
	end

	always @(posedge S_AXI_ACLK)
	if (f_past_valid && $past(S_AXI_ARESETN) && $past(o_busy) && !o_busy)
		assert(o_done);

//	assert property (@(posedge S_AXI_ACLK)
//		!o_done |-> !o_int);

	assert property (@(posedge S_AXI_ACLK)
		!o_done && !o_busy |-> !o_err);

	always @(*)
	if (o_pf_ready)
		assert(!o_dmac_wvalid || i_dmac_wready);

	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Cover checks
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//

	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Careless assumptions (i.e. constraints)
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//

	always @(*)
	if (f_never_abort)
		assume(!i_abort);

	// }}}
`endif
// }}}
endmodule
