blob: 819131da5b825d7ec9ee0fd4768c15aa679bbff0 [file] [log] [blame]
`timescale 1ns / 1ps
`default_nettype wire
////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer: Wenting Zhang
//
// Create Date: 17:30:26 02/08/2018
// Module Name: boy
// Project Name: VerilogBoy
// Description:
// VerilogBoy portable top level file. This is the file connect the CPU and
// all the peripherals in the LR35902 together.
// Dependencies:
// cpu
// Additional Comments:
// Hardware specific code should be implemented outside of this file
// So normally in an implementation, this will not be the top level.
////////////////////////////////////////////////////////////////////////////////
module boy(
input wire rst, // Async Reset Input
input wire clk, // 4.19MHz Clock Input
output wire phi, // 1.05MHz Reference Clock Output
output wire [1:0] ct, // 0-3T cycle number
// CPU/ DMA bus interface
output wire [15:0] a, // Address Bus
output wire [7:0] dout, // Data Bus
input wire [7:0] din,
output wire wr, // Write Enable
output wire rd, // Read Enable
// Keyboard input
input wire [7:0] key,
// LCD output
output wire hs, // Horizontal Sync Output
output wire vs, // Vertical Sync Output
output wire cpl, // Pixel Data Latch
output wire [1:0] pixel, // Pixel Data
output wire valid,
// Sound output
output reg [15:0] left,
output reg [15:0] right,
// PPU bus interface
output wire [12:0] ppu_a,
output wire ppu_wr,
output wire ppu_rd,
output wire [7:0] ppu_dout,
input wire [7:0] ppu_din,
// Debug interface
output wire done,
output wire fault
);
// CPU
wire cpu_rd; // CPU Read Enable
wire cpu_wr; // CPU Write Enable
reg [7:0] cpu_din; // CPU Data Bus, to CPU
wire [7:0] cpu_dout; // CPU Data Bus, from CPU
wire [15:0] cpu_a; // CPU Address Bus
wire [15:0] cpu_a_early; // CPU Address Unbuffered
wire [4:0] cpu_int_en; // CPU Interrupt Enable input
wire [4:0] cpu_int_flags_in; // CPU Interrupt Flags input
wire [4:0] cpu_int_flags_out; // CPU Interrupt Flags output
cpu cpu(
.clk(clk),
.rst(rst),
.phi(phi),
.ct(ct),
.a(cpu_a),
.a_early(cpu_a_early),
.dout(cpu_dout),
.din(cpu_din),
.rd(cpu_rd),
.wr(cpu_wr),
.int_en(cpu_int_en),
.int_flags_in(cpu_int_flags_in),
.int_flags_out(cpu_int_flags_out),
.key_in(key),
.done(done),
.fault(fault));
// High RAM
reg [7:0] high_ram [0:127];
wire high_ram_rd = cpu_rd;
reg high_ram_wr;
wire [6:0] high_ram_a = cpu_a[6:0];
wire [7:0] high_ram_din = cpu_dout;
reg [7:0] high_ram_dout;
always @(posedge clk) begin
if (high_ram_wr)
high_ram[high_ram_a] <= high_ram_din;
else
high_ram_dout <= (high_ram_rd) ? high_ram[high_ram_a] : 8'bx;
end
//DMA
wire dma_rd; // DMA Memory Write Enable
wire dma_wr; // DMA Memory Read Enable
wire [15:0] dma_a; // Main Address Bus
wire [7:0] dma_din; // Main Data Bus
wire [7:0] dma_dout;
wire [7:0] dma_mmio_dout;
reg dma_mmio_wr; // actually wire
wire dma_occupy_bus;
dma dma(
.clk(clk),
.rst(rst),
.ct(ct),
.dma_rd(dma_rd),
.dma_wr(dma_wr),
.dma_a(dma_a),
.dma_din(dma_din),
.dma_dout(dma_dout),
.mmio_wr(dma_mmio_wr),
.mmio_din(cpu_dout),
.mmio_dout(dma_mmio_dout),
.dma_occupy_bus(dma_occupy_bus)
);
assign dma_din = din;
// Interrupt
// int_req is the request signal from peripherals.
// When an interrupt is generated, the peripheral should send a pulse on
// the int_req for exactly one clock (using 4MHz clock).
wire [4:0] int_req;
wire int_key_req;
wire int_serial_req;
wire int_serial_ack;
wire int_tim_req;
wire int_tim_ack;
wire int_lcdc_req;
wire int_lcdc_ack;
wire int_vblank_req;
wire int_vblank_ack;
assign int_req[4] = int_key_req;
assign int_req[3] = int_serial_req;
assign int_req[2] = int_tim_req;
assign int_req[1] = int_lcdc_req;
assign int_req[0] = int_vblank_req;
//reg reg_ie_rd;
reg reg_ie_wr;
reg [4:0] reg_ie;
wire [4:0] reg_ie_din = cpu_dout[4:0];
wire [4:0] reg_ie_dout;
always @(posedge clk) begin
if (reg_ie_wr)
reg_ie <= reg_ie_din;
end
assign reg_ie_dout = reg_ie;
assign cpu_int_en = reg_ie_dout;
// Interrupt may be manually triggered
// int_req should only stay high for only 1 cycle for each interrupt
//reg reg_if_rd;
reg reg_if_wr;
reg [4:0] reg_if;
wire [4:0] reg_if_din = cpu_dout[4:0];
wire [4:0] reg_if_dout;
always @(posedge clk) begin
if (reg_if_wr)
reg_if <= reg_if_din | int_req;
else
reg_if <= cpu_int_flags_out | int_req;
end
assign reg_if_dout = reg_if | int_req;
assign cpu_int_flags_in = reg_if_dout;
assign int_serial_ack = reg_if[3];
assign int_tim_ack = reg_if[2];
assign int_lcdc_ack = reg_if[1];
assign int_vblank_ack = reg_if[0];
// PPU
wire [7:0] ppu_mmio_dout;
reg ppu_mmio_wr; // actually wire
wire [15:0] oam_a;
wire [7:0] oam_dout;
wire [7:0] oam_din;
wire oam_rd;
wire oam_wr;
reg oam_cpu_wr;
assign oam_a = (dma_occupy_bus) ? (dma_a) : (cpu_a);
assign oam_din = (dma_occupy_bus) ? (dma_dout) : (cpu_dout);
assign oam_rd = (dma_occupy_bus) ? (1'b0) : (cpu_rd);
assign oam_wr = (dma_occupy_bus) ? (dma_wr) : (oam_cpu_wr);
ppu ppu(
.clk(clk),
.rst(rst),
.ct(ct),
.mmio_a(cpu_a), // mmio bus is always accessable to CPU
.mmio_dout(ppu_mmio_dout),
.mmio_din(cpu_dout),
.mmio_rd(cpu_rd),
.mmio_wr(ppu_mmio_wr),
.oam_a(oam_a),
.oam_dout(oam_dout),
.oam_din(oam_din),
.oam_rd(oam_rd),
.oam_wr(oam_wr),
.int_vblank_req(int_vblank_req),
.int_lcdc_req(int_lcdc_req),
.int_vblank_ack(int_vblank_ack),
.int_lcdc_ack(int_lcdc_ack),
.cpl(cpl), // Pixel clock
.pixel(pixel), // Pixel Data (2bpp)
.valid(valid),
.hs(hs), // Horizontal Sync, Low Active
.vs(vs), // Vertical Sync, Low Active
.vram_a(ppu_a),
.vram_dout(ppu_din),
.vram_din(ppu_dout),
.vram_rd(ppu_rd),
.vram_wr(ppu_wr),
// Ignore the debugging interface
/* verilator lint_off PINCONNECTEMPTY */
.scx(),
.scy(),
.state()
/* verilator lint_on PINCONNECTEMPTY */
);
// Timer
wire [7:0] timer_dout;
reg timer_wr; // actually wire
timer timer(
.clk(clk),
.rst(rst),
.ct(ct),
.a(cpu_a),
.dout(timer_dout),
.din(cpu_dout),
.rd(cpu_rd),
.wr(timer_wr),
.int_tim_req(int_tim_req),
.int_tim_ack(int_tim_ack)
);
// Dummy Serial
wire [7:0] serial_dout;
reg serial_wr; // actually wire
serial serial(
.clk(clk),
.rst(rst),
.a(cpu_a),
.dout(serial_dout),
.din(cpu_dout),
.rd(cpu_rd),
.wr(serial_wr),
.int_serial_req(int_serial_req),
.int_serial_ack(int_serial_ack)
);
// Sound
wire [7:0] sound_dout;
reg sound_wr; // wire
wire [15:0] left_pre;
wire [15:0] right_pre;
sound sound(
.clk(clk),
.rst(rst),
.a(cpu_a),
.dout(sound_dout),
.din(cpu_dout),
.rd(cpu_rd),
.wr(sound_wr),
.left(left_pre),
.right(right_pre),
// Ignore the debugging signals
/* verilator lint_off PINCONNECTEMPTY */
.ch1_level(),
.ch2_level(),
.ch3_level(),
.ch4_level()
/* verilator lint_on PINCONNECTEMPTY */
);
always @(posedge clk) begin
left <= left_pre;
right <= right_pre;
end
// Boot ROM Enable Register
reg brom_disable;
reg brom_disable_wr; // actually wire
always @(posedge clk) begin
if (rst)
brom_disable <= 1'b0;
else
if (brom_disable_wr && (!brom_disable))
brom_disable <= cpu_dout[0];
end
wire [7:0] brom_dout;
brom brom(
.a(cpu_a[7:0]),
.d(brom_dout)
);
// Keypad
wire [7:0] keypad_reg;
reg keypad_reg_wr; // actually wire
reg [1:0] keypad_high;
always @(posedge clk) begin
if (rst)
keypad_high <= 2'b11;
else
if (keypad_reg_wr)
keypad_high <= cpu_dout[5:4];
end
assign keypad_reg[7:6] = 2'b11;
assign keypad_reg[5:4] = keypad_high[1:0];
assign keypad_reg[3:0] =
~(((keypad_high[1] == 1'b1) ? (key[7:4]) : 4'h0) |
((keypad_high[0] == 1'b1) ? (key[3:0]) : 4'h0));
assign int_key_req = (keypad_reg[3:0] != 4'hf) ? (1'b1) : (1'b0);
// External Bus (this includes CPU/DMA access to WRAM, VRAM, and cartridge)
reg ext_cpu_wr; // wire
assign a = (dma_occupy_bus) ? (dma_a) : (cpu_a_early);
assign dout = cpu_dout; // DMA never writes to external bus
assign wr = (dma_occupy_bus) ? (1'b0) : (ext_cpu_wr);
assign rd = (dma_occupy_bus) ? (dma_rd) : (cpu_rd);
// Bus Multiplexing, CPU
always @(*) begin
reg_ie_wr = 1'b0;
reg_if_wr = 1'b0;
keypad_reg_wr = 1'b0;
timer_wr = 1'b0;
serial_wr = 1'b0;
dma_mmio_wr = 1'b0;
brom_disable_wr = 1'b0;
high_ram_wr = 1'b0;
sound_wr = 1'b0;
ppu_mmio_wr = 1'b0;
oam_cpu_wr = 1'b0;
ext_cpu_wr = 1'b0;
// -- These are exclusive to CPU --
if (cpu_a == 16'hffff) begin // 0xFFFF - IE
//reg_ie_rd = bus_rd;
reg_ie_wr = cpu_wr;
cpu_din = {3'b0, reg_ie_dout};
end
else if (cpu_a == 16'hff0f) begin // 0xFF0F - IF
//reg_if_rd = bus_rd;
reg_if_wr = cpu_wr;
cpu_din = {3'b111, reg_if_dout};
end
else if (cpu_a == 16'hff00) begin // 0xFF00 - Keypad
keypad_reg_wr = cpu_wr;
cpu_din = keypad_reg;
end
else if ((cpu_a == 16'hff04) || (cpu_a == 16'hff05) || // Timer
(cpu_a == 16'hff06) || (cpu_a == 16'hff07)) begin
timer_wr = cpu_wr;
cpu_din = timer_dout;
end
else if ((cpu_a == 16'hff01) || (cpu_a == 16'hff02)) begin // Serial
serial_wr = cpu_wr;
cpu_din = serial_dout;
end
else if (cpu_a == 16'hff46) begin // 0xFF46 - DMA
dma_mmio_wr = cpu_wr;
cpu_din = dma_mmio_dout;
end
else if (cpu_a == 16'hff50) begin // 0xFF50 - BROM DISABLE
brom_disable_wr = cpu_wr;
cpu_din = {7'b0, brom_disable};
end
else if (cpu_a >= 16'hff80) begin // 0xFF80 - High RAM
high_ram_wr = cpu_wr;
cpu_din = high_ram_dout;
end
else if ((cpu_a >= 16'hff10 && cpu_a <= 16'hff1e) ||
(cpu_a >= 16'hff20 && cpu_a <= 16'hff26) ||
(cpu_a >= 16'hff30 && cpu_a <= 16'hff3f)) begin // Sound
sound_wr = cpu_wr;
cpu_din = sound_dout;
end
else if (cpu_a >= 16'hff40 && cpu_a <= 16'hff4b) begin // PPU MMIO
ppu_mmio_wr = cpu_wr;
cpu_din = ppu_mmio_dout;
end
else if ((cpu_a <= 16'h00ff) && (!brom_disable)) begin // Boot ROM
cpu_din = brom_dout;
end
// -- These are shared between CPU and DMA --
else if (cpu_a >= 16'hfe00 && cpu_a <= 16'hfe9f) begin // OAM
oam_cpu_wr = cpu_wr;
cpu_din = (dma_occupy_bus) ? (8'hff) : (oam_dout);
end
else if (cpu_a <= 16'hfdff) begin // External/ Work RAM/ Video RAM
ext_cpu_wr = cpu_wr;
cpu_din = (dma_occupy_bus) ? (8'hff) : (din);
end
else begin
// Unmapped area
cpu_din = 8'hff;
end
end
endmodule