/*
 * wb_dma.v
 *
 * vim: ts=4 sw=4
 *
 * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
 * SPDX-License-Identifier: CERN-OHL-P-2.0
 */

`default_nettype none

module wb_dma #(
	parameter integer A0W = 9,
	parameter integer A1W = 9,
	parameter integer DW = 32
)(
	// Master 0
	output wire [A0W-1:0] m0_addr,
	input  wire [ DW-1:0] m0_rdata,
	output wire [ DW-1:0] m0_wdata,
	output wire           m0_we,
	output wire           m0_cyc,
	input  wire           m0_ack,

	// Master 1
	output wire [A1W-1:0] m1_addr,
	input  wire [ DW-1:0] m1_rdata,
	output wire [ DW-1:0] m1_wdata,
	output wire           m1_we,
	output wire           m1_cyc,
	input  wire           m1_ack,

	// Slave (control)
	input  wire [   1:0] ctl_addr,
	output wire [DW-1:0] ctl_rdata,
	input  wire [DW-1:0] ctl_wdata,
	input  wire          ctl_we,
	input  wire          ctl_cyc,
	output wire          ctl_ack,

	// Clock / Reset
	input  wire clk,
	input  wire rst
);

	// Signals
	// -------

	// Control
	reg [1:0] state;	// [1] = busy [0] = phase 0(read) 1(write)
	reg [1:0] state_nxt;
	reg dir;			// 0 = M0->M1, 1 = M1->M0
	reg go;

	wire ack_rd;
	wire ack_wr;

	// Data register
	wire data_ce;
	reg  [DW-1:0] data_reg;

	// Address counters
	wire m0_addr_ce;
	wire m0_addr_ld;
	reg  [A0W-1:0] m0_addr_i;

	wire m1_addr_ce;
	wire m1_addr_ld;
	reg  [A1W-1:0] m1_addr_i;

	// Length counter
	wire len_ce;
	wire len_ld;
	reg  [12:0] len;
	wire len_last;

	// Control IF
	reg  ctl_do_write;
	reg  ctl_do_read;
	reg  ctl_ack_i;


	// Control
	// -------

	always @(posedge clk or posedge rst)
		if (rst)
			go <= 1'b0;
		else
			go <= ctl_do_write & (ctl_addr[1:0] == 2'b00) & ctl_wdata[15];

	always @(posedge clk or posedge rst)
		if (rst)
			state <= 2'b00;
		else
			state <= state_nxt;

	always @(*)
	begin
		state_nxt <= state;

		case (state)
			2'b00: begin
				if (go)
					state_nxt <= 2'b10;
			end

			2'b10: begin
				if (ack_rd)
					state_nxt <= 2'b11;
			end

			2'b11: begin
				if (ack_wr)
					state_nxt <= len_last ? 2'b00 : 2'b10;
			end

			default:
				state_nxt <= 2'b00;
		endcase
	end

	assign ack_rd = (m0_ack & ~dir) | (m1_ack &  dir);
	assign ack_wr = (m0_ack &  dir) | (m1_ack & ~dir);


	// WB transaction
	// --------------

	assign m0_cyc = state[1] & ~(state[0] ^ dir);
	assign m1_cyc = state[1] &  (state[0] ^ dir);

	assign m0_we  =  dir;
	assign m1_we  = ~dir;


	// Data register
	// -------------

	assign data_ce = ack_rd;

	always @(posedge clk)
		if (data_ce)
			data_reg <= dir ? m1_rdata : m0_rdata;

	assign m0_wdata = data_reg;
	assign m1_wdata = data_reg;


	// Address counters
	// ----------------

	always @(posedge clk)
		if (m0_addr_ce)
			m0_addr_i <= m0_addr_ld ? ctl_wdata[A0W-1:0] : (m0_addr_i + 1);

	always @(posedge clk)
		if (m1_addr_ce)
			m1_addr_i <= m1_addr_ld ? ctl_wdata[A1W-1:0] : (m1_addr_i + 1);

	assign m0_addr_ce = m0_addr_ld | ack_wr;
	assign m1_addr_ce = m1_addr_ld | ack_wr;

	assign m0_addr_ld = ctl_do_write & (ctl_addr[1:0] == 2'b10);
	assign m1_addr_ld = ctl_do_write & (ctl_addr[1:0] == 2'b11);

	assign m0_addr = m0_addr_i;
	assign m1_addr = m1_addr_i;


	// Length counter
	// --------------

	always @(posedge clk)
		if (len_ce)
			len <= len_ld ? { 1'b0, ctl_wdata[11:0] } : (len - 1);

	always @(posedge clk)
		if (len_ld)
			dir <= ctl_wdata[14];

	assign len_ce   = len_ld | ack_wr;
	assign len_ld   = ctl_do_write & (ctl_addr[1:0] == 2'b00);
	assign len_last = len[12];


	// Control IF
	// ----------

	always @(posedge clk or posedge rst)
		if (rst) begin
			ctl_do_write <= 1'b0;
			ctl_do_read  <= 1'b0;
			ctl_ack_i    <= 1'b0;
		end else begin
			ctl_do_write <= ~ctl_ack_i & ctl_cyc &  ctl_we;
			ctl_do_read  <= ~ctl_ack_i & ctl_cyc & ~ctl_we & (ctl_addr[1:0] == 2'b00);
			ctl_ack_i    <= ~ctl_ack_i & ctl_cyc;
		end

	assign ctl_ack = ctl_ack_i;

	assign ctl_rdata = {
		{(DW-16){1'b0}},
		(ctl_do_read ? { state[1], dir, 1'b0, len } : 16'h0000)
	};

endmodule // wb_dma
