`timescale 1ns / 1ps
/* vidc.v

 Copyright (c) 2012-2014, Stephen J. Leary
 All rights reserved.
 
 Redistribution and use in source and binary 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 binary 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 <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE 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 <COPYRIGHT HOLDER> 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.
 */
module vidc #(parameter CLKCPU)
(
	input 	 	  clkcpu, // cpu bus clock domain

	input 		  clkpix,
	input         cepix,
	output  [1:0] selpix,
	
	// "wishbone" interface
	input 	 	  rst_i,
	input			  vidw, 	// write to a register.		
	input  [31:0] cpu_dat,

	// dma control.
	input  [31:0] viddat, 
	input 	 	  vidak,
	output 		  vidrq,

	input 	 	  sndak,
	output 		  sndrq,

	output 	 	  flybk,

	// video outputs
	output 	 	  hsync, // active low
	output 	 	  vsync, // active low

	output reg [3:0] video_r,
	output reg [3:0] video_g,
	output reg [3:0] video_b,
	output           video_en,

	input         ceaud,

	output [15:0] audio_l,
	output [15:0] audio_r,
	output reg    audio_sync
);

wire cur_enabled;
reg  cur_enabled_r;
wire enabled;
wire border;

// registers 
reg [12:0] 		vidc_palette[0:15];	// palette register.
reg [15:0]		vidc_cr; 				// control register.
reg [12:0]		vidc_border; 			// border register.
reg [12:0]		cur_palette[1:3]; 	// border register.

// audio clock domain signals
wire [7:0] 		snd_sam_data;
wire			   snd_sam_en;

// pixel clock domain signals
//delayed enable.
reg				enabled_d1;
reg				enabled_d2;

reg 				pix_load;
wire 				pix_ack;
wire [7:0] 		pix_data;
reg  [2:0] 		pix_shift_count;
reg  [7:0] 		pix_data_latch = 8'd0;

// cursor pixel clock signals
reg 				csr_load;
wire 				csr_ack;
reg  [3:0] 		csr_load_count;
wire [7:0] 		csr_data;
reg  [2:0] 		csr_shift_count;
reg  [7:0] 		csr_data_latch = 8'd0;

// internal data request lines
wire		currq_int;
wire		vidrq_int;

assign   selpix = vidc_cr[1:0];

vidc_timing TIMING(
	
	.clkcpu		( clkcpu		),
	.wr			( vidw		),
	.cpu_dat 	( cpu_dat	),

	.clkvid		( clkpix		),
	.cevid		( cepix		),
	.rst			( rst_i		),
	
	.o_hsync		( hsync		),
	.o_vsync		( vsync		),
	.o_flyback	( flybk		),
	.o_enabled	( enabled	),
	.o_cursor	( cur_enabled ),
	.o_border	( border		)
);

// this module does the math for a DMA channel
vidc_dmachannel VIDEODMA (

	.rst		   ( flybk | rst_i ),
	.clkcpu		( clkcpu    ),
	.clkdev		( clkpix    ),
	.cedev		( cepix     ),

	.cpu_data	( viddat    ),
	.ak			( vidak     ),
	.rq			( vidrq_int	),
	.stall		( ~hsync    ),

	.dev_data	( pix_data  ),
	.dev_ak		( pix_ack   )
);

// this module does the math for a DMA channel
vidc_dmachannel #(.FIFO_SIZE(2)) CURSORDMA (

	.rst			( flybk | rst_i ),
	.clkcpu		( clkcpu    ),
	.clkdev		( clkpix    ),
	.cedev		( cepix     ),

	.cpu_data	( viddat    ),
	.ak			( vidak     ),
	.rq			( currq_int ),
	.stall		( hsync | vidrq_int ),

	.dev_data	( csr_data  ),
	.dev_ak		( csr_ack   )
);


always @(posedge clkcpu) begin : block
	reg old_en;
	
	old_en <= snd_sam_en;
	audio_sync <= ~old_en & snd_sam_en;
end


// this module does the math for a DMA channel
vidc_dmachannel SOUNDDMA (

	.rst			( rst_i     ),
	.clkcpu		( clkcpu    ),
	.clkdev		( clkpix    ),
	.cedev		( ceaud     ),
	
	.cpu_data	( viddat    ),
	.ak			( sndak     ),
	.rq			( sndrq     ),

	.dev_data	( snd_sam_data	),
	.dev_ak		( snd_sam_en	)
);

vidc_audio AUDIOMIXER(

    .cpu_clk    ( clkcpu    ),
    .cpu_wr     ( vidw      ),
    .cpu_data   ( cpu_dat   ),
    
    .aud_clk    ( clkpix    ),
    .aud_ce     ( ceaud     ),
    .aud_rst    ( rst_i     ),
    .aud_data   ( snd_sam_data ),
    .aud_en     ( snd_sam_en ),

    .aud_right  ( audio_r   ),
    .aud_left   ( audio_l   )
    
 );

integer c;

initial begin 

	// clear the palette. 
	for (c = 0; c < 16; c = c + 1) begin
	
		vidc_palette[c]= 13'd0;
 	
	end

	vidc_cr				= 16'hFFF0;
	
	pix_shift_count	= 3'd0;
	pix_data_latch		= 8'd0;
	pix_load				= 1'b0;
	
	csr_shift_count	= 3'd0;
	csr_data_latch		= 8'd0;
	csr_load_count		= 'd0;
	csr_load		   	= 1'b0;
	
end

localparam 	VIDEO_PALETTE 	= 6'b00xxxx;
localparam  VIDEO_CONTROL 	= 6'b111000; 
localparam  VIDEO_BORDER 	= 6'b010000; 
localparam  CURSOR_PALETTE = 6'b0100xx; 

// DMA interface control
// this is in the cpu clock domain. 
always @(posedge clkcpu) begin

	// register write control.
	if (vidw == 1'b1) begin 
	
		casex (cpu_dat[31:26])  
					
			VIDEO_PALETTE: begin // palette registers. 00-3CH
				
				vidc_palette[cpu_dat[29:26]] <= cpu_dat[12:0];
				
			end

			CURSOR_PALETTE: begin // cursor palette
				if (cpu_dat[27:26] == 2'b00) begin
					vidc_border <= cpu_dat[12:0];
				end else begin 
					cur_palette[cpu_dat[27:26]] <= cpu_dat[12:0];
				end
			end
			
			VIDEO_CONTROL: begin // control register.
				
				vidc_cr	<= cpu_dat[15:0];
				
			end

		endcase
	
	end

end 

// pixel clock domain logic. 
// this simulates the DAC.
always @(posedge clkpix) begin

	if(cepix) begin
		cur_enabled_r <= cur_enabled;
		pix_load 	<= pix_ack;
		csr_load		<= csr_ack;
		
		enabled_d1	<= enabled;
		enabled_d2	<= enabled_d1;
		
		if (flybk == 1'b1) begin 
		
			pix_data_latch <= 8'd0;
			
		end
		
		if (hsync == 1'b0) begin
		
			pix_shift_count <= 3'b111;
			csr_shift_count <= 3'b111;
			csr_load_count <= 'd0;
			
		end else if (enabled == 1'b1) begin

			case ({vidc_cr[3:2]})
				2'b00: begin 
					pix_data_latch <= {1'b0, pix_data_latch[7:1]};
					pix_shift_count <= pix_shift_count + 3'd1;
				end
				
				2'b01: begin 
					pix_data_latch <= {2'b00, pix_data_latch[7:2]};
					pix_shift_count <= pix_shift_count + 3'd2;
				end
				
				2'b10: begin
					pix_data_latch <= {4'b0000, pix_data_latch[7:4]};
					pix_shift_count <= pix_shift_count + 3'd4;
				end
				
				default: begin
					pix_data_latch <= 8'd0;						 
				end
			endcase
			
		end
		
		if (cur_enabled_r) begin 
				
			csr_data_latch <= {2'b00, csr_data_latch[7:2]};
			csr_shift_count <= csr_shift_count + 3'd2;
			
		end

		if (pix_load == 1'b1) begin
			
			pix_data_latch <= pix_data;
				
		end
		
		if (csr_load) begin 
			
			csr_load_count <= csr_load_count + 1'd1;
			csr_data_latch <= csr_data;
		
		end
	end
end

// eqn is CE + DE + ABC + BCD (where {E,D} = {vidc_cr[3:2]} and {C,B,A} = pix_shift_count)
assign pix_ack = hsync & enabled & ((pix_shift_count[2] & vidc_cr[3]) | ( vidc_cr[2] & vidc_cr[3]) | (pix_shift_count[0] & pix_shift_count[1] & pix_shift_count[2]) | (pix_shift_count[1] & pix_shift_count[2] &  vidc_cr[2]));
assign csr_ack = cur_enabled & (csr_shift_count[2] & csr_shift_count[0] ) & ~csr_load & ~csr_load_count[3];

// TODO: fix 8 bits per pixel colours.
wire [3:0] pix_lookup = vidc_cr[3:2] == 2'b00 ? {3'd0, pix_data_latch[0]} :
								vidc_cr[3:2] == 2'b01 ? {2'd0, pix_data_latch[1:0]} : pix_data_latch[3:0];

wire [1:0] csr_lookup = csr_data_latch[1:0];

wire [12:0] vidc_colour = cur_enabled & (csr_lookup != 2'd0) ? cur_palette[csr_lookup]   :
								  enabled_d2 ? vidc_palette[pix_lookup] : 
								  border  ? vidc_border : 
									13'd0;

// render a hicolour pixel if in hicolour mode, enabled and the cursor isnt being displayed.
wire   hicolour     = (vidc_cr[3:2] == 2'b11) & enabled_d2 & !(cur_enabled & (csr_lookup != 2'd0));
								
assign video_en     = border;

// two dma channels share the vidrq. 
assign vidrq = vidrq_int | currq_int;


always @(posedge clkpix) begin
	reg [3:0] r1,g1,b1,r2,g2,b2;

	if(cepix) begin
		r1[3]   <= hicolour ? pix_data_latch[4] : vidc_colour[3];
		r1[2:0] <= vidc_colour[2:0];
		g1[3:2] <= hicolour ? pix_data_latch[6:5] : vidc_colour[7:6];
		g1[1:0] <= vidc_colour[5:4];
		b1[3]	  <= hicolour ? pix_data_latch[7] : vidc_colour[11];
		b1[2:0] <= vidc_colour[10:8];
		
		r2 <= r1;
		g2 <= g1;
		b2 <= b1;
		
		video_r <= r2;
		video_g <= g2;
		video_b <= b2;
	end
end


endmodule
