From e5ad47870bf761cd7d5f3db95edd9c8b40e42355 Mon Sep 17 00:00:00 2001 From: Bruno Duarte Gouveia Date: Wed, 12 Dec 2018 20:46:25 +0000 Subject: [PATCH] GBC: start hdma and clock switching implementation --- gb.v | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- video.v | 3 +- 2 files changed, 135 insertions(+), 7 deletions(-) diff --git a/gb.v b/gb.v index 929664f..7173f99 100644 --- a/gb.v +++ b/gb.v @@ -21,8 +21,9 @@ module gb ( input reset, - input clk, - + input clk, + input clk2x, + input fast_boot, input [7:0] joystick, @@ -50,7 +51,7 @@ wire [15:0] cpu_addr; wire [7:0] cpu_do; wire sel_timer = (cpu_addr[15:4] == 12'hff0) && (cpu_addr[3:2] == 2'b01); -wire sel_video_reg = cpu_addr[15:4] == 12'hff4; +wire sel_video_reg = (cpu_addr[15:4] == 12'hff4); //video and oam dma wire sel_video_oam = cpu_addr[15:8] == 8'hfe; wire sel_joy = cpu_addr == 16'hff00; // joystick controller wire sel_sb = cpu_addr == 16'hff01; // serial SB - Serial transfer data @@ -66,12 +67,21 @@ wire sel_zpram = (cpu_addr[15:7] == 9'b111111111) && // 127 bytes zero pageram a wire sel_audio = (cpu_addr[15:8] == 8'hff) && // audio reg ff10 - ff3f ((cpu_addr[7:5] == 3'b001) || (cpu_addr[7:4] == 4'b0001)); +wire sel_hdma = (cpu_addr[15:4]==12'hff5) && + ((cpu_addr[3:0]!=4'd0)&&(cpu_addr[3:0]< 4'd6)); //HDMA FF51-FF55 +wire sel_key1 = cpu_addr == 16'hff4d; // KEY1 - CGB Mode Only - Prepare Speed Switch + //DMA can select from $0000 to $F100 wire dma_sel_rom = !dma_addr[15]; // lower 32k are rom wire dma_sel_cram = dma_addr[15:13] == 3'b101; // 8k cart ram at $a000 wire dma_sel_vram = dma_addr[15:13] == 3'b100; // 8k video ram at $8000 wire dma_sel_iram = (dma_addr[15:14] == 2'b11) && (dma_addr[15:8] != 8'hff); // 8k internal ram at $c000 +//HDMA can select from $0000 to $7ff0 or A000-DFF0 +wire hdma_sel_rom = !hdma_addr[15]; // lower 32k are rom +wire hdma_sel_cram = hdma_addr[15:13] == 3'b101; // 8k cart ram at $a000 +wire hdma_sel_iram = hdma_addr[15:13] == 3'b110; // 8k internal ram at $c000-$dff0 + // the boot roms sees a special $42 flag in $ff50 if it's supposed to to a fast boot wire sel_fast = fast_boot && cpu_addr == 16'hff50 && boot_rom_enabled; @@ -96,6 +106,8 @@ wire [7:0] cpu_di = sel_iram?iram_do: // internal ram sel_ie?{3'b000, ie_r}: // interrupt enable register sel_if?{3'b111, if_r}: // interrupt flag register + sel_hdma&&isGBC?{hdma_do}: //hdma GBC + sel_key1&&isGBC?{cpu_speed,6'd0,prepare_switch}: //key1 cpu speed register(GBC) 8'hff; wire cpu_wr_n; @@ -103,11 +115,13 @@ wire cpu_rd_n; wire cpu_iorq_n; wire cpu_m1_n; wire cpu_mreq_n; + +wire cpu_clken = isGBC ? !hdma_rd:1'b1; //when hdma is enabled stop CPU (GBC) GBse cpu ( .RESET_n ( !reset ), - .CLK_n ( clk ), - .CLKEN ( 1'b1 ), + .CLK_n ( clk_cpu ), + .CLKEN ( cpu_clken ), .WAIT_n ( 1'b1 ), .INT_n ( irq_n ), .NMI_n ( 1'b1 ), @@ -125,6 +139,22 @@ GBse cpu ( .DO ( cpu_do ) ); +// -------------------------------------------------------------------- +// --------------------- Speed Toggle KEY1 (GBC)----------------------- +// -------------------------------------------------------------------- +wire clk_cpu = cpu_speed?clk2x:clk; +reg cpu_speed; // - 0 Normal mode (4MHz) - 1 Double Speed Mode (8MHz) +reg prepare_switch; // set to 1 to toggle speed + +always @(posedge clk2x) begin + if(reset) begin + cpu_speed <= 1'b0; + end else if (sel_key1 && !cpu_wr_n && isGBC)begin + prepare_switch <= cpu_do[0]; + //TODO: wait for stop to toggle speed + end +end + // -------------------------------------------------------------------- // ------------------------------ audio ------------------------------- // -------------------------------------------------------------------- @@ -308,12 +338,15 @@ wire video_irq; wire [7:0] video_do; wire [12:0] video_addr; wire [15:0] dma_addr; -wire video_rd, dma_rd; +wire [15:0] hdma_addr; +wire video_rd, dma_rd, hdma_rd; wire [7:0] dma_data = dma_sel_iram?iram_do:dma_sel_vram?vram_do:cart_do; +wire [7:0] hdma_data = hdma_sel_iram?iram_do:cart_do; video video ( .reset ( reset ), .clk ( clk ), + .clk_dma ( clk_cpu ), //can be 2x in cgb double speed mode .irq ( video_irq ), @@ -336,6 +369,7 @@ video video ( .dma_rd ( dma_rd ), .dma_addr ( dma_addr ), .dma_data ( dma_data ) + ); // total 8k/16k (CGB) vram from $8000 to $9fff @@ -362,6 +396,99 @@ always @(posedge clk) begin vram_bank <= cpu_do[0]; end +// -------------------------------------------------------------------- +// -------------------------- HDMA engine(GBC) ------------------------ +// -------------------------------------------------------------------- + +//ff51-ff55 HDMA1-5 (GBC) +reg [7:0] hdma_source_h; // ff51 +reg [3:0] hdma_source_l; // ff52 only top 4 bits used +reg [4:0] hdma_target_h; // ff53 only lowest 5 bits used +reg [3:0] hdma_target_l; // ff54 only top 4 bits used +reg hdma_mode; // ff55 bit 7 - 1=General Purpose DMA 0=H-Blank DMA +reg hdma_enabled; // ff55 !bit 7 when read +reg [6:0] hdma_length; // ff55 bit 6:0 - dma transfer length (hdma_length+1)*16 bytes + +assign hdma_rd = hdma_active; +assign hdma_addr = { hdma_source_h,hdma_source_l,4'd0} + hdma_cnt[12:1]; + +reg hdma_active; + +// it takes about 8us to transfer a block of 16 bytes. -> 500ns per byte -> 2Mhz +// 32 cycles in Normal Speed Mode, and 64 'fast' cycles in Double Speed Mode +reg [12:0] hdma_cnt; +reg [4:0] hdma_16byte_cnt; //16bytes*2 + + +always @(posedge clk) begin + if(reset) + hdma_active <= 1'b0; + else begin + // writing the hdma register engages the dma engine + if(isGBC && !cpu_wr_n && (cpu_addr == 16'hff55)) begin + hdma_enabled <= 1'b1; + hdma_mode <= cpu_do[7]; + hdma_length <= cpu_do[6:0]; + hdma_cnt <= 12'd0; + hdma_16byte_cnt <= 5'h1f; + //TODO: disable HDMA in mode 1 + + end else if(hdma_mode==0) begin //mode 0 GDMA do the transfer in one go + + if(hdma_cnt != (((hdma_length+1)*16)-1)*2) begin + hdma_active <= 1'b1; + hdma_cnt <= hdma_cnt + 1'd1; + hdma_16byte_cnt <= hdma_16byte_cnt - 1'd1; + if (!hdma_16byte_cnt) + hdma_length <= hdma_length - 1'd1; + + end else begin + hdma_active <= 1'b0; + hdma_enabled <= 1'b0; + end + + end else begin //mode 1 HDMA transfer 1 block (16bytes) in each H-Blank only + + if(hdma_cnt != (((hdma_length+1)*16)-1)*2 && (lcd_mode==0) && hdma_enabled) begin //also check if hdma is enabled and in h-blank + //TODO: have to rethink this, maybe state machine + hdma_active <= 1'b0; + hdma_enabled <= 1'b0; + end + + end + + end +end + + +always @(posedge clk) begin + if(reset) begin + hdma_source_h <= 8'hFF; + hdma_source_l <= 4'hF; + hdma_target_h <= 5'h1F; + hdma_target_l <= 4'hF; + end else if(sel_hdma && !cpu_wr_n && isGBC) begin + + case (cpu_addr[3:0]) + 4'd1: hdma_source_h <= cpu_do; + 4'd2: hdma_source_l <= cpu_do[7:4]; + 4'd3: hdma_target_h <= cpu_do[4:0]; + 4'd4: hdma_target_l <= cpu_do[7:4]; + endcase + end +end + + +wire [7:0] hdma_do = isGBC&&sel_hdma? + (cpu_addr[3:0]==4'd1)?hdma_source_h: + (cpu_addr[3:0]==4'd2)?{hdma_source_l,4'd0}: + (cpu_addr[3:0]==4'd3)?{3'd0,hdma_target_h}: + (cpu_addr[3:0]==4'd4)?{hdma_target_l,,4'd0}: + (cpu_addr[3:0]==4'd5 && hdma_enabled)?{1'b0,hdma_length}: + 8'hFF: + 8'hFF; + + // -------------------------------------------------------------------- // -------------------------- zero page ram --------------------------- // -------------------------------------------------------------------- diff --git a/video.v b/video.v index 1bc0341..dbc56fb 100644 --- a/video.v +++ b/video.v @@ -22,6 +22,7 @@ module video ( input reset, input clk, // 4 Mhz cpu clock + input clk_dma, // cpu register adn oam interface input cpu_sel_oam, @@ -145,7 +146,7 @@ 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) begin +always @(posedge clk_dma) begin if(reset) dma_active <= 1'b0; else begin