1
0
mirror of https://github.com/mist-devel/mist-board.git synced 2026-02-08 00:41:19 +00:00
Files
mist-devel.mist-board/cores/gameboy/gb.v

580 lines
19 KiB
Verilog

//
// gb.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 gb (
input reset,
input clk,
input clk2x,
input fast_boot,
input [7:0] joystick,
input isGBC,
input isGBC_game,
// cartridge interface
// can adress up to 1MB ROM
output [15:0] cart_addr,
output cart_rd,
output cart_wr,
input [7:0] cart_do,
output [7:0] cart_di,
//gbc bios interface
output [11:0] gbc_bios_addr,
input [7:0] gbc_bios_do,
// audio
output [15:0] audio_l,
output [15:0] audio_r,
// lcd interface
output lcd_clkena,
output [14:0] lcd_data,
output [1:0] lcd_mode,
output lcd_on,
output speed //GBC
);
// include cpu
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) || (isGBC && (cpu_addr[15:2] == 14'h3fda)); //video and oam dma (+ ff68-ff6B when gbc)
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
wire sel_sc = cpu_addr == 16'hff02; // SC - Serial Transfer Control (R/W)
wire sel_rom = !cpu_addr[15]; // lower 32k are rom
wire sel_cram = cpu_addr[15:13] == 3'b101; // 8k cart ram at $a000
wire sel_vram = cpu_addr[15:13] == 3'b100; // 8k video ram at $8000
wire sel_ie = cpu_addr == 16'hffff; // interupt enable
wire sel_if = cpu_addr == 16'hff0f; // interupt flag
wire sel_iram = (cpu_addr[15:14] == 2'b11) && (cpu_addr[15:8] != 8'hff); // 8k internal ram at $c000
wire sel_zpram = (cpu_addr[15:7] == 9'b111111111) && // 127 bytes zero pageram at $ff80
(cpu_addr != 16'hffff);
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));
//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
//CGB
wire sel_vram_bank = (cpu_addr==16'hff4f);
wire sel_iram_bank = (cpu_addr==16'hff70);
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
wire sel_rp = cpu_addr == 16'hff56; //FF56 - RP - CGB Mode Only - Infrared Communications Port
//HDMA can select from $0000 to $7ff0 or A000-DFF0
wire hdma_sel_rom = !hdma_source_addr[15]; // lower 32k are rom
wire hdma_sel_cram = hdma_source_addr[15:13] == 3'b101; // 8k cart ram at $a000
wire hdma_sel_iram = hdma_source_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;
wire [7:0] sc_r = {sc_start,6'h3F,sc_shiftclock};
// http://gameboy.mongenel.com/dmg/asmmemmap.html
wire [7:0] cpu_di =
irq_ack?irq_vec:
sel_fast?8'h42: // fast boot flag
sel_if?{3'b111, if_r}: // interrupt flag register
isGBC&&sel_rp?8'h02:
isGBC&&sel_iram_bank?{5'h1f,iram_bank}:
isGBC&&sel_vram_bank?{7'h7f,vram_bank}:
isGBC&&sel_hdma?{hdma_do}: //hdma GBC
isGBC&&sel_key1?{cpu_speed,6'h3f,prepare_switch}: //key1 cpu speed register(GBC)
sel_joy?joy_do: // joystick register
sel_sb?8'hFF: // serial transfer data register
sel_sc?sc_r: // serial transfer control register
sel_timer?timer_do: // timer registers
sel_video_reg?video_do: // video registers
(sel_video_oam&&!(lcd_mode==3 || lcd_mode==2))?video_do: // video object attribute memory
sel_audio?audio_do: // audio registers
sel_rom?rom_do: // boot rom + cartridge rom
sel_cram?rom_do: // cartridge ram
(sel_vram&&lcd_mode!=3)?(isGBC&&vram_bank)?vram1_do:vram_do: // vram (GBC bank 0+1)
sel_zpram?zpram_do: // zero page ram
sel_iram?iram_do: // internal ram
sel_ie?{3'b000, ie_r}: // interrupt enable register
8'hff;
wire cpu_wr_n;
wire cpu_rd_n;
wire cpu_iorq_n;
wire cpu_m1_n;
wire cpu_mreq_n;
wire cpu_clken = isGBC ? !hdma_active:1'b1; //when hdma is enabled stop CPU (GBC)
wire cpu_stop;
GBse cpu (
.RESET_n ( !reset ),
.CLK_n ( clk_cpu ),
.CLKEN ( cpu_clken ),
.WAIT_n ( 1'b1 ),
.INT_n ( irq_n ),
.NMI_n ( 1'b1 ),
.BUSRQ_n ( 1'b1 ),
.M1_n ( cpu_m1_n ),
.MREQ_n ( cpu_mreq_n ),
.IORQ_n ( cpu_iorq_n ),
.RD_n ( cpu_rd_n ),
.WR_n ( cpu_wr_n ),
.RFSH_n ( ),
.HALT_n ( ),
.BUSAK_n ( ),
.A ( cpu_addr ),
.DI ( cpu_di ),
.DO ( cpu_do ),
.STOP ( cpu_stop )
);
// --------------------------------------------------------------------
// --------------------- Speed Toggle KEY1 (GBC)-----------------------
// --------------------------------------------------------------------
wire clk_cpu = cpu_speed?clk2x:clk;
//wire clk_cpu = clk;
reg cpu_speed; // - 0 Normal mode (4MHz) - 1 Double Speed Mode (8MHz)
reg prepare_switch; // set to 1 to toggle speed
assign speed = cpu_speed;
always @(posedge clk2x) begin
if(reset) begin
cpu_speed <= 1'b0;
prepare_switch <= 1'b0;
end else if (sel_key1 && !cpu_wr_n && isGBC)begin
prepare_switch <= cpu_do[0];
end
if (isGBC && prepare_switch && cpu_stop) begin
cpu_speed <= !cpu_speed;
prepare_switch <= 1'b0;
end
end
// --------------------------------------------------------------------
// ------------------------------ audio -------------------------------
// --------------------------------------------------------------------
wire audio_rd = !cpu_rd_n && sel_audio;
wire audio_wr = !cpu_wr_n && sel_audio;
wire [7:0] audio_do;
gbc_snd audio (
.clk ( clk2x ),
.reset ( reset ),
.s1_read ( audio_rd ),
.s1_write ( audio_wr ),
.s1_addr ( cpu_addr[5:0] ),
.s1_readdata ( audio_do ),
.s1_writedata ( cpu_do ),
.snd_left ( audio_l ),
.snd_right ( audio_r )
);
// --------------------------------------------------------------------
// -----------------------serial port(dummy)---------------------------
// --------------------------------------------------------------------
reg [3:0] serial_counter;
reg sc_start,sc_shiftclock;
reg serial_irq;
reg [8:0] serial_clk_div; //8192Hz
always @(posedge clk_cpu) begin
serial_irq <= 1'b0;
if(reset) begin
sc_start <= 1'b0;
sc_shiftclock <= 1'b0;
end else if (sel_sc && !cpu_wr_n) begin //cpu write
sc_start <= cpu_do[7];
sc_shiftclock <= cpu_do[0];
if (cpu_do[7]) begin //enable transfer
serial_clk_div <= 9'h1FF;
serial_counter <= 4'd8;
end
end else if (sc_start && sc_shiftclock) begin // serial transfer and serial clock enabled
serial_clk_div <= serial_clk_div - 9'd1;
if (serial_clk_div == 9'd0 && serial_counter)
serial_counter <= serial_counter - 4'd1;
if (!serial_counter) begin
serial_irq <= 1'b1; //trigger interrupt
sc_start <= 1'b0; //reset transfer state
serial_clk_div <= 9'h1FF;
serial_counter <= 4'd8;
end
end
end
// --------------------------------------------------------------------
// ------------------------------ inputs ------------------------------
// --------------------------------------------------------------------
wire [3:0] joy_p4 = ~{ joystick[2], joystick[3], joystick[1], joystick[0] } | {4{p54[0]}};
wire [3:0] joy_p5 = ~{ joystick[7], joystick[6], joystick[5], joystick[4] } | {4{p54[1]}};
reg [1:0] p54;
always @(posedge clk_cpu) begin
if(reset) p54 <= 2'b00;
else if(sel_joy && !cpu_wr_n) p54 <= cpu_do[5:4];
end
wire [7:0] joy_do = { 2'b11, p54, joy_p4 & joy_p5 };
// --------------------------------------------------------------------
// ---------------------------- interrupts ----------------------------
// --------------------------------------------------------------------
// interrupt flags are set when the event happens or when the cpu writes
// the register to 1. The "highest" one active is cleared when the cpu
// runs an interrupt ack cycle or when it writes a 0 to the register
wire irq_ack = !cpu_iorq_n && !cpu_m1_n;
// irq vector
wire [7:0] irq_vec =
if_r[0]&&ie_r[0]?8'h40: // vsync
if_r[1]&&ie_r[1]?8'h48: // lcdc
if_r[2]&&ie_r[2]?8'h50: // timer
if_r[3]&&ie_r[3]?8'h58: // serial
if_r[4]&&ie_r[4]?8'h60: // input
8'h55;
//wire vs = (lcd_mode == 2'b01);
//reg vsD, vsD2;
reg [7:0] inputD, inputD2;
// irq is low when an enable irq is active
wire irq_n = !(ie_r & if_r);
reg [4:0] if_r;
reg [4:0] ie_r; // writing $ffff sets the irq enable mask
always @(negedge clk_cpu) begin //negedge to trigger interrupt earlier
reg old_ack = 0;
if(reset) begin
ie_r <= 5'h00;
if_r <= 5'h00;
end
// rising edge on vs
// vsD <= vs;
// vsD2 <= vsD;
if(vblank_irq) if_r[0] <= 1'b1;
// video irq already is a 1 clock event
if(video_irq) if_r[1] <= 1'b1;
// timer_irq already is a 1 clock event
if(timer_irq) if_r[2] <= 1'b1;
// serial irq already is a 1 clock event
if(serial_irq) if_r[3] <= 1'b1;
// falling edge on any input line P10..P13
inputD <= {joy_p4, joy_p5};
inputD2 <= inputD;
if(~inputD & inputD2) if_r[4] <= 1'b1;
// cpu acknowledges irq. this clears the active irq with hte
// highest priority
old_ack <= irq_ack;
if(old_ack & ~irq_ack) begin
if(if_r[0] && ie_r[0]) if_r[0] <= 1'b0;
else if(if_r[1] && ie_r[1]) if_r[1] <= 1'b0;
else if(if_r[2] && ie_r[2]) if_r[2] <= 1'b0;
else if(if_r[3] && ie_r[3]) if_r[3] <= 1'b0;
else if(if_r[4] && ie_r[4]) if_r[4] <= 1'b0;
end
// cpu writes interrupt enable register
if(sel_ie && !cpu_wr_n)
ie_r <= cpu_do[4:0];
// cpu writes interrupt flag register
if(sel_if && !cpu_wr_n)
if_r <= cpu_do[4:0];
end
// --------------------------------------------------------------------
// ------------------------------ timer -------------------------------
// --------------------------------------------------------------------
wire timer_irq;
wire [7:0] timer_do;
timer timer (
.reset ( reset ),
.clk ( clk_cpu ), //2x in fast mode
.irq ( timer_irq ),
.cpu_sel ( sel_timer ),
.cpu_addr ( cpu_addr[1:0] ),
.cpu_wr ( !cpu_wr_n ),
.cpu_di ( cpu_do ),
.cpu_do ( timer_do )
);
// --------------------------------------------------------------------
// ------------------------------ video -------------------------------
// --------------------------------------------------------------------
// cpu tries to read or write the lcd controller registers
wire video_irq,vblank_irq;
wire [7:0] video_do;
wire [12:0] video_addr;
wire [15:0] dma_addr;
wire video_rd, dma_rd;
wire [7:0] dma_data = dma_sel_iram?iram_do:dma_sel_vram?(isGBC&&vram_bank)?vram1_do:vram_do:cart_do;
video video (
.reset ( reset ),
.clk ( clk ),
.clk_reg ( clk_cpu ), //can be 2x in cgb double speed mode
.isGBC ( isGBC ),
.isGBC_game ( isGBC_game|boot_rom_enabled ), //enable GBC mode during bootstrap rom
.irq ( video_irq ),
.vblank_irq ( vblank_irq ),
.cpu_sel_reg ( sel_video_reg ),
.cpu_sel_oam ( sel_video_oam ),
.cpu_addr ( cpu_addr[7:0] ),
.cpu_wr ( !cpu_wr_n ),
.cpu_di ( cpu_do ),
.cpu_do ( video_do ),
.lcd_on ( lcd_on ),
.lcd_clkena ( lcd_clkena ),
.lcd_data ( lcd_data ),
.mode ( lcd_mode ),
.vram_rd ( video_rd ),
.vram_addr ( video_addr ),
.vram_data ( vram_do ),
// vram connection bank1 (GBC)
.vram1_data ( vram1_do ),
.dma_rd ( dma_rd ),
.dma_addr ( dma_addr ),
.dma_data ( dma_data )
);
// total 8k/16k (CGB) vram from $8000 to $9fff
wire cpu_wr_vram = sel_vram && !cpu_wr_n && lcd_mode!=3;
reg vram_bank; //0-1 FF4F - VBK
wire [7:0] vram_do,vram1_do;
wire [7:0] vram_di = (hdma_rd&&isGBC)?
hdma_sel_iram?iram_do:
is_hdma_cart_addr?cart_do:
8'hFF:
cpu_do;
wire vram_wren = video_rd?1'b0:!vram_bank&&((hdma_rd&&isGBC)||cpu_wr_vram);
wire vram1_wren = video_rd?1'b0:vram_bank&&((hdma_rd&&isGBC)||cpu_wr_vram);
wire [12:0] vram_addr = video_rd?video_addr:(hdma_rd&&isGBC)?hdma_target_addr[12:0]:(dma_rd&&dma_sel_vram)?dma_addr[12:0]:cpu_addr[12:0];
spram #(13) vram0 (
.clock ( clk_cpu ),
.address ( vram_addr ),
.wren ( vram_wren ),
.data ( vram_di ),
.q ( vram_do )
);
//separate 8k for vbank1 for gbc because of BG reads
spram #(13) vram1 (
.clock ( clk_cpu ),
.address ( vram_addr ),
.wren ( vram1_wren ),
.data ( vram_di ),
.q ( vram1_do )
);
//GBC VRAM banking
always @(posedge clk_cpu) begin
if(reset)
vram_bank <= 1'd0;
else if((cpu_addr == 16'hff4f) && !cpu_wr_n && isGBC)
vram_bank <= cpu_do[0];
end
// --------------------------------------------------------------------
// -------------------------- HDMA engine(GBC) ------------------------
// --------------------------------------------------------------------
wire [15:0] hdma_source_addr;
wire [15:0] hdma_target_addr;
wire [7:0] hdma_do;
wire hdma_rd;
wire hdma_active;
hdma hdma(
.reset ( reset ),
.clk ( clk2x ),
.speed ( cpu_speed ),
// cpu register interface
.sel_reg ( sel_hdma ),
.addr ( cpu_addr[3:0] ),
.wr ( !cpu_wr_n ),
.dout ( hdma_do ),
.din ( cpu_do ),
.lcd_mode ( lcd_mode ),
// dma connection
.hdma_rd ( hdma_rd ),
.hdma_active ( hdma_active ),
.hdma_source_addr ( hdma_source_addr ),
.hdma_target_addr ( hdma_target_addr )
);
// --------------------------------------------------------------------
// -------------------------- zero page ram ---------------------------
// --------------------------------------------------------------------
// 127 bytes internal zero page ram from $ff80 to $fffe
wire cpu_wr_zpram = sel_zpram && !cpu_wr_n;
wire [7:0] zpram_do;
spram #(7) zpram (
.clock ( clk_cpu ),
.address ( cpu_addr[6:0] ),
.wren ( cpu_wr_zpram ),
.data ( cpu_do ),
.q ( zpram_do )
);
// --------------------------------------------------------------------
// ------------------------ 8k/32k(GBC) internal ram -----------------
// --------------------------------------------------------------------
reg [2:0] iram_bank; //1-7 FF70 - SVBK
wire iram_wren = (dma_rd&&dma_sel_iram)||(isGBC&&hdma_rd&&hdma_sel_iram)?1'b0:cpu_wr_iram;
wire [14:0] iram_addr = (isGBC&&hdma_rd&&hdma_sel_iram)? //hdma transfer?
(hdma_source_addr[12])?{iram_bank,hdma_source_addr[11:0]}: //bank 1-7 D000-DFFF
{3'd0,hdma_source_addr[11:0]}: //bank 0
(dma_rd&&dma_sel_iram)? //dma transfer?
(dma_addr[12])?{iram_bank,dma_addr[11:0]}: //bank 1-7
{3'd0,dma_addr[11:0]}: //bank 0
//cpu
(cpu_addr[12])?{iram_bank,cpu_addr[11:0]}: //bank 1-7
{3'd0,cpu_addr[11:0]}; //bank 0
wire cpu_wr_iram = sel_iram && !cpu_wr_n;
wire [7:0] iram_do;
spram #(15) iram (
.clock ( clk_cpu ),
.address ( iram_addr ),
.wren ( iram_wren ),
.data ( cpu_do ),
.q ( iram_do )
);
//GBC WRAM banking
always @(posedge clk_cpu) begin
if(reset)
iram_bank <= 3'd1;
else if((cpu_addr == 16'hff70) && !cpu_wr_n && isGBC) begin
if (cpu_do[2:0]==3'd0) // 0 -> 1;
iram_bank <= 3'd1;
else
iram_bank <= cpu_do[2:0];
end
end
// --------------------------------------------------------------------
// ------------------------ internal boot rom -------------------------
// --------------------------------------------------------------------
// writing 01(GB) or 11(GBC) to $ff50 disables the internal rom
reg boot_rom_enabled;
always @(posedge clk) begin
if(reset)
boot_rom_enabled <= 1'b1;
else if((cpu_addr == 16'hff50) && !cpu_wr_n)
if ((isGBC && cpu_do[7:0]==8'h11) || (!isGBC && cpu_do[0]))
boot_rom_enabled <= 1'b0;
end
// combine boot rom data with cartridge data
wire [7:0] rom_do = isGBC? //GameBoy Color?
(((cpu_addr[14:8] == 7'h00) || (hdma_rd&& hdma_source_addr[14:8] == 7'h00))&& boot_rom_enabled)?gbc_bios_do: //0-FF bootrom 1st part
((cpu_addr[14:9] == 6'h00) || (hdma_rd&& hdma_source_addr[14:9] == 6'h00))? cart_do: //100-1FF Cart Header
(((cpu_addr[14:12] == 3'h0) || (hdma_rd&& hdma_source_addr[14:12] == 3'h0)) && boot_rom_enabled)?gbc_bios_do: //200-8FF bootrom 2nd part
cart_do: //rest of card
((cpu_addr[14:8] == 7'h00) && boot_rom_enabled)?boot_rom_do:cart_do; //GB
wire is_dma_cart_addr = (dma_sel_rom || dma_sel_cram); //rom or external ram
wire is_hdma_cart_addr = (hdma_sel_rom || hdma_sel_cram); //rom or external ram
assign cart_di = cpu_do;
assign cart_addr = (isGBC&&hdma_rd&&is_hdma_cart_addr)?hdma_source_addr:(dma_rd&&is_dma_cart_addr)?dma_addr:cpu_addr;
assign cart_rd = (isGBC&&hdma_rd&&is_hdma_cart_addr) || (dma_rd&&is_dma_cart_addr) || ((sel_rom || sel_cram) && !cpu_rd_n);
assign cart_wr = (sel_rom || sel_cram) && !cpu_wr_n && !hdma_rd;
assign gbc_bios_addr = hdma_rd?hdma_source_addr[11:0]:cpu_addr[11:0];
wire [7:0] boot_rom_do;
boot_rom boot_rom (
.address ( cpu_addr[7:0] ),
.clock ( clk ),
.q ( boot_rom_do )
);
endmodule