mirror of
https://github.com/MiSTer-devel/Gameboy_MiSTer.git
synced 2026-04-19 03:04:09 +00:00
Maximum of 6 extra sprites per line Extra sprites are fetched during mode2 to not break mode3 timing
1180 lines
43 KiB
Verilog
1180 lines
43 KiB
Verilog
//
|
|
// video.v
|
|
//
|
|
// Gameboy for the MIST board https://github.com/mist-devel
|
|
//
|
|
// Copyright (c) 2015 Till Harbaum <till@harbaum.org>
|
|
//
|
|
// This source file is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This source file is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
|
|
module video (
|
|
input reset,
|
|
input clk,
|
|
input ce, // 4 Mhz cpu clock
|
|
input ce_n, // falling edge
|
|
input ce_cpu, // 4 or 8Mhz
|
|
input isGBC,
|
|
input isGBC_mode,
|
|
input megaduck,
|
|
|
|
input boot_rom_en,
|
|
|
|
// cpu register adn oam interface
|
|
input cpu_sel_oam,
|
|
input cpu_sel_reg,
|
|
input [7:0] cpu_addr,
|
|
input cpu_wr,
|
|
input [7:0] cpu_di,
|
|
output [7:0] cpu_do,
|
|
|
|
// output to lcd
|
|
output lcd_on,
|
|
output lcd_clkena,
|
|
output [14:0] lcd_data,
|
|
output [1:0] lcd_data_gb,
|
|
output lcd_vsync,
|
|
|
|
output irq,
|
|
output vblank_irq,
|
|
|
|
// vram connection
|
|
output [1:0] mode,
|
|
output oam_cpu_allow,
|
|
output vram_cpu_allow,
|
|
output vram_rd,
|
|
output [12:0] vram_addr,
|
|
input [7:0] vram_data,
|
|
|
|
// vram connection bank1 (GBC)
|
|
input [7:0] vram1_data,
|
|
|
|
// dma connection
|
|
output dma_rd,
|
|
output [15:0] dma_addr,
|
|
input [7:0] dma_data,
|
|
|
|
input extra_spr_en,
|
|
input extra_wait,
|
|
|
|
// savestates
|
|
input [7:0] Savestate_OAMRAMAddr,
|
|
input Savestate_OAMRAMRWrEn,
|
|
input [7:0] Savestate_OAMRAMWriteData,
|
|
output[7:0] Savestate_OAMRAMReadData,
|
|
|
|
input [63:0] SaveStateBus_Din,
|
|
input [9:0] SaveStateBus_Adr,
|
|
input SaveStateBus_wren,
|
|
input SaveStateBus_rst,
|
|
output [63:0] SaveStateBus_Dout
|
|
);
|
|
|
|
// savestates
|
|
localparam SAVESTATE_MODULES = 19;
|
|
wire [63:0] SaveStateBus_wired_or[0:SAVESTATE_MODULES-1];
|
|
|
|
wire [60:0] SS_Video1;
|
|
wire [60:0] SS_Video1_BACK;
|
|
wire [63:0] SS_Video2;
|
|
wire [63:0] SS_Video2_BACK;
|
|
wire [63:0] SS_Video3;
|
|
wire [63:0] SS_Video3_BACK;
|
|
|
|
eReg_SavestateV #(0, 9, 60, 0, 64'h0000000000000000) iREG_SAVESTATE_Video1 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[ 0], SS_Video1_BACK, SS_Video1);
|
|
eReg_SavestateV #(0, 10, 63, 0, 64'h00000000FFFFFC00) iREG_SAVESTATE_Video2 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[ 1], SS_Video2_BACK, SS_Video2);
|
|
eReg_SavestateV #(0, 27, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_Video3 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[18], SS_Video3_BACK, SS_Video3);
|
|
|
|
wire [63:0] SS_BPAL [7:0];
|
|
wire [63:0] SS_BPAL_BACK [7:0];
|
|
wire [63:0] SS_OPAL [7:0];
|
|
wire [63:0] SS_OPAL_BACK [7:0];
|
|
eReg_SavestateV #(0, 11, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_BPAL0 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[2], SS_BPAL_BACK[0], SS_BPAL[0]);
|
|
eReg_SavestateV #(0, 12, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_BPAL1 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[3], SS_BPAL_BACK[1], SS_BPAL[1]);
|
|
eReg_SavestateV #(0, 13, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_BPAL2 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[4], SS_BPAL_BACK[2], SS_BPAL[2]);
|
|
eReg_SavestateV #(0, 14, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_BPAL3 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[5], SS_BPAL_BACK[3], SS_BPAL[3]);
|
|
eReg_SavestateV #(0, 15, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_BPAL4 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[6], SS_BPAL_BACK[4], SS_BPAL[4]);
|
|
eReg_SavestateV #(0, 16, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_BPAL5 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[7], SS_BPAL_BACK[5], SS_BPAL[5]);
|
|
eReg_SavestateV #(0, 17, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_BPAL6 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[8], SS_BPAL_BACK[6], SS_BPAL[6]);
|
|
eReg_SavestateV #(0, 18, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_BPAL7 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[9], SS_BPAL_BACK[7], SS_BPAL[7]);
|
|
|
|
eReg_SavestateV #(0, 19, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_OPAL0 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[10], SS_OPAL_BACK[0], SS_OPAL[0]);
|
|
eReg_SavestateV #(0, 20, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_OPAL1 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[11], SS_OPAL_BACK[1], SS_OPAL[1]);
|
|
eReg_SavestateV #(0, 21, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_OPAL2 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[12], SS_OPAL_BACK[2], SS_OPAL[2]);
|
|
eReg_SavestateV #(0, 22, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_OPAL3 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[13], SS_OPAL_BACK[3], SS_OPAL[3]);
|
|
eReg_SavestateV #(0, 23, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_OPAL4 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[14], SS_OPAL_BACK[4], SS_OPAL[4]);
|
|
eReg_SavestateV #(0, 24, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_OPAL5 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[15], SS_OPAL_BACK[5], SS_OPAL[5]);
|
|
eReg_SavestateV #(0, 25, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_OPAL6 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[16], SS_OPAL_BACK[6], SS_OPAL[6]);
|
|
eReg_SavestateV #(0, 26, 63, 0, 64'h0000000000000000) iREG_SAVESTATE_OPAL7 (clk, SaveStateBus_Din, SaveStateBus_Adr, SaveStateBus_wren, SaveStateBus_rst, SaveStateBus_wired_or[17], SS_OPAL_BACK[7], SS_OPAL[7]);
|
|
|
|
assign SaveStateBus_Dout = SaveStateBus_wired_or[ 0] | SaveStateBus_wired_or[ 1] | SaveStateBus_wired_or[ 2] | SaveStateBus_wired_or[ 3] |
|
|
SaveStateBus_wired_or[ 4] | SaveStateBus_wired_or[ 5] | SaveStateBus_wired_or[ 6] | SaveStateBus_wired_or[ 7] |
|
|
SaveStateBus_wired_or[ 8] | SaveStateBus_wired_or[ 9] | SaveStateBus_wired_or[10] | SaveStateBus_wired_or[11] |
|
|
SaveStateBus_wired_or[12] | SaveStateBus_wired_or[13] | SaveStateBus_wired_or[14] | SaveStateBus_wired_or[15] |
|
|
SaveStateBus_wired_or[16] | SaveStateBus_wired_or[17] | SaveStateBus_wired_or[18];
|
|
|
|
|
|
wire [7:0] oam_do;
|
|
wire [10:0] sprite_addr;
|
|
|
|
wire sprite_found;
|
|
wire [7:0] sprite_attr;
|
|
wire [3:0] sprite_index;
|
|
|
|
wire oam_eval_end;
|
|
|
|
reg dma_active;
|
|
reg [7:0] dma;
|
|
reg [9:0] dma_cnt; // dma runs 4*160 clock cycles = 160us @ 4MHz
|
|
|
|
// give dma access to oam
|
|
wire [7:0] oam_addr = dma_active?dma_addr[7:0]:cpu_addr;
|
|
wire oam_wr = dma_active?(dma_cnt[1:0] == 2):(cpu_wr && cpu_sel_oam && oam_cpu_allow);
|
|
wire [7:0] oam_di = dma_active?dma_data:cpu_di;
|
|
|
|
// $ff40 LCDC
|
|
reg [7:0] lcdc;
|
|
wire lcdc_on = megaduck ? lcdc[7] : lcdc[7];
|
|
wire lcdc_win_tile_map_sel = megaduck ? lcdc[3] : lcdc[6];
|
|
wire lcdc_win_ena = megaduck ? lcdc[5] : lcdc[5];
|
|
wire lcdc_tile_data_sel = megaduck ? lcdc[4] : lcdc[4];
|
|
wire lcdc_bg_tile_map_sel = megaduck ? lcdc[2] : lcdc[3];
|
|
wire lcdc_spr_siz = megaduck ? lcdc[1] : lcdc[2];
|
|
wire lcdc_spr_ena = megaduck ? lcdc[0] : lcdc[1];
|
|
// "CGB in CGB Mode: BG and Window Master Priority
|
|
// When Bit 0 is cleared, the background and window lose their priority
|
|
// - the sprites will be always displayed on top of background and window,
|
|
// independently of the priority flags in OAM and BG Map attributes."
|
|
wire lcdc_bg_ena = (megaduck ? lcdc[6] : lcdc[0]) | (isGBC&&isGBC_mode);
|
|
wire lcdc_bg_prio = megaduck ? lcdc[6] : lcdc[0];
|
|
|
|
assign lcd_on = lcdc_on;
|
|
|
|
// ff41 STAT
|
|
reg [7:0] stat_r;
|
|
// DMG STAT bug: IRQs are enabled for 1 cycle during a write
|
|
wire [7:0] stat = (cpu_sel_reg & cpu_wr & cpu_addr == 8'h41 & ~isGBC) ? 8'hFF : stat_r;
|
|
|
|
// ff42, ff43 background scroll registers
|
|
reg [7:0] scy;
|
|
reg [7:0] scx;
|
|
|
|
// ff44 line counter
|
|
reg [6:0] h_cnt; // 0-113 at 1MHz
|
|
reg [1:0] h_div_cnt; // Divide by 4
|
|
reg [7:0] v_cnt; // max 153
|
|
wire [7:0] ly = v_cnt;
|
|
|
|
// ff45 line counter compare
|
|
reg [7:0] lyc_r_dmg, lyc_r_gbc;
|
|
wire [7:0] lyc = (isGBC ? lyc_r_gbc : lyc_r_dmg);
|
|
wire lyc_match = (ly == lyc);
|
|
|
|
reg [7:0] bgp;
|
|
reg [7:0] obp0;
|
|
reg [7:0] obp1;
|
|
|
|
reg [7:0] wy;
|
|
reg [7:0] wx;
|
|
|
|
//ff68-ff6A GBC
|
|
//FF68 - BCPS/BGPI - Background Palette Index
|
|
reg [5:0] bgpi; //Bit 0-5 Index (00-3F)
|
|
reg bgpi_ai; //Bit 7 Auto Increment (0=Disabled, 1=Increment after Writing)
|
|
|
|
//FF69 - BCPD/BGPD - Background Palette Data
|
|
reg[7:0] bgpd [63:0]; //64 bytes
|
|
|
|
//FF6A - OCPS/OBPI - Sprite Palette Index
|
|
reg [5:0] obpi; //Bit 0-5 Index (00-3F)
|
|
reg obpi_ai; //Bit 7 Auto Increment (0=Disabled, 1=Increment after Writing)
|
|
|
|
//FF6B - OCPD/OBPD - Sprite Palette Data
|
|
reg[7:0] obpd [63:0]; //64 bytes
|
|
|
|
//FF6C Bit 0 OBJ priority mode select
|
|
// 0: smaller OBJ-NO has higher priority (GBC)
|
|
// 1: smaller X coordinate has higher priority (DMG)
|
|
// https://forums.nesdev.org/viewtopic.php?t=19888
|
|
reg ff6c_opri;
|
|
reg obj_prio_dmg_mode;
|
|
|
|
integer i;
|
|
|
|
// --------------------------------------------------------------------
|
|
// ----------------------------- DMA engine ---------------------------
|
|
// --------------------------------------------------------------------
|
|
|
|
assign SS_Video1_BACK[ 0] = dma_active;
|
|
assign SS_Video1_BACK[10: 1] = dma_cnt;
|
|
|
|
assign dma_addr = { dma, dma_cnt[9:2] };
|
|
assign dma_rd = dma_active;
|
|
|
|
always @(posedge clk) begin
|
|
if(reset) begin
|
|
dma_active <= SS_Video1[ 0]; //1'b0;
|
|
dma_cnt <= SS_Video1[10: 1]; //10'd0;
|
|
end else if (ce_cpu) begin
|
|
// writing the dma register engages the dma engine
|
|
if(cpu_sel_reg && cpu_wr && (cpu_addr == 8'h46)) begin
|
|
dma_active <= 1'b1;
|
|
dma_cnt <= 10'd0;
|
|
end else if(dma_cnt != 160*4-1)
|
|
dma_cnt <= dma_cnt + 10'd1;
|
|
else
|
|
dma_active <= 1'b0;
|
|
end
|
|
end
|
|
|
|
// --------------------------------------------------------------------
|
|
// ------------------------------- IRQs -------------------------------
|
|
// --------------------------------------------------------------------
|
|
//
|
|
// "The interrupt is triggered when transitioning from "No conditions met" to
|
|
// "Any condition met", which can cause the interrupt to not fire."
|
|
//
|
|
// Example: Altered Space enables OAM+Hblank+Vblank interrupt and requires the
|
|
// interrupt to trigger only on a Mode 3 to Hblank transition.
|
|
// OAM follows immediately after Hblank/Vblank so no OAM interrupt is triggered.
|
|
|
|
wire h_clk_en = (lcd_on && h_div_cnt == 2'd0);
|
|
wire h_clk_en_neg = (lcd_on && h_div_cnt == 2'd2);
|
|
|
|
wire hcnt_end = (h_cnt == 7'd113);
|
|
wire vblank = (v_cnt >= 144);
|
|
|
|
reg vblank_l, vblank_t;
|
|
reg end_of_line, end_of_line_l;
|
|
reg lyc_match_l, lyc_match_t;
|
|
|
|
assign SS_Video3_BACK[0] = vblank_l;
|
|
assign SS_Video3_BACK[1] = end_of_line;
|
|
assign SS_Video3_BACK[2] = end_of_line_l;
|
|
assign SS_Video3_BACK[3] = 0;
|
|
assign SS_Video3_BACK[6] = vblank_t;
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) begin
|
|
vblank_l <= SS_Video3[0]; //1'b0;
|
|
end_of_line <= SS_Video3[1]; //1'b0;
|
|
end_of_line_l <= SS_Video3[2]; //1'b0;
|
|
vblank_t <= SS_Video3[6]; //1'b0;
|
|
end else if (!lcd_on) begin
|
|
vblank_l <= 1'b0;
|
|
end_of_line <= 1'b0;
|
|
end_of_line_l <= 1'b0;
|
|
vblank_t <= 1'b0;
|
|
end else if (ce) begin
|
|
vblank_l <= vblank_t;
|
|
|
|
if (h_clk_en_neg & hcnt_end)
|
|
end_of_line <= 1'b1;
|
|
else if (end_of_line) begin
|
|
// Vblank is latched a few cycles after line end.
|
|
// This causes an OAM interrupt at the beginning of line 144.
|
|
// It also makes the OAM interrupt at line 0 after Vblank a few cycles late.
|
|
if (h_clk_en) begin
|
|
vblank_t <= vblank;
|
|
end
|
|
// end_of_line is active for 4 cycles
|
|
if (h_clk_en_neg) begin
|
|
end_of_line <= 1'b0;
|
|
end
|
|
end
|
|
|
|
end_of_line_l <= end_of_line;
|
|
end
|
|
end
|
|
|
|
assign SS_Video3_BACK[4] = lyc_match_l;
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) begin
|
|
lyc_match_l <= SS_Video3[4]; // 1'b0; // lyc_match_l does not reset when lcd is off
|
|
end else if (ce) begin
|
|
lyc_match_l <= lyc_match_t;
|
|
|
|
if (h_clk_en) begin
|
|
lyc_match_t <= lyc_match;
|
|
if (lyc_match_t & ~lyc_match & ~isGBC) begin // DMG: lyc_match falling edge must be 1 cycle faster to pass Wilbertpol LYC tests.
|
|
lyc_match_l <= lyc_match;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
wire pcnt_reset, pcnt_end;
|
|
wire mode3_end = ~sprite_found & pcnt_end & ~(isGBC & win_first_fetch);
|
|
|
|
reg mode3_end_l;
|
|
|
|
assign SS_Video3_BACK[5] = mode3_end_l;
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) begin
|
|
mode3_end_l <= SS_Video3[5]; //1'b0;
|
|
end else if (!lcd_on) begin
|
|
mode3_end_l <= 1'b0;
|
|
end else if (ce) begin
|
|
if (pcnt_reset)
|
|
mode3_end_l <= 1'b0;
|
|
else
|
|
mode3_end_l <= mode3_end;
|
|
end
|
|
end
|
|
|
|
wire int_lyc = (stat[6] & lyc_match_l);
|
|
wire int_oam = (stat[5] & end_of_line_l & ~vblank_l);
|
|
wire int_vbl = (stat[4] & vblank_l);
|
|
wire int_hbl = (stat[3] & mode3_end_l & ~vblank_l);
|
|
|
|
assign irq = (int_lyc | int_oam | int_hbl | int_vbl);
|
|
assign vblank_irq = vblank_l;
|
|
|
|
wire oam_eval;
|
|
wire mode3 = lcd_on & ~mode3_end_l & oam_eval_end;
|
|
|
|
reg mode3_l, oam_eval_l;
|
|
|
|
assign SS_Video1_BACK[59] = mode3_l;
|
|
assign SS_Video1_BACK[60] = oam_eval_l;
|
|
|
|
// Delay mode 2/3 to pass Mooneye/Wilbertpol Lcd on timing tests.
|
|
// The CPU cannot read from OAM/VRAM 1 cycle before Mode 2/3 is read
|
|
always @(posedge clk) begin
|
|
if (reset) begin
|
|
mode3_l <= SS_Video1[59]; // 1'b0;
|
|
oam_eval_l <= SS_Video1[60]; // 1'b0;
|
|
end else if (~lcd_on) begin
|
|
mode3_l <= 1'b0;
|
|
oam_eval_l <= 1'b0;
|
|
end else if (ce) begin
|
|
mode3_l <= mode3;
|
|
oam_eval_l <= oam_eval;
|
|
end
|
|
end
|
|
|
|
// DMG: STAT reads mode 0 for 1 Tcycle between Vblank and mode 2
|
|
// but the interrupt signals of Vblank and mode 2 do overlap
|
|
wire mode_vblank = isGBC ? vblank_l : (vblank_l & vblank_t);
|
|
|
|
assign mode =
|
|
mode_vblank ? 2'b01 :
|
|
oam_eval_l ? 2'b10 :
|
|
mode3_l & ~mode3_end ? 2'b11 :
|
|
2'b00;
|
|
|
|
assign oam_cpu_allow = ~(oam_eval | mode3 | dma_active);
|
|
assign vram_cpu_allow = ~mode3;
|
|
|
|
// --------------------------------------------------------------------
|
|
// --------------------- CPU register interface -----------------------
|
|
// --------------------------------------------------------------------
|
|
|
|
assign SS_Video1_BACK[18:11] = dma;
|
|
assign SS_Video1_BACK[26:19] = lcdc;
|
|
assign SS_Video1_BACK[34:27] = scy;
|
|
assign SS_Video1_BACK[42:35] = scx;
|
|
assign SS_Video1_BACK[50:43] = wy;
|
|
assign SS_Video1_BACK[58:51] = wx;
|
|
|
|
assign SS_Video2_BACK[ 7: 0] = stat_r;
|
|
assign SS_Video2_BACK[15: 8] = bgp;
|
|
assign SS_Video2_BACK[23:16] = obp0;
|
|
assign SS_Video2_BACK[31:24] = obp1;
|
|
assign SS_Video2_BACK[37:32] = bgpi;
|
|
assign SS_Video2_BACK[43:38] = obpi;
|
|
assign SS_Video2_BACK[ 44] = bgpi_ai;
|
|
assign SS_Video2_BACK[ 45] = obpi_ai;
|
|
assign SS_Video2_BACK[53:46] = lyc_r_gbc;
|
|
assign SS_Video2_BACK[61:54] = lyc_r_dmg;
|
|
assign SS_Video2_BACK[ 62] = ff6c_opri;
|
|
assign SS_Video2_BACK[ 63] = obj_prio_dmg_mode;
|
|
|
|
genvar palI;
|
|
generate
|
|
for (palI=0;palI<8;palI=palI+1) begin : palette_readback
|
|
assign SS_BPAL_BACK[palI][ 7: 0] = bgpd[palI*8+0];
|
|
assign SS_BPAL_BACK[palI][15: 8] = bgpd[palI*8+1];
|
|
assign SS_BPAL_BACK[palI][23:16] = bgpd[palI*8+2];
|
|
assign SS_BPAL_BACK[palI][31:24] = bgpd[palI*8+3];
|
|
assign SS_BPAL_BACK[palI][39:32] = bgpd[palI*8+4];
|
|
assign SS_BPAL_BACK[palI][47:40] = bgpd[palI*8+5];
|
|
assign SS_BPAL_BACK[palI][55:48] = bgpd[palI*8+6];
|
|
assign SS_BPAL_BACK[palI][63:56] = bgpd[palI*8+7];
|
|
assign SS_OPAL_BACK[palI][ 7: 0] = obpd[palI*8+0];
|
|
assign SS_OPAL_BACK[palI][15: 8] = obpd[palI*8+1];
|
|
assign SS_OPAL_BACK[palI][23:16] = obpd[palI*8+2];
|
|
assign SS_OPAL_BACK[palI][31:24] = obpd[palI*8+3];
|
|
assign SS_OPAL_BACK[palI][39:32] = obpd[palI*8+4];
|
|
assign SS_OPAL_BACK[palI][47:40] = obpd[palI*8+5];
|
|
assign SS_OPAL_BACK[palI][55:48] = obpd[palI*8+6];
|
|
assign SS_OPAL_BACK[palI][63:56] = obpd[palI*8+7];
|
|
end
|
|
endgenerate
|
|
|
|
|
|
// Use negedge on some registers to pass tests
|
|
always @(negedge clk) begin
|
|
if(reset) begin
|
|
lcdc <= SS_Video1[26:19]; // 8'h00; // screen must be off since dmg rom writes to vram
|
|
lyc_r_dmg <= SS_Video2[61:54]; // 8'h00;
|
|
end else if (ce_cpu) begin
|
|
if(cpu_sel_reg && cpu_wr) begin
|
|
case(cpu_addr)
|
|
8'h40: lcdc <= cpu_di;
|
|
8'h45: lyc_r_dmg <= cpu_di;
|
|
endcase
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
|
|
if(reset) begin
|
|
|
|
dma <= SS_Video1[18:11]; // 8'h00;
|
|
scy <= SS_Video1[34:27]; // 8'h00;
|
|
scx <= SS_Video1[42:35]; // 8'h00;
|
|
wy <= SS_Video1[50:43]; // 8'h00;
|
|
wx <= SS_Video1[58:51]; // 8'h00;
|
|
stat_r <= SS_Video2[ 7: 0]; // 8'h00;
|
|
bgp <= SS_Video2[15: 8]; // 8'hfc;
|
|
obp0 <= SS_Video2[23:16]; // 8'hff;
|
|
obp1 <= SS_Video2[31:24]; // 8'hff;
|
|
bgpi <= SS_Video2[37:32]; // 6'h0;
|
|
obpi <= SS_Video2[43:38]; // 6'h0;
|
|
bgpi_ai <= SS_Video2[ 44]; // 1'b0;
|
|
obpi_ai <= SS_Video2[ 45]; // 1'b0;
|
|
lyc_r_gbc <= SS_Video2[53:46]; // 8'h00;
|
|
ff6c_opri <= SS_Video2[ 62]; // 1'b0;
|
|
obj_prio_dmg_mode <= SS_Video2[ 63]; // 1'b0;
|
|
|
|
bgpd[ 0] <= SS_BPAL[0][ 7: 0]; bgpd[16] <= SS_BPAL[2][ 7: 0]; bgpd[32] <= SS_BPAL[4][ 7: 0]; bgpd[48] <= SS_BPAL[6][ 7: 0]; //8'h00;
|
|
bgpd[ 1] <= SS_BPAL[0][15: 8]; bgpd[17] <= SS_BPAL[2][15: 8]; bgpd[33] <= SS_BPAL[4][15: 8]; bgpd[49] <= SS_BPAL[6][15: 8]; //8'h00;
|
|
bgpd[ 2] <= SS_BPAL[0][23:16]; bgpd[18] <= SS_BPAL[2][23:16]; bgpd[34] <= SS_BPAL[4][23:16]; bgpd[50] <= SS_BPAL[6][23:16]; //8'h00;
|
|
bgpd[ 3] <= SS_BPAL[0][31:24]; bgpd[19] <= SS_BPAL[2][31:24]; bgpd[35] <= SS_BPAL[4][31:24]; bgpd[51] <= SS_BPAL[6][31:24]; //8'h00;
|
|
bgpd[ 4] <= SS_BPAL[0][39:32]; bgpd[20] <= SS_BPAL[2][39:32]; bgpd[36] <= SS_BPAL[4][39:32]; bgpd[52] <= SS_BPAL[6][39:32]; //8'h00;
|
|
bgpd[ 5] <= SS_BPAL[0][47:40]; bgpd[21] <= SS_BPAL[2][47:40]; bgpd[37] <= SS_BPAL[4][47:40]; bgpd[53] <= SS_BPAL[6][47:40]; //8'h00;
|
|
bgpd[ 6] <= SS_BPAL[0][55:48]; bgpd[22] <= SS_BPAL[2][55:48]; bgpd[38] <= SS_BPAL[4][55:48]; bgpd[54] <= SS_BPAL[6][55:48]; //8'h00;
|
|
bgpd[ 7] <= SS_BPAL[0][63:56]; bgpd[23] <= SS_BPAL[2][63:56]; bgpd[39] <= SS_BPAL[4][63:56]; bgpd[55] <= SS_BPAL[6][63:56]; //8'h00;
|
|
bgpd[ 8] <= SS_BPAL[1][ 7: 0]; bgpd[24] <= SS_BPAL[3][ 7: 0]; bgpd[40] <= SS_BPAL[5][ 7: 0]; bgpd[56] <= SS_BPAL[7][ 7: 0]; //8'h00;
|
|
bgpd[ 9] <= SS_BPAL[1][15: 8]; bgpd[25] <= SS_BPAL[3][15: 8]; bgpd[41] <= SS_BPAL[5][15: 8]; bgpd[57] <= SS_BPAL[7][15: 8]; //8'h00;
|
|
bgpd[10] <= SS_BPAL[1][23:16]; bgpd[26] <= SS_BPAL[3][23:16]; bgpd[42] <= SS_BPAL[5][23:16]; bgpd[58] <= SS_BPAL[7][23:16]; //8'h00;
|
|
bgpd[11] <= SS_BPAL[1][31:24]; bgpd[27] <= SS_BPAL[3][31:24]; bgpd[43] <= SS_BPAL[5][31:24]; bgpd[59] <= SS_BPAL[7][31:24]; //8'h00;
|
|
bgpd[12] <= SS_BPAL[1][39:32]; bgpd[28] <= SS_BPAL[3][39:32]; bgpd[44] <= SS_BPAL[5][39:32]; bgpd[60] <= SS_BPAL[7][39:32]; //8'h00;
|
|
bgpd[13] <= SS_BPAL[1][47:40]; bgpd[29] <= SS_BPAL[3][47:40]; bgpd[45] <= SS_BPAL[5][47:40]; bgpd[61] <= SS_BPAL[7][47:40]; //8'h00;
|
|
bgpd[14] <= SS_BPAL[1][55:48]; bgpd[30] <= SS_BPAL[3][55:48]; bgpd[46] <= SS_BPAL[5][55:48]; bgpd[62] <= SS_BPAL[7][55:48]; //8'h00;
|
|
bgpd[15] <= SS_BPAL[1][63:56]; bgpd[31] <= SS_BPAL[3][63:56]; bgpd[47] <= SS_BPAL[5][63:56]; bgpd[63] <= SS_BPAL[7][63:56]; //8'h00;
|
|
|
|
obpd[ 0] <= SS_OPAL[0][ 7: 0]; obpd[16] <= SS_OPAL[2][ 7: 0]; obpd[32] <= SS_OPAL[4][ 7: 0]; obpd[48] <= SS_OPAL[6][ 7: 0]; //8'h00;
|
|
obpd[ 1] <= SS_OPAL[0][15: 8]; obpd[17] <= SS_OPAL[2][15: 8]; obpd[33] <= SS_OPAL[4][15: 8]; obpd[49] <= SS_OPAL[6][15: 8]; //8'h00;
|
|
obpd[ 2] <= SS_OPAL[0][23:16]; obpd[18] <= SS_OPAL[2][23:16]; obpd[34] <= SS_OPAL[4][23:16]; obpd[50] <= SS_OPAL[6][23:16]; //8'h00;
|
|
obpd[ 3] <= SS_OPAL[0][31:24]; obpd[19] <= SS_OPAL[2][31:24]; obpd[35] <= SS_OPAL[4][31:24]; obpd[51] <= SS_OPAL[6][31:24]; //8'h00;
|
|
obpd[ 4] <= SS_OPAL[0][39:32]; obpd[20] <= SS_OPAL[2][39:32]; obpd[36] <= SS_OPAL[4][39:32]; obpd[52] <= SS_OPAL[6][39:32]; //8'h00;
|
|
obpd[ 5] <= SS_OPAL[0][47:40]; obpd[21] <= SS_OPAL[2][47:40]; obpd[37] <= SS_OPAL[4][47:40]; obpd[53] <= SS_OPAL[6][47:40]; //8'h00;
|
|
obpd[ 6] <= SS_OPAL[0][55:48]; obpd[22] <= SS_OPAL[2][55:48]; obpd[38] <= SS_OPAL[4][55:48]; obpd[54] <= SS_OPAL[6][55:48]; //8'h00;
|
|
obpd[ 7] <= SS_OPAL[0][63:56]; obpd[23] <= SS_OPAL[2][63:56]; obpd[39] <= SS_OPAL[4][63:56]; obpd[55] <= SS_OPAL[6][63:56]; //8'h00;
|
|
obpd[ 8] <= SS_OPAL[1][ 7: 0]; obpd[24] <= SS_OPAL[3][ 7: 0]; obpd[40] <= SS_OPAL[5][ 7: 0]; obpd[56] <= SS_OPAL[7][ 7: 0]; //8'h00;
|
|
obpd[ 9] <= SS_OPAL[1][15: 8]; obpd[25] <= SS_OPAL[3][15: 8]; obpd[41] <= SS_OPAL[5][15: 8]; obpd[57] <= SS_OPAL[7][15: 8]; //8'h00;
|
|
obpd[10] <= SS_OPAL[1][23:16]; obpd[26] <= SS_OPAL[3][23:16]; obpd[42] <= SS_OPAL[5][23:16]; obpd[58] <= SS_OPAL[7][23:16]; //8'h00;
|
|
obpd[11] <= SS_OPAL[1][31:24]; obpd[27] <= SS_OPAL[3][31:24]; obpd[43] <= SS_OPAL[5][31:24]; obpd[59] <= SS_OPAL[7][31:24]; //8'h00;
|
|
obpd[12] <= SS_OPAL[1][39:32]; obpd[28] <= SS_OPAL[3][39:32]; obpd[44] <= SS_OPAL[5][39:32]; obpd[60] <= SS_OPAL[7][39:32]; //8'h00;
|
|
obpd[13] <= SS_OPAL[1][47:40]; obpd[29] <= SS_OPAL[3][47:40]; obpd[45] <= SS_OPAL[5][47:40]; obpd[61] <= SS_OPAL[7][47:40]; //8'h00;
|
|
obpd[14] <= SS_OPAL[1][55:48]; obpd[30] <= SS_OPAL[3][55:48]; obpd[46] <= SS_OPAL[5][55:48]; obpd[62] <= SS_OPAL[7][55:48]; //8'h00;
|
|
obpd[15] <= SS_OPAL[1][63:56]; obpd[31] <= SS_OPAL[3][63:56]; obpd[47] <= SS_OPAL[5][63:56]; obpd[63] <= SS_OPAL[7][63:56]; //8'h00;
|
|
|
|
end else if (ce_cpu) begin
|
|
if(cpu_sel_reg && cpu_wr) begin
|
|
case(cpu_addr)
|
|
8'h41: stat_r <= cpu_di;
|
|
8'h42: scy <= cpu_di;
|
|
8'h43: scx <= cpu_di;
|
|
8'h45: lyc_r_gbc <= cpu_di;
|
|
8'h46: dma <= cpu_di;
|
|
8'h47: bgp <= cpu_di;
|
|
8'h48: obp0 <= cpu_di;
|
|
8'h49: obp1 <= cpu_di;
|
|
8'h4a: wy <= cpu_di;
|
|
8'h4b: wx <= cpu_di;
|
|
endcase
|
|
|
|
//gbc
|
|
if (isGBC) case(cpu_addr)
|
|
8'h68: begin
|
|
bgpi <= cpu_di[5:0];
|
|
bgpi_ai <= cpu_di[7];
|
|
end
|
|
8'h69: if (isGBC_mode) begin
|
|
if (vram_cpu_allow) begin
|
|
bgpd[bgpi] <= cpu_di;
|
|
end
|
|
//"Writing to FF69 during rendering still causes auto-increment to occur."
|
|
if (bgpi_ai) bgpi <= bgpi + 6'h1;
|
|
end
|
|
8'h6A: begin
|
|
obpi <= cpu_di[5:0];
|
|
obpi_ai <= cpu_di[7];
|
|
end
|
|
8'h6B: if (isGBC_mode) begin
|
|
if (vram_cpu_allow) begin
|
|
obpd[obpi] <= cpu_di;
|
|
end
|
|
if (obpi_ai) obpi <= obpi + 6'h1;
|
|
end
|
|
8'h6C: begin
|
|
// Reportedly can be written to when boot rom is enabled or FF4C Bit2=0 (GBC mode)
|
|
// but only affects OBJ prio if written when boot rom is enabled.
|
|
if (boot_rom_en | isGBC_mode) begin
|
|
ff6c_opri <= cpu_di[0];
|
|
end
|
|
if (boot_rom_en) obj_prio_dmg_mode <= cpu_di[0];
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
assign cpu_do =
|
|
cpu_sel_oam?oam_do:
|
|
(cpu_addr == 8'h40)?lcdc:
|
|
(cpu_addr == 8'h41)?{1'b1,stat[6:3], lyc_match_l, mode}:
|
|
(cpu_addr == 8'h42)?scy:
|
|
(cpu_addr == 8'h43)?scx:
|
|
(cpu_addr == 8'h44)?ly:
|
|
(cpu_addr == 8'h45)?lyc:
|
|
(cpu_addr == 8'h46)?dma:
|
|
(cpu_addr == 8'h47)?bgp:
|
|
(cpu_addr == 8'h48)?obp0:
|
|
(cpu_addr == 8'h49)?obp1:
|
|
(cpu_addr == 8'h4a)?wy:
|
|
(cpu_addr == 8'h4b)?wx:
|
|
isGBC?
|
|
(cpu_addr == 8'h68)?{bgpi_ai,1'd1,bgpi}:
|
|
(cpu_addr == 8'h69 && isGBC_mode && vram_cpu_allow)?bgpd[bgpi]:
|
|
(cpu_addr == 8'h6a)?{obpi_ai,1'd1,obpi}:
|
|
(cpu_addr == 8'h6b && isGBC_mode && vram_cpu_allow)?obpd[obpi]:
|
|
(cpu_addr == 8'h6c) ? { 7'h7f, ff6c_opri } :
|
|
8'hff:
|
|
8'hff;
|
|
|
|
// --------------------------------------------------------------------
|
|
// -------------- counters & background tilemap address----------------
|
|
// --------------------------------------------------------------------
|
|
|
|
reg [7:0] pcnt;
|
|
reg bg_first_fetch_done;
|
|
reg win_first_fetch;
|
|
reg bg_scroll_done;
|
|
|
|
wire bg_paused = ~bg_first_fetch_done | win_first_fetch | sprite_found | mode3_end;
|
|
wire bg_scroll_end = ~bg_paused & (bg_shift_cnt == scx[2:0]) & ~bg_scroll_done;
|
|
wire pcnt_paused = bg_paused | ~(bg_scroll_done | bg_scroll_end);
|
|
|
|
assign pcnt_end = ( pcnt == 8'd167 );
|
|
assign pcnt_reset = h_clk_en & end_of_line & ~vblank;
|
|
|
|
assign SS_Video3_BACK[17:10] = pcnt;
|
|
assign SS_Video3_BACK[ 18] = bg_scroll_done;
|
|
|
|
// Pixel counter
|
|
// Pixels 0-7 are for fetching partially offscreen sprites and window.
|
|
// Pixels 8-167 are output to the display.
|
|
always @(posedge clk) begin
|
|
if (reset) begin
|
|
pcnt <= SS_Video3[17:10]; // 8'd0;
|
|
bg_scroll_done <= SS_Video3[ 18]; // 1'b0;
|
|
end else if (!lcd_on) begin
|
|
pcnt <= 8'd0;
|
|
bg_scroll_done <= 1'b0;
|
|
end else if (ce) begin
|
|
|
|
if (pcnt_reset) begin
|
|
pcnt <= 8'd0;
|
|
end else if (~pcnt_paused) begin
|
|
pcnt <= pcnt + 1'b1;
|
|
end
|
|
|
|
if (~mode3) begin
|
|
bg_scroll_done <= 1'b0;
|
|
end else if (bg_scroll_end) begin
|
|
bg_scroll_done <= 1'b1;
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
wire line153 = (v_cnt == 8'd153);
|
|
reg vcnt_reset;
|
|
reg vsync;
|
|
reg vcnt_eol_l;
|
|
|
|
assign SS_Video3_BACK[ 22] = vcnt_eol_l;
|
|
assign SS_Video3_BACK[ 23] = vcnt_reset;
|
|
assign SS_Video3_BACK[31:24] = v_cnt;
|
|
assign SS_Video3_BACK[ 32] = vsync;
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) begin
|
|
vcnt_eol_l <= SS_Video3[ 22]; // 1'b0;
|
|
vcnt_reset <= SS_Video3[ 23]; // 1'b0;
|
|
v_cnt <= SS_Video3[31:24]; // 8'd0;
|
|
vsync <= SS_Video3[ 32]; // 1'b0;
|
|
end else if (!lcdc_on) begin
|
|
vcnt_eol_l <= 1'b0;
|
|
vcnt_reset <= 1'b0;
|
|
v_cnt <= 8'd0;
|
|
vsync <= 1'b0;
|
|
end else if (ce) begin
|
|
if (~vcnt_reset & h_clk_en_neg & hcnt_end) begin
|
|
v_cnt <= v_cnt + 1'b1;
|
|
end
|
|
|
|
// Line 153->0 reset happens a few cycles later on GBC
|
|
if ( (~isGBC & h_clk_en) | (isGBC & h_clk_en_neg) ) begin
|
|
vcnt_eol_l <= end_of_line;
|
|
if (vcnt_eol_l & ~end_of_line) begin
|
|
// vcnt_reset goes high a few cycles after v_cnt is incremented to 153.
|
|
// It resets v_cnt back to 0 and keeps it in reset until the following line.
|
|
// This results in v_cnt 0 lasting for almost 2 lines.
|
|
vcnt_reset <= line153;
|
|
if (line153) begin
|
|
v_cnt <= 8'd0;
|
|
end
|
|
|
|
// VSync goes high on line 0 but it takes a full frame after the LCD is enabled
|
|
// because the first line where end_of_line is high after LCD is enabled is line 1.
|
|
vsync <= !v_cnt;
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
assign lcd_vsync = vsync;
|
|
|
|
// line inside the background currently being drawn
|
|
wire [7:0] bg_line = v_cnt + scy;
|
|
wire [7:0] bg_col = pcnt + scx;
|
|
wire [9:0] bg_map_addr = {bg_line[7:3], bg_col[7:3]};
|
|
|
|
reg [4:0] win_col;
|
|
reg [7:0] win_line;
|
|
wire window_ena;
|
|
wire bg_fetch_done;
|
|
wire bg_reload_shift;
|
|
|
|
wire [9:0] win_map_addr = {win_line[7:3], win_col[4:0]};
|
|
|
|
wire [9:0] bg_tile_map_addr = window_ena ? win_map_addr : bg_map_addr;
|
|
|
|
wire [2:0] tile_line = window_ena ? win_line[2:0] : bg_line[2:0];
|
|
|
|
reg wy_match, wxy_match_d, window_match, window_ena_d;
|
|
|
|
wire wxy_match = wy_match & (pcnt == wx);
|
|
|
|
// Using the latched wxy_match_d here causes 2 Window glitches on DMG:
|
|
// 1. WX = 166 causes all following lines and the first line after VBlank to show the Window.
|
|
// This happens because the last latch is at pixel 166 and it is not reset in H/VBlank.
|
|
// 2. Window start can be delayed by 1 pixel if lcdc_win_ena goes high right after wxy_match goes low.
|
|
// This can be seen in Mealybug test m3_lcdc_win_en_change_multiple_wx.
|
|
wire win_start = ~window_match & lcdc_win_ena & ((~bg_paused & wxy_match) | wxy_match_d);
|
|
wire win_reset = pcnt_reset | ~lcdc_win_ena;
|
|
|
|
assign window_ena = window_match & ~win_reset;
|
|
|
|
assign SS_Video3_BACK[39:33] = h_cnt ;
|
|
assign SS_Video3_BACK[41:40] = h_div_cnt ;
|
|
assign SS_Video3_BACK[ 42] = window_match;
|
|
assign SS_Video3_BACK[ 43] = window_ena_d;
|
|
assign SS_Video3_BACK[48:44] = win_col ;
|
|
assign SS_Video3_BACK[56:49] = win_line ;
|
|
assign SS_Video3_BACK[ 63] = wy_match ;
|
|
assign SS_Video3_BACK[ 19] = wxy_match_d ;
|
|
|
|
always @(posedge clk) begin
|
|
|
|
if (reset) begin
|
|
h_cnt <= SS_Video3[39:33]; // 7'd0;
|
|
h_div_cnt <= SS_Video3[41:40]; // 2'd0;
|
|
window_match <= SS_Video3[ 42]; // 1'b0;
|
|
window_ena_d <= SS_Video3[ 43]; // 1'b0;
|
|
win_col <= SS_Video3[48:44]; // 5'd0;
|
|
win_line <= SS_Video3[56:49]; // 8'd0;
|
|
wy_match <= SS_Video3[ 63]; // 1'b0;
|
|
wxy_match_d <= SS_Video3[ 19]; // 1'b0;
|
|
end else if (!lcdc_on) begin
|
|
//reset counters
|
|
h_cnt <= 7'd0;
|
|
h_div_cnt <= 2'd0;
|
|
window_match <= 1'b0;
|
|
window_ena_d <= 1'b0;
|
|
win_col <= 5'd0;
|
|
win_line <= 8'd0;
|
|
wy_match <= 1'b0;
|
|
wxy_match_d <= 1'b0;
|
|
end else if (ce) begin
|
|
|
|
h_div_cnt <= h_div_cnt + 1'b1;
|
|
if (h_clk_en) begin
|
|
h_cnt <= hcnt_end ? 7'd0 : h_cnt + 1'b1;
|
|
end
|
|
|
|
if (vblank_l) begin
|
|
wy_match <= 1'b0;
|
|
end else if (h_clk_en & lcdc_win_ena & v_cnt == wy) begin
|
|
wy_match <= 1'b1;
|
|
end
|
|
|
|
if (isGBC & ~mode3) begin
|
|
wxy_match_d <= 0;
|
|
end else if (~bg_paused) begin
|
|
wxy_match_d <= wxy_match;
|
|
end
|
|
|
|
if (win_reset) begin
|
|
window_match <= 1'b0;
|
|
end else if (win_start) begin
|
|
window_match <= 1'b1;
|
|
end
|
|
|
|
// Increment line when Window turns off. This means it is possible to increment
|
|
// multiple times per line by changing WX and toggling the Window bit in LCDC.
|
|
window_ena_d <= window_ena;
|
|
if (vblank_l)
|
|
win_line <= 8'd0;
|
|
else if (window_ena_d & ~window_ena)
|
|
win_line <= win_line + 1'b1;
|
|
|
|
// Increment when fetching is done and not waiting for sprites.
|
|
if (win_reset) begin
|
|
win_col <= 5'd0;
|
|
end else if (window_ena & bg_reload_shift) begin
|
|
win_col <= win_col + 1'b1;
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
// --------------------------------------------------------------------
|
|
// ------------------- bg, window and sprite fetch -------------------
|
|
// --------------------------------------------------------------------
|
|
|
|
function [7:0] bit_reverse;
|
|
input [7:0] a;
|
|
begin
|
|
bit_reverse = {a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7]};
|
|
end
|
|
endfunction
|
|
|
|
// output shift registers for both video data bits
|
|
reg [7:0] tile_shift_0;
|
|
reg [7:0] tile_shift_1;
|
|
|
|
// sprite output shift registers
|
|
reg [7:0] spr_tile_shift_0;
|
|
reg [7:0] spr_tile_shift_1;
|
|
reg [7:0] spr_pal_shift;
|
|
reg [7:0] spr_prio_shift;
|
|
reg [7:0] spr_cgb_pal_shift[0:2];
|
|
reg [7:0] spr_cgb_index_shift[0:3];
|
|
|
|
reg [7:0] bg_tile;
|
|
reg [7:0] bg_tile_data0;
|
|
reg [7:0] bg_tile_data1;
|
|
|
|
reg [7:0] spr_tile_data0;
|
|
|
|
reg [7:0] bg_tile_attr,bg_tile_attr_new; //GBC
|
|
//gbc check tile bank
|
|
wire [7:0] bg_vram_data_a = (isGBC & isGBC_mode & bg_tile_attr_new[3]) ? vram1_data : vram_data;
|
|
//gbc x-flip
|
|
wire [7:0] bg_vram_data_in = (isGBC & isGBC_mode & bg_tile_attr_new[5]) ? bit_reverse(bg_vram_data_a) : bg_vram_data_a;
|
|
|
|
// A bg or sprite fetch takes 6 cycles
|
|
reg [2:0] bg_fetch_cycle;
|
|
reg [2:0] sprite_fetch_cycle;
|
|
|
|
assign bg_fetch_done = (bg_fetch_cycle >= 3'd5);
|
|
wire sprite_fetch_done = (sprite_found && sprite_fetch_cycle >= 3'd5);
|
|
|
|
reg [2:0] bg_shift_cnt;
|
|
|
|
wire bg_shift_end = &bg_shift_cnt;
|
|
|
|
// The pixel shift register is reloaded when:
|
|
// 1. The first BG or Window fetch is done. (bg_shift_cnt = 5)
|
|
// 2. Last pixel is shifted out (bg_shift_cnt = 7) and not paused.
|
|
// If wxy_match is high here but the Window is disabled then the
|
|
// reload is delayed. That is what causes the extra pixel glitch
|
|
// on DMG and also the WX=0 & SCX=7 glitch.
|
|
assign bg_reload_shift = (bg_fetch_done & (~bg_first_fetch_done | win_first_fetch))
|
|
| (~bg_paused & bg_shift_end & ~(wxy_match & (lcdc_win_ena | ~isGBC)));
|
|
|
|
wire spr_prio = sprite_attr[7];
|
|
wire spr_attr_h_flip = sprite_attr[5];
|
|
wire spr_pal = sprite_attr[4];
|
|
wire spr_attr_cgb_bank = sprite_attr[3];
|
|
wire [2:0] spr_cgb_pal = sprite_attr[2:0];
|
|
|
|
wire [7:0] spr_vram_data = (isGBC & isGBC_mode & spr_attr_cgb_bank) ? vram1_data : vram_data;
|
|
wire [7:0] spr_tile_data_in = spr_attr_h_flip ? bit_reverse(spr_vram_data) : spr_vram_data;
|
|
|
|
// DMG sprite pixels are only loaded into the shift register if the old pixel is transparent.
|
|
wire [7:0] spr_pixel_empty = ~(spr_tile_shift_0 | spr_tile_shift_1);
|
|
|
|
// CGB sprite priority. Non-transparent pixels with lower sprite_index have priority.
|
|
reg [7:0] spr_cgb_higher_prio, spr_extra_cgb_higher_prio;
|
|
always @(*) begin
|
|
for (i = 0; i < 8; i = i + 1) begin
|
|
spr_cgb_higher_prio[i] = (sprite_index < {spr_cgb_index_shift[3][i], spr_cgb_index_shift[2][i], spr_cgb_index_shift[1][i], spr_cgb_index_shift[0][i]}) & (spr_tile_data_in[i] | spr_tile_data0[i]);
|
|
spr_extra_cgb_higher_prio[i] = (spr_extra_index < {spr_cgb_index_shift[3][i], spr_cgb_index_shift[2][i], spr_cgb_index_shift[1][i], spr_cgb_index_shift[0][i]}) & (spr_extra_tile[0][i] | spr_extra_tile[1][i]);
|
|
end
|
|
end
|
|
|
|
wire [7:0] spr_extra_tile[0:1];
|
|
wire [2:0] spr_extra_cgb_pal;
|
|
wire [3:0] spr_extra_index;
|
|
wire spr_extra_pal;
|
|
wire spr_extra_prio;
|
|
wire spr_extra_found;
|
|
|
|
// cycle through the B01s states
|
|
wire bg_tile_map_rd = (mode3 && bg_fetch_cycle[2:1] == 2'b00);
|
|
wire bg_tile_data0_rd = (mode3 && bg_fetch_cycle[2:1] == 2'b01);
|
|
wire bg_tile_data1_rd = (mode3 && bg_fetch_cycle[2:1] == 2'b10);
|
|
wire bg_tile_obj0_rd = (mode3 && sprite_fetch_cycle[2:1] == 2'b01);
|
|
wire bg_tile_obj1_rd = (mode3 && sprite_fetch_cycle[2:1] == 2'b10);
|
|
|
|
assign SS_Video3_BACK[59:57] = bg_fetch_cycle;
|
|
assign SS_Video3_BACK[62:60] = sprite_fetch_cycle;
|
|
assign SS_Video3_BACK[ 9: 7] = bg_shift_cnt;
|
|
assign SS_Video3_BACK[ 20] = bg_first_fetch_done;
|
|
assign SS_Video3_BACK[ 21] = win_first_fetch;
|
|
|
|
always @(posedge clk) begin
|
|
|
|
if (reset) begin
|
|
bg_fetch_cycle <= SS_Video3[59:57]; // 3'b000;
|
|
sprite_fetch_cycle <= SS_Video3[62:60]; // 3'b000;
|
|
bg_shift_cnt <= SS_Video3[ 9: 7]; // 3'b000;
|
|
bg_first_fetch_done <= SS_Video3[ 20]; // 1'b0;
|
|
win_first_fetch <= SS_Video3[ 21]; // 1'b0;
|
|
end else if (ce) begin
|
|
|
|
// every memory access is two pixel cycles
|
|
if (bg_fetch_cycle[0]) begin
|
|
if(bg_tile_map_rd) begin
|
|
bg_tile <= vram_data;
|
|
if (isGBC & isGBC_mode) bg_tile_attr_new <= vram1_data; //get tile attr from vram bank1
|
|
end
|
|
|
|
if(bg_tile_data0_rd) bg_tile_data0 <= bg_vram_data_in;
|
|
if(bg_tile_data1_rd) bg_tile_data1 <= bg_vram_data_in;
|
|
end
|
|
|
|
if (~&bg_fetch_cycle) begin
|
|
bg_fetch_cycle <= bg_fetch_cycle + 1'b1;
|
|
end
|
|
|
|
if (~mode3) begin
|
|
bg_first_fetch_done <= 0;
|
|
end else if (bg_fetch_done) begin
|
|
bg_first_fetch_done <= 1'b1;
|
|
end
|
|
|
|
if (~pcnt_paused) begin
|
|
// shift bg/window pixels out
|
|
tile_shift_0 <= { tile_shift_0[6:0], 1'b0 };
|
|
tile_shift_1 <= { tile_shift_1[6:0], 1'b0 };
|
|
end
|
|
|
|
if (bg_reload_shift) begin
|
|
bg_tile_attr <= bg_tile_attr_new;
|
|
tile_shift_0 <= bg_tile_data0;
|
|
// bg_tile_data1 only has valid data at cycle 6 so at cycle 5 bg_vram_data_in is loaded instead.
|
|
tile_shift_1 <= (bg_fetch_cycle == 3'd5) ? bg_vram_data_in : bg_tile_data1;
|
|
bg_shift_cnt <= 0;
|
|
bg_fetch_cycle <= 0;
|
|
end
|
|
|
|
if (~bg_paused) begin
|
|
if (~bg_shift_end) bg_shift_cnt <= bg_shift_cnt + 1'b1;
|
|
end
|
|
|
|
// Start sprite fetch after background fetching is done and not in BG or Window first fetch.
|
|
// Sprite fetching continues until there are no more sprites on the current x position
|
|
if (sprite_found & bg_fetch_done & ~win_first_fetch & bg_first_fetch_done) begin
|
|
sprite_fetch_cycle <= sprite_fetch_cycle + 1'b1;
|
|
end
|
|
|
|
if (~sprite_found || sprite_fetch_done) sprite_fetch_cycle <= 0;
|
|
|
|
// Window start, reset fetching
|
|
if (win_start | ~mode3) begin
|
|
bg_fetch_cycle <= 0;
|
|
bg_shift_cnt <= 0;
|
|
end
|
|
|
|
if (~lcd_on | (win_first_fetch & bg_fetch_done)) begin
|
|
win_first_fetch <= 0;
|
|
end else if (win_start) begin
|
|
win_first_fetch <= 1'b1;
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) begin
|
|
|
|
end else begin
|
|
|
|
if (ce) begin
|
|
// Shift sprite data out
|
|
if (~pcnt_paused) begin
|
|
spr_tile_shift_0 <= spr_tile_shift_0 << 1;
|
|
spr_tile_shift_1 <= spr_tile_shift_1 << 1;
|
|
spr_pal_shift <= spr_pal_shift << 1;
|
|
spr_prio_shift <= spr_prio_shift << 1;
|
|
spr_cgb_pal_shift[0] <= spr_cgb_pal_shift[0] << 1;
|
|
spr_cgb_pal_shift[1] <= spr_cgb_pal_shift[1] << 1;
|
|
spr_cgb_pal_shift[2] <= spr_cgb_pal_shift[2] << 1;
|
|
spr_cgb_index_shift[0] <= spr_cgb_index_shift[0] << 1;
|
|
spr_cgb_index_shift[1] <= spr_cgb_index_shift[1] << 1;
|
|
spr_cgb_index_shift[2] <= spr_cgb_index_shift[2] << 1;
|
|
spr_cgb_index_shift[3] <= spr_cgb_index_shift[3] << 1;
|
|
end
|
|
|
|
// Fetch sprite new data
|
|
if (sprite_fetch_cycle[0]) begin
|
|
if(bg_tile_obj0_rd) spr_tile_data0 <= spr_tile_data_in;
|
|
|
|
if(bg_tile_obj1_rd) begin
|
|
for (i = 0; i < 8; i = i + 1) begin
|
|
if (spr_pixel_empty[i] | (isGBC & ~obj_prio_dmg_mode & spr_cgb_higher_prio[i])) begin
|
|
spr_tile_shift_0[i] <= spr_tile_data0[i];
|
|
spr_tile_shift_1[i] <= spr_tile_data_in[i];
|
|
spr_pal_shift[i] <= spr_pal;
|
|
spr_prio_shift[i] <= spr_prio;
|
|
spr_cgb_pal_shift[0][i] <= spr_cgb_pal[0];
|
|
spr_cgb_pal_shift[1][i] <= spr_cgb_pal[1];
|
|
spr_cgb_pal_shift[2][i] <= spr_cgb_pal[2];
|
|
spr_cgb_index_shift[0][i] <= sprite_index[0];
|
|
spr_cgb_index_shift[1][i] <= sprite_index[1];
|
|
spr_cgb_index_shift[2][i] <= sprite_index[2];
|
|
spr_cgb_index_shift[3][i] <= sprite_index[3];
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
// Load extra sprite
|
|
if (ce_n) begin
|
|
if (~pcnt_paused & spr_extra_found) begin
|
|
for (i = 0; i < 8; i = i + 1) begin
|
|
if (spr_pixel_empty[i] | (isGBC & ~obj_prio_dmg_mode & spr_extra_cgb_higher_prio[i])) begin
|
|
spr_tile_shift_0[i] <= spr_extra_tile[0][i];
|
|
spr_tile_shift_1[i] <= spr_extra_tile[1][i];
|
|
spr_pal_shift[i] <= spr_extra_pal;
|
|
spr_prio_shift[i] <= spr_extra_prio;
|
|
spr_cgb_pal_shift[0][i] <= spr_extra_cgb_pal[0];
|
|
spr_cgb_pal_shift[1][i] <= spr_extra_cgb_pal[1];
|
|
spr_cgb_pal_shift[2][i] <= spr_extra_cgb_pal[2];
|
|
spr_cgb_index_shift[0][i] <= spr_extra_index[0];
|
|
spr_cgb_index_shift[1][i] <= spr_extra_index[1];
|
|
spr_cgb_index_shift[2][i] <= spr_extra_index[2];
|
|
spr_cgb_index_shift[3][i] <= spr_extra_index[3];
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
assign vram_rd = lcdc_on && (bg_tile_map_rd || bg_tile_data0_rd ||
|
|
bg_tile_data1_rd || bg_tile_obj0_rd ||
|
|
bg_tile_obj1_rd || tile_obj_extra_rd);
|
|
|
|
wire bg_tile_a12 = !lcdc_tile_data_sel?(~bg_tile[7]):1'b0;
|
|
|
|
wire tile_map_sel = window_ena?lcdc_win_tile_map_sel:lcdc_bg_tile_map_sel;
|
|
|
|
//GBC: check if flipped y
|
|
wire [2:0] tile_line_flip = (isGBC && isGBC_mode && bg_tile_attr_new[6]) ? ~tile_line : tile_line;
|
|
|
|
wire tile_obj_extra_rd;
|
|
wire [11:0] sprite_extra_addr;
|
|
|
|
assign vram_addr =
|
|
bg_tile_map_rd?{2'b11, tile_map_sel, bg_tile_map_addr}:
|
|
bg_tile_data0_rd?{bg_tile_a12, bg_tile, tile_line_flip, 1'b0}:
|
|
bg_tile_data1_rd?{bg_tile_a12, bg_tile, tile_line_flip, 1'b1}:
|
|
bg_tile_obj0_rd ? {1'b0, sprite_addr, 1'b0} :
|
|
bg_tile_obj1_rd ? {1'b0, sprite_addr, 1'b1} :
|
|
{1'b0, sprite_extra_addr };
|
|
|
|
sprites sprites (
|
|
.clk ( clk ),
|
|
.ce ( ce ),
|
|
.ce_cpu ( ce_cpu ),
|
|
.size16 ( lcdc_spr_siz ),
|
|
.isGBC ( isGBC ),
|
|
.sprite_en( lcdc_spr_ena ),
|
|
.lcd_on ( lcd_on ),
|
|
|
|
.v_cnt ( v_cnt ),
|
|
.h_cnt ( pcnt ),
|
|
|
|
.oam_eval ( oam_eval ),
|
|
.oam_fetch ( mode3 ),
|
|
.oam_eval_reset ( pcnt_reset ),
|
|
.oam_eval_end ( oam_eval_end ),
|
|
|
|
.sprite_fetch (sprite_found),
|
|
.sprite_addr ( sprite_addr ),
|
|
.sprite_attr ( sprite_attr ),
|
|
.sprite_index ( sprite_index ),
|
|
.sprite_fetch_c1 ( sprite_fetch_cycle == 3'd1 ),
|
|
.sprite_fetch_done ( sprite_fetch_done) ,
|
|
|
|
.dma_active ( dma_active),
|
|
.oam_wr ( oam_wr ),
|
|
.oam_addr_in( oam_addr ),
|
|
.oam_di ( oam_di ),
|
|
.oam_do ( oam_do ),
|
|
|
|
// For extra sprites
|
|
.extra_spr_en ( extra_spr_en ),
|
|
.extra_wait ( extra_wait ),
|
|
.tile_data_in ( spr_tile_data_in ),
|
|
.extra_tile_fetch ( tile_obj_extra_rd ),
|
|
.extra_tile_addr ( sprite_extra_addr ),
|
|
|
|
.spr_extra_found ( spr_extra_found ),
|
|
.spr_extra_tile0 ( spr_extra_tile[0] ),
|
|
.spr_extra_tile1 ( spr_extra_tile[1] ),
|
|
.spr_extra_pal ( spr_extra_pal ),
|
|
.spr_extra_prio ( spr_extra_prio ),
|
|
.spr_extra_cgb_pal( spr_extra_cgb_pal ),
|
|
.spr_extra_index ( spr_extra_index ),
|
|
|
|
.Savestate_OAMRAMAddr (Savestate_OAMRAMAddr),
|
|
.Savestate_OAMRAMRWrEn (Savestate_OAMRAMRWrEn),
|
|
.Savestate_OAMRAMWriteData (Savestate_OAMRAMWriteData),
|
|
.Savestate_OAMRAMReadData (Savestate_OAMRAMReadData)
|
|
);
|
|
|
|
// --------------------------------------------------------------------
|
|
// ----------------------- lcd output stage -------------------------
|
|
// --------------------------------------------------------------------
|
|
|
|
// https://forums.nesdev.com/viewtopic.php?f=20&t=10771&sid=8fdb6e110fd9b5434d4a567b1199585e#p122222
|
|
// priority list: BG0 < OBJL < BGL < OBJH < BGH
|
|
wire bg_piority = isGBC && isGBC_mode && bg_tile_attr[7];
|
|
|
|
reg sprite_pixel_visible;
|
|
|
|
wire [1:0] sprite_pixel_data = {spr_tile_shift_1[7], spr_tile_shift_0[7]};
|
|
wire sprite_pixel_prio = spr_prio_shift[7];
|
|
|
|
wire [1:0] bg_pix_data = { tile_shift_1[7], tile_shift_0[7] };
|
|
|
|
always @(*) begin
|
|
sprite_pixel_visible = 1'b0;
|
|
if (|sprite_pixel_data && lcdc_spr_ena) begin // pixel active and sprites enabled
|
|
|
|
if (isGBC&&isGBC_mode) begin
|
|
if (sprite_pixel_data == 2'b00)
|
|
sprite_pixel_visible = 1'b0;
|
|
else if (bg_pix_data == 2'b00)
|
|
sprite_pixel_visible = 1'b1;
|
|
else if (!lcdc_bg_prio)
|
|
sprite_pixel_visible = 1'b1;
|
|
else if (bg_piority)
|
|
sprite_pixel_visible = 1'b0;
|
|
else if (!sprite_pixel_prio) //sprite pixel priority is enabled
|
|
sprite_pixel_visible = 1'b1;
|
|
|
|
end else begin
|
|
if (sprite_pixel_data == 2'b00)
|
|
sprite_pixel_visible = 1'b0;
|
|
else if (bg_pix_data == 2'b00)
|
|
sprite_pixel_visible = 1'b1;
|
|
else if (!sprite_pixel_prio) //sprite pixel priority is enabled
|
|
sprite_pixel_visible = 1'b1;
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// If lcdc_bg_ena is off in Non-GBC mode then both background and window are blank. (BG color 0)
|
|
wire [1:0] bgp_data = (!lcdc_bg_ena || bg_pix_data == 2'b00) ? bgp[1:0] :
|
|
(bg_pix_data == 2'b01) ? bgp[3:2] :
|
|
(bg_pix_data == 2'b10) ? bgp[5:4] :
|
|
bgp[7:6];
|
|
|
|
wire sprite_pixel_cmap = spr_pal_shift[7];
|
|
wire [7:0] obp = sprite_pixel_cmap?obp1:obp0;
|
|
wire [1:0] obp_data = (sprite_pixel_data == 2'b00) ? obp[1:0] :
|
|
(sprite_pixel_data == 2'b01) ? obp[3:2] :
|
|
(sprite_pixel_data == 2'b10) ? obp[5:4] :
|
|
obp[7:6];
|
|
|
|
wire [5:0] palette_index = isGBC_mode ? {bg_tile_attr[2:0], bg_pix_data, 1'b0} : //GBC game
|
|
{3'd0, bgp_data , 1'b0}; //GB game in GBC mode
|
|
|
|
// apply bg palette
|
|
wire [14:0] pix_rgb_data = isGBC ? {bgpd[palette_index+1][6:0],bgpd[palette_index]} : //gbc
|
|
{13'd0,bgp_data};
|
|
|
|
// apply sprite palette
|
|
wire [2:0] spr_cgb_pal_out = {spr_cgb_pal_shift[2][7], spr_cgb_pal_shift[1][7],spr_cgb_pal_shift[0][7]};
|
|
wire [5:0] sprite_palette_index = isGBC_mode? {spr_cgb_pal_out, sprite_pixel_data, 1'b0 } : //gbc game
|
|
{sprite_pixel_cmap, obp_data, 1'b0}; //GB game in GBC mode
|
|
|
|
wire [14:0] sprite_pix = isGBC ? {obpd[sprite_palette_index+1][6:0],obpd[sprite_palette_index]} : //gbc
|
|
{13'd0,obp_data};
|
|
|
|
wire lcd_clk = (~pcnt_paused & (pcnt >= 8)) | (~mode3_end_l & mode3_end);
|
|
|
|
reg [14:0] lcd_data_out;
|
|
reg [1:0] lcd_data_gb_out;
|
|
reg lcd_clk_out;
|
|
always @(posedge clk) begin
|
|
if (ce) begin
|
|
lcd_clk_out <= lcd_clk;
|
|
if (lcd_clk) begin
|
|
lcd_data_out <= (sprite_pixel_visible) ? sprite_pix : pix_rgb_data;
|
|
lcd_data_gb_out <= (sprite_pixel_visible) ? obp_data : bgp_data;
|
|
end
|
|
|
|
// Output blank pixels if lcd is off.
|
|
if (~lcd_on) lcd_data_out <= isGBC ? 15'h7FFF : 15'd0;
|
|
end
|
|
end
|
|
|
|
assign lcd_data = lcd_data_out;
|
|
assign lcd_data_gb = lcd_data_gb_out;
|
|
assign lcd_clkena = lcd_clk_out;
|
|
|
|
endmodule
|