//
// Copyright (c) MikeJ - Jan 2005
// Copyright (c) 2016-2018 Sorgelig
//
// All rights reserved
//
// Redistribution and use in source and synthezised forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// Redistributions in synthesized form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// Neither the name of the author nor the names of other contributors may
// be used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS CODE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//


// BDIR  BC  MODE
//   0   0   inactive
//   0   1   read value
//   1   0   write value
//   1   1   set address
//

module YM2149
(
	input        CLK,       // Global clock
	input        CE,        // PSG Clock enable
	input        RESET,     // Chip RESET (set all Registers to '0', active hi)
	input        BDIR,      // Bus Direction (0 - read , 1 - write)
	input        BC,        // Bus control
	input  [7:0] DI,        // Data In
	output [7:0] DO,        // Data Out
	output [7:0] CHANNEL_A, // PSG Output channel A
	output [7:0] CHANNEL_B, // PSG Output channel B
	output [7:0] CHANNEL_C, // PSG Output channel C

	input        SEL,
	input        MODE,
	
	output [5:0] ACTIVE,
	input        A8,

	input  [7:0] IOA_in,
	output [7:0] IOA_out,

	input  [7:0] IOB_in,
	output [7:0] IOB_out
);

reg [7:0] addr;
reg [7:0] ymreg[16];

assign ACTIVE  = ~ymreg[7][5:0];
assign IOA_out = ymreg[14];
assign IOB_out = ymreg[15];

// Write to PSG
reg env_reset;
reg old_BDIR;
always @(posedge CLK) begin
	if(RESET) begin
		ymreg     <= '{default:0};
		ymreg[7]  <= '1;
		addr      <= '0;
		env_reset <= 0;
	end else begin
		env_reset <= 0;
		old_BDIR <= BDIR;
		if(~old_BDIR & BDIR & A8) begin
			if(BC) addr <= DI;
			else if(!addr[7:4]) begin
				ymreg[addr[3:0]] <= DI;
				env_reset <= (addr == 13);
			end
		end
	end
end

// Read from PSG
reg [7:0] dout;
assign DO = dout;
always_comb begin
	dout = 8'hFF;
	if(~BDIR & BC & !addr[7:4]) begin
		case(addr[3:0])
			 0: dout = ymreg[0];
			 1: dout = ymreg[1][3:0];
			 2: dout = ymreg[2];
			 3: dout = ymreg[3][3:0];
			 4: dout = ymreg[4];
			 5: dout = ymreg[5][3:0];
			 6: dout = ymreg[6][4:0];
			 7: dout = ymreg[7];
			 8: dout = ymreg[8][4:0];
			 9: dout = ymreg[9][4:0];
			10: dout = ymreg[10][4:0];
			11: dout = ymreg[11];
			12: dout = ymreg[12];
			13: dout = ymreg[13][3:0];
			14: dout = ymreg[7][6] ? ymreg[14] & IOA_in : IOA_in;
			15: dout = ymreg[7][7] ? ymreg[15] & IOA_in : IOB_in;
		endcase
	end
end

reg ena_div;
reg ena_div_noise;

