`timescale 1ns / 1ps
/* memc.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 memc
(
	input         clkcpu,
	input         rst_i,

	// cpu bus.
	input         cpu_we,
	input         cpu_stb,
	input         cpu_cyc,
	output        cpu_err,
	output        cpu_ack, 
	output [31:0] cpu_dout,

	input  [25:0] cpu_address,
	input   [3:0] cpu_sel,

	// external memory bus.
	output [23:2] mem_addr_o,
	output        mem_stb_o,
	output        mem_cyc_o,
	output        mem_we_o,
	output  [3:0] mem_sel_o,
	input  [31:0] mem_dat_i,

	input         mem_ack_i, 
	output  [2:0] mem_cti_o, // burst / normal

	// supervisor mode

	input         spvmd,

	// vidc interface 
	input         flybk,
	input         hsync,

	input         sndrq,
	output        sndak,

	input         vidrq,
	output        vidak,
	output        vidw, // write to the video registers.

	// ioc interface
	output        ioc_cs,
	output        rom_low_cs,
	output        ram_cs, // accessing ram.

	// interrupts
	output        sirq_n 
);

parameter INITIAL_CURSOR_BASE = 19'h0_0000;
parameter INITIAL_SCREEN_BASE = 19'h0_0000;
parameter INITIAL_SCREEN_SIZE = 19'h4_b000;

reg 			rom_overlay;

reg [18:0] 	cur_address; // actual
reg [18:0] 	cur_init;

reg [18:0] 	vid_address; // actual
reg [18:0] 	vid_init;
reg [18:0] 	vid_start;
reg [18:0] 	vid_end;

reg [18:0] 	snd_sptr; // actual/current
reg [18:0] 	snd_endc;

reg			snd_next_valid;
reg [18:0] 	snd_start;
reg [18:0] 	snd_endn;

reg [13:0]	memc_control = 14'd0;
reg			cur_load;
reg			vid_load;
reg			snd_load;
reg			cpu_load;

reg [3:0]	dma_ack_r;
wire        dma_in_progress = cur_load | vid_load | snd_load;
wire        dma_request     = (~flybk & vidrq) | (memc_control[11] & sndrq);
reg         dma_request_r;    
wire        video_dma_ip    = cur_load | vid_load;
wire        sound_dma_ip    = snd_load;
wire        phycs, tablew, romcs, memcw;

// register addresses.
localparam REG_Vinit 	= 3'b000;
localparam REG_Vstart	= 3'b001;
localparam REG_Vend 		= 3'b010;
localparam REG_Cinit 	= 3'b011;
localparam REG_Sstart	= 3'b100;
localparam REG_SendN		= 3'b101;
localparam REG_Sptr		= 3'b110;
localparam REG_Ctrl		= 3'b111;

wire[25:0] phys_address;
wire       table_valid;

memc_translator PAGETABLES
(
	.clkcpu		( clkcpu		),
	.wr			( tablew		),
	.spvmd		( spvmd			),
	.page_size	( memc_control[3:2]	),
	.osmd			( memc_control[12]	),
	.mem_write	( cpu_we		),
	.addr_i		( cpu_address	),
	.addr_o		( phys_address	),
	.valid		( table_valid 	)
);

reg [31:0] cache_data[0:3];
reg        cache_valid;
reg [23:4] cache_addr;
reg        cache_ack;

always @(posedge clkcpu) begin : block
	reg cache_rcv, cache_test;
	reg [1:0] cache_cnt;
	reg [1:0] cache_wraddr;

	cache_ack <= 0;

	if (rst_i) begin 
		
		vid_init 	<= INITIAL_SCREEN_BASE;
		cur_init 	<= INITIAL_CURSOR_BASE;
		vid_start 	<= INITIAL_SCREEN_BASE;
		vid_end		<= INITIAL_SCREEN_BASE + INITIAL_SCREEN_SIZE;
		vid_address <= INITIAL_SCREEN_BASE;
		cur_address <= INITIAL_CURSOR_BASE;

		vid_load = 1'b0;
		snd_load = 1'b0;
		cur_load = 1'b0;
		cpu_load 	<= 1'b0;
		rom_overlay	<= 1'b1; // start with rom overlay 
	
		memc_control[11] <= 1'b0; // disable sound dma on reset.

      dma_request_r <= 1'b0;
		cache_rcv <= 0;
      cache_valid <= 0;
		cache_test <= 0;

		snd_next_valid <= 1'b0;  // sound init.

		dma_ack_r = 4'd0; // video init.

	end else begin 

		if(cache_rcv & mem_ack_i) begin
			cache_data[cache_wraddr] <= mem_dat_i;
			cache_wraddr <= cache_wraddr + 1'd1;
			cache_cnt <= cache_cnt + 1'd1;
			if(cache_cnt == 2) cache_ack <= 1;
			if(&cache_cnt) begin
				cache_rcv <= 0;
				cache_valid <= 1;
			end
		end

		dma_request_r <= dma_request;

		// cpu cycle.
		if (cpu_cyc & cpu_stb) begin
			cache_test <= 1;
			if(cache_valid & (cache_addr == caddr[23:4]) & ~cpu_mem_we) begin
				// cache hit
				if(~cache_test) cache_ack <= 1;
			end
			else begin
				// logic to ensure that the rom overlay gets deactivated.
				if (cpu_address[25:24] == 2'b11) begin
				
					rom_overlay	<= 1'b0;
				
				end
			
				// ensure no video cycle is active or about to start.
				if (~dma_request_r & ~dma_in_progress) begin
					cpu_load <= 1'b1;
					if(~cpu_load) begin
						if(cpu_mem_we) begin
							if(cache_addr == caddr[23:4]) cache_valid <= 0;
						end
						else begin
							{cache_addr,cache_wraddr} <= caddr[23:2];
							cache_valid <= 0;
							cache_rcv <= 1;
							cache_cnt <= 0;
						end
					end
				end
				
				if (memw) begin 
					
					// load the registers. 
					// all the registers are loaded here.
					case (cpu_address[19:17])

						REG_Vinit:  vid_init  <= {cpu_address[16:2], 4'b0000};
						REG_Vstart: vid_start <= {cpu_address[16:2], 4'b0000};
						REG_Vend:   vid_end   <= {cpu_address[16:2], 4'b0000};
						REG_Cinit:  cur_init  <= {cpu_address[16:2], 4'b0000};

						REG_Sstart: begin 
							$display("Sstart: %x", {cpu_address[16:2], 4'b0000}); 	
							snd_next_valid <= 1'b1;
							snd_start	<= {cpu_address[16:2], 4'b0000}; 
						end
						
						REG_SendN: begin 
						
							$display("SendN: %x", {cpu_address[16:2], 4'b0000});
							snd_endn	<= {cpu_address[16:2], 4'b0000};  
						
						end
						
						REG_Sptr: begin 
						
							$display("Sound buffer swap");
							snd_sptr 	<= snd_start;
													
							if (snd_next_valid == 1'b1) begin
								snd_endc	<= snd_endn;
								snd_next_valid 	<= 1'b0;
							end
						
						end
						
						REG_Ctrl: begin 
							
							$display("MEMC Control Register: %x", cpu_address[13:0]);
							memc_control <= cpu_address[13:0];
						
						end
						
					endcase
				
				end
			end
		
		end else begin 
		
			cpu_load <= 0;
			cache_rcv <= 0;
			cache_test <= 0;
		end 
	
		// video dma stuff.
		if (flybk == 1'b1) begin

			// stop all video dma on flybk
			vid_address <= vid_init;
			cur_address <= cur_init;
			
			if (vid_load | cur_load) begin
		   
				dma_ack_r 	<= 4'd0;
			
			end
			
			vid_load <= 1'b0;
			cur_load <= 1'b0;
      
		end 

		// do the dma count for all cycle types.
		if (dma_in_progress & mem_ack_i) begin 
			
			dma_ack_r <= dma_ack_r + 3'd1;
			
			if (dma_ack_r == 4'd3) begin 
									
				vid_load  <= 1'b0;
				snd_load  <= 1'b0;
				cur_load  <= 1'b0;
				dma_ack_r <= 4'd0;
						
			end
			
		end
		
		if (dma_request_r === 1'b1) begin
		
			// priority is to video over sound.
			if (vidrq  & ~dma_in_progress & ~cpu_load) begin
	 			
				if (hsync == 1'b1) begin 
				   
					vid_load <= 1'b1;
				
				end else begin
				
					cur_load <= 1'b1;
				
				end
				
			end else if (sndrq  & ~dma_in_progress & ~cpu_load) begin
				
				snd_load <= 1'b1;
			
			end
		
		end
			
		if (video_dma_ip) begin 
	
			if ((vidak & vid_load) == 1'b1) begin 
		
					// advance the pointer to the next location.
					vid_address <= vid_address + 19'd4;
					
			end else if ((vidak & cur_load) == 1'b1) begin 
			
				// advance the cursor pointer to the next location.
				cur_address <= cur_address + 19'd4;

			end 
		
		end else begin 
		
			// cant wrap during a burst
			if (vid_address > vid_end) begin

					// loop back to vid_start when we reach the end.
					vid_address <= vid_start;
			
			end
			
		end 
			
		if (sound_dma_ip) begin 
								
           if ((sndak & snd_load) == 1'b1) begin 
		
				// advance the pointer to the next location.
				snd_sptr <= snd_sptr + 19'd4;
		   end 
           
        end else begin 
		
			// cant wrap during a burst
			if (snd_sptr > snd_endc) begin

				snd_sptr 	<= snd_start;

				if (snd_next_valid == 1'b1) begin
					snd_endc	<= snd_endn;
					snd_next_valid 	<= 1'b0;
				end
				
			end
            
		end 
			
	end 

end


wire [21:2] ram_page = memc_control[3:2] == 2'b00 ? {3'd0, cpu_address[18:2]} :
                       memc_control[3:2] == 2'b01 ? {2'd0, cpu_address[19:2]} :
                       memc_control[3:2] == 2'b10 ? {1'd0, cpu_address[20:2]} :
                                                    cpu_address[21:2];
  
assign mem_addr_o    = vid_load ? {5'd0, vid_address[18:2]}	:
                       cur_load ? {5'd0, cur_address[18:2]} :
                       snd_load ? {5'd0, snd_sptr[18:2]}    :
                                  caddr;

wire [23:2] caddr    = phycs    ? {2'd0, ram_page}                : // use physical memory
                       romcs    ? {3'b010, cpu_address[20:2]} 	   : // use 2mb and up for rom space.  
                       table_valid	& logcs	? phys_address[23:2] : // use logical memory.
                       22'd0;

assign mem_cyc_o     = cpu_load ? cpu_cyc & ~err : dma_in_progress;
assign mem_stb_o     = cpu_load ? cpu_stb        : dma_in_progress;
assign mem_sel_o     = cpu_load ? cpu_sel        : 4'b1111;
assign mem_we_o      = cpu_load ? cpu_mem_we     : 1'b0;
assign mem_cti_o     = 3'b010;                   

wire   address_valid = (logcs & table_valid) | rom_low_cs| ioc_cs | memw | tablew | vidc_cs | (phycs & ~cpu_we) | (phycs & spvmd & cpu_we) | romcs; 
wire   err           = ~address_valid;

assign cpu_ack       = (mem_we_o ? mem_ack_i : cache_ack) & ~err;
assign cpu_err       = cpu_load ? mem_ack_i & err : 1'b0;
assign cpu_dout      = cache_data[caddr[3:2]];
wire   cpu_mem_we    = cpu_we & ((phycs & spvmd) | (table_valid & logcs)) & ~romcs;

assign tablew        = cpu_cyc & cpu_we & spvmd & (cpu_address[25:23] == 3'b111) & (cpu_address[12] == 0) & (cpu_address[7] == 0); // &3800000+ 
wire   memw          = cpu_cyc & cpu_we & spvmd & (cpu_address[25:21] == 5'b11011); // &3600000
assign vidw          = cpu_cyc  & cpu_we & vidc_cs; // &3400000

// bus chip selects
wire   logcs         = cpu_address[25] == 1'b0;                                                        // 0000000 - 1FFFFFF
assign phycs         = cpu_address[25:24] == 2'b10;                                                    // 2000000 - 2FFFFFF
assign ioc_cs        = spvmd & (cpu_address[25:22] == 4'b1100);                                        // 3000000 - 33FFFFF
wire   vidc_cs       = spvmd & (cpu_address[25:21] == 5'b11010);                                       // 3400000 - 35FFFFF (WE & SPVMD)
assign rom_low_cs    = (cpu_address[25:22] == 4'b1101);                                                // 3400000 - 37FFFFF
assign romcs         = ((cpu_address[25:23] == 3'b111) | (cpu_address[25:19] == 7'h00) & rom_overlay); // 3800000 - 3FFFFFF

assign vidak         = cpu_load ? 1'b0 : video_dma_ip & mem_ack_i;  
assign sndak         = cpu_load ? 1'b0 : sound_dma_ip & mem_ack_i;  

assign sirq_n        = snd_next_valid;
assign ram_cs        = table_valid | phycs | romcs;

endmodule
