////////////////////////////////////////////////////////////////////////////////
//
// Filename:	rtl/apbxclk.v
// {{{
// Project:	WB2AXIPSP: bus bridges and other odds and ends
//
// Purpose:	
//
// 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	apbxclk #(
		// {{{
		parameter	C_APB_ADDR_WIDTH = 12,
		parameter	C_APB_DATA_WIDTH = 32,
		parameter [0:0]	OPT_REGISTERED = 1'b0,
		localparam	AW = C_APB_ADDR_WIDTH,
		localparam	DW = C_APB_DATA_WIDTH
		// }}}
	) (
		// {{{
		input	wire			S_APB_PCLK, S_PRESETn,
		input	wire			S_APB_PSEL,
		input	wire			S_APB_PENABLE,
		output	reg			S_APB_PREADY,
		input	wire	[AW-1:0]	S_APB_PADDR,
		input	wire			S_APB_PWRITE,
		input	wire	[DW-1:0]	S_APB_PWDATA,
		input	wire	[DW/8-1:0]	S_APB_PWSTRB,
		input	wire	[2:0]		S_APB_PPROT,
		output	wire	[DW-1:0]	S_APB_PRDATA,
		output	wire			S_APB_PSLVERR,
		//
		input	wire			M_APB_PCLK,
		output	reg			M_PRESETn,
		output	reg			M_APB_PSEL,
		output	reg			M_APB_PENABLE,
		input	wire			M_APB_PREADY,
		output	wire	[AW-1:0]	M_APB_PADDR,
		output	wire			M_APB_PWRITE,
		output	wire	[DW-1:0]	M_APB_PWDATA,
		output	wire	[DW/8-1:0]	M_APB_PWSTRB,
		output	wire	[2:0]		M_APB_PPROT,
		input	wire	[DW-1:0]	M_APB_PRDATA,
		input	wire			M_APB_PSLVERR
		// }}}
	);

	// Local declarations
	// {{{
	reg			reset_pipe, full_reset_pipe, full_reset;

	reg			s_request, s_tfr_request;
	reg			m_request, m_request_pipe;

	reg			m_ack;
	reg			s_ack, s_ack_pipe;
	reg	[DW-1:0]	m_prdata;
	reg			m_pslverr;

	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Synchronize resets
	// {{{
	always @(posedge M_APB_PCLK or negedge S_PRESETn)
	if (!S_PRESETn)
		{ M_PRESETn, reset_pipe } <= 0;
	else
		{ M_PRESETn, reset_pipe } <= { reset_pipe, 1'b1 };

	always @(posedge S_APB_PCLK or negedge M_PRESETn)
	if (!M_PRESETn)
		{ full_reset, full_reset_pipe } <= 0;
	else
		{ full_reset, full_reset_pipe } <= { full_reset_pipe, 1'b1 };
	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Move the request across clock domains
	// {{{

	// s_request
	// {{{
	// Step 1: Register the request
	//	Always register anything before moving cross clock domains
	always @(posedge S_APB_PCLK or negedge S_PRESETn)
	if (!S_PRESETn)
		s_request <= 0;
	else if (S_APB_PREADY)
		s_request <= 0;
	else if (S_APB_PSEL && !S_APB_PENABLE)
		s_request <= 1;
	// }}}

	// s_tfr_request
	// {{{
	// Step 2: Forward requests--but only when the downstream handshake
	//	is ready to accept them
	always @(posedge S_APB_PCLK or negedge S_PRESETn)
	if (!S_PRESETn)
		s_tfr_request <= 0;
	else if (S_APB_PREADY)
		s_tfr_request <= 0;
	else if ((S_APB_PSEL && !S_APB_PENABLE) || s_request)
		s_tfr_request <= (full_reset && !s_ack);
	// }}}

	// m_request, m_request_pipe -- 3. Capture the request in new clk domain
	// {{{
	always @(posedge M_APB_PCLK or negedge M_PRESETn)
	if (!M_PRESETn)
		{ m_request, m_request_pipe } <= 0;
	else
		{ m_request, m_request_pipe }
				<= { m_request_pipe, s_tfr_request };
	// }}}

	// M_APB_PSEL, M_APB_PENABLE -- 4. Forward the request downstream
	// {{{
	always @(posedge M_APB_PCLK or negedge M_PRESETn)
	if (!M_PRESETn)
	begin
		M_APB_PSEL <= 0;
		M_APB_PENABLE <= 1'b0;
	end else begin
		M_APB_PSEL    <= m_request && !m_ack
				&& (!M_APB_PSEL || !M_APB_PENABLE || !M_APB_PREADY);
		M_APB_PENABLE <= M_APB_PSEL && (!M_APB_PSEL || !M_APB_PENABLE || !M_APB_PREADY);
	end
	// }}}
	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Move the response back across clock domains
	// {{{

	// m_ack
	// {{{
	always @(posedge M_APB_PCLK or negedge M_PRESETn)
	if (!M_PRESETn)
		m_ack <= 0;
	else if (m_request && !m_ack && M_APB_PSEL && M_APB_PREADY && M_APB_PENABLE)
		m_ack <= 1'b1;
	else if (!m_request)
		m_ack <= 1'b0;
	// }}}

	// s_ack, s_ack_pipe
	// {{{
	always @(posedge S_APB_PCLK or negedge S_PRESETn)
	if (!S_PRESETn)
		{ s_ack, s_ack_pipe } <= 0;
	else
		{ s_ack, s_ack_pipe } <= { s_ack_pipe, m_ack };
	// }}}

	// S_APB_PREADY
	// {{{
	always @(posedge S_APB_PCLK or negedge S_PRESETn)
	if (!S_PRESETn)
		S_APB_PREADY <= 0;
	else
		S_APB_PREADY <= !S_APB_PREADY && s_tfr_request && s_ack;
	// }}}

	// m_prdata
	// {{{
	always @(posedge M_APB_PCLK)
	if (M_APB_PSEL && M_APB_PREADY)
		m_prdata  <= M_APB_PRDATA;
	// }}}

	// m_pslverr
	// {{{
	always @(posedge M_APB_PCLK or negedge M_PRESETn)
	if (!M_PRESETn)
		m_pslverr <= 0;
	else if (M_APB_PSEL && M_APB_PREADY)
		m_pslverr <= M_APB_PSLVERR;
	// }}}
	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Handle the ancillary (data bearing) signals
	// {{{

	generate if (OPT_REGISTERED)
	begin : GEN_REGISTERS
		// {{{
		// Register all results into the new domain when
		// crossing clock domains
		reg	[AW-1:0]	m_paddr;
		reg			m_pwrite;
		reg	[DW-1:0]	m_pwdata;
		reg	[DW/8-1:0]	m_pwstrb;
		reg	[2:0]		m_pprot;

		reg	[DW-1:0]	s_prdata;
		reg			s_pslverr;

		always @(posedge M_APB_PCLK)
		if (m_request && !m_ack)
		begin
			m_paddr  <= S_APB_PADDR;
			m_pwrite <= S_APB_PWRITE;
			m_pwdata <= S_APB_PWDATA;
			m_pwstrb <= S_APB_PWSTRB;
			m_pprot  <= S_APB_PPROT;
		end

		assign	M_APB_PADDR  = m_paddr;
		assign	M_APB_PWRITE = m_pwrite;
		assign	M_APB_PWDATA = m_pwdata;
		assign	M_APB_PWSTRB = m_pwstrb;
		assign	M_APB_PPROT  = m_pprot;

		always @(posedge S_APB_PCLK or negedge S_PRESETn)
		if (!S_PRESETn)
			s_pslverr <= 1'b0;
		else if (s_tfr_request && s_ack
					&& (!S_APB_PREADY || !S_APB_PENABLE))
			s_pslverr <= m_pslverr;
		else
			s_pslverr <= 0;

		always @(posedge S_APB_PCLK)
		if (s_request && s_ack)
		begin
			s_prdata  <= m_prdata;
		end else begin
			s_prdata  <= 0;
		end

		assign	S_APB_PRDATA  = s_prdata;
		assign	S_APB_PSLVERR = s_pslverr;
		// }}}
	end else begin : NO_REGISTERING
		// {{{
		// Results will be stable whenever PSEL is true.  There's
		// really no *need* to register them

		assign	M_APB_PADDR  = S_APB_PADDR;
		assign	M_APB_PWRITE = S_APB_PWRITE;
		assign	M_APB_PWDATA = S_APB_PWDATA;
		assign	M_APB_PWSTRB = S_APB_PWSTRB;
		assign	M_APB_PPROT  = S_APB_PPROT;

		assign	S_APB_PRDATA  = m_prdata;
		assign	S_APB_PSLVERR = m_pslverr && S_APB_PREADY;
		// }}}
	end endgenerate
	// }}}

	// Make Verilator happy
	// {{{
	// Verilator lint_off UNUSED
	wire	unused;
	assign	unused = &{ 1'b0 };
	// Verilator lint_on  UNUSED
	// }}}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// Formal properties
// {{{
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
`ifdef	FORMAL
	(* gclk *)	reg	gbl_clk;

	////////////////////////////////////////////////////////////////////////
	//
	// Interface properties
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//

	fapb_slave #(
		// {{{
		.AW(C_APB_ADDR_WIDTH), .DW(C_APB_DATA_WIDTH),
		.F_OPT_MAXSTALL(0), .F_OPT_ASYNC_RESET(1'b1),
		.F_OPT_SLVERR(1'b1)
		// }}}
	) fslv (
		// {{{
		.PCLK(S_APB_PCLK), .PRESETn(S_PRESETn),
		.PSEL(S_APB_PSEL),
			.PENABLE(S_APB_PENABLE),
			.PREADY(S_APB_PREADY),
			.PADDR(S_APB_PADDR),
		.PWRITE(S_APB_PWRITE), .PWDATA(S_APB_PWDATA),
			.PWSTRB(S_APB_PWSTRB),
		.PPROT(S_APB_PPROT),
		.PRDATA(S_APB_PRDATA), .PSLVERR(S_APB_PSLVERR)
		// }}}
	);

	fapb_master #(
		// {{{
		.AW(C_APB_ADDR_WIDTH), .DW(C_APB_DATA_WIDTH),
		.F_OPT_MAXSTALL(0), .F_OPT_ASYNC_RESET(1'b1),
		.F_OPT_SLVERR(1'b1)
		// }}}
	) fmas (
		// {{{
		.PCLK(M_APB_PCLK), .PRESETn(M_PRESETn),
		.PSEL(M_APB_PSEL), .PENABLE(M_APB_PENABLE),
			.PREADY(M_APB_PREADY), .PADDR(M_APB_PADDR),
		.PWRITE(M_APB_PWRITE), .PWDATA(M_APB_PWDATA),
			.PWSTRB(M_APB_PWSTRB),
		.PPROT(M_APB_PPROT),
		.PRDATA(M_APB_PRDATA), .PSLVERR(M_APB_PSLVERR)
		// }}}
	);

	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Clock stability
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//
	reg	f_past_valid_gbl;

	initial	f_past_valid_gbl <= 1'b0;
	always @(posedge gbl_clk)
		f_past_valid_gbl <= 1'b1;

	always @(*)
	if (!f_past_valid_gbl)
		assume(!S_PRESETn);

	always @(posedge gbl_clk)
	if (!$rose(S_APB_PCLK))
	begin
		assume(!$rose(S_PRESETn));
		assume($stable(S_APB_PSEL));
		assume($stable(S_APB_PENABLE));
		assume($stable(S_APB_PADDR));
		assume($stable(S_APB_PWRITE));
		assume($stable(S_APB_PWDATA));
		assume($stable(S_APB_PWSTRB));
		assume($stable(S_APB_PPROT));
		//
		if (f_past_valid_gbl && S_PRESETn)
		begin
			assert($stable(S_APB_PREADY));
			if (OPT_REGISTERED)
			begin
				assert($stable(S_APB_PRDATA));
				assert($stable(S_APB_PSLVERR));
			end else if ($past(S_APB_PREADY) && S_APB_PREADY)
			begin
				assert($stable(S_APB_PRDATA));
				assert($stable(S_APB_PSLVERR));
			end
		end
	end

	always @(posedge gbl_clk)
	if (!$rose(M_APB_PCLK))
	begin
		if (f_past_valid_gbl)
		begin
			assert(!$rose(M_PRESETn));
		end

		if (f_past_valid_gbl && M_PRESETn)
		begin
			assert($stable(M_APB_PSEL));
			assert($stable(M_APB_PENABLE));
			if (OPT_REGISTERED)
			begin
				assert($stable(M_APB_PADDR));
				assert($stable(M_APB_PWRITE));
				assert($stable(M_APB_PWDATA));
				assert($stable(M_APB_PWSTRB));
				assert($stable(M_APB_PPROT));
			end else if ($past(M_APB_PSEL) && M_APB_PSEL)
			begin
				assert($stable(M_APB_PADDR));
				assert($stable(M_APB_PWRITE));
				assert($stable(M_APB_PWDATA));
				assert($stable(M_APB_PWSTRB));
				assert($stable(M_APB_PPROT));
			end
		end
		//
		assume($stable(M_APB_PREADY));
		assume($stable(M_APB_PRDATA));
		assume($stable(M_APB_PSLVERR));
	end

	always @(posedge gbl_clk)
	if ($past(S_APB_PSEL && !S_APB_PREADY) && $past(S_PRESETn) && S_PRESETn)
	begin
		assume($stable(S_APB_PSEL));
		assume($stable(S_APB_PADDR));
		assume($stable(S_APB_PWRITE));
		assume($stable(S_APB_PWDATA));
		assume($stable(S_APB_PWSTRB));
		assume($stable(S_APB_PPROT));
	end


	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Induction invariants
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//

	always @(*)
	case({ S_PRESETn, reset_pipe, M_PRESETn, full_reset_pipe, full_reset })
	5'b0_00_00: begin end
	5'b1_00_00: begin end
	5'b1_10_00: begin end
	5'b1_11_00: begin end
	5'b1_11_10: begin end
	5'b1_11_11: begin end
	default: assert(0);
	endcase

	always @(*)
	casez({ s_request, s_tfr_request, m_request_pipe, m_request, m_ack, s_ack_pipe, s_ack })
	7'b00_00_000: begin end
	7'b10_00_000: begin end
	7'b11_00_000: begin end
	7'b11_10_000: begin end
	7'b11_11_000: begin end
	7'b11_11_100: begin end
	7'b11_11_110: begin end
	7'b11_11_111: begin end
	7'b?0_11_111: begin end
	7'b?0_01_111: begin end
	7'b?0_00_111: begin end
	7'b?0_00_011: begin end
	7'b?0_00_001: begin end
	7'b?0_00_000: begin end
	default: assert(0);
	endcase

	always @(posedge S_APB_PCLK)
	if (s_request && S_PRESETn)
		assert(S_APB_PSEL && S_APB_PENABLE);

	always @(posedge gbl_clk)
	if (!s_tfr_request && S_PRESETn)
		assert(!M_APB_PSEL);

	always @(posedge gbl_clk)
	if (M_APB_PSEL)
		assert(m_request && !m_ack);

	always @(posedge gbl_clk)
	if (S_PRESETn && (m_ack || s_ack_pipe || s_ack || S_APB_PREADY))
		assert(!M_APB_PSEL);

	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// Contract check
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//

	(* anyconst *)	reg			fnvr_check;
	(* anyconst *)	reg	[3+DW/8+DW:0]	fnvr_write;
	(* anyconst *)	reg	[AW-1:0]	fnvr_addr;
	(* anyconst *)	reg	[1+DW-1:0]	fnvr_return;

	always @(posedge gbl_clk)
	if (S_APB_PSEL && fnvr_check)
	begin
		assume({ S_APB_PPROT, S_APB_PWSTRB, S_APB_PWDATA } != fnvr_write);
		assume(S_APB_PADDR != fnvr_addr);
	end

	always @(posedge gbl_clk)
	if (M_APB_PSEL && fnvr_check && M_PRESETn)
	begin
		assert({ M_APB_PPROT, M_APB_PWSTRB, M_APB_PWDATA } != fnvr_write);
		assert(M_APB_PADDR != fnvr_addr);
	end

	always @(posedge gbl_clk)
	if (M_APB_PSEL && M_PRESETn)
	begin
		assert(M_APB_PADDR  == S_APB_PADDR);
		assert(M_APB_PWRITE == S_APB_PWRITE);
		assert(M_APB_PWDATA == S_APB_PWDATA);
		assert(M_APB_PWSTRB == S_APB_PWSTRB);
	end

	always @(posedge gbl_clk)
	if (fnvr_check && M_APB_PSEL && M_APB_PENABLE && M_APB_PREADY)
		assume({ M_APB_PSLVERR, M_APB_PRDATA } != fnvr_return);
	
	always @(posedge gbl_clk)
	if (fnvr_check && m_ack)
		assert({ m_pslverr, m_prdata } != fnvr_return);

	always @(posedge gbl_clk)
	if (fnvr_check && S_APB_PSEL && S_APB_PENABLE && S_APB_PREADY)
		assert({ S_APB_PSLVERR, S_APB_PRDATA } != fnvr_return);
	

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

	reg	[2:0]	cvr_reads, cvr_writes;

	initial	cvr_writes = 0;
	always @(posedge S_APB_PCLK)
	if (!S_PRESETn)
		cvr_writes <= 0;
	else if (S_APB_PSEL && S_APB_PENABLE && S_APB_PREADY && S_APB_PWRITE
			&& !cvr_writes[2])
		cvr_writes <= cvr_writes + 1;

	initial	cvr_reads = 0;
	always @(posedge S_APB_PCLK)
	if (!S_PRESETn)
		cvr_reads <= 0;
	else if (S_APB_PSEL && S_APB_PENABLE && S_APB_PREADY && !S_APB_PWRITE
			&& !cvr_reads[2])
		cvr_reads <= cvr_reads + 1;

	always @(*)
	if (S_PRESETn)
	begin
		cover(cvr_writes >= 2);
		cover(cvr_reads  >= 2);

		cover(cvr_writes >= 3);
		cover(cvr_reads  >= 3);
	end

	// }}}
	////////////////////////////////////////////////////////////////////////
	//
	// "Careless" assumptions
	// {{{
	////////////////////////////////////////////////////////////////////////
	//
	//
	localparam	F_NCLK = 5;
	reg	[F_NCLK-1:0]	gbl_sclk, gbl_mclk;

	always @(posedge gbl_clk)
		gbl_sclk <= { gbl_sclk[F_NCLK-2:0], S_APB_PCLK };

	always @(posedge gbl_clk)
		gbl_mclk <= { gbl_mclk[F_NCLK-2:0], M_APB_PCLK };

	always @(posedge gbl_clk)
	if (gbl_sclk == 0)
		assume(S_APB_PCLK);
	else if (&gbl_sclk)
		assume(!S_APB_PCLK);

	always @(posedge gbl_clk)
	if (gbl_mclk == 0)
		assume(M_APB_PCLK);
	else if (&gbl_mclk)
		assume(!M_APB_PCLK);


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