// // video.v // // Gameboy for the MIST board https://github.com/mist-devel // // Copyright (c) 2015 Till Harbaum // // 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 . // 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