//  p_divider
always @(posedge CLK) begin
	reg [3:0]  cnt_div;
	reg        noise_div;

	if(CE) begin
		ena_div <= 0;
		ena_div_noise <= 0;
		if(!cnt_div) begin
			cnt_div <= {SEL, 3'b111};
			ena_div <= 1;
            
			noise_div <= (~noise_div);
			if (noise_div) ena_div_noise <= 1;
		end else begin
			cnt_div <= cnt_div - 1'b1;
		end
	end
end


reg [2:0] noise_gen_op;

//  p_noise_gen
always @(posedge CLK) begin
	reg [16:0] poly17;
	reg [4:0]  noise_gen_cnt;

	if(CE) begin
		if (ena_div_noise) begin
			if (!ymreg[6][4:0] || (noise_gen_cnt >= ymreg[6][4:0] - 1'd1)) begin
				noise_gen_cnt <= 0;
				poly17 <= {(poly17[0] ^ poly17[2] ^ !poly17), poly17[16:1]};
			end else begin
				noise_gen_cnt <= noise_gen_cnt + 1'd1;
			end
			noise_gen_op <= {3{poly17[0]}};
		end
	end
end

wire [11:0] tone_gen_freq[1:3];
assign tone_gen_freq[1] = {ymreg[1][3:0], ymreg[0]};
assign tone_gen_freq[2] = {ymreg[3][3:0], ymreg[2]};
assign tone_gen_freq[3] = {ymreg[5][3:0], ymreg[4]};

reg [3:1] tone_gen_op;

//p_tone_gens
always @(posedge CLK) begin
	integer i;
	reg [11:0] tone_gen_cnt[1:3];

	if(CE) begin
		// looks like real chips count up - we need to get the Exact behaviour ..
	
		for (i = 1; i <= 3; i = i + 1) begin
			if(ena_div) begin
				if (!tone_gen_freq[i] || (tone_gen_cnt[i] >= (tone_gen_freq[i] - 1'd1))) begin
					tone_gen_cnt[i] <= 0;
					tone_gen_op[i]  <= ~tone_gen_op[i];
				end else begin
					tone_gen_cnt[i] <= tone_gen_cnt[i] + 1'd1;
				end
			end
		end
	end
end

reg env_ena;
wire [15:0] env_gen_comp = {ymreg[12], ymreg[11]} ? {ymreg[12], ymreg[11]} - 1'd1 : 16'd0;

//p_envelope_freq
always @(posedge CLK) begin
	reg [15:0] env_gen_cnt;

	if(CE) begin
		env_ena <= 0;
		if(ena_div) begin
			if (env_gen_cnt >= env_gen_comp) begin
				env_gen_cnt <= 0;
				env_ena <= 1;
			end else begin
				env_gen_cnt <= (env_gen_cnt + 1'd1);
			end
		end
	end
end

reg [4:0] env_vol;

wire is_bot    = (env_vol == 5'b00000);
wire is_bot_p1 = (env_vol == 5'b00001);
wire is_top_m1 = (env_vol == 5'b11110);
wire is_top    = (env_vol == 5'b11111);

always @(posedge CLK) begin
	reg env_hold;
	reg env_inc;

	// envelope shapes
	// C AtAlH
	// 0 0 x x  \___
	//
	// 0 1 x x  /___
	//
	// 1 0 0 0  \\\\
	//
	// 1 0 0 1  \___
	//
	// 1 0 1 0  \/\/
	//           ___
	// 1 0 1 1  \
	//
	// 1 1 0 0  ////
	//           ___
	// 1 1 0 1  /
	//
	// 1 1 1 0  /\/\
	//
	// 1 1 1 1  /___

	if(env_reset | RESET) begin
		// load initial state
		if(!ymreg[13][2]) begin		// attack
			env_vol <= 5'b11111;
			env_inc <= 0;		// -1
		end else begin
			env_vol <= 5'b00000;
			env_inc <= 1;		// +1
		end
		env_hold <= 0;
	end
	else if(CE) begin
		if (env_ena) begin
			if (!env_hold) begin
				if (env_inc) env_vol <= (env_vol + 5'b00001);
					else env_vol <= (env_vol + 5'b11111);
			end

			// envelope shape control.
			if(!ymreg[13][3]) begin
				if(!env_inc) begin	// down
					if(is_bot_p1) env_hold <= 1;
				end else if (is_top) env_hold <= 1;
			end else if(ymreg[13][0]) begin		// hold = 1
				if(!env_inc) begin	// down
					if(ymreg[13][1]) begin		// alt
						if(is_bot) env_hold <= 1;
					end else if(is_bot_p1) env_hold <= 1;
				end else if(ymreg[13][1]) begin	// alt
					if(is_top) env_hold <= 1;
				end else if(is_top_m1) env_hold <= 1;
			end else if(ymreg[13][1]) begin		// alternate
				if(env_inc == 1'b0) begin		// down
					if(is_bot_p1) env_hold <= 1;
					if(is_bot) begin
						env_hold <= 0;
						env_inc  <= 1;
					end
				end else begin
					if(is_top_m1) env_hold <= 1;
					if(is_top) begin
						env_hold <= 0;
						env_inc  <= 0;
					end
				end
			end
		end
	end
end

reg [5:0] A,B,C;
always @(posedge CLK) begin
	A <= {MODE, ~((ymreg[7][0] | tone_gen_op[1]) & (ymreg[7][3] | noise_gen_op[0])) ? 5'd0 : ymreg[8][4]  ? env_vol[4:0] : { ymreg[8][3:0],  ymreg[8][3]}};
	B <= {MODE, ~((ymreg[7][1] | tone_gen_op[2]) & (ymreg[7][4] | noise_gen_op[1])) ? 5'd0 : ymreg[9][4]  ? env_vol[4:0] : { ymreg[9][3:0],  ymreg[9][3]}};
	C <= {MODE, ~((ymreg[7][2] | tone_gen_op[3]) & (ymreg[7][5] | noise_gen_op[2])) ? 5'd0 : ymreg[10][4] ? env_vol[4:0] : {ymreg[10][3:0], ymreg[10][3]}};
end

wire [7:0] volTable[64] = '{
	//YM2149
	8'h00, 8'h01, 8'h01, 8'h02, 8'h02, 8'h03, 8'h03, 8'h04, 
	8'h06, 8'h07, 8'h09, 8'h0a, 8'h0c, 8'h0e, 8'h11, 8'h13, 
	8'h17, 8'h1b, 8'h20, 8'h25, 8'h2c, 8'h35, 8'h3e, 8'h47, 
	8'h54, 8'h66, 8'h77, 8'h88, 8'ha1, 8'hc0, 8'he0, 8'hff,

	//AY8910
	8'h00, 8'h00, 8'h03, 8'h03, 8'h04, 8'h04, 8'h06, 8'h06, 
	8'h0a, 8'h0a, 8'h0f, 8'h0f, 8'h15, 8'h15, 8'h22, 8'h22, 
	8'h28, 8'h28, 8'h41, 8'h41, 8'h5b, 8'h5b, 8'h72, 8'h72, 
	8'h90, 8'h90, 8'hb5, 8'hb5, 8'hd7, 8'hd7, 8'hff, 8'hff 
};

assign CHANNEL_A = volTable[A];
assign CHANNEL_B = volTable[B];
assign CHANNEL_C = volTable[C];

endmodule

