mirror of
https://github.com/mist-devel/mist-board.git
synced 2026-02-07 00:17:07 +00:00
685 lines
19 KiB
Verilog
685 lines
19 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, // 4 Mhz cpu clock
|
|
input clk_reg,
|
|
input isGBC,
|
|
input isGBC_game,
|
|
|
|
// 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 reg irq,
|
|
output reg vblank_irq,
|
|
|
|
// vram connection
|
|
output [1:0] mode,
|
|
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
|
|
);
|
|
|
|
localparam STAGE2 = 9'd250; // oam + disp + pause
|
|
localparam OAM_LEN = 80;
|
|
localparam OAM_LEN16 = OAM_LEN/16;
|
|
localparam MODE3_OFFSET = 8'd16;
|
|
|
|
wire sprite_pixel_active;
|
|
wire [1:0] sprite_pixel_data;
|
|
wire sprite_pixel_cmap;
|
|
wire sprite_pixel_prio;
|
|
|
|
wire [7:0] oam_do;
|
|
wire [3:0] sprite_index = h_cnt[7:4] - OAM_LEN16[3:0]; // memory io starts at h_cnt == 16
|
|
wire [10:0] sprite_addr;
|
|
|
|
//gbc
|
|
wire [2:0] sprite_pixel_cmap_gbc;
|
|
|
|
// "data strobe" for the two bytes each sprite line consists of
|
|
wire [1:0] sprite_dvalid = {
|
|
(h_cnt[3:0] == 4'hf) && !vblank && !hblank,
|
|
(h_cnt[3:0] == 4'h7) && !vblank && !hblank };
|
|
|
|
sprites sprites (
|
|
.clk ( clk ),
|
|
.clk_reg ( clk_reg ),
|
|
.size16 ( lcdc_spr_siz ),
|
|
.isGBC_game ( isGBC&&isGBC_game ),
|
|
|
|
.v_cnt ( v_cnt ),
|
|
.h_cnt ( h_cnt-STAGE2 ), // sprites are added in second stage
|
|
.sort ( h_cnt == 0 ), // start of oam phase
|
|
|
|
.pixel_active ( sprite_pixel_active ),
|
|
.pixel_data ( sprite_pixel_data ),
|
|
.pixel_cmap ( sprite_pixel_cmap ),
|
|
.pixel_prio ( sprite_pixel_prio ),
|
|
|
|
.index ( sprite_index ),
|
|
.addr ( sprite_addr ),
|
|
.dvalid ( sprite_dvalid ),
|
|
.data ( vram_data ),
|
|
.data1 ( isGBC?vram1_data:vram_data ),
|
|
|
|
//gbc
|
|
.pixel_cmap_gbc ( sprite_pixel_cmap_gbc ),
|
|
|
|
.oam_wr ( oam_wr ),
|
|
.oam_addr ( oam_addr ),
|
|
.oam_di ( oam_di ),
|
|
.oam_do ( oam_do )
|
|
);
|
|
|
|
// 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 && !(mode==3 || mode==2));
|
|
wire [7:0] oam_di = dma_active?dma_data:cpu_di;
|
|
|
|
|
|
assign lcd_on = lcdc_on;
|
|
|
|
// $ff40 LCDC
|
|
wire lcdc_on = lcdc[7];
|
|
wire lcdc_win_tile_map_sel = lcdc[6];
|
|
wire lcdc_win_ena = lcdc[5];
|
|
wire lcdc_tile_data_sel = lcdc[4];
|
|
wire lcdc_bg_tile_map_sel = lcdc[3];
|
|
wire lcdc_spr_siz = lcdc[2];
|
|
wire lcdc_spr_ena = lcdc[1];
|
|
wire lcdc_bg_ena = lcdc[0];
|
|
reg [7:0] lcdc;
|
|
|
|
// ff41 STAT
|
|
reg [7:0] stat;
|
|
|
|
// ff42, ff43 background scroll registers
|
|
reg [7:0] scy;
|
|
reg [7:0] scy_r; // stable over line
|
|
reg [7:0] scx;
|
|
reg [7:0] scx_r; // stable over line
|
|
|
|
// ff44 line counter
|
|
wire [7:0] ly = v_cnt;
|
|
|
|
// ff45 line counter compare
|
|
wire lyc_match = (ly == lyc);
|
|
reg [7:0] lyc;
|
|
reg lyc_changed=0;
|
|
|
|
reg [7:0] bgp;
|
|
reg [7:0] obp0;
|
|
reg [7:0] obp1;
|
|
|
|
reg [7:0] wy;
|
|
reg [7:0] wy_r; // stable over line
|
|
|
|
reg [7:0] wx;
|
|
reg [7:0] wx_r; // stable over line
|
|
|
|
|
|
//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
|
|
|
|
// --------------------------------------------------------------------
|
|
// ----------------------------- DMA engine ---------------------------
|
|
// --------------------------------------------------------------------
|
|
|
|
assign dma_addr = { dma, dma_cnt[9:2] };
|
|
assign dma_rd = dma_active;
|
|
|
|
reg dma_active;
|
|
reg [7:0] dma;
|
|
reg [9:0] dma_cnt; // dma runs 4*160 clock cycles = 160us @ 4MHz
|
|
always @(posedge clk_reg) begin
|
|
if(reset)
|
|
dma_active <= 1'b0;
|
|
else 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 -------------------------------
|
|
// --------------------------------------------------------------------
|
|
|
|
always @(posedge clk_reg) begin
|
|
irq <= 1'b0;
|
|
vblank_irq <= 1'b0;
|
|
|
|
if(stat[6] && h_cnt == 0 && lyc_match)
|
|
irq <= 1'b1;
|
|
|
|
if(stat[6] && lyc_changed == 1 && h_cnt > 0 && lyc_match)
|
|
irq <= 1'b1;
|
|
|
|
// begin of oam phase
|
|
if(stat[5] && (h_cnt == 0))
|
|
irq <= 1'b1;
|
|
|
|
// begin of vblank
|
|
if((h_cnt == 455) && (v_cnt == 143)) begin
|
|
if (stat[4])
|
|
irq <= 1'b1;
|
|
vblank_irq <= 1'b1;
|
|
end
|
|
|
|
// begin of hblank
|
|
if(stat[3] && (h_cnt == OAM_LEN + 160 + hextra))
|
|
irq <= 1'b1;
|
|
end
|
|
|
|
// --------------------------------------------------------------------
|
|
// --------------------- CPU register interface -----------------------
|
|
// --------------------------------------------------------------------
|
|
integer ii=0;
|
|
always @(posedge clk_reg) begin
|
|
if(reset) begin
|
|
lcdc <= 8'h00; // screen must be off since dmg rom writes to vram
|
|
scy <= 8'h00;
|
|
scx <= 8'h00;
|
|
wy <= 8'h00;
|
|
wx <= 8'h00;
|
|
stat <= 8'h00;
|
|
bgp <= 8'hfc;
|
|
obp0 <= 8'hff;
|
|
obp1 <= 8'hff;
|
|
|
|
bgpi <= 6'h0;
|
|
obpi <= 6'h0;
|
|
bgpi_ai <= 1'b0;
|
|
obpi_ai <= 1'b0;
|
|
|
|
for (ii=0;ii<64;ii=ii+1)begin
|
|
bgpd[ii] <= 8'h00;
|
|
obpd[ii] <= 8'h00;
|
|
end
|
|
|
|
end else begin
|
|
lyc_changed<=0;
|
|
if(cpu_sel_reg && cpu_wr) begin
|
|
case(cpu_addr)
|
|
8'h40: lcdc <= cpu_di;
|
|
8'h41: stat <= cpu_di;
|
|
8'h42: scy <= cpu_di;
|
|
8'h43: scx <= cpu_di;
|
|
// a write to 4 is supposed to reset the v_cnt
|
|
8'h45: begin
|
|
lyc <= cpu_di;
|
|
lyc_changed<=1;
|
|
end
|
|
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;
|
|
|
|
//gbc
|
|
8'h68: begin
|
|
bgpi <= cpu_di[5:0];
|
|
bgpi_ai <= cpu_di[7];
|
|
end
|
|
8'h69: begin
|
|
if (mode != 3) begin
|
|
bgpd[bgpi] <= cpu_di;
|
|
if (bgpi_ai)
|
|
bgpi <= bgpi + 6'h1;
|
|
end
|
|
end
|
|
8'h6A: begin
|
|
obpi <= cpu_di[5:0];
|
|
obpi_ai <= cpu_di[7];
|
|
end
|
|
8'h6B: begin
|
|
if (mode != 3) begin
|
|
obpd[obpi] <= cpu_di;
|
|
if (obpi_ai)
|
|
obpi <= obpi + 6'h1;
|
|
end
|
|
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, 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'd0,bgpi}:
|
|
(cpu_addr == 8'h69 && mode != 3)?bgpd[bgpi]:
|
|
(cpu_addr == 8'h6a)?{obpi_ai,1'd0,obpi}:
|
|
(cpu_addr == 8'h6b && mode != 3)?obpd[obpi]:
|
|
8'hff:
|
|
8'hff;
|
|
|
|
// --------------------------------------------------------------------
|
|
// ----------------- second output stage: sprites ---------------------
|
|
// --------------------------------------------------------------------
|
|
|
|
assign lcd_data = stage2_data;
|
|
assign lcd_clkena = stage2_clkena;
|
|
|
|
reg [14:0] stage2_data;
|
|
reg stage2_clkena;
|
|
|
|
reg [1:0] stage2_buffer [159:0];
|
|
reg [3:0] stage2_bgp_buffer_pix [159:0]; //GBC keep record of palette used for tile and priority
|
|
|
|
reg [7:0] stage2_wptr;
|
|
reg [7:0] stage2_rptr;
|
|
|
|
|
|
wire [5:0] palette_index = isGBC_game?
|
|
(stage2_bgp_buffer_pix[stage2_rptr][2:0] << 3) + (stage2_buffer[stage2_rptr]<<1): //GBC game
|
|
//GB game in GBC mode
|
|
(stage2_buffer[stage2_rptr] == 2'b00)?{3'd0,bgp[1:0],1'b0}:
|
|
(stage2_buffer[stage2_rptr] == 2'b01)?{3'd0,bgp[3:2],1'b0}:
|
|
(stage2_buffer[stage2_rptr] == 2'b10)?{3'd0,bgp[5:4],1'b0}:
|
|
{3'd0,bgp[7:6],1'b0};
|
|
|
|
// apply bg palette
|
|
wire [14:0] stage2_bg_pix = (!lcdc_bg_ena && !window_ena)?15'h7FFF: // background off?
|
|
isGBC?{bgpd[palette_index+1][6:0],bgpd[palette_index]}: //gbc
|
|
(stage2_buffer[stage2_rptr] == 2'b00)?{13'd0,bgp[1:0]}:
|
|
(stage2_buffer[stage2_rptr] == 2'b01)?{13'd0,bgp[3:2]}:
|
|
(stage2_buffer[stage2_rptr] == 2'b10)?{13'd0,bgp[5:4]}:
|
|
{13'd0,bgp[7:6]};
|
|
|
|
|
|
|
|
// apply sprite palette
|
|
|
|
wire [5:0] sprite_palette_index = isGBC_game?
|
|
(sprite_pixel_cmap_gbc << 3) + (sprite_pixel_data<<1): //gbc game
|
|
//GB game in GBC mode
|
|
(sprite_pixel_data == 2'b00)? (sprite_pixel_cmap << 3) + (obp[1:0]<<1):
|
|
(sprite_pixel_data == 2'b01)? (sprite_pixel_cmap << 3) + (obp[3:2]<<1):
|
|
(sprite_pixel_data == 2'b10)? (sprite_pixel_cmap << 3) + (obp[5:4]<<1):
|
|
(sprite_pixel_cmap << 3) + (obp[7:6]<<1);
|
|
|
|
wire [7:0] obp = sprite_pixel_cmap?obp1:obp0;
|
|
wire [14:0] sprite_pix = isGBC?{obpd[sprite_palette_index+1][6:0],obpd[sprite_palette_index]}: //gbc
|
|
(sprite_pixel_data == 2'b00)?{13'd0,obp[1:0]}:
|
|
(sprite_pixel_data == 2'b01)?{13'd0,obp[3:2]}:
|
|
(sprite_pixel_data == 2'b10)?{13'd0,obp[5:4]}:
|
|
{13'd0,obp[7:6]};
|
|
|
|
// 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_game&&stage2_bgp_buffer_pix[stage2_rptr][3];
|
|
wire [1:0] bg_color = stage2_buffer[stage2_rptr];
|
|
reg sprite_pixel_visible;
|
|
|
|
always @(*) begin
|
|
sprite_pixel_visible = 1'b0;
|
|
if (sprite_pixel_active && lcdc_spr_ena) begin // pixel active and sprites enabled
|
|
|
|
if (isGBC&&isGBC_game) begin
|
|
if (sprite_pixel_data == 2'b00)
|
|
sprite_pixel_visible = 1'b0;
|
|
else if (bg_color == 2'b00)
|
|
sprite_pixel_visible = 1'b1;
|
|
else if (!lcdc_bg_ena)
|
|
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_color == 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
|
|
|
|
always @(posedge clk) begin
|
|
if(h_cnt == 455) begin
|
|
stage2_wptr <= 8'h00;
|
|
stage2_rptr <= 8'h00;
|
|
end
|
|
|
|
if(stage1_clkena) begin
|
|
stage2_buffer[stage2_wptr] <= stage1_data;
|
|
stage2_bgp_buffer_pix[stage2_wptr] <= {bg_tile_attr_old[7],bg_tile_attr_old[2:0]}; //GBC: buffer palette and Priority Bit
|
|
stage2_wptr <= stage2_wptr + 8'd1;
|
|
end
|
|
|
|
stage2_clkena = !vblank && stage2;
|
|
if(stage2) begin
|
|
// mix sprites and bg
|
|
if(sprite_pixel_visible) stage2_data <= sprite_pix;
|
|
else stage2_data <= stage2_bg_pix;
|
|
|
|
stage2_rptr <= stage2_rptr + 8'd1;
|
|
end
|
|
end
|
|
|
|
// --------------------------------------------------------------------
|
|
// --------------- first output stage: bg and window ------------------
|
|
// --------------------------------------------------------------------
|
|
|
|
reg window_ena;
|
|
|
|
// output shift registers for both video data bits
|
|
reg [7:0] tile_shift_0;
|
|
reg [7:0] tile_shift_1;
|
|
|
|
reg [7:0] tile_shift_0_x;
|
|
reg [7:0] tile_shift_1_x;
|
|
|
|
|
|
reg [7:0] bg_tile;
|
|
reg [7:0] bg_tile_data0;
|
|
reg [7:0] bg_tile_data1;
|
|
|
|
|
|
wire stage1_clkena = !vblank && hdvalid;
|
|
wire [1:0] stage1_data = (isGBC&&isGBC_game&&bg_tile_attr_old[5])?{ tile_shift_1_x[0], tile_shift_0_x[0] }:{ tile_shift_1[7], tile_shift_0[7] };
|
|
|
|
wire [7:0] vram_gbc_data = bg_tile_attr_new[3]?vram1_data:vram_data; //gbc check tile bank
|
|
reg [7:0] bg_tile_attr_old,bg_tile_attr_new; //GBC
|
|
|
|
// read data half a clock cycle after ram has been selected
|
|
always @(posedge clk) begin
|
|
|
|
// every memory access is two pixel cycles
|
|
if(h_cnt[0]) begin
|
|
if(bg_tile_map_rd) bg_tile <= vram_data;
|
|
|
|
if (isGBC&&isGBC_game) begin
|
|
if(bg_tile_map_rd) bg_tile_attr_new <= vram1_data; //get tile attr from vram bank1
|
|
if(bg_tile_data0_rd) bg_tile_data0 <= vram_gbc_data;
|
|
if(bg_tile_data1_rd) bg_tile_data1 <= vram_gbc_data;
|
|
end else begin
|
|
if(bg_tile_data0_rd) bg_tile_data0 <= vram_data;
|
|
if(bg_tile_data1_rd) bg_tile_data1 <= vram_data;
|
|
end
|
|
|
|
// sprite data is evaluated inside the sprite engine
|
|
end
|
|
|
|
// shift bg/window pixels out
|
|
if(bg_tile_obj_rd && h_cnt[0]) begin
|
|
bg_tile_attr_old <= bg_tile_attr_new;
|
|
tile_shift_0 <= bg_tile_data0;
|
|
tile_shift_1 <= bg_tile_data1;
|
|
tile_shift_0_x <= bg_tile_data0;
|
|
tile_shift_1_x <= bg_tile_data1;
|
|
end else begin
|
|
tile_shift_0 <= { tile_shift_0[6:0], 1'b0 };
|
|
tile_shift_1 <= { tile_shift_1[6:0], 1'b0 };
|
|
//GBC x-flip
|
|
tile_shift_0_x <= { 1'b0,tile_shift_0_x[7:1]};
|
|
tile_shift_1_x <= { 1'b0,tile_shift_1_x[7:1]};
|
|
end
|
|
end
|
|
|
|
assign vram_rd = lcdc_on && (bg_tile_map_rd || bg_tile_data0_rd ||
|
|
bg_tile_data1_rd || bg_tile_obj_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;
|
|
|
|
wire [2:0] tile_line_gbc = bg_tile_attr_new[6]? (3'b111 - tile_line): tile_line; //GBC: check if flipped y
|
|
|
|
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, isGBC&&isGBC_game?tile_line_gbc:tile_line, 1'b0}:
|
|
bg_tile_data1_rd?{bg_tile_a12, bg_tile, isGBC&&isGBC_game?tile_line_gbc:tile_line, 1'b1}:
|
|
{1'b0, sprite_addr, h_cnt[3]};
|
|
|
|
reg [9:0] bg_tile_map_addr;
|
|
|
|
wire vblank = (v_cnt >= 144);
|
|
|
|
// x scroll & 7 needs one more memory read per line
|
|
reg [1:0] hextra_tiles;
|
|
wire [7:0] hextra = { 3'b000, hextra_tiles, 3'b000 }+MODE3_OFFSET;
|
|
wire hblank = ((h_cnt < OAM_LEN) || (h_cnt >= 160+OAM_LEN+hextra));
|
|
wire oam = (h_cnt <= OAM_LEN); // 80 clocks oam
|
|
wire stage2 = ((h_cnt >= STAGE2) && (h_cnt < STAGE2+160)); // output out of stage2
|
|
|
|
// first valid pixels are delivered 8 clocks after end of hblank
|
|
// wire hdvalid = ((h_cnt >= OAM_LEN+8) && (h_cnt < 160+OAM_LEN+8));
|
|
wire hdvalid = de;
|
|
|
|
reg de;
|
|
reg [7:0] skip;
|
|
reg [7:0] pcnt;
|
|
|
|
localparam STATE_HBLANK = 0;
|
|
localparam STATE_OAM = 1;
|
|
localparam STATE_ACTIVE = 2;
|
|
|
|
always @(negedge clk) begin
|
|
if(h_cnt == 455) begin
|
|
// end of line
|
|
|
|
de <= 1'b0;
|
|
hextra_tiles <= 2'd0;
|
|
pcnt <= 8'd0;
|
|
skip <= 8'd0;
|
|
end else if(h_cnt == OAM_LEN) begin
|
|
// start of line
|
|
|
|
// skip entire oam time plus time until first data is delivered plus
|
|
// time to skip the pixels according to the horizontal scroll position
|
|
// (or the window start if line starts with window)
|
|
if(lcdc_win_ena && (v_cnt >= wy_r) && (wx_r < 8))
|
|
skip <= 8'd8 + (8'd7 - wx_r) - 8'd1;
|
|
else
|
|
skip <= 8'd8 + scx_r[2:0] - 8'd1;
|
|
|
|
// calculate how many extra tiles will have to be read in this line
|
|
if(lcdc_win_ena && (v_cnt >= wy_r) && (wx_r < 168)) begin
|
|
// window needs at least one extra cycle, two if bg scroll position or
|
|
// window are not 8 pixel aligned
|
|
if((wx_r[2:0] != 3'd7) || (scx_r[2:0] != 3'd0)) begin
|
|
if(wx_r[2:0] > ~scx_r[2:0])
|
|
hextra_tiles <= 2'd3;
|
|
else
|
|
hextra_tiles <= 2'd2;
|
|
end else
|
|
hextra_tiles <= 2'd1;
|
|
end else
|
|
if(scx_r[2:0] != 3'd0)
|
|
hextra_tiles <= 2'd1;
|
|
end else begin
|
|
if(win_start) begin
|
|
// if window starts skip until end of current cycle and skip
|
|
// pixels until new window data is ready
|
|
skip <= { 5'b00000 ,~h_cnt[2:0] } + 8'd8;
|
|
de <= 1'b0;
|
|
end
|
|
|
|
if(skip) skip <= skip - 8'd1;
|
|
|
|
// (re-)enable display at the end of the wait phase
|
|
if(skip == 1)
|
|
de <= 1'b1;
|
|
|
|
if(de) begin
|
|
if(pcnt != 160)
|
|
pcnt <= pcnt + 8'd1;
|
|
else
|
|
de <= 1'b0;
|
|
end
|
|
end
|
|
end
|
|
|
|
// cycle through the B01s states
|
|
wire bg_tile_map_rd = (!vblank) && (!hblank) && (h_cnt[2:1] == 2'b00);
|
|
wire bg_tile_data0_rd = (!vblank) && (!hblank) && (h_cnt[2:1] == 2'b01);
|
|
wire bg_tile_data1_rd = (!vblank) && (!hblank) && (h_cnt[2:1] == 2'b10);
|
|
wire bg_tile_obj_rd = (!vblank) && (!hblank) && (h_cnt[2:1] == 2'b11);
|
|
|
|
// Mode 00: h-blank
|
|
// Mode 01: v-blank
|
|
// Mode 10: oam
|
|
// Mode 11: oam and vram
|
|
assign mode =
|
|
(ly <= 144 && h_cnt<4)?2'b00: //AntonioND https://github.com/AntonioND/giibiiadvance/blob/master/docs/TCAGBD.pdf
|
|
!lcdc_on?2'b00:
|
|
vblank?2'b01:
|
|
oam?2'b10:
|
|
hblank?2'b00:
|
|
2'b11;
|
|
|
|
reg [8:0] h_cnt; // max 455
|
|
reg [7:0] v_cnt; // max 153
|
|
|
|
// line inside the background/window currently being drawn
|
|
wire [7:0] win_line = v_cnt - wy_r;
|
|
wire [7:0] bg_line = v_cnt + scy_r;
|
|
wire [2:0] tile_line = window_ena?win_line[2:0]:bg_line[2:0];
|
|
|
|
wire win_start = lcdc_win_ena && (v_cnt >= wy_r) && de && (wx_r >= 7) && (pcnt == wx_r-8);
|
|
|
|
|
|
|
|
// each memory access takes two cycles
|
|
always @(negedge clk) begin
|
|
|
|
if (!lcdc_on) begin // don't increase counters if lcdoff
|
|
//reset counters
|
|
h_cnt <= 9'd6;
|
|
v_cnt <= 8'd0;
|
|
|
|
end else begin
|
|
|
|
if(h_cnt != 455) begin
|
|
h_cnt <= h_cnt + 9'd1;
|
|
|
|
// make sure sginals don't change during the line
|
|
// latch at latest possible moment (one clock before display starts)
|
|
if(h_cnt == OAM_LEN-2) begin
|
|
scx_r <= scx;
|
|
wx_r <= wx;
|
|
scy_r <= scy;
|
|
wy_r <= wy;
|
|
end
|
|
|
|
// increment address at the end of each 8-pixel-cycle. But don't
|
|
// increment while waiting for current cycle to end due to window start
|
|
if(!hblank && h_cnt[2:0] == 3'b111 && (skip <= 8))
|
|
bg_tile_map_addr[4:0] <= bg_tile_map_addr[4:0] + 1'd1;
|
|
|
|
// begin of line
|
|
if(h_cnt == OAM_LEN-1) begin
|
|
// set tile map address for this line, assume there is no window
|
|
bg_tile_map_addr[9:5] <= bg_line[7:3];
|
|
bg_tile_map_addr[4:0] <= scx_r[7:3];
|
|
|
|
// special case wx < 8: line starts with window, no background
|
|
// visible at all
|
|
if(lcdc_win_ena && (v_cnt >= wy_r) && (wx_r < 8)) begin
|
|
window_ena <= 1'b1;
|
|
bg_tile_map_addr[9:5] <= win_line[7:3];
|
|
bg_tile_map_addr[4:0] <= 5'd0; // window always start with its very left
|
|
end
|
|
end
|
|
|
|
// check if the window starts here
|
|
if(win_start) begin
|
|
window_ena <= 1'b1;
|
|
bg_tile_map_addr[9:5] <= win_line[7:3];
|
|
bg_tile_map_addr[4:0] <= 5'd0; // window always start with its very left
|
|
end
|
|
end else begin
|
|
window_ena <= 1'b0; // next line starts with background
|
|
|
|
// end of line reached
|
|
h_cnt <= 9'd0;
|
|
|
|
if(v_cnt != 153)
|
|
v_cnt <= v_cnt + 8'd1;
|
|
else begin
|
|
// start of new image
|
|
v_cnt <= 8'd0;
|
|
|
|
// make sure sginals don't change during the image
|
|
// wy_r <= wy;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
endmodule
|