Import VerilogBoy RTL
diff --git a/verilog/rtl/alu.v b/verilog/rtl/alu.v new file mode 100644 index 0000000..944e11f --- /dev/null +++ b/verilog/rtl/alu.v
@@ -0,0 +1,263 @@ +`timescale 1ns / 1ps +`default_nettype wire +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 17:30:26 02/08/2018 +// Module Name: alu +// Project Name: VerilogBoy +// Description: +// The Game Boy ALU. +// Dependencies: +// +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// + +module alu( + input [7:0] alu_b, + input [7:0] alu_a, + input [2:0] alu_bit_index, + output reg [7:0] alu_result, + input [3:0] alu_flags_in, + output reg [3:0] alu_flags_out, + input [4:0] alu_op + ); + + localparam OP_ADD = 5'b00000; + localparam OP_ADC = 5'b00001; + localparam OP_SUB = 5'b00010; + localparam OP_SBC = 5'b00011; + localparam OP_AND = 5'b00100; + localparam OP_XOR = 5'b00101; + localparam OP_OR = 5'b00110; + localparam OP_CP = 5'b00111; + localparam OP_RLC = 5'b01000; + localparam OP_RRC = 5'b01001; + localparam OP_RL = 5'b01010; + localparam OP_RR = 5'b01011; + localparam OP_SLA = 5'b01100; + localparam OP_SRA = 5'b01101; + localparam OP_SWAP= 5'b01110; + localparam OP_SRL = 5'b01111; + localparam OP_LF = 5'b10000; // Load Flags + // unused 5'b10001 + localparam OP_SF = 5'b10010; // Save Flags + // unused 5'b10011 + localparam OP_DAA = 5'b10100; + localparam OP_CPL = 5'b10101; + localparam OP_SCF = 5'b10110; + localparam OP_CCF = 5'b10111; + // unused 5'b11000 + // unused 5'b11001 + // unused 5'b11010 + // unused 5'b11011 + // unused 5'b11100 + localparam OP_BIT = 5'b11101; + localparam OP_RES = 5'b11110; + localparam OP_SET = 5'b11111; + + localparam F_Z = 2'd3; + localparam F_N = 2'd2; + localparam F_H = 2'd1; + localparam F_C = 2'd0; + + reg [8:0] intermediate_result1, intermediate_result2; + reg [4:0] result_low; + reg [4:0] result_high; + wire [2:0] bit_index; + reg carry; + + assign bit_index = alu_bit_index; + + always@(*) begin + alu_flags_out = 4'b0; + carry = 1'b0; + result_low = 5'd0; + result_high = 5'd0; + intermediate_result1 = 9'd0; + intermediate_result2 = 9'd0; + case (alu_op) + OP_ADD, OP_ADC: begin + carry = (alu_op == OP_ADC) ? alu_flags_in[F_C] : 1'b0; + result_low = {1'b0, alu_a[3:0]} + {1'b0, alu_b[3:0]} + + {4'b0, carry}; + alu_flags_out[F_H] = result_low[4]; + result_high = {1'b0, alu_a[7:4]} + + {1'b0, alu_b[7:4]} + + {4'b0, result_low[4]}; + alu_flags_out[F_C] = result_high[4]; + alu_result = {result_high[3:0], result_low[3:0]}; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_SUB, OP_SBC, OP_CP: begin + alu_flags_out[F_N] = 1'b1; + carry = (alu_op == OP_SBC) ? alu_flags_in[F_C] : 1'b0; + result_low = {1'b0, alu_a[3:0]} + + ~({1'b0, alu_b[3:0]} + + {4'b0, carry}) + 5'b1; + alu_flags_out[F_H] = result_low[4]; + result_high = {1'b0, alu_a[7:4]} + + ~({1'b0, alu_b[7:4]}) + + {4'b0, ~result_low[4]}; + alu_flags_out[F_C] = result_high[4]; + alu_result = (alu_op == OP_CP) ? (alu_a[7:0]) : {result_high[3:0], result_low[3:0]}; + alu_flags_out[F_Z] = ({result_high[3:0], result_low[3:0]} == 8'd0) ? 1'b1 : 1'b0; + end + OP_AND: begin + alu_flags_out[F_H] = 1'b1; + alu_result = alu_a & alu_b; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_OR: begin + alu_result = alu_a | alu_b; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_XOR: begin + alu_result = alu_a ^ alu_b; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_DAA: begin + if (~alu_flags_in[F_N]) begin + if (alu_flags_in[F_H] | + ((alu_a & 8'h0f) > 8'h9)) begin + intermediate_result1 = {1'b0, alu_a} + 9'h6; + end + else begin + intermediate_result1 = {1'b0, alu_a}; + end + if (alu_flags_in[F_C] | (intermediate_result1 > 9'h9f)) begin + intermediate_result2 = intermediate_result1 + 9'h60; + end + else begin + intermediate_result2 = intermediate_result1; + end + end + else begin + if (alu_flags_in[F_H]) begin + intermediate_result1 = {1'b0, (alu_a - 8'h6)}; + end + else begin + intermediate_result1 = {1'b0, alu_a}; + end + if (alu_flags_in[F_C]) begin + intermediate_result2 = intermediate_result1 - 9'h60; + end + else begin + intermediate_result2 = intermediate_result1; + end + end // else: !if(alu_flags_in[F_N]) + + alu_result = intermediate_result2[7:0]; + + alu_flags_out[F_N] = alu_flags_in[F_N]; + alu_flags_out[F_H] = 1'b0; + alu_flags_out[F_C] = intermediate_result2[8] ? 1'b1 : + alu_flags_in[F_C]; + alu_flags_out[F_Z] = (intermediate_result2[7:0] == 8'd0) ? + 1'b1 : 1'b0; + end + OP_CPL: begin + alu_flags_out[F_Z] = alu_flags_in[F_Z]; + alu_flags_out[F_N] = 1'b1; + alu_flags_out[F_H] = 1'b1; + alu_flags_out[F_C] = alu_flags_in[F_C]; + alu_result = ~alu_a; + end + OP_CCF: begin + alu_flags_out[F_Z] = alu_flags_in[F_Z]; + alu_flags_out[F_N] = 1'b0; + alu_flags_out[F_H] = 1'b0; + alu_flags_out[F_C] = ~alu_flags_in[F_C]; + alu_result = alu_b; + end + OP_SCF: begin + alu_flags_out[F_Z] = alu_flags_in[F_Z]; + alu_flags_out[F_N] = 1'b0; + alu_flags_out[F_H] = 1'b0; + alu_flags_out[F_C] = 1'b1; + alu_result = alu_b; + end + OP_RLC: begin + alu_result[0] = alu_a[7]; + alu_result[7:1] = alu_a[6:0]; + alu_flags_out[F_C] = alu_a[7]; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_RL: begin + alu_result[0] = alu_flags_in[F_C]; + alu_result[7:1] = alu_a[6:0]; + alu_flags_out[F_C] = alu_a[7]; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_RRC: begin + alu_result[7] = alu_a[0]; + alu_result[6:0] = alu_a[7:1]; + alu_flags_out[F_C] = alu_a[0]; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_RR: begin + alu_result[7] = alu_flags_in[F_C]; + alu_result[6:0] = alu_a[7:1]; + alu_flags_out[F_C] = alu_a[0]; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_SLA: begin + alu_result[7:1] = alu_a[6:0]; + alu_result[0] = 1'b0; + alu_flags_out[F_C] = alu_a[7]; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_SRA: begin + alu_result[7] = alu_a[7]; + alu_result[6:0] = alu_a[7:1]; + alu_flags_out[F_C] = alu_a[0]; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_SRL: begin + alu_result[7] = 1'b0; + alu_result[6:0] = alu_a[7:1]; + alu_flags_out[F_C] = alu_a[0]; + alu_flags_out[F_Z] = (alu_result == 8'd0) ? 1'b1 : 1'b0; + end + OP_BIT: begin + // Bit index must be in data0[5:3] + alu_flags_out[F_C] = alu_flags_in[F_C]; + alu_flags_out[F_H] = 1'b1; + alu_flags_out[F_N] = 1'b0; + alu_flags_out[F_Z] = ~alu_a[bit_index]; + alu_result = alu_b; + end + OP_SET: begin + alu_flags_out = alu_flags_in; + alu_result = alu_a; + alu_result[bit_index] = 1'b1; + end + OP_RES: begin + alu_flags_out = alu_flags_in; + alu_result = alu_a; + alu_result[bit_index] = 1'b0; + end + OP_SWAP: begin + alu_flags_out[F_Z] = (alu_a == 8'd0) ? 1'd1: 1'd0; + alu_flags_out[F_H] = 1'b0; + alu_flags_out[F_C] = 1'b0; + alu_flags_out[F_N] = 1'b0; + alu_result = {alu_a[3:0], alu_a[7:4]}; + end + OP_SF: begin + alu_flags_out = alu_b[7:4]; + alu_result = alu_a; + end + OP_LF: begin + alu_result = {alu_flags_in, 4'b0}; + end + default: begin + alu_result = alu_b; + alu_flags_out = alu_flags_in; + end + endcase + end + +endmodule
diff --git a/verilog/rtl/bootrom.mif b/verilog/rtl/bootrom.mif new file mode 100644 index 0000000..77d25b3 --- /dev/null +++ b/verilog/rtl/bootrom.mif
@@ -0,0 +1,256 @@ +31 +FE +FF +AF +21 +FF +9F +32 +CB +7C +20 +FB +3E +00 +E0 +42 +3E +91 +E0 +40 +3E +01 +C3 +FE +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +00 +E0 +50
diff --git a/verilog/rtl/boy.v b/verilog/rtl/boy.v new file mode 100644 index 0000000..69c2d65 --- /dev/null +++ b/verilog/rtl/boy.v
@@ -0,0 +1,459 @@ +`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 + // Cartridge 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, + // 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 [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 + wire [1:0] cpu_ct; // 0-3 T cycle number inside one M cycle + + cpu cpu( + .clk(clk), + .rst(rst), + .phi(phi), + .ct(cpu_ct), + .a(cpu_a), + .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 + reg [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_extbus; // 0x0000 - 0x7FFF, 0xA000 - 0xFFFF + wire dma_occupy_vidbus; // 0x8000 - 0x9FFF + wire dma_occupy_oambus; // 0xFE00 - 0xFE9F + dma dma( + .clk(clk), + .rst(rst), + .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_extbus(dma_occupy_extbus), + .dma_occupy_vidbus(dma_occupy_vidbus), + .dma_occupy_oambus(dma_occupy_oambus) + ); + + // 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] vram_a; + wire [7:0] vram_dout; + //wire [7:0] vram_din; + wire vram_rd; + wire vram_wr; + reg vram_cpu_wr; + 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 vram_a = (dma_occupy_vidbus) ? (dma_a) : (cpu_a); + //assign vram_din = (dma_occupy_vidbus) ? (dma_dout) : (cpu_dout); + assign vram_rd = (dma_occupy_vidbus) ? (dma_rd) : (cpu_rd); + assign vram_wr = (dma_occupy_vidbus) ? (1'b0) : (vram_cpu_wr); + assign oam_a = (dma_occupy_oambus) ? (dma_a) : (cpu_a); + assign oam_din = (dma_occupy_oambus) ? (dma_dout) : (cpu_dout); + assign oam_rd = (dma_occupy_oambus) ? (1'b0) : (cpu_rd); + assign oam_wr = (dma_occupy_oambus) ? (dma_wr) : (oam_cpu_wr); + + ppu ppu( + .clk(clk), + .rst(rst), + .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), + .vram_a(vram_a), + .vram_dout(vram_dout), + .vram_din(cpu_dout), // DMA never writes to VRAM + .vram_rd(vram_rd), + .vram_wr(vram_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 + // 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(cpu_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) + ); + + // Work RAM + wire [7:0] wram_dout; + wire [12:0] wram_a; + wire wram_wr; + reg wram_cpu_wr; // actually wire + + assign wram_a = (dma_occupy_extbus) ? (dma_a[12:0]) : (cpu_a[12:0]); + assign wram_wr = (dma_occupy_extbus) ? (1'b0) : (wram_cpu_wr); + + singleport_ram #( + .WORDS(8192) + ) br_wram ( + .clka(clk), + .wea(wram_wr), + .addra(wram_a), + .dina(cpu_dout), // DMA never writes to Work RAM + .douta(wram_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 + reg ext_cpu_wr; // wire + assign a = (dma_occupy_extbus) ? (dma_a) : (cpu_a); + assign dout = cpu_dout; // DMA never writes to external bus + assign wr = (dma_occupy_extbus) ? (1'b0) : (ext_cpu_wr); + assign rd = (dma_occupy_extbus) ? (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; + vram_cpu_wr = 1'b0; + oam_cpu_wr = 1'b0; + wram_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'h8000 && cpu_a <= 16'h9fff) begin // VRAM + vram_cpu_wr = cpu_wr; + cpu_din = (dma_occupy_vidbus) ? (8'hff) : (vram_dout); + end + else if (cpu_a >= 16'hfe00 && cpu_a <= 16'hfe9f) begin // OAM + oam_cpu_wr = cpu_wr; + cpu_din = (dma_occupy_oambus) ? (8'hff) : (oam_dout); + end + else if ((cpu_a >= 16'hc000 && cpu_a <= 16'hdfff) || + (cpu_a >= 16'he000 && cpu_a <= 16'hfdff)) begin // WRAM + wram_cpu_wr = cpu_wr; + cpu_din = (dma_occupy_extbus) ? (8'hff) : (wram_dout); + end + else if ((cpu_a <= 16'h7fff) || + (cpu_a >= 16'ha000 && cpu_a <= 16'hbfff)) begin // External + ext_cpu_wr = cpu_wr; + cpu_din = (dma_occupy_extbus) ? (8'hff) : (din); + end + else begin + // Unmapped area + cpu_din = 8'hff; + end + end + + // Bus Multiplexing, DMA + always @(*) begin + if (dma_a >= 16'h8000 && dma_a <= 16'h9fff) begin // VRAM + dma_din = vram_dout; + end + else if ((dma_a >= 16'hc000 && dma_a <= 16'hdfff) || + (dma_a >= 16'he000 && dma_a <= 16'hfdff)) begin // WRAM + dma_din = wram_dout; + end + else begin + dma_din = din; + end + end + +endmodule
diff --git a/verilog/rtl/brom.v b/verilog/rtl/brom.v new file mode 100644 index 0000000..1b6896b --- /dev/null +++ b/verilog/rtl/brom.v
@@ -0,0 +1,35 @@ +`timescale 1ns / 1ps +`default_nettype wire +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: +// +// Create Date: 21:10:17 02/09/2018 +// Design Name: +// Module Name: brom +// Project Name: +// Target Devices: +// Tool versions: +// Description: +// +// Dependencies: +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module brom( + input [7:0] a, + output [7:0] d + ); + + reg [7:0] brom_array [0:255]; // 256 Bytes BROM array + + initial begin + $readmemh("bootrom.mif", brom_array, 0, 255); + end + + assign d = brom_array[a]; + +endmodule
diff --git a/verilog/rtl/clk_div.v b/verilog/rtl/clk_div.v new file mode 100644 index 0000000..715bfed --- /dev/null +++ b/verilog/rtl/clk_div.v
@@ -0,0 +1,35 @@ +`timescale 1ns / 1ps +`default_nettype wire +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 09:50:37 04/07/2018 +// Module Name: clk_div +// Project Name: VerilogBoy +// Description: +// +// Dependencies: +// +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module clk_div( + input i, + output reg o = 0 + ); + + parameter WIDTH = 15, DIV = 1000; + + reg [WIDTH - 1:0] counter = 0; + + always @(posedge i) + begin + if (counter == (DIV / 2 - 1)) begin + o <= ~o; + counter <= 0; + end + else + counter <= counter + 1'b1; + end +endmodule
diff --git a/verilog/rtl/common.v b/verilog/rtl/common.v new file mode 100644 index 0000000..f71c77f --- /dev/null +++ b/verilog/rtl/common.v
@@ -0,0 +1,93 @@ +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 17:30:26 02/08/2018 +// Module Name: None +// Project Name: VerilogBoy +// Description: +// Common definitions for VerilogBoy. Use as a header inclusion. +// Dependencies: +// +// Additional Comments: +// It is also used in Verilated simulation +////////////////////////////////////////////////////////////////////////////////// + + +`define ALU_ADD 3'b000 +`define ALU_ADC 3'b001 +`define ALU_SUB 3'b010 +`define ALU_SBC 3'b011 +`define ALU_AND 3'b100 +`define ALU_XOR 3'b101 +`define ALU_OR 3'b110 +`define ALU_CP 3'b111 + +`define INT_LCDC 0 +`define INT_STAT 1 +`define INT_TIMER 2 +`define INT_SERIAL 3 +`define INT_JOYPAD 4 + +`define ALU_SRC_A_ACC 2'b00 +`define ALU_SRC_A_PC 2'b01 +`define ALU_SRC_A_REG 2'b10 +`define ALU_SRC_A_DB 2'b11 +`define ALU_SRC_B_ACC 3'b000 +`define ALU_SRC_B_CARRY 3'b001 +`define ALU_SRC_B_ZERO 3'b010 +`define ALU_SRC_B_ONE 3'b011 +`define ALU_SRC_B_H 3'b100 +`define ALU_SRC_B_L 3'b101 +`define ALU_SRC_B_ABSIMM 3'b110 +`define ALU_SRC_B_IMM 3'b111 +`define ALU_OP_PREFIX_NORMAL 2'b00 +`define ALU_OP_PREFIX_SHIFT_ROTATE 2'b01 +`define ALU_OP_PREFIX_SPECIAL 2'b10 +`define ALU_OP_PREFIX_CB 2'b11 +`define ALU_OP_SRC_INSTR_5TO3 2'b00 +`define ALU_OP_SRC_INSTR_7TO6 2'b01 +`define ALU_OP_SRC_ADD_FTOR 2'b10 +`define ALU_OP_SRC_SUB_ATOF 2'b11 +`define ALU_OP_SIGNED_FORCE 1'b1 +`define ALU_OP_SIGNED_AUTO 1'b0 +`define ALU_DST_ACC 2'b00 +`define ALU_DST_PC 2'b01 +`define ALU_DST_REG 2'b10 +`define ALU_DST_DB 2'b11 +`define PC_SRC_REG 2'b00 +`define PC_SRC_RST 2'b01 +`define PC_SRC_TEMP 2'b10 +`define PC_WRITE_ENABLE 1'b1 +`define RF_SEL_B 3'b000 +`define RF_SEL_C 3'b001 +`define RF_SEL_D 3'b010 +`define RF_SEL_E 3'b011 +`define RF_SEL_H 3'b100 +`define RF_SEL_L 3'b101 +`define RF_SEL_SP_H 3'b110 +`define RF_SEL_SP_L 3'b111 +`define RF_SEL_BC 3'b001 +`define RF_SEL_DE 3'b011 +`define RF_SEL_HL 3'b101 +`define RF_SEL_SP 3'b111 +`define BUS_OP_IDLE 2'b00 +`define BUS_OP_IF 2'b01 +`define BUS_OP_WRITE 2'b10 +`define BUS_OP_READ 2'b11 +`define DB_SRC_ACC 2'b00 +`define DB_SRC_ALU 2'b01 +`define DB_SRC_REG 2'b10 +`define DB_SRC_DB 2'b11 +`define AB_SRC_PC 2'b00 +`define AB_SRC_TEMP 2'b01 +`define AB_SRC_REG 2'b10 +`define AB_SRC_SP 2'b11 +`define CT_OP_IDLE 2'b00 +`define CT_OP_PC_INC 2'b01 +`define CT_OP_SP_DEC 2'b10 +`define CT_OP_SP_INC 2'b11 +`define FLAGS_ZNHC 2'b00 +`define FLAGS_x0HC 2'b01 +`define FLAGS_00HC 2'b10 +`define FLAGS_ZNHx 2'b11
diff --git a/verilog/rtl/control.v b/verilog/rtl/control.v new file mode 100644 index 0000000..e272690 --- /dev/null +++ b/verilog/rtl/control.v
@@ -0,0 +1,1165 @@ +`timescale 1ns / 1ps +`default_nettype wire +`include "common.v" +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Module Name: control +// Project Name: VerilogBoy +// Description: +// The control unit of Game Boy CPU. +// Dependencies: +// +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// + +module control( + input clk, + input rst, + input [7:0] opcode_early, + /* verilator lint_off UNUSED */ + input [7:0] imm, + /* verilator lint_on UNUSED */ + input [7:0] cb, + input [2:0] m_cycle_early, + input [1:0] ct_state, + input f_z, + input f_c, + output reg [1:0] alu_src_a, + output reg [2:0] alu_src_b, + output reg alu_src_xchg, + output reg [1:0] alu_op_prefix, + output reg [1:0] alu_op_src, + output reg alu_op_signed, + output reg [1:0] alu_dst, + output reg [1:0] pc_src, + output reg pc_we, + output reg pc_b_sel, + output reg pc_jr, + output reg pc_revert, + output reg [2:0] rf_wr_sel, + output reg [2:0] rf_rd_sel, + output reg [1:0] rf_rdw_sel, + output reg temp_redir, + output reg opcode_redir, + output reg [1:0] bus_op, + output reg [1:0] db_src, + output reg [1:0] ab_src, + output reg [1:0] ct_op, + output reg flags_we, + output reg [1:0] flags_pattern, + output reg high_mask, + output int_master_en, + input int_dispatch, + output reg int_ack, + output reg next, + output reg stop, + output reg halt, + input wake, + output reg fault + ); + + // Comb signal generated by control logic + reg ime_clear; + reg ime_set; + reg ime_delay_set; + // FF + reg ime_delay_set_ff; + reg ime; + assign int_master_en = ime; + + wire [7:0] opcode = opcode_early; + wire [2:0] m_cycle = m_cycle_early; + + always @(posedge clk) + if (ct_state == 2'd2) + ime_delay_set_ff <= ime_delay_set; + + always @(posedge clk) begin + if (rst) + ime <= 1'b0; + else if (ime_clear) + ime <= 1'b0; + else if (ime_set) + ime <= 1'b1; + else if (ime_delay_set_ff) + ime <= 1'b1; + end + + reg halt_last; + reg stop_last; + reg fault_last; + always @(posedge clk) begin + if (rst) begin + halt_last <= 1'b0; + stop_last <= 1'b0; + fault_last <= 1'b0; + end + else begin + halt_last <= halt; + stop_last <= stop; + fault_last <= fault; + end + end + + reg wake_by_int; + always @(posedge clk) begin + if (rst) + wake_by_int <= 1'b0; + else begin + if (int_dispatch && (m_cycle == 0)) begin + wake_by_int <= wake; + end + end + end + + // Combinational control signal + reg [1:0] comb_alu_src_a; + reg [2:0] comb_alu_src_b; + reg comb_alu_src_xchg; + reg [1:0] comb_alu_op_prefix; + reg [1:0] comb_alu_op_src; + reg comb_alu_op_signed; + reg [1:0] comb_alu_dst; + reg [1:0] comb_pc_src; + reg comb_pc_we; + reg comb_pc_b_sel; + reg comb_pc_jr; + reg comb_pc_revert; + reg [2:0] comb_rf_wr_sel; + reg [2:0] comb_rf_rd_sel; + reg [1:0] comb_rf_rdw_sel; + reg comb_temp_redir; + reg comb_opcode_redir; + reg [1:0] comb_bus_op; + reg [1:0] comb_db_src; + reg [1:0] comb_ab_src; + reg [1:0] comb_ct_op; + reg comb_flags_we; + reg [1:0] comb_flags_pattern; + reg comb_high_mask; + reg comb_int_ack; + reg comb_next; + reg comb_stop; + reg comb_halt; + reg comb_fault; + + // All these nonsense will be replaced by a vector decoding ROM... + // in the future + always @(*) begin + // Set default output + // ACC = ACC + 0 + comb_alu_src_a = `ALU_SRC_A_ACC; + comb_alu_src_b = `ALU_SRC_B_ZERO; + comb_alu_op_prefix = `ALU_OP_PREFIX_NORMAL; + comb_alu_op_src = `ALU_OP_SRC_ADD_FTOR; + comb_alu_dst = `ALU_DST_ACC; + comb_pc_we = 0; + comb_rf_wr_sel = `RF_SEL_B; // Doesn't matter + comb_rf_rd_sel = `RF_SEL_B; // Doesn't matter + comb_bus_op = `BUS_OP_IF; // Fetch comb_next instruction + comb_db_src = `DB_SRC_DB; // Should != ACC + comb_ab_src = `AB_SRC_PC; // Output PC + comb_ct_op = `CT_OP_PC_INC; // PC = PC + 1 + comb_flags_we = 0; + comb_next = 0; + comb_alu_src_xchg = 0; + comb_rf_rdw_sel = 2'b10; // Select HL + comb_pc_src = 2'b00; + comb_pc_b_sel = m_cycle[0]; + comb_pc_jr = 1'b0; + comb_pc_revert = 1'b0; + comb_stop = 1'b0; + comb_halt = 1'b0; + comb_fault = 1'b0; + comb_high_mask = 1'b0; + comb_alu_op_signed = 1'b0; + comb_temp_redir = 1'b0; + comb_opcode_redir = 1'b0; + ime_set = 1'b0; + ime_delay_set = 1'b0; + ime_clear = 1'b0; + comb_int_ack = 1'b0; + comb_flags_pattern = 2'b00; + // Though the idea behind the original GB is that when in comb_halt or comb_stop + // mode, the clock can be comb_stopped, thus lower the power consumption and + // save the battery. On FPGA, this is hard to achieve since clocking in + // FPGA works very differently than on ASIC. So here, when comb_halted, CPU + // would executing NOP in place as if it was comb_halted. + if (halt_last || stop_last || fault_last) begin + if (wake) begin + comb_halt = 1'b0; + comb_stop = 1'b0; + // Fault could not be waked up + end + else begin + // Keep sleeping + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_halt = halt_last; + comb_stop = stop_last; + end + // Fault cannot be waken up + comb_fault = fault_last; + end + if (int_dispatch) begin + // Interrupt dispatch process + case (m_cycle) + 0: begin + // Revert PC + comb_pc_revert = 1'b1; + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_SP_DEC; + comb_next = 1'b1; + end + 1: begin + // Save PCh + comb_alu_src_a = `ALU_SRC_A_PC; + comb_alu_dst = `ALU_DST_DB; + comb_bus_op = `BUS_OP_WRITE; + comb_ab_src = `AB_SRC_SP; + comb_db_src = `DB_SRC_DB; + comb_ct_op = `CT_OP_SP_DEC; + comb_next = 1'b1; + end + 2: begin + // Save PCl + comb_alu_src_a = `ALU_SRC_A_PC; + comb_alu_dst = `ALU_DST_DB; + comb_bus_op = `BUS_OP_WRITE; + comb_ab_src = `AB_SRC_SP; + comb_db_src = `DB_SRC_DB; + comb_ct_op = `CT_OP_IDLE; + comb_pc_we = 1; + comb_next = 1'b1; + end + 3: begin + // Delay + if (wake_by_int) begin + ime_clear = 1'b1; + comb_int_ack = 1'b1; + end + else begin + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1'b1; + end + end + 4: begin + // Normal instruction fetch process + ime_clear = 1'b1; + comb_int_ack = 1'b1; + end + endcase + end + //else begin + // If waken up + if (!comb_halt && !comb_stop && !comb_fault && !int_dispatch) begin + if (opcode == 8'h00) begin // NOP + // Default behavior is enough + end + else if (opcode == 8'h10) begin // STOP + comb_stop = 1; + end + else if (opcode == 8'h76) begin // HALT + comb_halt = 1; + end + else if (opcode == 8'hF3) begin // DI + ime_clear = 1'b1; + end + else if (opcode == 8'hFB) begin // EI + // EI here need to be delayed for 1 clock? + ime_delay_set = 1'b1; + end + // 16-bit IMM to register LD instructions + else if ((opcode[7:6] == 2'b00) && (opcode[3:0] == 4'b0001)) begin + comb_alu_src_a = `ALU_SRC_A_DB; // Load from databus + comb_alu_dst = `ALU_DST_REG; // Load to register + comb_db_src = `DB_SRC_DB; // DB destination to databus buffer + if ((m_cycle == 0) || (m_cycle == 1)) begin + comb_rf_wr_sel = {opcode[5:4], 1'b1}; // Register no based on opcode + comb_bus_op = `BUS_OP_READ; // Read from databus + comb_next = 1; + end + else begin + comb_rf_wr_sel = {opcode[5:4], 1'b0}; + comb_next = 0; + end + end + // LD (nn), SP + else if (opcode == 8'h08) begin + if ((m_cycle == 0) || (m_cycle == 1)) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_ab_src = `AB_SRC_PC; + comb_ct_op = `CT_OP_PC_INC; + comb_next = 1; + end + else if (m_cycle == 2) begin + comb_ab_src = `AB_SRC_TEMP; + comb_db_src = `DB_SRC_REG; + comb_rf_rd_sel = `RF_SEL_SP_L; + comb_bus_op = `BUS_OP_WRITE; + comb_ct_op = `CT_OP_SP_INC; + comb_temp_redir = 1'b1; + comb_next = 1'b1; + end + else if (m_cycle == 3) begin + comb_ab_src = `AB_SRC_TEMP; + comb_db_src = `DB_SRC_REG; + comb_rf_rd_sel = `RF_SEL_SP_H; + comb_bus_op = `BUS_OP_WRITE; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1'b1; + end + else begin + // Default behaviour is enough. + end + end + // 8 bit reg-to-reg, mem-to-reg, or reg-to-mem LD instructions + else if (opcode[7:6] == 2'b01) begin + if (opcode[2:0] == 3'b110) + comb_alu_src_a = `ALU_SRC_A_DB; // Src A from data bus + else if (opcode[2:0] == 3'b111) + comb_alu_src_a = `ALU_SRC_A_ACC; // Src A from accumulator + else + comb_alu_src_a = `ALU_SRC_A_REG; // Src A from register file + + if (opcode[5:3] == 3'b110) + comb_alu_dst = `ALU_DST_DB; // Destination is (HL) + else if (opcode[5:3] == 3'b111) + comb_alu_dst = `ALU_DST_ACC; // Destination is A + else + comb_alu_dst = `ALU_DST_REG; // Destination is register + + comb_rf_wr_sel = opcode[5:3]; + comb_rf_rd_sel = opcode[2:0]; + + if (opcode[5:3] == 3'b110) begin // Register to Memory + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_WRITE; + comb_db_src = `DB_SRC_ALU; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + else if (opcode[2:0] == 3'b110) begin // Memory to Register + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + end + // 8 bit imm-to-reg, imm-to-mem LD instructions + else if ((opcode[7:6] == 2'b00) && (opcode[2:0] == 3'b110)) begin + comb_alu_src_a = `ALU_SRC_A_DB; + + if (opcode[5:3] == 3'b110) begin // imm to mem + comb_alu_dst = `ALU_DST_DB; + comb_rf_rd_sel = `RF_SEL_HL; + end + else if (opcode[5:3] == 3'b111) begin + comb_alu_dst = `ALU_DST_ACC; + end + else begin + comb_alu_dst = `ALU_DST_REG; + comb_rf_wr_sel = opcode[5:3]; + end + + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_next = 1; + end + else if (m_cycle == 1) begin + if (opcode[5:3] == 3'b110) begin + comb_bus_op = `BUS_OP_WRITE; + comb_db_src = `DB_SRC_DB; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + end + // LD (BC)/(DE), A + else if ((opcode == 8'h02) || (opcode == 8'h12)) begin + comb_alu_dst = `ALU_DST_DB; + if (opcode == 8'h02) + comb_rf_rdw_sel = 2'b00; // Select BC + else + comb_rf_rdw_sel = 2'b01; // Select DE + if (m_cycle == 0) begin + comb_next = 1; + comb_bus_op = `BUS_OP_WRITE; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + end + end + // LD (HL+)/(HL-), A + else if ((opcode == 8'h22) || (opcode == 8'h32)) begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_dst = `ALU_DST_REG; + if (opcode == 8'h22) + comb_alu_op_src = `ALU_OP_SRC_ADD_FTOR; + else + comb_alu_op_src = `ALU_OP_SRC_SUB_ATOF; + if (m_cycle == 0) begin + // A being written to the memory, calculate L +/- 1 + comb_alu_src_b = `ALU_SRC_B_ONE; + comb_rf_rd_sel = `RF_SEL_L; + comb_rf_wr_sel = `RF_SEL_L; + comb_bus_op = `BUS_OP_WRITE; + comb_db_src = `DB_SRC_ACC; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + else begin + // calculate H +/- carry + comb_alu_src_b = `ALU_SRC_B_CARRY; + comb_rf_rd_sel = `RF_SEL_H; + comb_rf_wr_sel = `RF_SEL_H; + end + end + // LD A, (BC)/(DE) + else if ((opcode == 8'h0A) || (opcode == 8'h1A)) begin + comb_alu_src_a = `ALU_SRC_A_DB; + if (opcode == 8'h0A) begin + comb_rf_rdw_sel = 2'b00; // Select BC + end + else begin + comb_rf_rdw_sel = 2'b01; // Select DE + end + + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + // LD A, (HL+)/(HL-) + else if ((opcode == 8'h2A) || (opcode == 8'h3A)) begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_src_b = `ALU_SRC_B_ONE; + if (opcode == 8'h2A) + comb_alu_op_src = `ALU_OP_SRC_ADD_FTOR; + else + comb_alu_op_src = `ALU_OP_SRC_SUB_ATOF; + comb_alu_dst = `ALU_DST_REG; + if (m_cycle == 0) begin + comb_alu_src_b = `ALU_SRC_B_ONE; + comb_rf_rd_sel = `RF_SEL_L; + comb_rf_wr_sel = `RF_SEL_L; + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_ACC; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + else begin + comb_alu_src_b = `ALU_SRC_B_CARRY; + comb_rf_rd_sel = `RF_SEL_H; + comb_rf_wr_sel = `RF_SEL_H; + end + end + // 16-bit INC/DEC + else if ((opcode[7:6] == 2'b00) && (opcode[2:0] == 3'b011)) begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_dst = `ALU_DST_REG; + if (opcode[3] == 1) begin + comb_alu_op_src = `ALU_OP_SRC_SUB_ATOF; + end + if (m_cycle == 0) begin + comb_alu_src_b = `ALU_SRC_B_ONE; + comb_rf_rd_sel = {opcode[5:4], 1'b1}; + comb_rf_wr_sel = {opcode[5:4], 1'b1}; + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else begin + comb_alu_src_b = `ALU_SRC_B_CARRY; + comb_rf_rd_sel = {opcode[5:4], 1'b0}; + comb_rf_wr_sel = {opcode[5:4], 1'b0}; + end + end + // 8-bit INC/DEC + else if ((opcode[7:6] == 2'b00) && (opcode[2:1] == 2'b10)) begin + comb_alu_src_b = `ALU_SRC_B_ONE; + comb_flags_pattern = `FLAGS_ZNHx; + comb_flags_we = 1'b1; + + // INC or DEC + if (opcode[0]) + comb_alu_op_src = `ALU_OP_SRC_SUB_ATOF; + else + comb_alu_op_src = `ALU_OP_SRC_ADD_FTOR; + + if (opcode[5:3] == 3'b110) begin + // INC/DEC (HL) + comb_alu_src_a = `ALU_SRC_A_DB; + comb_alu_dst = `ALU_DST_DB; + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_REG; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + else if (m_cycle == 1) begin + comb_bus_op = `BUS_OP_WRITE; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + else begin + // End cycle + comb_flags_we = 0; + end + end + else if (opcode[5:3] == 3'b111) begin + // INC/DEC A + comb_alu_src_a = `ALU_SRC_A_ACC; + comb_alu_dst = `ALU_DST_ACC; + end + else begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_dst = `ALU_DST_REG; + comb_rf_rd_sel = opcode[5:3]; + comb_rf_wr_sel = opcode[5:3]; + end + end + // ADD HL, r16 + else if ((opcode[7:6] == 2'b00) && (opcode[3:0] == 4'b1001)) begin + comb_alu_dst = `ALU_DST_REG; + comb_flags_we = 1'b1; + comb_flags_pattern = `FLAGS_x0HC; + if (m_cycle == 0) begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_src_b = `ALU_SRC_B_L; + comb_rf_wr_sel = `RF_SEL_L; + comb_rf_rd_sel = {opcode[5:4], 1'b1}; + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + else begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_src_b = `ALU_SRC_B_H; + comb_rf_wr_sel = `RF_SEL_H; + comb_rf_rd_sel = {opcode[5:4], 1'b0}; + comb_alu_op_signed = 1'b1; + end + end + // 8 bit reg-to-reg, mem-to-reg ALU operation + else if (opcode[7:6] == 2'b10) begin + comb_alu_src_b = `ALU_SRC_B_ACC; + comb_alu_op_src = `ALU_OP_SRC_INSTR_5TO3; + comb_rf_rd_sel = opcode[2:0]; + comb_flags_we = 1'b1; + if ((opcode[5:4] == 2'b01) || (opcode[5:3] == 3'b111)) begin + // Sub or CP + comb_alu_src_xchg = 1'b1; + end + if (opcode[2:0] == 3'b110) begin // Source from HL + comb_alu_src_a = `ALU_SRC_A_DB; + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_ab_src = `AB_SRC_REG; + // Do not writeback in the first cycle + comb_alu_dst = `ALU_DST_DB; + comb_flags_we = 1'b0; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + else if (opcode[2:0] == 3'b111) begin // Source from A + comb_alu_src_a = `ALU_SRC_A_ACC; + end + else begin + comb_alu_src_a = `ALU_SRC_A_REG; + end + end + // 8 bit imm-to-reg ALU operation + else if ((opcode[7:6] == 2'b11) && (opcode[2:0] == 3'b110)) begin + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_next = 1; + end + else begin + if ((opcode[5:4] == 2'b01) || (opcode[5:3] == 3'b111)) begin + // Sub or CP + comb_alu_src_xchg = 1'b1; + end + comb_alu_src_a = `ALU_SRC_A_DB; + comb_alu_src_b = `ALU_SRC_B_ACC; + comb_alu_op_src = `ALU_OP_SRC_INSTR_5TO3; + comb_flags_we = 1'b1; + end + end + // 16-bit PUSH + else if ((opcode[7:6] == 2'b11) && (opcode[3:0] == 4'b0101)) begin + if (opcode[5:4] == 2'b11) begin + // AF + comb_alu_op_prefix = `ALU_OP_PREFIX_SPECIAL; + comb_db_src = `DB_SRC_ACC; + end + else begin + comb_db_src = `DB_SRC_DB; + end + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_dst = `ALU_DST_DB; + + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_IDLE; + comb_ab_src = `AB_SRC_SP; + comb_ct_op = `CT_OP_SP_DEC; + comb_next = 1; + end + else if (m_cycle == 1) begin + comb_bus_op = `BUS_OP_WRITE; + comb_ab_src = `AB_SRC_SP; + comb_ct_op = `CT_OP_SP_DEC; + comb_rf_rd_sel = {opcode[5:4], 1'b0}; + comb_next = 1; + end + else if (m_cycle == 2) begin + comb_bus_op = `BUS_OP_WRITE; + comb_ab_src = `AB_SRC_SP; + comb_ct_op = `CT_OP_IDLE; + comb_rf_rd_sel = {opcode[5:4], 1'b1}; + if (opcode[5:4] == 2'b11) begin + comb_db_src = `DB_SRC_ALU; + end + comb_next = 1; + end + end + // 16-bit POP + else if ((opcode[7:6] == 2'b11) && (opcode[3:0] == 4'b0001)) begin + if ((m_cycle == 1) || (m_cycle == 2)) begin + comb_alu_src_a = `ALU_SRC_A_DB; + if (opcode[5:4] == 2'b11) begin + comb_alu_dst = `ALU_DST_ACC; + end + else begin + comb_alu_dst = `ALU_DST_REG; + end + end + + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_ab_src = `AB_SRC_SP; + comb_ct_op = `CT_OP_SP_INC; + comb_next = 1; + end + else if (m_cycle == 1) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_ab_src = `AB_SRC_SP; + comb_ct_op = `CT_OP_SP_INC; + comb_rf_wr_sel = {opcode[5:4], 1'b1}; + comb_next = 1; + end + else if (m_cycle == 2) begin + comb_rf_wr_sel = {opcode[5:4], 1'b0}; + if (opcode[5:4] == 2'b11) begin + // Copy from memory to flags + comb_alu_op_prefix = `ALU_OP_PREFIX_SPECIAL; + comb_alu_op_src = `ALU_OP_SRC_SUB_ATOF; + comb_alu_src_b = `ALU_SRC_B_ACC; + comb_flags_we = 1'b1; + end + end + end + // LD (C), A + else if (opcode == 8'he2) begin + comb_rf_rdw_sel = 2'b00; // Select BC + comb_high_mask = 1'b1; // Select C only + comb_alu_src_a = `ALU_SRC_A_ACC; + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_WRITE; + comb_db_src = `DB_SRC_ACC; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + // LD A, (C) + else if (opcode == 8'hf2) begin + comb_rf_rdw_sel = 2'b00; // Select BC + comb_high_mask = 1'b1; // Select C only + comb_alu_src_a = `ALU_SRC_A_DB; + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1'b1; + end + end + // ADD SP, r8 + else if (opcode == 8'he8) begin + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else if (m_cycle == 1) begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_src_b = `ALU_SRC_B_IMM; + comb_alu_dst = `ALU_DST_REG; + comb_rf_rd_sel = `RF_SEL_SP_L; + comb_rf_wr_sel = `RF_SEL_SP_L; + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_flags_pattern = `FLAGS_00HC; + comb_flags_we = 1'b1; + comb_next = 1; + end + else if (m_cycle == 2) begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_src_b = `ALU_SRC_B_IMM; + comb_alu_dst = `ALU_DST_REG; + comb_alu_op_signed = 1'b1; + comb_rf_rd_sel = `RF_SEL_SP_H; + comb_rf_wr_sel = `RF_SEL_SP_H; + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + // LD HL, SP+r8 + else if (opcode == 8'hf8) begin + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else if (m_cycle == 1) begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_src_b = `ALU_SRC_B_IMM; + comb_alu_dst = `ALU_DST_REG; + comb_rf_rd_sel = `RF_SEL_SP_L; + comb_rf_wr_sel = `RF_SEL_L; + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_flags_pattern = `FLAGS_00HC; + comb_flags_we = 1'b1; + comb_next = 1; + end + else begin + comb_alu_op_signed = 1'b1; + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_src_b = `ALU_SRC_B_IMM; + comb_alu_dst = `ALU_DST_REG; + comb_rf_rd_sel = `RF_SEL_SP_H; + comb_rf_wr_sel = `RF_SEL_H; + end + end + // LD SP, HL + else if (opcode == 8'hf9) begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_dst = `ALU_DST_REG; + + if (m_cycle == 0) begin + comb_rf_wr_sel = `RF_SEL_SP_H; + comb_rf_rd_sel = `RF_SEL_H; + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + else begin + comb_rf_wr_sel = `RF_SEL_SP_L; + comb_rf_rd_sel = `RF_SEL_L; + end + end + // LDH (a8), A + else if (opcode == 8'hE0) begin + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else if (m_cycle == 1) begin + comb_alu_src_a = `ALU_SRC_A_ACC; + comb_alu_dst = `ALU_DST_DB; + comb_bus_op = `BUS_OP_WRITE; + comb_db_src = `DB_SRC_ACC; + comb_ab_src = `AB_SRC_TEMP; + comb_ct_op = `CT_OP_IDLE; + comb_high_mask = 1; + comb_next = 1; + end + end + // LDH A, (a8) + else if (opcode == 8'hF0) begin + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else if (m_cycle == 1) begin + comb_bus_op = `BUS_OP_READ; + comb_ab_src = `AB_SRC_TEMP; + comb_ct_op = `CT_OP_IDLE; + comb_high_mask = 1; + comb_next = 1; + end + else begin + comb_alu_src_a = `ALU_SRC_A_DB; + comb_alu_dst = `ALU_DST_ACC; + end + end + // LD (a16), A + else if (opcode == 8'hEA) begin + if ((m_cycle == 0) || (m_cycle == 1)) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else if (m_cycle == 2) begin + comb_alu_src_a = `ALU_SRC_A_ACC; + comb_alu_dst = `ALU_DST_DB; + comb_bus_op = `BUS_OP_WRITE; + comb_db_src = `DB_SRC_ACC; + comb_ab_src = `AB_SRC_TEMP; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + // LDH A, (a16) + else if (opcode == 8'hFA) begin + if ((m_cycle == 0) || (m_cycle == 1)) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else if (m_cycle == 2) begin + comb_bus_op = `BUS_OP_READ; + comb_ab_src = `AB_SRC_TEMP; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + else begin + comb_alu_src_a = `ALU_SRC_A_DB; + comb_alu_dst = `ALU_DST_ACC; + end + end + // JP HL + else if (opcode == 8'hE9) begin + comb_rf_rd_sel = `RF_SEL_H; + comb_ab_src = `AB_SRC_REG; + comb_pc_we = 1; + end + // JP CC, a16 + else if ((opcode == 8'hC3) || (opcode == 8'hC2) || (opcode == 8'hD2) + || (opcode == 8'hCA) || (opcode == 8'hDA)) begin + if ((m_cycle == 0) || (m_cycle == 1)) begin + // Read 16 bit imm + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else if (m_cycle == 2) begin + if (((opcode == 8'hC2) && (!f_z)) || // JP NZ + ((opcode == 8'hD2) && (!f_c)) || // JP NC + ((opcode == 8'hC3)) || // JP + ((opcode == 8'hCA) && (f_z)) || // JP Z + ((opcode == 8'hDA) && (f_c))) begin // JP C + // Branch taken + comb_pc_src = `PC_SRC_TEMP; + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_pc_we = 1; + comb_next = 1; + end + // Branch not taken + end + end + // CALL CC, a16 + else if ((opcode == 8'hCD) || (opcode == 8'hCC) || (opcode == 8'hDC) + || (opcode == 8'hC4) || (opcode == 8'hD4)) begin + if ((m_cycle == 0) || (m_cycle == 1)) begin + // Read 16 bit imm + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else if (m_cycle == 2) begin + if (((opcode == 8'hC4) && (!f_z)) || // CALL NZ + ((opcode == 8'hD4) && (!f_c)) || // CALL NC + ((opcode == 8'hCD)) || // CALL + ((opcode == 8'hCC) && (f_z)) || // CALL Z + ((opcode == 8'hDC) && (f_c))) begin // CALL C + // Call taken + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_SP_DEC; + comb_next = 1; + end + end + else if (m_cycle == 3) begin + comb_alu_src_a = `ALU_SRC_A_PC; + comb_alu_dst = `ALU_DST_DB; + comb_bus_op = `BUS_OP_WRITE; + comb_ab_src = `AB_SRC_SP; + comb_db_src = `DB_SRC_DB; + comb_ct_op = `CT_OP_SP_DEC; + comb_next = 1; + end + else if (m_cycle == 4) begin + comb_alu_src_a = `ALU_SRC_A_PC; + comb_alu_dst = `ALU_DST_DB; + comb_pc_src = `PC_SRC_TEMP; + comb_pc_we = 1; + comb_bus_op = `BUS_OP_WRITE; + comb_ab_src = `AB_SRC_SP; + comb_db_src = `DB_SRC_DB; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + // JR CC, imm8 + else if ((opcode == 8'h20) || (opcode == 8'h30) || (opcode == 8'h18) + || (opcode == 8'h28) || (opcode == 8'h38)) begin + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else if (m_cycle == 1) begin + if (((opcode == 8'h20) && (!f_z)) || // JR NZ + ((opcode == 8'h30) && (!f_c)) || // JR NC + ((opcode == 8'h18)) || // JR + ((opcode == 8'h28) && (f_z)) || // JR Z + ((opcode == 8'h38) && (f_c))) begin // JR C + comb_bus_op = `BUS_OP_IDLE; + comb_pc_jr = 1; + comb_next = 1; + end + end + end + // RET, RETI + else if ((opcode == 8'hC9) || (opcode == 8'hD9)) begin + if (m_cycle == 0) begin + if (opcode == 8'hD9) begin + ime_set = 1; + end + comb_ab_src = `AB_SRC_SP; + comb_db_src = `DB_SRC_DB; + comb_bus_op = `BUS_OP_READ; + comb_ct_op = `CT_OP_SP_INC; + comb_next = 1; + end + else if (m_cycle == 1) begin + comb_ab_src = `AB_SRC_SP; + comb_db_src = `DB_SRC_DB; + comb_bus_op = `BUS_OP_READ; + comb_ct_op = `CT_OP_SP_INC; + comb_alu_src_a = `ALU_SRC_A_DB; + comb_alu_dst = `ALU_DST_PC; + comb_pc_b_sel = 0; + comb_next = 1; + end + else if (m_cycle == 2) begin + comb_alu_src_a = `ALU_SRC_A_DB; + comb_alu_dst = `ALU_DST_PC; + comb_pc_b_sel = 1; + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + // RET CC + else if ((opcode[7:5] == 3'b110) && (opcode[2:0] == 3'b000)) begin + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + else if (m_cycle == 1) begin + if (((opcode == 8'hC0) && (!f_z)) || // RET NZ + ((opcode == 8'hD0) && (!f_c)) || // RET NC + ((opcode == 8'hC8) && (f_z)) || // RET Z + ((opcode == 8'hD8) && (f_c))) begin // RET C + comb_ab_src = `AB_SRC_SP; + comb_db_src = `DB_SRC_DB; + comb_bus_op = `BUS_OP_READ; + comb_ct_op = `CT_OP_SP_INC; + comb_next = 1; + end + end + else if (m_cycle == 2) begin + comb_ab_src = `AB_SRC_SP; + comb_db_src = `DB_SRC_DB; + comb_bus_op = `BUS_OP_READ; + comb_ct_op = `CT_OP_SP_INC; + comb_alu_src_a = `ALU_SRC_A_DB; + comb_alu_dst = `ALU_DST_PC; + comb_pc_b_sel = 0; + comb_next = 1; + end + else if (m_cycle == 3) begin + comb_alu_src_a = `ALU_SRC_A_DB; + comb_alu_dst = `ALU_DST_PC; + comb_pc_b_sel = 1; + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + // RST + else if ((opcode[7:6] == 2'b11) && (opcode[2:0] == 3'b111)) begin + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_IDLE; + comb_ct_op = `CT_OP_SP_DEC; + comb_next = 1; + end + else if (m_cycle == 1) begin + comb_alu_src_a = `ALU_SRC_A_PC; + comb_alu_dst = `ALU_DST_DB; + comb_db_src = `DB_SRC_DB; + comb_ab_src = `AB_SRC_SP; + comb_bus_op = `BUS_OP_WRITE; + comb_ct_op = `CT_OP_SP_DEC; + comb_next = 1; + end + else if (m_cycle == 2) begin + comb_alu_src_a = `ALU_SRC_A_PC; + comb_alu_dst = `ALU_DST_DB; + comb_db_src = `DB_SRC_DB; + comb_ab_src = `AB_SRC_SP; + comb_pc_src = `PC_SRC_RST; + comb_pc_we = 1; + comb_bus_op = `BUS_OP_WRITE; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + end + // RLCA, RRCA, RLA, RRA + else if ((opcode[7:5] == 3'b000) && (opcode[2:0] == 3'b111)) begin + comb_alu_src_b = `ALU_SRC_B_ACC; + comb_alu_op_prefix = `ALU_OP_PREFIX_SHIFT_ROTATE; + comb_alu_op_src = `ALU_OP_SRC_INSTR_5TO3; + comb_flags_pattern = `FLAGS_00HC; + comb_flags_we = 1; + end + // DAA, CPL, SCF, CCF + else if ((opcode[7:5] == 3'b001) && (opcode[2:0] == 3'b111)) begin + comb_alu_src_b = `ALU_SRC_B_ACC; + comb_alu_op_prefix = `ALU_OP_PREFIX_SPECIAL; + comb_alu_op_src = `ALU_OP_SRC_INSTR_5TO3; + comb_flags_we = 1; + end + // CB prefix + else if (opcode == 8'hCB) begin + if (m_cycle == 0) begin + comb_bus_op = `BUS_OP_READ; + comb_db_src = `DB_SRC_DB; + comb_next = 1; + end + else if (m_cycle == 1) begin + comb_opcode_redir = 1'b1; + if (cb[2:0] == 3'b110) begin + comb_alu_src_a = `ALU_SRC_A_DB; + comb_alu_dst = `ALU_DST_DB; + comb_ct_op = `CT_OP_IDLE; + comb_ab_src = `AB_SRC_REG; + comb_bus_op = `BUS_OP_READ; + comb_flags_we = 0; + comb_next = 1; + end + else if (cb[2:0] == 3'b111) begin + comb_alu_src_a = `ALU_SRC_A_ACC; + comb_alu_dst = `ALU_DST_ACC; + comb_flags_we = !cb[7]; + end + else begin + comb_alu_src_a = `ALU_SRC_A_REG; + comb_alu_dst = `ALU_DST_REG; + comb_rf_rd_sel = cb[2:0]; + comb_rf_wr_sel = cb[2:0]; + comb_flags_we = !cb[7]; + end + if (cb[7:6] == 2'b00) begin + comb_alu_op_prefix = `ALU_OP_PREFIX_SHIFT_ROTATE; + comb_alu_op_src = `ALU_OP_SRC_INSTR_5TO3; + end + else begin + comb_alu_op_prefix = `ALU_OP_PREFIX_CB; + comb_alu_op_src = `ALU_OP_SRC_INSTR_7TO6; + end + if (cb[7:6] == 2'b01) begin + // Only affects flags + comb_alu_dst = `ALU_DST_DB; + end + end + else if (m_cycle == 2) begin + comb_opcode_redir = 1'b1; + comb_alu_src_a = `ALU_SRC_A_DB; + comb_alu_dst = `ALU_DST_DB; + if (cb[7:6] == 2'b00) begin + comb_alu_op_prefix = `ALU_OP_PREFIX_SHIFT_ROTATE; + comb_alu_op_src = `ALU_OP_SRC_INSTR_5TO3; + end + else begin + comb_alu_op_prefix = `ALU_OP_PREFIX_CB; + comb_alu_op_src = `ALU_OP_SRC_INSTR_7TO6; + end + if (cb[7:6] != 2'b01) begin + // Write-back cycle required. + comb_bus_op = `BUS_OP_WRITE; + comb_db_src = `DB_SRC_ALU; + comb_ab_src = `AB_SRC_REG; + comb_ct_op = `CT_OP_IDLE; + comb_next = 1; + end + comb_flags_we = !cb[7]; + end + end + end + end + + always @(posedge clk) begin + if ((ct_state == 2'd3) || (rst == 1'b1)) begin + alu_src_a <= comb_alu_src_a; + alu_src_b <= comb_alu_src_b; + alu_src_xchg <= comb_alu_src_xchg; + alu_op_prefix <= comb_alu_op_prefix; + alu_op_src <= comb_alu_op_src; + alu_op_signed <= comb_alu_op_signed; + alu_dst <= comb_alu_dst; + pc_src <= comb_pc_src; + pc_we <= comb_pc_we; + pc_b_sel <= comb_pc_b_sel; + pc_jr <= comb_pc_jr; + pc_revert <= comb_pc_revert; + rf_wr_sel <= comb_rf_wr_sel; + rf_rd_sel <= comb_rf_rd_sel; + rf_rdw_sel <= comb_rf_rdw_sel; + temp_redir <= comb_temp_redir; + opcode_redir <= comb_opcode_redir; + bus_op <= comb_bus_op; + db_src <= comb_db_src; + ab_src <= comb_ab_src; + ct_op <= comb_ct_op; + flags_we <= comb_flags_we; + flags_pattern <= comb_flags_pattern; + high_mask <= comb_high_mask; + int_ack <= comb_int_ack; + next <= comb_next; + stop <= comb_stop; + halt <= comb_halt; + fault <= comb_fault; + end + end + +endmodule
diff --git a/verilog/rtl/cpu.v b/verilog/rtl/cpu.v new file mode 100644 index 0000000..6b59982 --- /dev/null +++ b/verilog/rtl/cpu.v
@@ -0,0 +1,647 @@ +`timescale 1ns / 1ps +`default_nettype wire +//////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 17:30:26 02/08/2018 +// Module Name: cpu +// Project Name: VerilogBoy +// Description: +// The Game Boy CPU. +// Dependencies: +// +// Additional Comments: +// See doc/cpu_internal.md for signal definitions +//////////////////////////////////////////////////////////////////////////////// + +module cpu( + input clk, + input rst, + output reg phi, + output wire [1:0] ct, + output reg [15:0] a, + output reg [7:0] dout, + input [7:0] din, + output reg rd, + output reg wr, + input [4:0] int_en, + input [4:0] int_flags_in, + output wire [4:0] int_flags_out, + input [7:0] key_in, + output reg done, + output wire fault + ); + + reg [7:0] opcode; + reg [7:0] cb; + wire [2:0] m_cycle; + reg [2:0] m_cycle_early; + wire [1:0] alu_src_a; + wire [2:0] alu_src_b; + wire alu_src_xchg; + wire [1:0] alu_op_prefix; + wire [1:0] alu_op_src; + wire [1:0] alu_dst; + wire [1:0] pc_src; + wire pc_we; + wire [2:0] rf_wr_sel; + wire [2:0] rf_rd_sel; + wire [1:0] rf_rdw_sel; + wire [1:0] bus_op; + wire [1:0] db_src; + wire [1:0] ab_src; + wire [1:0] ct_op; + wire flags_we; + wire [1:0] flags_pattern; + wire high_mask; + wire next; + wire stop; + wire halt; + reg wake; + //wire fault; + reg int_dispatch; + wire int_master_en; + wire int_ack; + + wire [2:0] rf_rdn; + wire [7:0] rf_rd; + reg [7:0] rf_rd_ex; // Buffer Rd selected during EX stage + wire [1:0] rf_rdwn; + wire [15:0] rf_rdw; + wire [7:0] rf_h; + wire [7:0] rf_l; + wire [15:0] rf_sp; + wire [2:0] rf_wrn; + wire [7:0] rf_wr; + wire rf_we; + + wire [7:0] alu_a; + wire [7:0] alu_b; + wire [7:0] alu_result; + reg [7:0] alu_result_buffer; + wire [3:0] alu_flags_in; + wire [3:0] alu_flags_out; + wire [4:0] alu_op; + wire alu_op_signed; + wire alu_carry_out; + reg alu_carry_out_ex; + reg alu_carry_out_ct; + + wire [7:0] acc_wr; + wire acc_we; + wire [7:0] acc_rd; + + wire [15:0] pc_rd; + wire [7:0] pc_rd_b; + wire pc_b_sel; // byte select + wire [15:0] pc_wr; + wire [7:0] pc_wr_b; + wire pc_we_h; + wire pc_we_l; + + wire [15:0] temp_rd; // temp value for 16bit imm + + wire [3:0] flags_rd; + wire [3:0] flags_wr; + + wire [7:0] db_wr; // Data into buffer + wire [7:0] db_rd; // Data out from buffer + wire db_we; + + wire [7:0] imm_abs; + wire [7:0] imm_low; + wire [7:0] imm_ext; + + reg [1:0] ct_state; + + // Control Logic + // Control Logic is only used in EX stage + // Signals are gated. + wire [1:0] alu_src_a_ex; + wire [2:0] alu_src_b_ex; + wire [1:0] alu_op_prefix_ex; + wire [1:0] alu_op_src_ex; + wire alu_op_signed_ex; + wire [1:0] alu_dst_ex; + wire [2:0] rf_wr_sel_ex; + wire [2:0] rf_rd_sel_ex; + wire flags_we_ex; + wire pc_b_sel_ex; + wire pc_jr; + wire pc_we_ex; + wire pc_revert; + wire temp_redir; // redirect regfile operation to temp register + wire opcode_redir; + + control control( + .clk(clk), + .rst(rst), + .opcode_early(opcode), + .cb(cb), + .imm(imm_low), + .m_cycle_early(m_cycle_early), + .ct_state(ct_state), + .f_z(flags_rd[3]), + .f_c(flags_rd[0]), + .alu_src_a(alu_src_a_ex), + .alu_src_b(alu_src_b_ex), + .alu_src_xchg(alu_src_xchg), + .alu_op_prefix(alu_op_prefix_ex), + .alu_op_src(alu_op_src_ex), + .alu_op_signed(alu_op_signed_ex), + .alu_dst(alu_dst_ex), + .pc_src(pc_src), + .pc_we(pc_we_ex), + .pc_b_sel(pc_b_sel_ex), + .pc_jr(pc_jr), + .pc_revert(pc_revert), + .rf_wr_sel(rf_wr_sel_ex), + .rf_rd_sel(rf_rd_sel_ex), + .rf_rdw_sel(rf_rdw_sel), + .temp_redir(temp_redir), + .opcode_redir(opcode_redir), + .bus_op(bus_op), + .db_src(db_src), + .ab_src(ab_src), + .ct_op(ct_op), + .flags_we(flags_we_ex), + .flags_pattern(flags_pattern), + .high_mask(high_mask), + .int_master_en(int_master_en), + .int_dispatch(int_dispatch), + .int_ack(int_ack), + .next(next), + .stop(stop), + .halt(halt), + .wake(wake), + .fault(fault) + ); + + always @(posedge clk) begin + done <= stop | halt | fault; + // only used to stop simulation if needed + // and delay 1 clk + end + + wire wake_comb = + // Any enabled interrupt can wake up halted CPU, IME doesn't matter + (halt) ? ((int_flags_in & int_en) != 0) : ( + // Any enabled interrupt and any keypress can wake up stopped CPU + // IME doesn't matter. Though the typical usage is clear the IE before + // entering STOP mode, so only keypad can wake up the CPU. + (stop) ? (((int_flags_in & int_en) != 0) || (key_in != 0)) : + (1'b0)); + reg wake_delay; // Wake should be delayed for 1 Mcycle + always @(posedge clk) begin + if (ct_state == 2'b10) begin + wake_delay <= wake_comb; + wake <= wake_delay; + end + end + + wire [7:3] current_opcode; + + // Data Bus Buffer + reg [7:0] db_wr_buffer; + reg [7:0] db_rd_buffer; + + // Logic: if buffer is selected, use the data in the buffer, + // otherwise the buffer is overrided. + always @(posedge clk) begin + if (db_we) + db_wr_buffer <= alu_result; + end + assign db_rd = db_rd_buffer; + assign db_wr = ( + (db_src == 2'b00) ? (acc_rd) : ( + (db_src == 2'b01) ? (alu_result_buffer) : ( + (db_src == 2'b10) ? (rf_rd_ex) : ( + (db_src == 2'b11) ? (db_wr_buffer) : (8'b0))))); + assign db_we = (alu_dst == 2'b11); + + // Address Bus Buffer + wire [15:0] ab_wr; + assign ab_wr = ( + (ab_src == 2'b00) ? (pc_rd) : ( + (ab_src == 2'b01) ? ((high_mask) ? ({8'hFF, temp_rd[7:0]}) : (temp_rd)) : ( + (ab_src == 2'b10) ? ((high_mask) ? ({8'hFF, rf_rdw[7:0]}) : (rf_rdw)) : ( + (ab_src == 2'b11) ? (rf_sp) : (16'b0))))); + + // Interrupt + wire [4:0] int_flags_masked = int_flags_in & int_en & {5{int_master_en}}; + wire [4:0] int_flags_out_cleared = + (int_flags_masked[0]) ? (int_flags_in & 5'b11110) : ( + (int_flags_masked[1]) ? (int_flags_in & 5'b11101) : ( + (int_flags_masked[2]) ? (int_flags_in & 5'b11011) : ( + (int_flags_masked[3]) ? (int_flags_in & 5'b10111) : ( + (int_flags_masked[4]) ? (int_flags_in & 5'b01111) : ( + int_flags_in + ))))); + + assign int_flags_out = + ((int_dispatch)&&(pc_we)) ? (int_flags_out_cleared) : (int_flags_in); + + // Regisiter file + wire [7:0] rf_rd_raw; + regfile regfile( + .clk(clk), + .rst(rst), + .rdn(rf_rdn), + .rd(rf_rd_raw), + .rdwn(rf_rdwn), + .rdw(rf_rdw), + .h(rf_h), + .l(rf_l), + .sp(rf_sp), + .wrn(rf_wrn), + .wr(rf_wr), + .we(rf_we) + ); + assign rf_wr = alu_result; + assign rf_we = (alu_dst == 2'b10) && (!temp_redir); + assign rf_wrn = rf_wr_sel; + assign rf_rdn = rf_rd_sel; + assign rf_rdwn = rf_rdw_sel; + assign rf_rd = (!temp_redir) ? (rf_rd_raw) : ((rf_rd_sel[0]) ? (temp_rd[7:0]) : (temp_rd[15:8])); + always@(posedge clk) begin + if (rst) + rf_rd_ex <= 8'b0; + else + if (ct_state == 2'b00) + rf_rd_ex <= rf_rd_raw; + end + + // Register A + reg [15:0] imm_reg; + singlereg #(8) acc( + .clk(clk), + .rst(rst), + .wr(acc_wr), + .we(acc_we), + .rd(acc_rd) + ); + assign acc_wr = ((db_src == 2'b00) && (bus_op == 2'b11)) ? (imm_reg[7:0]) : (alu_result); + assign acc_we = ((alu_dst == 2'b00) || ((db_src == 2'b00) && (bus_op == 2'b11))); + + // Register PC + reg [15:0] pc; + reg [15:0] last_pc; + assign pc_rd = pc; + assign pc_rd_b = (pc_b_sel == 1'b0) ? (pc[7:0]) : (pc[15:8]); + assign pc_wr_b = alu_result; + assign pc_wr = ( + (pc_src == 2'b00) ? (rf_rdw) : ( + (pc_src == 2'b01) ? ({10'b00, opcode[5:3], 3'b000}) : ( + (pc_src == 2'b10) ? (temp_rd) : ( + (pc_src == 2'b11) ? (16'b0) : (16'b0))))); + wire [15:0] pc_int = + (int_flags_masked[0]) ? (16'h0040) : ( + (int_flags_masked[1]) ? (16'h0048) : ( + (int_flags_masked[2]) ? (16'h0050) : ( + (int_flags_masked[3]) ? (16'h0058) : ( + (int_flags_masked[4]) ? (16'h0060) : ( + // no interrupts anymore, dispatching is cancelled. + // jump to 0000 instead + // this behavior is tested by acceptence/interrupts/ie_push + 16'h0000 + ))))); + assign pc_we_l = ((alu_dst == 2'b01) && (pc_b_sel == 1'b0)) ? (1'b1) : (1'b0); + assign pc_we_h = ((alu_dst == 2'b01) && (pc_b_sel == 1'b1)) ? (1'b1) : (1'b0); + always @(posedge clk) begin + if (rst) + pc <= 16'b0; + else begin + if (pc_we_l) begin + pc[7:0] <= pc_wr_b; + last_pc[7:0] <= pc[7:0]; + end + else if (pc_we_h) begin + pc[15:8] <= pc_wr_b; + last_pc[15:8] <= pc[15:8]; + end + else if (pc_revert) + pc <= last_pc; + else if (pc_we) + if (int_dispatch) + // this might need to be deffered + pc <= pc_int; + else begin + pc <= pc_wr; + last_pc <= pc; + end + end + end + + // Register F + /*singlereg #(4) flags( + .clk(clk), + .rst(rst), + .wr(flags_wr), + .we((flags_we != 2'b00) ? 1'b1 : 1'b0), + .rd(flags_rd) + );*/ + reg [3:0] flags; + always @(posedge clk) begin + if (rst) + flags <= 4'b0; + else if (flags_we) + if (flags_pattern == 2'b00) + flags[3:0] <= flags_wr[3:0]; + else if (flags_pattern == 2'b01) + flags[2:0] <= {1'b0, flags_wr[1:0]}; + else if (flags_pattern == 2'b10) + flags[3:0] <= {2'b0, flags_wr[1:0]}; + else if (flags_pattern == 2'b11) + flags[3:1] <= flags_wr[3:1]; + end + assign flags_rd = flags; + assign flags_wr = alu_flags_out; + + + // ALU + wire [2:0] alu_op_mux; + wire [7:0] alu_a_pre; + wire [7:0] alu_b_pre; + + alu alu( + .alu_a(alu_a), + .alu_b(alu_b), + .alu_bit_index(imm_reg[5:3]), + .alu_result(alu_result), + .alu_flags_in(alu_flags_in), + .alu_flags_out(alu_flags_out), + .alu_op(alu_op) + ); + + assign alu_a_pre = ( + (alu_src_a == 2'b00) ? (acc_rd) : ( + (alu_src_a == 2'b01) ? (pc_rd_b) : ( + (alu_src_a == 2'b10) ? (rf_rd) : ( + (alu_src_a == 2'b11) ? (db_rd) : (8'b0))))); + + assign alu_b_pre = ( + (alu_src_b == 3'b000) ? (acc_rd) : ( + (alu_src_b == 3'b001) ? ({7'b0, alu_carry_out}) : ( + (alu_src_b == 3'b010) ? (8'd0) : ( + (alu_src_b == 3'b011) ? (8'd1) : ( + (alu_src_b == 3'b100) ? (rf_h) : ( + (alu_src_b == 3'b101) ? (rf_l) : ( + (alu_src_b == 3'b110) ? (imm_abs) : ( + (alu_src_b == 3'b111) ? ((pc_b_sel) ? (imm_low) : (imm_ext)) : (8'b0))))))))); // cursed + + assign alu_a = (alu_src_xchg) ? (alu_b_pre) : (alu_a_pre); + assign alu_b = (alu_src_xchg) ? (alu_a_pre) : (alu_b_pre); + + assign alu_op_mux = ( + (alu_op_src == 2'b00) ? (current_opcode[5:3]) : ( + (alu_op_src == 2'b01) ? ({1'b1, current_opcode[7:6]}) : ( + (alu_op_src == 2'b10) ? ((alu_op_signed) ? (3'b001) : (3'b000)) : ( + (alu_op_src == 2'b11) ? ((alu_op_signed) ? (3'b011) : (3'b010)) : (3'b0))))); + + assign alu_flags_in = flags_rd; + assign alu_op = {alu_op_prefix, alu_op_mux}; + + assign current_opcode[7:3] = (opcode_redir) ? (imm_reg[7:3]) : (opcode[7:3]); + + // CT FSM + wire [1:0] ct_next_state; + + assign ct_next_state = ct_state + 2'b01; + always @(posedge clk) begin + if (rst) + ct_state <= 2'b00; + else + ct_state <= ct_next_state; + end + + assign ct = ct_state; + + //reg [15:0] imm_reg; decleared before + assign temp_rd = imm_reg; + assign imm_low = imm_reg[7:0]; + assign imm_ext = {8{imm_reg[7]}}; + assign imm_abs = (imm_reg[7]) ? (~imm_reg[7:0] + 1'b1) : (imm_reg[7:0]); + + // CT - FSM / Bus Operation + always @(posedge clk) begin + if (rst) begin + a <= 16'b0; + rd <= 1'b0; + wr <= 1'b0; + phi <= 1; + opcode <= 8'b0; + imm_reg <= 16'b0; + db_rd_buffer <= 8'b0; + dout <= 8'b0; + int_dispatch <= 1'b0; + alu_result_buffer <= 8'b0; + end + else begin + if ((alu_dst == 2'b10) && temp_redir && !(ct_state == 2'b10 && bus_op == 2'b11)) + if (rf_wr_sel[0]) imm_reg[7:0] <= rf_wr; + else imm_reg[15:8] <= rf_wr; + + case (ct_state) + 2'b00: begin + // Setup Address + a <= ab_wr; + rd <= ((bus_op == 2'b01)||(bus_op == 2'b11)) ? (1'b1) : (1'b0); + wr <= 0; + phi <= 1; + // Backup ALU results + alu_result_buffer <= alu_result; + end + 2'b01: begin + // Read in progress + end + 2'b10: begin + if (bus_op == 2'b10) begin + // Write cycle + wr <= 1; + dout <= db_wr; + end + else if (bus_op == 2'b01) begin + // Instruction Fetch Cycle + wr <= 0; + opcode <= din; + end + else if (bus_op == 2'b11) begin + // Data Read cycle + wr <= 0; + db_rd_buffer <= din; + if ((opcode == 8'hCB) && (m_cycle == 0)) cb <= din[7:0]; + // mcycle is slower + if (m_cycle == 3'd0) imm_reg[7:0] <= din; + else if (m_cycle == 3'd1) imm_reg[15:8] <= din; + end + else begin + wr <= 0; + end + rd <= 0; + phi <= 0; + + // Interrupt dispatch happens here + // Guarenteed if it is at instruction fetch cycle, + // It is at instruction boundaries, + // and m_cycle will start from 0. + if ((!int_dispatch) && (int_flags_masked != 0) && (int_master_en) && ((bus_op == 2'b01) || (halt == 1'b1))) + int_dispatch <= 1'b1; + else if ((int_dispatch) && (int_ack)) begin + int_dispatch <= 1'b0; + end + end + 2'b11: begin + // Bus Idle + rd <= 0; + wr <= 0; + dout <= 8'b0; + end + endcase + end + end + + // CT - FSM / Instruction Execution + reg [1:0] alu_src_a_ct; + reg [2:0] alu_src_b_ct; + wire [1:0] alu_op_prefix_ct = 2'b00; + reg [1:0] alu_op_src_ct; + reg [1:0] alu_dst_ct; + reg [2:0] rf_wr_sel_ct; + reg [2:0] rf_rd_sel_ct; + reg pc_b_sel_ct; + + + always @(*) begin + // Do nothing by default + alu_src_a_ct = 2'b00; // From A + alu_src_b_ct = 3'b010; // Constant 0 + alu_op_src_ct = 2'b10; // Add + alu_dst_ct = 2'b00; // To A + rf_wr_sel_ct = 3'b000; + rf_rd_sel_ct = 3'b000; + pc_b_sel_ct = 1'b0; + case (ct_state) + 2'b00: begin + // Decoding and Execution + // Actually cannot control anything + end + 2'b01: begin + // CT_OP first clock + case (ct_op) + 2'b00: begin + // Do nothing + end + 2'b01: begin + // Calculate PC low + 1 + pc_b_sel_ct = 1'b0; + alu_src_a_ct = 2'b01; // From PC byte + alu_src_b_ct = (pc_jr) ? (3'b110) : (3'b011); // Imm Abs or Constant 1 + alu_op_src_ct = (pc_jr) ? (imm_low[7] ? 2'b11 : 2'b10) : 2'b10; // Add + alu_dst_ct = 2'b01; // To PC byte + end + 2'b10: begin + // Calculate SP low - 1 + rf_rd_sel_ct = 3'b111; // Read from SP low + rf_wr_sel_ct = 3'b111; // Write to SP low + alu_src_a_ct = 2'b10; // From register file + alu_src_b_ct = 3'b011; // Constant 1 + alu_op_src_ct = 2'b11; // Sub + alu_dst_ct = 2'b10; // To register file + end + 2'b11: begin + // Calculate SP low + 1 + rf_rd_sel_ct = 3'b111; // Read from SP low + rf_wr_sel_ct = 3'b111; // Write to SP low + alu_src_a_ct = 2'b10; // From register file + alu_src_b_ct = 3'b011; // Constant 1 + alu_op_src_ct = 2'b10; // Add + alu_dst_ct = 2'b10; // To register file + end + endcase + end + 2'b10: begin + // CT_OP second clock + case (ct_op) + 2'b00: begin + // Do nothing + end + 2'b01: begin + // Calculate PC high + carry + pc_b_sel_ct = 1'b1; + alu_src_a_ct = 2'b01; // From PC byte + alu_src_b_ct = 3'b001; // Carry + alu_op_src_ct = (pc_jr) ? (imm_low[7] ? 2'b11 : 2'b10) : 2'b10; // Add + alu_dst_ct = 2'b01; // To PC byte + end + 2'b10: begin + // Calculate SP high - carry + rf_rd_sel_ct = 3'b110; // Read from SP high + rf_wr_sel_ct = 3'b110; // Write to SP high + alu_src_a_ct = 2'b10; // From register file + alu_src_b_ct = 3'b001; // Carry + alu_op_src_ct = 2'b11; // Sub + alu_dst_ct = 2'b10; // To register file + end + 2'b11: begin + // Calculate SP high + carry + rf_rd_sel_ct = 3'b110; // Read from SP high + rf_wr_sel_ct = 3'b110; // Write to SP high + alu_src_a_ct = 2'b10; // From register file + alu_src_b_ct = 3'b001; // Carry + alu_op_src_ct = 2'b10; // Add + alu_dst_ct = 2'b10; // To register file + end + endcase + end + 2'b11: begin + // End, it is safe to overwrite DB as doing nothing + alu_dst_ct = 2'b11; + end + endcase + end + + assign alu_src_a = (ct_state == 2'b00) ? (alu_src_a_ex) : (alu_src_a_ct); + assign alu_src_b = (ct_state == 2'b00) ? (alu_src_b_ex) : (alu_src_b_ct); + assign alu_op_prefix = (ct_state == 2'b00) ? (alu_op_prefix_ex) : (alu_op_prefix_ct); + assign alu_op_src = (ct_state == 2'b00) ? (alu_op_src_ex) : (alu_op_src_ct); + assign alu_op_signed = (ct_state == 2'b00) ? (alu_op_signed_ex) : (1'b0); + assign alu_dst = (ct_state == 2'b00) ? (alu_dst_ex) : (alu_dst_ct); + assign rf_wr_sel = (ct_state == 2'b00) ? (rf_wr_sel_ex) : (rf_wr_sel_ct); + assign rf_rd_sel = (ct_state == 2'b00) ? (rf_rd_sel_ex) : (rf_rd_sel_ct); + assign flags_we = (ct_state == 2'b00) ? (flags_we_ex) : (1'b0); + assign pc_b_sel = (ct_state == 2'b00) ? (pc_b_sel_ex) : (pc_b_sel_ct); + assign pc_we = (ct_state == 2'b00) ? (pc_we_ex) : (1'b0); + assign alu_carry_out = (ct_state == 2'b00) ? (alu_carry_out_ex) : (alu_carry_out_ct); + + // EX - FSM / Mutli-M-cycle Instruction Handling + reg [2:0] ex_state; + wire [2:0] ex_next_state; + + assign ex_next_state = (next) ? (ex_state + 3'd1) : (3'd0); + + always @(posedge clk) begin + if (rst) begin + ex_state <= 3'd0; + m_cycle_early <= 3'd0; + alu_carry_out_ex <= 1'b0; + alu_carry_out_ct <= 1'b0; + end + else begin + alu_carry_out_ct <= alu_flags_out[0]; + if (ct_state == 2'b11) begin + ex_state <= ex_next_state; + end + else if (ct_state == 2'b10) begin + m_cycle_early <= ex_next_state; + end + else if (ct_state == 2'b00) begin + // Backup flag output + alu_carry_out_ex <= alu_flags_out[0]; + end + end + end + + assign m_cycle = ex_state; + +endmodule
diff --git a/verilog/rtl/dma.v b/verilog/rtl/dma.v new file mode 100644 index 0000000..5aaadaa --- /dev/null +++ b/verilog/rtl/dma.v
@@ -0,0 +1,164 @@ +`timescale 1ns / 1ps +/** + * Block transfer unit for the GB80 CPU. + * + * Original Author: Joseph Carlos (jdcarlos1@gmail.com) + * Modified: Wenting Zhang (zephray@outlook.com) + */ + +/** + * The DMA unit. + * + * Contains the DMA register and performs DMA transfers when the register is + * written to. Each transfer takes 320 cycles rather than the canon 640, this + * is because there's no reason to take 640. + * + * @inout addr_ext The address bus. + * @inout data_ext The data bus. + * @output dma_transfer 1 if a transfer is occurring, 0 otherwise. + * @input mem_re 1 if the processor is reading from memory. + * @input mem_we 1 if the processor is writing to memory. + * @input clock The CPU clock. + * @input reset The CPU reset. + */ +module dma( + input wire clk, + //input wire phi, + input wire rst, + output reg dma_rd, + output reg dma_wr, + //output wire dma_rd_comb, + //output wire dma_wr_comb, + output reg [15:0] dma_a, + input wire [7:0] dma_din, + output reg [7:0] dma_dout, + input wire mmio_wr, + input wire [7:0] mmio_din, + output wire [7:0] mmio_dout, + output wire dma_occupy_extbus, + output wire dma_occupy_vidbus, + output wire dma_occupy_oambus + ); + + // DMA data blocks ///////////////////////////////////////////////////////// + + reg [7:0] dma_start_addr; + reg [7:0] count; + + assign mmio_dout = dma_start_addr; + + reg cpu_mem_disable; + + assign dma_occupy_extbus = cpu_mem_disable & + ((dma_start_addr <= 8'h7f) || (dma_start_addr >= 8'ha0)); + assign dma_occupy_vidbus = cpu_mem_disable & + ((dma_start_addr >= 8'h80) && (dma_start_addr <= 8'h9f)); + assign dma_occupy_oambus = cpu_mem_disable; + + // DMA transfer logic ////////////////////////////////////////////////////// + + localparam DMA_IDLE = 'd0; + localparam DMA_TRANSFER_READ_ADDR = 'd1; + localparam DMA_TRANSFER_READ_DATA = 'd2; + localparam DMA_TRANSFER_WRITE_DATA = 'd3; + localparam DMA_TRANSFER_WRITE_WAIT = 'd4; + localparam DMA_DELAY = 'd5; + + reg [2:0] state; + + always @(posedge clk) begin + if (rst) begin + dma_start_addr <= 8'h00; + end + else begin + if (mmio_wr) begin + // Writing is always valid regardless of the state + dma_start_addr <= mmio_din; + end + end + end + + always @(posedge clk) begin + if (rst) begin + state <= DMA_IDLE; + count <= 8'd0; + dma_wr <= 1'b0; + dma_rd <= 1'b0; + cpu_mem_disable <= 1'b0; + end + else begin + case (state) + DMA_IDLE: begin + dma_wr <= 1'b0; + dma_rd <= 1'b0; + cpu_mem_disable <= 1'b0; + if (mmio_wr) begin + // Transfer starts on next cycle + state <= DMA_DELAY; + count <= 8'd3; // Delay before start + end + else + count <= 8'd0; + end + DMA_DELAY: begin + if (count != 8'd0) begin + count <= count - 1; + end + else begin + state <= DMA_TRANSFER_READ_ADDR; + end + end + DMA_TRANSFER_READ_ADDR: begin + dma_wr <= 1'b0; + cpu_mem_disable <= 1'b1; + // Load the temp register with data from memory + dma_a <= {dma_start_addr, count}; // Output read address + dma_rd <= 1'b1; + if (mmio_wr) begin // Allow re-triggering + state <= DMA_DELAY; + count <= 8'd3; // Delay before start + end + else + state <= DMA_TRANSFER_READ_DATA; + end + DMA_TRANSFER_READ_DATA: begin + state <= DMA_TRANSFER_WRITE_DATA; + // Basically wait + end + DMA_TRANSFER_WRITE_DATA: begin + // Read data + dma_dout <= dma_din; + dma_rd <= 1'b0; + // Write the temp register to memory + dma_a <= {8'hfe, count}; // Output write address + dma_wr <= 1'b1; + if (mmio_wr) begin // Allow re-triggering + state <= DMA_DELAY; + count <= 8'd3; // Delay before start + end + else + state <= DMA_TRANSFER_WRITE_WAIT; + end + DMA_TRANSFER_WRITE_WAIT: begin + // Wait + if (mmio_wr) begin // Allow re-triggering + state <= DMA_DELAY; + count <= 8'd3; // Delay before start + end + else + if (count == 8'h9f) begin + state <= DMA_IDLE; + count <= 8'd0; + end + else begin + state <= DMA_TRANSFER_READ_ADDR; + count <= count + 8'd1; + end + end + default: begin + end + endcase + end + end + +endmodule // dma
diff --git a/verilog/rtl/mbc5.v b/verilog/rtl/mbc5.v new file mode 100644 index 0000000..97ffd67 --- /dev/null +++ b/verilog/rtl/mbc5.v
@@ -0,0 +1,82 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 23:34:43 03/15/2018 +// Design Name: +// Module Name: mbc5 +// Project Name: +// Target Devices: +// Tool versions: +// Description: +// +// Dependencies: +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module mbc5( + input vb_clk, + input [15:12] vb_a, + input [7:0] vb_d, + input vb_wr, + input vb_rd, + input vb_rst, + output [22:14] rom_a, + output [16:13] ram_a, + output rom_cs_n, + output ram_cs_n + ); + + reg [8:0] rom_bank; + reg [3:0] ram_bank; + reg ram_en = 1'b0; // RAM Access Enable + + wire rom_addr_en; // RW Address in ROM range + wire ram_addr_en; // RW Address in RAM range + wire rom_addr_lo; // RW Address in LoROM range + + wire [15:0] vb_addr; + + assign vb_addr[15:12] = vb_a[15:12]; + assign vb_addr[11:0] = 12'b0; + + assign rom_addr_en = (vb_addr >= 16'h0000)&(vb_addr <= 16'h7FFF); //Request Addr in ROM range + assign ram_addr_en = (vb_addr >= 16'hA000)&(vb_addr <= 16'hBFFF); //Request Addr in RAM range + assign rom_addr_lo = (vb_addr >= 16'h0000)&(vb_addr <= 16'h3FFF); //Request Addr in LoROM range + + assign rom_cs_n = ((rom_addr_en) & (vb_rst == 0)) ? 1'b0 : 1'b1; //ROM output enable + assign ram_cs_n = ((ram_addr_en) & (ram_en) & (vb_rst == 0)) ? 1'b0 : 1'b1; //RAM output enable + + assign rom_a[22:14] = rom_addr_lo ? 9'b0 : rom_bank[8:0]; + assign ram_a[16:13] = ram_bank[3:0]; + + reg vb_wr_last; + + always@(posedge vb_clk, posedge vb_rst) + begin + if (vb_rst) begin + vb_wr_last <= 1'b0; + rom_bank[8:0] <= 9'b000000001; + ram_bank[3:0] <= 4'b0000; + ram_en <= 1'b0; + end + else begin + vb_wr_last <= vb_wr; + if ((vb_wr_last == 0)&&(vb_wr == 1)) begin + case (vb_addr) + 16'h0000: ram_en <= (vb_d[3:0] == 4'hA) ? 1'b1 : 1'b0; + 16'h1000: ram_en <= (vb_d[3:0] == 4'hA) ? 1'b1 : 1'b0; + 16'h2000: rom_bank[7:0] <= vb_d[7:0]; + 16'h3000: rom_bank[8] <= vb_d[0]; + 16'h4000: ram_bank[3:0] <= vb_d[3:0]; + 16'h5000: ram_bank[3:0] <= vb_d[3:0]; + endcase + end + end + end + +endmodule
diff --git a/verilog/rtl/ppu.v b/verilog/rtl/ppu.v new file mode 100644 index 0000000..bf4e1e8 --- /dev/null +++ b/verilog/rtl/ppu.v
@@ -0,0 +1,855 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 18:48:36 02/14/2018 +// Design Name: +// Module Name: ppu +// Project Name: +// Target Devices: +// Tool versions: +// Description: +// GameBoy PPU +// Additional Comments: +// There are three hardware layers in the GameBoy PPU: Background, Window, and +// Object (or sprites). +// +// Window will render above the background and the object can render above the +// background or under the background. Each object have a priority bit to +// indicate where it should be rendered. +// +// Background, Window, and Object can be individually turned on or off. When +// nothing is turned on, it displays white. +// +// The whole render logic does NOT require a scanline buffer to work, and it +// runs at 4MHz (VRAM runs at 2MHz) +// +// There are two main parts of the logic, implemented in a big FSM. The first +// one is the fetch unit, and the other is the pixel FIFO. +// +// The pixel FIFO shifts out one pixel when it contains more than 8 pixels, the +// fetch unit would generally render 8 pixels in 6 cycles (so 2 wait cycles are +// inserted so they are in sync generally). When there is no enough pixels, +// the FIFO would stop and wait for the fetch unit. +// +// Windows Trigger is handled in the next state logic, there is a distinct state +// for the PPU to switch from background rendering to window rendering (flush +// the fifo and add wait cycles.) +// +// Object Trigger is handled in the state change block, in order to backup the +// previous state. Current RAM address is also backed up during the handling of +// object rendering. Once all the objects at this position has been rendered, +// the render state machine could be restored to its previous state. +// +// The output pixel clock is the inverted main clock, which is the same as the +// real Game Boy Pixel data would be put on the pixel bus on the negedge of +// clock, so the LCD would latch the data on the posedge. The original Game Boy +// used a gated clock to control if output is valid. Since gated clock is not +// recommend, I used a valid signal to indicate is output should be considered +// valid. +////////////////////////////////////////////////////////////////////////////////// +`default_nettype wire +module ppu( + input clk, + input rst, + // MMIO Bus, 0xFF40 - 0xFF4B, always visible to CPU + input wire [15:0] mmio_a, + output reg [7:0] mmio_dout, + input wire [7:0] mmio_din, + input wire mmio_rd, + input wire mmio_wr, + // VRAM Bus, 0x8000 - 0x9FFF + input wire [15:0] vram_a, + output wire [7:0] vram_dout, + input wire [7:0] vram_din, + input wire vram_rd, + input wire vram_wr, + // OAM Bus, 0xFE00 - 0xFE9F + input wire [15:0] oam_a, + output wire [7:0] oam_dout, + input wire [7:0] oam_din, + input wire oam_rd, + input wire oam_wr, + // Interrupt interface + output reg int_vblank_req, + output reg int_lcdc_req, + input int_vblank_ack, + input int_lcdc_ack, + // Pixel output + output cpl, // Pixel Clock, = ~clk + output reg [1:0] pixel, // Pixel Output + output reg valid, // Pixel Valid + output reg hs, // Horizontal Sync, High Valid + output reg vs, // Vertical Sync, High Valid + //Debug output + output [7:0] scx, + output [7:0] scy, + output [4:0] state + ); + + // Global Wires ? + integer i; + + // PPU registers + reg [7:0] reg_lcdc; //$FF40 LCD Control (R/W) + reg [7:0] reg_stat; //$FF41 LCDC Status (R/W) + reg [7:0] reg_scy; //$FF42 Scroll Y (R/W) + reg [7:0] reg_scx; //$FF43 Scroll X (R/W) + reg [7:0] reg_ly; //$FF44 LCDC Y-Coordinate (R) Write will reset the counter + reg [7:0] reg_dma; //$FF46 DMA, actually handled outside of PPU for now + reg [7:0] reg_lyc; //$FF45 LY Compare (R/W) + reg [7:0] reg_bgp; //$FF47 BG Palette Data (R/W) Non-CGB mode only + reg [7:0] reg_obp0; //$FF48 Object Palette 0 Data (R/W) Non-CGB mode only + reg [7:0] reg_obp1; //$FF49 Object Palette 1 Data (R/W) Non-CGB mode only + reg [7:0] reg_wy; //$FF4A Window Y Position (R/W) + reg [7:0] reg_wx; //$FF4B Window X Position (R/W) + + // Some interrupt related register + reg [7:0] reg_ly_last; + reg [1:0] reg_mode_last; // Next mode based on next state + + wire reg_lcd_en = reg_lcdc[7]; //0=Off, 1=On + wire reg_win_disp_sel = reg_lcdc[6]; //0=9800-9BFF, 1=9C00-9FFF + wire reg_win_en = reg_lcdc[5]; //0=Off, 1=On + wire reg_bg_win_data_sel = reg_lcdc[4]; //0=8800-97FF, 1=8000-8FFF + wire reg_bg_disp_sel = reg_lcdc[3]; //0=9800-9BFF, 1=9C00-9FFF + wire reg_obj_size = reg_lcdc[2]; //0=8x8, 1=8x16 + wire reg_obj_en = reg_lcdc[1]; //0=Off, 1=On + wire reg_bg_disp = reg_lcdc[0]; //0=Off, 1=On + wire reg_lyc_int = reg_stat[6]; + wire reg_oam_int = reg_stat[5]; + wire reg_vblank_int = reg_stat[4]; + wire reg_hblank_int = reg_stat[3]; + wire reg_coin_flag = reg_stat[2]; + wire [1:0] reg_mode = reg_stat[1:0]; + + localparam PPU_MODE_H_BLANK = 2'b00; + localparam PPU_MODE_V_BLANK = 2'b01; + localparam PPU_MODE_OAM_SEARCH = 2'b10; + localparam PPU_MODE_PIX_TRANS = 2'b11; + + localparam PPU_PAL_BG = 2'b00; + localparam PPU_PAL_OB0 = 2'b01; + localparam PPU_PAL_OB1 = 2'b10; + + reg [12:0] vram_addr_bg; + reg [12:0] vram_addr_obj; + wire [12:0] vram_addr_int; + wire [12:0] vram_addr_ext; + wire vram_addr_int_sel; // 0 - BG, 1 - OBJ + + assign vram_addr_int = (vram_addr_int_sel == 1'b1) ? (vram_addr_obj) : (vram_addr_bg); + + wire vram_access_ext = ((reg_mode == PPU_MODE_H_BLANK)|| + (reg_mode == PPU_MODE_V_BLANK)|| + (reg_mode == PPU_MODE_OAM_SEARCH)); + wire vram_access_int = ~vram_access_ext; + wire oam_access_ext = ((reg_mode == PPU_MODE_H_BLANK)|| + (reg_mode == PPU_MODE_V_BLANK)); + + wire [12:0] window_map_addr = (reg_win_disp_sel) ? (13'h1C00) : (13'h1800); + wire [12:0] bg_map_addr = (reg_bg_disp_sel) ? (13'h1C00) : (13'h1800); + wire [12:0] bg_window_tile_addr = (reg_bg_win_data_sel) ? (13'h0000) : (13'h0800); + + // PPU Memories + + // 8 bit WR, 16 bit RD, 160Bytes OAM + reg [7:0] oam_u [0: 79]; + reg [7:0] oam_l [0: 79]; + reg [7:0] oam_rd_addr_int; + wire [7:0] oam_rd_addr; + wire [7:0] oam_wr_addr; + reg [15:0] oam_data_out; + wire [7:0] oam_data_out_byte; + wire [7:0] oam_data_in; + wire oam_we; + + always @ (negedge clk) + begin + if (oam_we) begin + if (oam_wr_addr[0]) + oam_u[oam_wr_addr[7:1]] <= oam_data_in; + else + oam_l[oam_wr_addr[7:1]] <= oam_data_in; + end + else begin + oam_data_out <= {oam_u[oam_rd_addr[7:1]], oam_l[oam_rd_addr[7:1]]}; + end + end + + assign oam_wr_addr = oam_a[7:0]; + assign oam_rd_addr = (oam_access_ext) ? (oam_a[7:0]) : (oam_rd_addr_int); + assign oam_data_in = oam_din; + assign oam_data_out_byte = (oam_rd_addr[0]) ? oam_data_out[15:8] : oam_data_out[7:0]; + //assign oam_we = (wr)&(oam_access_ext); + assign oam_we = oam_wr; // What if always allow OAM access? + assign oam_dout = (oam_access_ext) ? (oam_data_out_byte) : (8'hFF); + + // 8 bit WR, 8 bit RD, 8KB VRAM + wire vram_we; + wire [12:0] vram_addr; + wire [7:0] vram_data_in; + wire [7:0] vram_data_out; + + singleport_ram #( + .WORDS(8192) + ) br_vram ( + .clka(~clk), + .wea(vram_we), + .addra(vram_addr[12:0]), + .dina(vram_data_in), + .douta(vram_data_out)); + + assign vram_addr_ext = vram_a[12:0]; + assign vram_addr = (vram_access_ext) ? (vram_addr_ext) : (vram_addr_int); + assign vram_data_in = vram_din; + assign vram_we = (vram_wr)&(vram_access_ext); + assign vram_dout = (vram_access_ext) ? (vram_data_out) : (8'hFF); + + // Pixel Pipeline + + // The pixel FIFO: 16 pixels, 4 bits each (2 bits color index, 2 bits palette index) + // Since in and out are 8 pixels aligned, it can be modeled as a ping-pong buffer + // of two 32 bits (8 pixels * 4 bits) group + reg [63:0] pf_data; // Pixel FIFO Data + wire [1:0] pf_output_pixel; + wire [7:0] pf_output_palette; + wire [1:0] pf_output_pixel_id; + wire [1:0] pf_output_palette_id; + assign {pf_output_pixel_id, pf_output_palette_id} = pf_data[63:60]; + assign pf_output_palette = (pf_output_palette_id == PPU_PAL_BG) ? (reg_bgp) : + (pf_output_palette_id == PPU_PAL_OB0) ? (reg_obp0) : + (pf_output_palette_id == PPU_PAL_OB1) ? (reg_obp1) : (8'hFF); + assign pf_output_pixel = (pf_output_pixel_id == 2'b11) ? (pf_output_palette[7:6]) : + (pf_output_pixel_id == 2'b10) ? (pf_output_palette[5:4]) : + (pf_output_pixel_id == 2'b01) ? (pf_output_palette[3:2]) : + (pf_output_pixel_id == 2'b00) ? (pf_output_palette[1:0]) : (2'b00); + reg [2:0] pf_empty; // Indicate if the Pixel FIFO is empty. + localparam PF_INITA = 3'd5; // When a line start... + localparam PF_INITB = 3'd4; // Line start, 2 pixels out, 8 rendered + localparam PF_EMPTY = 3'd3; // When the pipeline get flushed + localparam PF_HALF = 3'd2; // After flushed, 8 pixels in + localparam PF_FIN = 3'd1; // 16 pixels in, but still no wait cycles + localparam PF_FULL = 3'd0; // Normal + + assign cpl = ~clk; + //assign pixel = pf_output_pixel; + + // HV Timing + localparam PPU_H_FRONT = 9'd76; + localparam PPU_H_SYNC = 9'd4; // So front porch + sync = OAM search + localparam PPU_H_TOTAL = 9'd456; + localparam PPU_H_PIXEL = 9'd160; + // 8 null pixels in the front for objects which have x < 8, 8 bit counter + localparam PPU_H_OUTPUT = 8'd168; + localparam PPU_V_ACTIVE = 8'd144; + localparam PPU_V_BACK = 8'd9; + localparam PPU_V_SYNC = 8'd1; + localparam PPU_V_BLANK = 8'd10; + localparam PPU_V_TOTAL = 8'd154; + + // Raw timing counter + reg [8:0] h_count; + reg [7:0] v_count; + + // HV counter + always @(posedge clk) + begin + if (rst) begin + h_count <= 0; + hs <= 0; + v_count <= 0; + vs <= 0; + end + else begin + if(h_count < PPU_H_TOTAL - 1) + h_count <= h_count + 1'b1; + else begin + h_count <= 0; + if(v_count < PPU_V_TOTAL - 1) + v_count <= v_count + 1'b1; + else + v_count <= 0; + if(v_count == PPU_V_ACTIVE + PPU_V_BACK - 1) + vs <= 1; + if(v_count == PPU_V_ACTIVE + PPU_V_BACK + PPU_V_SYNC - 1) + vs <= 0; + end + if(h_count == PPU_H_FRONT - 1) + hs <= 1; + if(h_count == PPU_H_FRONT + PPU_H_SYNC - 1) + hs <= 0; + end + end + + // Render FSM + localparam S_IDLE = 5'd0; + localparam S_BLANK = 5'd1; // H Blank and V Blank + localparam S_OAMX = 5'd2; // OAM Search X check + localparam S_OAMY = 5'd3; // OAM Search Y check + localparam S_FTIDA = 5'd4; // Fetch Read Tile ID Stage A (Address Setup) + localparam S_FTIDB = 5'd5; // Fetch Read Tile ID Stage B (Data Read) + localparam S_FRD0A = 5'd6; // Fetch Read Data 0 Stage A + localparam S_FRD0B = 5'd7; // Fetch Read Data 0 Stage B + localparam S_FRD1A = 5'd8; // Fetch Read Data 1 Stage A + localparam S_FRD1B = 5'd9; // Fetch Read Data 1 Stage B + localparam S_FWAITA = 5'd10; // Fetch Wait Stage A (Idle) + localparam S_FWAITB = 5'd11; // Fetch Wait Stage B (Load to FIFO?) + localparam S_SWW = 5'd12; // Fetch Switch to Window + localparam S_OAMRDA = 5'd13; // OAM Read Stage A + localparam S_OAMRDB = 5'd14; // OAM Read Stage B + localparam S_OFRD0A = 5'd15; // Object Fetch Read Data 0 Stage A + localparam S_OFRD0B = 5'd16; // Object Fetch Read Data 0 Stage B + localparam S_OFRD1A = 5'd17; // Object Fetch Read Data 1 Stage A + localparam S_OFRD1B = 5'd18; // Object Fetch Read Data 1 Stage B + localparam S_OWB = 5'd19; // Object Write Back + + localparam PPU_OAM_SEARCH_LENGTH = 6'd40; + + reg [2:0] h_drop; //Drop pixels when SCX % 8 != 0 + wire [2:0] h_extra = reg_scx[2:0]; //Extra line length when SCX % 8 != 0 + reg [7:0] h_pix_render; // Horizontal Render Pixel pointer + reg [7:0] h_pix_output; // Horizontal Output Pixel counter + wire [7:0] h_pix_obj = h_pix_output + 1'b1; // Coordinate used to trigger the object rendering + wire [7:0] v_pix = v_count; + wire [7:0] v_pix_in_map = v_pix + reg_scy; + wire [7:0] v_pix_in_win = v_pix - reg_wy; + + reg [4:0] r_state = 0; + reg [4:0] r_next_backup; + reg [4:0] r_next_state; + wire is_in_v_blank = ((v_count >= PPU_V_ACTIVE) && (v_count < PPU_V_ACTIVE + PPU_V_BLANK)); + + reg window_triggered; // Indicate whether window has been triggered, should be replaced by a edge detector + wire render_window_or_bg = window_triggered; + wire window_trigger = (((h_pix_output) == (reg_wx))&&(v_pix >= reg_wy)&&(reg_win_en)&&(~window_triggered)) ? 1 : 0; + + wire [2:0] line_to_tile_v_offset_bg = v_pix_in_map[2:0]; // Current line in a tile being rendered + wire [4:0] line_in_tile_v_bg = v_pix_in_map[7:3]; // Current tile Y coordinate being rendered + wire [2:0] line_to_tile_v_offset_win = v_pix_in_win[2:0]; + wire [4:0] line_in_tile_v_win = v_pix_in_win[7:3]; + wire [2:0] line_to_tile_v_offset = (render_window_or_bg) ? (line_to_tile_v_offset_win) : (line_to_tile_v_offset_bg); + wire [4:0] line_in_tile_v = (render_window_or_bg) ? (line_in_tile_v_win) : (line_in_tile_v_bg); + + wire [4:0] h_tile_bg = h_pix_render[7:3] + reg_scx[7:3]; // Current tile X coordinate being rendered + wire [4:0] h_tile_win = h_pix_render[7:3]; + wire [4:0] h_tile = (render_window_or_bg) ? (h_tile_win) : (h_tile_bg); + + wire [12:0] current_map_address = (((render_window_or_bg) ? (window_map_addr) : (bg_map_addr)) + (line_in_tile_v) * 32 + {8'd0, h_tile}); //Background address + reg [7:0] current_tile_id; + wire [7:0] current_tile_id_adj = {~((reg_bg_win_data_sel)^(current_tile_id[7])), current_tile_id[6:0]}; // Adjust for 8800 Adressing mode + wire [12:0] current_tile_address_0 = (bg_window_tile_addr) + current_tile_id_adj * 16 + (line_to_tile_v_offset * 2); + wire [12:0] current_tile_address_1 = (current_tile_address_0) | 13'h0001; + reg [7:0] current_tile_data_0; + reg [7:0] current_tile_data_1; + + // Data that will be pushed into pixel FIFO + // Organized in pixels + reg [31:0] current_fetch_result; + always@(current_tile_data_1, current_tile_data_0) begin + for (i = 0; i < 8; i = i + 1) begin + current_fetch_result[i*4+3] = current_tile_data_1[i]; + current_fetch_result[i*4+2] = current_tile_data_0[i]; + current_fetch_result[i*4+1] = PPU_PAL_BG[1]; // Fetch could only fetch BG + current_fetch_result[i*4+0] = PPU_PAL_BG[0]; + end + end + + reg [5:0] oam_search_count; // Counter during OAM search stage + reg [5:0] obj_visible_list [0:9]; // Total visible list + reg [7:0] obj_trigger_list [0:9]; // Where the obj should be triggered + reg [7:0] obj_y_list [0:9]; // Where the obj is + reg obj_valid_list [0:9]; // Is obj visible entry valid + reg [3:0] oam_visible_count; // ??? + + wire [7:0] oam_search_x; + wire [7:0] oam_search_y; + wire [7:0] obj_size_h = (reg_obj_size == 1'b1) ? (8'd16) : (8'd8); + wire [7:0] obj_h_upper_boundary = (v_pix + 8'd16); + wire [7:0] obj_h_lower_boundary = obj_h_upper_boundary - obj_size_h; + + reg [3:0] obj_trigger_id; // The object currently being/ or have been rendered, in the visible list + + localparam OBJ_TRIGGER_NOT_FOUND = 4'd15; + + // Cascade mux used to implement the searching of next id would be triggered + reg [3:0] obj_trigger_id_from[0:10]; + reg [3:0] obj_trigger_id_next; + always@(h_pix_obj, obj_trigger_id) begin + obj_trigger_id_from[10] = OBJ_TRIGGER_NOT_FOUND; // There is no more after the 10th + for (i = 9; i >= 0; i = i - 1) begin + /* verilator lint_off WIDTH */ + obj_trigger_id_from[i] = + ((h_pix_obj == obj_trigger_list[i])&&(obj_valid_list[i])) ? (i) : (obj_trigger_id_from[i+1]); + // See if this one match, if not, cascade down. + /* verilator lint_on WIDTH */ + end + if (obj_trigger_id == OBJ_TRIGGER_NOT_FOUND) // currently not triggered yet + obj_trigger_id_next = obj_trigger_id_from[0]; // Search from start + else + obj_trigger_id_next = obj_trigger_id_from[obj_trigger_id + 1]; // Search start from next one + end + + //!-- DEBUG -- + //wire [3:0] obj_trigger_id_next = ((h_pix_obj == obj_trigger_list[4'd0])&&(obj_valid_list[4'd0])) ? (4'd0) : (4'd15); + + wire obj_trigger = ((reg_obj_en)&&(obj_trigger_id_next != OBJ_TRIGGER_NOT_FOUND)) ? 1 : 0; + //wire obj_trigger = 0; + + wire [5:0] obj_triggered = obj_visible_list[obj_trigger_id]; // The global id of object being rendered + wire [7:0] current_obj_y = obj_y_list[obj_trigger_id]; + wire [7:0] current_obj_x = obj_trigger_list[obj_trigger_id]; //h_pix gets incremented before render + reg [7:0] current_obj_tile_id_raw; // Tile ID without considering the object size + reg [7:0] current_obj_flags; // Flags + wire current_obj_to_bg_priority = current_obj_flags[7]; + wire current_obj_y_flip = current_obj_flags[6]; + wire current_obj_x_flip = current_obj_flags[5]; + wire current_obj_pal_id = current_obj_flags[4]; + wire [1:0] current_obj_pal= (current_obj_pal_id) ? (PPU_PAL_OB1) : (PPU_PAL_OB0); + /* verilator lint_off WIDTH */ + wire [3:0] line_to_obj_v_offset_raw = (v_pix + 8'd16 - current_obj_y); // Compensate 16 pixel offset and truncate to 4 bits + /* verilator lint_on WIDTH */ + wire [7:0] current_obj_tile_id = (reg_obj_size == 1'b1) ? + ({current_obj_tile_id_raw[7:1], (((line_to_obj_v_offset_raw[3])^(current_obj_y_flip)) ? 1'b1 : 1'b0)}) : // Select Hi or Lo tile + (current_obj_tile_id_raw); // Use tile ID directly + wire [2:0] line_to_obj_v_offset = (current_obj_y_flip) ? (~line_to_obj_v_offset_raw[2:0]) : (line_to_obj_v_offset_raw[2:0]); + + wire [12:0] current_obj_address_0 = current_obj_tile_id * 16 + line_to_obj_v_offset * 2; + wire [12:0] current_obj_address_1 = current_obj_address_0 | 13'h0001; + reg [7:0] current_obj_tile_data_0; + reg [7:0] current_obj_tile_data_1; + // Data that will be merged into pixel FIFO + // Organized in pixels + reg [31:0] merge_result; + always@(*) begin + for (i = 0; i < 8; i = i + 1) begin + if ( + ((current_obj_tile_data_1[i] != 1'b0)||(current_obj_tile_data_0[i] != 1'b0))&& + ((pf_data[32+i*4+1] == PPU_PAL_BG[1])&&(pf_data[32+i*4+0] == PPU_PAL_BG[0]))&& + ( + ((current_obj_to_bg_priority)&&(pf_data[32+i*4+3] == 1'b0)&&(pf_data[32+i*4+2] == 1'b0))|| + (~current_obj_to_bg_priority) + ) + ) //(OBJ is not transparent) and ((BG priority and BG is transparent) or (OBJ priority)) + begin + merge_result[i*4+3] = current_obj_tile_data_1[i]; + merge_result[i*4+2] = current_obj_tile_data_0[i]; + merge_result[i*4+1] = current_obj_pal[1]; + merge_result[i*4+0] = current_obj_pal[0]; + end + else begin + merge_result[i*4+3] = pf_data[32+i*4+3]; + merge_result[i*4+2] = pf_data[32+i*4+2]; + merge_result[i*4+1] = pf_data[32+i*4+1]; + merge_result[i*4+0] = pf_data[32+i*4+0]; + end + end + end + + assign vram_addr_int_sel = + ((r_state == S_OAMRDB) || (r_state == S_OFRD0A) || (r_state == S_OFRD0B) + || (r_state == S_OFRD1A) || (r_state == S_OFRD1B)) ? 1'b1 : 1'b0; + + + // Current mode logic, based on current state + always @ (posedge clk) + begin + if (rst) begin + reg_stat[1:0] <= PPU_MODE_V_BLANK; + end + else begin + case (r_state) + S_IDLE: reg_stat[1:0] <= (reg_lcd_en) ? (PPU_MODE_V_BLANK) : (PPU_MODE_H_BLANK); + S_BLANK: reg_stat[1:0] <= (is_in_v_blank) ? (PPU_MODE_V_BLANK) : (PPU_MODE_H_BLANK); + S_OAMX: reg_stat[1:0] <= PPU_MODE_OAM_SEARCH; + S_OAMY: reg_stat[1:0] <= PPU_MODE_OAM_SEARCH; + S_FTIDA: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_FTIDB: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_FRD0A: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_FRD0B: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_FRD1A: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_FRD1B: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_FWAITA: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_FWAITB: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_SWW: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_OAMRDA: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_OAMRDB: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_OFRD0A: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_OFRD0B: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_OFRD1A: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_OFRD1B: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + S_OWB: reg_stat[1:0] <= PPU_MODE_PIX_TRANS; + default: reg_stat[1:0] <= PPU_MODE_V_BLANK; + endcase + end + end + + assign oam_search_y = oam_data_out[7:0]; + assign oam_search_x = oam_data_out[15:8]; + + // Render logic + always @(posedge clk) + begin + reg_ly <= v_pix[7:0]; + + case (r_state) + // nothing to do for S_IDLE + S_IDLE: begin end + S_BLANK: begin + h_pix_render <= 8'd0; // Render pointer + oam_search_count <= 6'd0; + oam_visible_count <= 4'd0; + for (i = 0; i < 10; i = i + 1) begin + obj_valid_list[i] <= 1'b0; + end + oam_rd_addr_int <= 8'b0; + window_triggered <= 1'b0; + // Line start, need to render 16 pixels in 12 clocks + // and output 8 null pixels starting from the 4th clock + end + S_OAMX: begin + oam_rd_addr_int <= oam_search_count * 4; + end + S_OAMY: begin + if ((oam_search_y <= obj_h_upper_boundary)&& + (oam_search_y > obj_h_lower_boundary)&& + (oam_search_x != 8'd0)&& + (oam_visible_count < 4'd10)) begin + obj_visible_list[oam_visible_count] <= oam_search_count; + obj_trigger_list[oam_visible_count] <= oam_search_x; + obj_y_list[oam_visible_count] <= oam_search_y; + obj_valid_list[oam_visible_count] <= 1'b1; + oam_visible_count <= oam_visible_count + 1'b1; + end + oam_search_count <= oam_search_count + 1'b1; + end + S_FTIDA: vram_addr_bg <= current_map_address; + S_FTIDB: current_tile_id <= vram_data_out; + S_FRD0A: vram_addr_bg <= current_tile_address_0; + S_FRD0B: current_tile_data_0 <= vram_data_out; + S_FRD1A: vram_addr_bg <= current_tile_address_1; + S_FRD1B: begin + current_tile_data_1 <= vram_data_out; + h_pix_render <= h_pix_render + 8'd8; + end + // nothing to do for S_FWAITA, S_FWAITB + S_FWAITA: begin end + S_FWAITB: begin end + S_SWW: begin + h_pix_render <= 8'd0; + window_triggered <= 1'b1; + end + S_OAMRDA: oam_rd_addr_int <= obj_triggered * 4 + 8'd2; + S_OAMRDB: begin + current_obj_tile_id_raw <= oam_data_out[7:0]; + current_obj_flags <= oam_data_out[15:8]; + end + S_OFRD0A: vram_addr_obj <= current_obj_address_0; + S_OFRD0B: + if (current_obj_x_flip == 1'b1) + current_obj_tile_data_0[7:0] <= { + vram_data_out[0], vram_data_out[1], vram_data_out[2], vram_data_out[3], + vram_data_out[4], vram_data_out[5], vram_data_out[6], vram_data_out[7] + }; + else + current_obj_tile_data_0 <= vram_data_out; + S_OFRD1A: vram_addr_obj <= current_obj_address_1; + S_OFRD1B: + if (current_obj_x_flip == 1'b1) + current_obj_tile_data_1[7:0] <= { + vram_data_out[0], vram_data_out[1], vram_data_out[2], vram_data_out[3], + vram_data_out[4], vram_data_out[5], vram_data_out[6], vram_data_out[7] + }; + else + current_obj_tile_data_1 <= vram_data_out; + // nothing to do for S_OWB + S_OWB: begin end + default: begin + $display("Invalid state!"); + end + endcase + end + + reg [31:0] half_merge_result; + always @(current_fetch_result, pf_data) begin + for (i = 0; i < 8; i = i + 1) begin + if ((pf_data[32+i*4+1] == PPU_PAL_BG[1])&&(pf_data[32+i*4+0] == PPU_PAL_BG[0])) begin + half_merge_result[i*4+3] = current_fetch_result[i*4+3]; + half_merge_result[i*4+2] = current_fetch_result[i*4+2]; + half_merge_result[i*4+1] = current_fetch_result[i*4+1]; + half_merge_result[i*4+0] = current_fetch_result[i*4+0]; + end + else begin + half_merge_result[i*4+3] = pf_data[32+i*4+3]; + half_merge_result[i*4+2] = pf_data[32+i*4+2]; + half_merge_result[i*4+1] = pf_data[32+i*4+1]; + half_merge_result[i*4+0] = pf_data[32+i*4+0]; + end + end + end + + // Output logic + always @(posedge clk) + begin + if (r_state == S_BLANK) begin + valid <= 1'b0; + h_pix_output <= 8'd0; // Output pointer + h_drop <= reg_scx[2:0]; + pf_empty <= PF_INITA; + end + else if ((r_state == S_FTIDA) || (r_state == S_FTIDB) || (r_state == S_FRD0A) || (r_state == S_FRD0B) || + (r_state == S_FRD1A) || (r_state == S_FRD1B) || (r_state == S_FWAITA) || (r_state == S_FWAITB)) + begin + + if (r_state == S_FRD1B) begin + if (pf_empty == PF_INITA) pf_empty <= PF_INITB; + if (pf_empty == PF_INITB) pf_empty <= PF_FIN; + if (pf_empty == PF_EMPTY) pf_empty <= PF_HALF; + if (pf_empty == PF_HALF) pf_empty <= PF_FIN; + end else + if (pf_empty == PF_FIN) pf_empty <= PF_FULL; // should NOT wait through end + + // If it is in one of the output stages + if (pf_empty == PF_EMPTY) begin + // Just started, no data available + valid <= 1'b0; + end + else if (pf_empty == PF_HALF) begin + valid <= 1'b0; + if (r_state == S_FTIDA) begin + // One batch done, and they can be push into pipeline, but could not be output yet + // We need to be careful not to overwrite the sprites... + pf_data[63:32] <= half_merge_result[31:0]; + end + end + else if (((pf_empty == PF_INITA)&&((r_state == S_FRD1A)||(r_state == S_FRD1B))) + ||(pf_empty == PF_INITB)||(pf_empty == PF_FULL)||(pf_empty == PF_FIN)) begin + if (r_state == S_FTIDA) begin // reload and shift + if (pf_empty == PF_INITB) begin + pf_data[63:0] <= {20'b0, current_fetch_result[31:0], 12'b0}; + end + else begin // PF_FULL or PF_FIN + pf_data[63:0] <= {pf_data[59:32], current_fetch_result[31:0], 4'b0}; + end + end + else begin // just shift + pf_data <= {pf_data[59:0], 4'b0}; + end + + if (h_drop != 3'd0) begin + h_drop <= h_drop - 1'd1; + valid <= 0; + end + else begin + if (h_pix_output >= 8) + valid <= 1; + else + valid <= 0; + pixel <= pf_output_pixel; + h_pix_output <= h_pix_output + 1'b1; + end + end + end + else if (r_state == S_OAMRDA) begin + h_pix_output <= h_pix_output - 1'b1; //revert adding + valid <= 1'b0; + end + else if (r_state == S_OWB) begin + h_pix_output <= h_pix_output + 1'b1; //restore adding + pf_data <= {merge_result[31:0], pf_data[31:0]}; + valid <= 1'b0; + end + else if (r_state == S_SWW) begin + pf_empty <= PF_EMPTY; // Flush the pipeline + valid <= 1'b0; + end + else begin + // Not even in output stages + valid <= 1'b0; + end + end + + // Enter Next State + // and handle object interrupt + // (sorry but I need to backup next state so I could not handle these in the next state logic) + always @(posedge clk) + begin + if (rst) begin + //h_pix_obj <= 8'b0; + r_state <= 0; + r_next_backup <= 0; + obj_trigger_id <= OBJ_TRIGGER_NOT_FOUND;//not triggered + end + else + begin + if (obj_trigger && (reg_mode == PPU_MODE_PIX_TRANS)) begin + // If already in object rendering stages + if ((r_state == S_OFRD0A)||(r_state == S_OFRD0B)|| + (r_state == S_OFRD1A)||(r_state == S_OFRD1B)|| + (r_state == S_OAMRDA)||(r_state == S_OAMRDB)) begin + r_state <= r_next_state; + end + // Finished one object, but there is more + else if (r_state == S_OWB) begin + r_state <= S_OAMRDA; + obj_trigger_id <= obj_trigger_id_next; + end + // Not rendering object before, start now + else begin + r_next_backup <= r_next_state; + r_state <= S_OAMRDA; + obj_trigger_id <= obj_trigger_id_next; + end + end + else begin + //h_pix_obj <= h_pix_output + 8'd2; + r_state <= r_next_state; + // Finished one object, and there is no more currently + if (r_state == S_OWB) begin + obj_trigger_id <= OBJ_TRIGGER_NOT_FOUND; + end + end + end + end + + // Next State Logic + // Since new state get updated during posedge + always @(*) + begin + case (r_state) + S_IDLE: r_next_state = ((reg_lcd_en)&(is_in_v_blank)) ? (S_BLANK) : (S_IDLE); + S_BLANK: r_next_state = + (reg_lcd_en) ? ( + (is_in_v_blank) ? + (((v_count == (PPU_V_TOTAL - 1))&&(h_count == (PPU_H_TOTAL - 1))) ? + (S_OAMX) : (S_BLANK) + ) : + ((h_count == (PPU_H_TOTAL - 1)) ? + ((v_count == (PPU_V_ACTIVE - 1)) ? + (S_BLANK) : (S_OAMX)): + (S_BLANK) + ) + ) : (S_IDLE); + S_OAMX: r_next_state = (reg_lcd_en) ? (S_OAMY) : (S_IDLE); + S_OAMY: r_next_state = (reg_lcd_en) ? ((oam_search_count == (PPU_OAM_SEARCH_LENGTH - 1'b1)) ? (S_FTIDA) : (S_OAMX)) : (S_IDLE); + S_FTIDA: r_next_state = (reg_lcd_en) ? ((h_pix_output == (PPU_H_OUTPUT - 1'b1)) ? (S_BLANK) : ((window_trigger) ? (S_SWW) : (S_FTIDB))) : (S_IDLE); + S_FTIDB: r_next_state = (reg_lcd_en) ? ((h_pix_output == (PPU_H_OUTPUT - 1'b1)) ? (S_BLANK) : ((window_trigger) ? (S_SWW) : (S_FRD0A))) : (S_IDLE); + S_FRD0A: r_next_state = (reg_lcd_en) ? ((h_pix_output == (PPU_H_OUTPUT - 1'b1)) ? (S_BLANK) : ((window_trigger) ? (S_SWW) : (S_FRD0B))) : (S_IDLE); + S_FRD0B: r_next_state = (reg_lcd_en) ? ((h_pix_output == (PPU_H_OUTPUT - 1'b1)) ? (S_BLANK) : ((window_trigger) ? (S_SWW) : (S_FRD1A))) : (S_IDLE); + S_FRD1A: r_next_state = (reg_lcd_en) ? ((h_pix_output == (PPU_H_OUTPUT - 1'b1)) ? (S_BLANK) : ((window_trigger) ? (S_SWW) : (S_FRD1B))) : (S_IDLE); + S_FRD1B: r_next_state = (reg_lcd_en) ? ((h_pix_output == (PPU_H_OUTPUT - 1'b1)) ? (S_BLANK) : ((window_trigger) ? (S_SWW) : ((pf_empty != PF_FULL) ? (S_FTIDA) : (S_FWAITA)))) : (S_IDLE); // If fifo not full, no wait state is needed + S_FWAITA: r_next_state = (reg_lcd_en) ? ((h_pix_output == (PPU_H_OUTPUT - 1'b1)) ? (S_BLANK) : ((window_trigger) ? (S_SWW) : (S_FWAITB))) : (S_IDLE); + S_FWAITB: r_next_state = (reg_lcd_en) ? ((h_pix_output == (PPU_H_OUTPUT - 1'b1)) ? (S_BLANK) : ((window_trigger) ? (S_SWW) : (S_FTIDA))) : (S_IDLE); + S_SWW: r_next_state = (reg_lcd_en) ? ((h_pix_output == (PPU_H_OUTPUT - 1'b1)) ? (S_BLANK) : (S_FTIDA)) : (S_IDLE); + S_OAMRDA: r_next_state = (reg_lcd_en) ? (S_OAMRDB) : (S_IDLE); + S_OAMRDB: r_next_state = (reg_lcd_en) ? (S_OFRD0A) : (S_IDLE); + S_OFRD0A: r_next_state = (reg_lcd_en) ? (S_OFRD0B) : (S_IDLE); + S_OFRD0B: r_next_state = (reg_lcd_en) ? (S_OFRD1A) : (S_IDLE); + S_OFRD1A: r_next_state = (reg_lcd_en) ? (S_OFRD1B) : (S_IDLE); + S_OFRD1B: r_next_state = (reg_lcd_en) ? (S_OWB) : (S_IDLE); + S_OWB: r_next_state = (reg_lcd_en) ? (r_next_backup) : (S_IDLE); + default: r_next_state = S_IDLE; + endcase + end + + // Interrupt + always @(posedge clk) + if (rst) + reg_stat[2] <= 0; + else + // TODO: what's the timing for this? + reg_stat[2] <= (reg_ly == reg_lyc) ? 1 : 0; + + always @(posedge clk) + begin + if (rst) begin + int_vblank_req <= 0; + int_lcdc_req <= 0; + reg_ly_last[7:0] <= 0; + //reg_stat[1:0] <= PPU_MODE_V_BLANK; + end + else + begin + if ((reg_mode == PPU_MODE_V_BLANK)&&(reg_mode_last != PPU_MODE_V_BLANK)) + int_vblank_req <= 1; + else if (int_vblank_ack) + int_vblank_req <= 0; + if (((reg_lyc_int == 1'b1)&&(reg_ly == reg_lyc)&&(reg_ly_last != reg_lyc))|| + ((reg_oam_int == 1'b1)&&(reg_mode == PPU_MODE_OAM_SEARCH)&&(reg_mode_last != PPU_MODE_OAM_SEARCH))|| + ((reg_vblank_int == 1'b1)&&(reg_mode == PPU_MODE_V_BLANK)&&(reg_mode_last != PPU_MODE_V_BLANK))|| + ((reg_hblank_int == 1'b1)&&(reg_mode == PPU_MODE_H_BLANK)&&(reg_mode_last != PPU_MODE_H_BLANK))) + int_lcdc_req <= 1; + else if (int_lcdc_ack) + int_lcdc_req <= 0; + reg_ly_last <= reg_ly; + reg_mode_last <= reg_mode; + end + end + + // Bus RW + // Bus RW - Combinational Read + always @(*) + begin + // MMIO Bus + mmio_dout = 8'hFF; + case (mmio_a) + 16'hFF40: mmio_dout = reg_lcdc; + 16'hFF41: mmio_dout = reg_stat; + 16'hFF42: mmio_dout = reg_scy; + 16'hFF43: mmio_dout = reg_scx; + 16'hFF44: mmio_dout = reg_ly; + 16'hFF45: mmio_dout = reg_lyc; + 16'hFF46: mmio_dout = reg_dma; + 16'hFF47: mmio_dout = reg_bgp; + 16'hFF48: mmio_dout = reg_obp0; + 16'hFF49: mmio_dout = reg_obp1; + 16'hFF4A: mmio_dout = reg_wy; + 16'hFF4B: mmio_dout = reg_wx; + endcase + end + + // Bus RW - Sequential Write + always @(posedge clk) + begin + if (rst) begin + reg_lcdc <= 8'h00; + reg_stat[7:3] <= 5'h00; + reg_scy <= 8'h00; + reg_scx <= 8'h00; + reg_lyc <= 8'h00; + reg_dma <= 8'h00; + reg_bgp <= 8'hFC; + reg_obp0 <= 8'h00; + reg_obp1 <= 8'h00; + reg_wy <= 8'h00; + reg_wx <= 8'h00; + end + else + begin + if (mmio_wr) begin + case (mmio_a) + 16'hFF40: reg_lcdc <= mmio_din; + 16'hFF41: reg_stat[7:3] <= mmio_din[7:3]; + 16'hFF42: reg_scy <= mmio_din; + 16'hFF43: reg_scx <= mmio_din; + //16'hFF44: reg_ly <= mmio_din; + 16'hFF45: reg_lyc <= mmio_din; + 16'hFF46: reg_dma <= mmio_din; + 16'hFF47: reg_bgp <= mmio_din; + 16'hFF48: reg_obp0 <= mmio_din; + 16'hFF49: reg_obp1 <= mmio_din; + 16'hFF4A: reg_wy <= mmio_din; + 16'hFF4B: reg_wx <= mmio_din; + endcase + // VRAM and OAM access are not handled here + end + end + end + + // Debug Outputs + assign scx = reg_scx; + assign scy = reg_scy; + assign state = r_state; + +endmodule
diff --git a/verilog/rtl/regfile.v b/verilog/rtl/regfile.v new file mode 100644 index 0000000..575070c --- /dev/null +++ b/verilog/rtl/regfile.v
@@ -0,0 +1,54 @@ +`timescale 1ns / 1ps +`default_nettype wire +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Module Name: regfile +// Project Name: VerilogBoy +// Description: +// The register file of Game Boy CPU. +// Dependencies: +// +// Additional Comments: +// Only BCDEHLSP are in the register file +////////////////////////////////////////////////////////////////////////////////// + +module regfile( + input clk, + input rst, + input [2:0] rdn, + output [7:0] rd, + input [1:0] rdwn, + output [15:0] rdw, + output [7:0] h, // H, L output for 16bit addition + output [7:0] l, + output [15:0] sp, // SP output for addressing + input [2:0] wrn, + input [7:0] wr, + input we + ); + + reg [7:0] regs [0:7]; + + wire [7:0] rdhigh = regs[{rdwn, 1'b0}]; + wire [7:0] rdlow = regs[{rdwn, 1'b1}]; + assign rdw = {rdhigh, rdlow}; + assign rd = regs[rdn]; + assign h = regs[3'd4]; + assign l = regs[3'd5]; + assign sp = {regs[3'd6], regs[3'd7]}; + + integer i; + always @(posedge clk) begin + if (rst) begin + for (i = 0; i < 8; i = i + 1) + regs[i] <= 8'b0; + end + else begin + if (we) + regs[wrn] <= wr; + end + end + +endmodule
diff --git a/verilog/rtl/serial.v b/verilog/rtl/serial.v new file mode 100644 index 0000000..30b03fa --- /dev/null +++ b/verilog/rtl/serial.v
@@ -0,0 +1,104 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 13:13:04 04/13/2018 +// Module Name: serial +// Project Name: VerilogBoy +// Description: +// Dummy serial interface +// Dependencies: +// +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module serial( + input clk, + input rst, + input wire [15:0] a, + output reg [7:0] dout, + input wire [7:0] din, + input wire rd, + input wire wr, + output reg int_serial_req, + input wire int_serial_ack + ); + + reg clk_spi; //8kHz SPI Clock + + /*clk_div #(.WIDTH(10), .DIV(512)) spi_div( + .i(clk), + .o(clk_spi) + );*/ + + reg [8:0] counter; + + always @(posedge clk) + begin + if (rst) begin + counter <= 9'h72; + clk_spi <= 1'b0; + end + else begin + if (counter == (512 / 2 - 1)) begin + clk_spi <= ~clk_spi; + counter <= 0; + end + else + counter <= counter + 1'b1; + end + end + + //reg [7:0] reg_sb; + reg reg_sc_start; + reg reg_sc_int; + + always @(*) begin + dout = 8'hff; + if (a == 16'hff01) dout = 8'hff; else + if (a == 16'hff02) dout = {reg_sc_start, 6'b111111, reg_sc_int}; + end + + reg [3:0] count; + reg last_clk; + + always @(posedge clk) begin + if (rst) begin + //reg_sb <= 8'h00; + reg_sc_start <= 1'b0; + reg_sc_int <= 1'b0; + int_serial_req <= 1'b0; + count <= 4'd0; + last_clk <= 1'b0; + end + else begin + last_clk <= clk_spi; + //if (wr && (a == 16'hff01)) reg_sb <= din; + if (wr && (a == 16'hff02)) begin + reg_sc_start <= din[7]; + reg_sc_int <= din[0]; + if (din[7] && din[0]) count <= 4'd8; + else count <= 4'd0; + end + else begin + // Dummy serial interface + if (count != 4'd0) begin + if (!last_clk && clk_spi) begin + count <= count - 4'd1; + if ((count - 4'd1) == 0) begin + int_serial_req <= 1'b1; + end + end + end + else begin + if ((int_serial_req)&&(int_serial_ack)) begin + int_serial_req <= 1'b0; + end + end + end + end + end + + +endmodule
diff --git a/verilog/rtl/singleport_ram.v b/verilog/rtl/singleport_ram.v new file mode 100644 index 0000000..0c8d630 --- /dev/null +++ b/verilog/rtl/singleport_ram.v
@@ -0,0 +1,24 @@ +`timescale 1ns / 1ps +module singleport_ram #( + parameter integer WORDS = 8192, + parameter ABITS = 13 +)( + input clka, + input wea, + input [ABITS - 1:0] addra, + input [7:0] dina, + output reg [7:0] douta +); + + reg [7:0] ram [0:WORDS-1]; + + always@(posedge clka) begin + if (wea) + ram[addra] <= dina; + end + + always@(posedge clka) begin + douta <= ram[addra]; + end + +endmodule
diff --git a/verilog/rtl/singlereg.v b/verilog/rtl/singlereg.v new file mode 100644 index 0000000..783e04c --- /dev/null +++ b/verilog/rtl/singlereg.v
@@ -0,0 +1,38 @@ +`timescale 1ns / 1ps +`default_nettype wire +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Module Name: reg +// Project Name: VerilogBoy +// Description: +// The register file of Game Boy CPU. +// Dependencies: +// +// Additional Comments: +// Single 8-bit register +////////////////////////////////////////////////////////////////////////////////// + +module singlereg(clk, rst, wr, rd, we); + parameter WIDTH = 8; + + input clk; + input rst; + input [WIDTH-1:0] wr; + output [WIDTH-1:0] rd; + input we; + + reg [WIDTH-1:0] data; + + assign rd = data; + + always @(posedge clk) begin + if (rst) + data <= 0; + else + if (we) + data <= wr; + end + +endmodule
diff --git a/verilog/rtl/sound.v b/verilog/rtl/sound.v new file mode 100644 index 0000000..2cabe88 --- /dev/null +++ b/verilog/rtl/sound.v
@@ -0,0 +1,360 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 12:29:37 04/07/2018 +// Module Name: sound +// Project Name: VerilogBoy +// Description: +// GameBoy sound unit main file +// Dependencies: +// +// Additional Comments: +// On a real gameboy, audio mixing is done with an OpAmp (I am not sure, but +// this makes most sense according to the documents we have). I am using adder +// here to make that happen. Also, audio volume control is probably done with a +// PGA on a real gameboy, and I am using multiplication to implement that here. +// So this would synthesis some additional adders and multipliers which should +// not be part of a Game Boy. +////////////////////////////////////////////////////////////////////////////////// +module sound( + input wire clk, + input wire rst, + input wire [15:0] a, + output reg [7:0] dout, + input wire [7:0] din, + input wire rd, + input wire wr, + output wire [15:0] left, + output wire [15:0] right, + // debug + output wire [3:0] ch1_level, + output wire [3:0] ch2_level, + output wire [3:0] ch3_level, + output wire [3:0] ch4_level + ); + + // Sound registers + reg [7:0] regs [0:31]; + + /* verilator lint_off UNUSED */ + wire [7:0] reg_nr10 = regs[00]; // $FF10 Channel 1 Sweep register (RW) + wire [7:0] reg_nr11 = regs[01]; // $FF11 Channel 1 Sound length/wave patternduty (RW) + wire [7:0] reg_nr12 = regs[02]; // $FF12 Channel 1 Volume envelope (RW) + wire [7:0] reg_nr13 = regs[03]; // $FF13 Channel 1 Freqency lo (W) + wire [7:0] reg_nr14 = regs[04]; // $FF14 Channel 1 Freqency hi (RW) + wire [7:0] reg_nr21 = regs[06]; // $FF16 Channel 2 Sound length/wave patternduty (RW) + wire [7:0] reg_nr22 = regs[07]; // $FF17 Channel 2 Volume envelope (RW) + wire [7:0] reg_nr23 = regs[08]; // $FF18 Channel 2 Freqency lo (W) + wire [7:0] reg_nr24 = regs[09]; // $FF19 Channel 2 Freqency hi (RW) + wire [7:0] reg_nr30 = regs[10]; // $FF1A Channel 3 Sound on/off (RW) + wire [7:0] reg_nr31 = regs[11]; // $FF1B Channel 3 Sound length (?) + wire [7:0] reg_nr32 = regs[12]; // $FF1C Channel 3 Select output level (RW) + wire [7:0] reg_nr33 = regs[13]; // $FF1D Channel 3 Frequency lo (W) + wire [7:0] reg_nr34 = regs[14]; // $FF1E Channel 3 Frequency hi (RW) + wire [7:0] reg_nr41 = regs[16]; // $FF20 Channel 4 Sound length (RW) + wire [7:0] reg_nr42 = regs[17]; // $FF21 Channel 4 Volume envelope (RW) + wire [7:0] reg_nr43 = regs[18]; // $FF22 Channel 4 Polynomial counter (RW) + wire [7:0] reg_nr44 = regs[19]; // $FF23 Channel 4 Counter/consecutive; Initial(RW) + wire [7:0] reg_nr50 = regs[20]; // $FF24 Channel contorl / ON-OFF / Volume (RW) + wire [7:0] reg_nr51 = regs[21]; // $FF25 Selection of Sound output terminal (RW) + wire [7:0] reg_nr52 = regs[22]; // $FF26 Sound on/off + /* verilator lint_on UNUSED */ + wire [4:0] reg_addr = {~a[4], a[3:0]}; // Convert 10-20 to 00-10 + + wire [2:0] ch1_sweep_time = reg_nr10[6:4]; + wire ch1_sweep_decreasing = reg_nr10[3]; + wire [2:0] ch1_num_sweep_shifts = reg_nr10[2:0]; + wire [1:0] ch1_wave_duty = reg_nr11[7:6]; + wire [5:0] ch1_length = reg_nr11[5:0]; + wire [3:0] ch1_initial_volume = reg_nr12[7:4]; + wire ch1_envelope_increasing = reg_nr12[3]; + wire [2:0] ch1_num_envelope_sweeps = reg_nr12[2:0]; + reg ch1_start; + wire ch1_single = reg_nr14[6]; + wire [10:0] ch1_frequency = {reg_nr14[2:0], reg_nr13[7:0]}; + wire [1:0] ch2_wave_duty = reg_nr21[7:6]; + wire [5:0] ch2_length = reg_nr21[5:0]; + wire [3:0] ch2_initial_volume = reg_nr22[7:4]; + wire ch2_envelope_increasing = reg_nr22[3]; + wire [2:0] ch2_num_envelope_sweeps = reg_nr22[2:0]; + reg ch2_start; + wire ch2_single = reg_nr24[6]; + wire [10:0] ch2_frequency = {reg_nr24[2:0], reg_nr23[7:0]}; + wire [7:0] ch3_length = reg_nr31[7:0]; + wire ch3_on = reg_nr30[7]; + wire [1:0] ch3_volume = reg_nr32[6:5]; + reg ch3_start; + wire ch3_single = reg_nr34[6]; + wire [10:0] ch3_frequency = {reg_nr34[2:0], reg_nr33[7:0]}; + wire [5:0] ch4_length = reg_nr41[5:0]; + wire [3:0] ch4_initial_volume = reg_nr42[7:4]; + wire ch4_envelope_increasing = reg_nr42[3]; + wire [2:0] ch4_num_envelope_sweeps = reg_nr42[2:0]; + wire [3:0] ch4_shift_clock_freq = reg_nr43[7:4]; + wire ch4_counter_width = reg_nr43[3]; // 0 = 15 bits, 1 = 7 bits + wire [2:0] ch4_freq_dividing_ratio = reg_nr43[2:0]; + reg ch4_start; + wire ch4_single = reg_nr44[6]; + wire s02_vin = reg_nr50[7]; + wire [2:0] s02_output_level = reg_nr50[6:4]; + wire s01_vin = reg_nr50[3]; + wire [2:0] s01_output_level = reg_nr50[2:0]; + wire s02_ch4_enable = reg_nr51[7]; + wire s02_ch3_enable = reg_nr51[6]; + wire s02_ch2_enable = reg_nr51[5]; + wire s02_ch1_enable = reg_nr51[4]; + wire s01_ch4_enable = reg_nr51[3]; + wire s01_ch3_enable = reg_nr51[2]; + wire s01_ch2_enable = reg_nr51[1]; + wire s01_ch1_enable = reg_nr51[0]; + wire sound_enable = reg_nr52[7]; + wire ch4_on_flag; + wire ch3_on_flag; + wire ch2_on_flag; + wire ch1_on_flag; + + reg [7:0] wave [0:15]; + wire [3:0] wave_addr_ext = a[3:0]; + wire [3:0] wave_addr_int; + wire [3:0] wave_addr = (ch3_on) ? (wave_addr_int) : (wave_addr_ext); + wire [7:0] wave_data = wave[wave_addr]; + + wire addr_in_regs = (a >= 16'hFF10 && a <= 16'hFF2F); + wire addr_in_wave = (a >= 16'hFF30 && a <= 16'hFF3F); + + // Bus RW + // Bus RW - Combinational Read + // This is a drawback of ISE XST, one can not use always@(*) and reg array together, + // so one have to write something (does not need to make sense, just as a place holder) + // and let the synthesizer to determine the correct sensitvity list. (Or one would have + // to enumerate EACH item in an array, otherwise it will give an error. + always @(a) + begin + dout = 8'hFF; + if (addr_in_regs) begin + if (a == 16'hFF26) + dout = {sound_enable, 3'b0, ch4_on_flag, ch3_on_flag, ch2_on_flag, ch1_on_flag}; + else + dout = regs[reg_addr]; + end + else + if (addr_in_wave) begin + dout = wave[wave_addr]; + end + end + + // Bus RW - Sequential Write + integer i; + + always @(posedge clk) + begin + if (rst) begin + for (i = 0; i < 32; i = i+1) begin + regs[i] <= 8'b0; + end + // wave pattern should not be initialized + end + else begin + if (wr) begin + if (addr_in_regs) begin + if (a == 16'hFF26) begin + if (din[7] == 0) begin + for (i = 0; i < 32; i = i+1) begin + regs[i] <= 8'b0; + end + end + else + regs[reg_addr] <= din; + end + else if (sound_enable) begin + regs[reg_addr] <= din; + end + end + else if (addr_in_wave) + //wave[wave_addr_ext] <= din; //what if we allow Write any way? + wave[wave_addr] <= din; // This is what happens trying to write to wave sample while it is on + end + // Initialize signal, should be triggered whenever a 1 is written + if ((wr)&&(a == 16'hFF14)) ch1_start <= din[7]; + else ch1_start <= 0; + if ((wr)&&(a == 16'hFF19)) ch2_start <= din[7]; + else ch2_start <= 0; + if ((wr)&&(a == 16'hFF1E)) ch3_start <= din[7]; + else ch3_start <= 0; + if ((wr)&&(a == 16'hFF23)) ch4_start <= din[7]; + else ch4_start <= 0; + end + end + + // Clocks + wire clk_frame; // 512Hz Base Clock + wire clk_length_ctr; // 256Hz Length Control Clock + wire clk_vol_env; // 64Hz Volume Enevelope Clock + wire clk_sweep; // 128Hz Sweep Clock + wire clk_freq_div; // 1048576Hz Frequency Division Clock + + clk_div #(.WIDTH(15), .DIV(8192)) frame_div( + .i(clk), + .o(clk_frame) + ); + + reg [2:0] sequencer_state = 3'b0; + always@(posedge clk_frame) + begin + sequencer_state <= sequencer_state + 1'b1; + end + + assign clk_length_ctr = (sequencer_state[0]) ? 1'b0 : 1'b1; + assign clk_vol_env = (sequencer_state == 3'd7) ? 1'b1 : 1'b0; + assign clk_sweep = ((sequencer_state == 3'd2) || (sequencer_state == 3'd6)) ? 1'b1 : 1'b0; + + clk_div #(.WIDTH(2), .DIV(2)) freq_div( + .i(clk), + .o(clk_freq_div) + ); + + // Channels + wire [3:0] ch1; + wire [3:0] ch2; + wire [3:0] ch3; + wire [3:0] ch4; + + sound_square sound_ch1( + .rst(~sound_enable), + .clk(clk), + .clk_length_ctr(clk_length_ctr), + .clk_vol_env(clk_vol_env), + .clk_sweep(clk_sweep), + .clk_freq_div(clk_freq_div), + .sweep_time(ch1_sweep_time), + .sweep_decreasing(ch1_sweep_decreasing), + .num_sweep_shifts(ch1_num_sweep_shifts), + .wave_duty(ch1_wave_duty), + .length(ch1_length), + .initial_volume(ch1_initial_volume), + .envelope_increasing(ch1_envelope_increasing), + .num_envelope_sweeps(ch1_num_envelope_sweeps), + .start(ch1_start), + .single(ch1_single), + .frequency(ch1_frequency), + .level(ch1), + .enable(ch1_on_flag) + ); + + sound_square sound_ch2( + .rst(~sound_enable), + .clk(clk), + .clk_length_ctr(clk_length_ctr), + .clk_vol_env(clk_vol_env), + .clk_sweep(clk_sweep), + .clk_freq_div(clk_freq_div), + .sweep_time(3'b0), + .sweep_decreasing(1'b0), + .num_sweep_shifts(3'b0), + .wave_duty(ch2_wave_duty), + .length(ch2_length), + .initial_volume(ch2_initial_volume), + .envelope_increasing(ch2_envelope_increasing), + .num_envelope_sweeps(ch2_num_envelope_sweeps), + .start(ch2_start), + .single(ch2_single), + .frequency(ch2_frequency), + .level(ch2), + .enable(ch2_on_flag) + ); + + sound_wave sound_ch3( + .rst(~sound_enable), + .clk(clk), + .clk_length_ctr(clk_length_ctr), + .length(ch3_length), + .volume(ch3_volume), + .on(ch3_on), + .single(ch3_single), + .start(ch3_start), + .frequency(ch3_frequency), + .wave_a(wave_addr_int), + .wave_d(wave_data), + .level(ch3), + .enable(ch3_on_flag) + ); + + sound_noise sound_ch4( + .rst(~sound_enable), + .clk(clk), + .clk_length_ctr(clk_length_ctr), + .clk_vol_env(clk_vol_env), + .length(ch4_length), + .initial_volume(ch4_initial_volume), + .envelope_increasing(ch4_envelope_increasing), + .num_envelope_sweeps(ch4_num_envelope_sweeps), + .shift_clock_freq(ch4_shift_clock_freq), + .counter_width(ch4_counter_width), + .freq_dividing_ratio(ch4_freq_dividing_ratio), + .start(ch4_start), + .single(ch4_single), + .level(ch4), + .enable(ch4_on_flag) + ); + + // Mixer + + /* + // Signed mixer + wire [5:0] sign_extend_ch1 = {{3{ch1[3]}}, ch1[2:0]}; + wire [5:0] sign_extend_ch2 = {{3{ch2[3]}}, ch2[2:0]}; + wire [5:0] sign_extend_ch3 = {{3{ch3[3]}}, ch3[2:0]}; + wire [5:0] sign_extend_ch4 = {{3{ch4[3]}}, ch4[2:0]}; + reg [5:0] mixed_s01; + reg [5:0] mixed_s02; + + always @(*) + begin + mixed_s01 = 6'd0; + mixed_s02 = 6'd0; + if (s01_ch1_enable) mixed_s01 = mixed_s01 + sign_extend_ch1; + if (s01_ch2_enable) mixed_s01 = mixed_s01 + sign_extend_ch2; + if (s01_ch3_enable) mixed_s01 = mixed_s01 + sign_extend_ch3; + if (s01_ch4_enable) mixed_s01 = mixed_s01 + sign_extend_ch4; + if (s02_ch1_enable) mixed_s02 = mixed_s02 + sign_extend_ch1; + if (s02_ch2_enable) mixed_s02 = mixed_s02 + sign_extend_ch2; + if (s02_ch3_enable) mixed_s02 = mixed_s02 + sign_extend_ch3; + if (s02_ch4_enable) mixed_s02 = mixed_s02 + sign_extend_ch4; + end + + assign left = (sound_enable) ? {mixed_s01[5:0], 14'b0} : 20'b0; + assign right = (sound_enable) ? {mixed_s02[5:0], 14'b0} : 20'b0; + */ + + // Unsigned mixer + reg [5:0] added_s01; + reg [5:0] added_s02; + always @(*) + begin + added_s01 = 6'd0; + added_s02 = 6'd0; + if (s01_ch1_enable) added_s01 = added_s01 + {2'b0, ch1}; + if (s01_ch2_enable) added_s01 = added_s01 + {2'b0, ch2}; + if (s01_ch3_enable) added_s01 = added_s01 + {2'b0, ch3}; + if (s01_ch4_enable) added_s01 = added_s01 + {2'b0, ch4}; + if (s02_ch1_enable) added_s02 = added_s02 + {2'b0, ch1}; + if (s02_ch2_enable) added_s02 = added_s02 + {2'b0, ch2}; + if (s02_ch3_enable) added_s02 = added_s02 + {2'b0, ch3}; + if (s02_ch4_enable) added_s02 = added_s02 + {2'b0, ch4}; + end + + wire [8:0] mixed_s01 = added_s01 * s01_output_level; + wire [8:0] mixed_s02 = added_s02 * s02_output_level; + + assign left = (sound_enable) ? {1'b0, mixed_s01[8:0], 6'b0} : 16'b0; + assign right = (sound_enable) ? {1'b0, mixed_s02[8:0], 6'b0} : 16'b0; + + // Debug Output + assign ch1_level = ch1; + assign ch2_level = ch2; + assign ch3_level = ch3; + assign ch4_level = ch4; + +endmodule
diff --git a/verilog/rtl/sound_channel_mix.v b/verilog/rtl/sound_channel_mix.v new file mode 100644 index 0000000..a9b03ed --- /dev/null +++ b/verilog/rtl/sound_channel_mix.v
@@ -0,0 +1,58 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: +// +// Create Date: 22:29:14 04/08/2018 +// Design Name: +// Module Name: sound_channel_mix +// Project Name: +// Target Devices: +// Tool versions: +// Description: +// +// Dependencies: +// +// Revision: +// Revision 0.01 - File Created +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module sound_channel_mix( + input enable, + input modulate, + input [3:0] target_vol, + output [3:0] level + ); + + /*// Converting Volume envelope to 2s compliment number + reg [3:0] target_vol_low; + wire [3:0] target_vol_high; + always@(target_vol) + begin + case (target_vol) + 4'b0000: target_vol_low = 4'b0000; + 4'b0001: target_vol_low = 4'b1111; + 4'b0010: target_vol_low = 4'b1111; + 4'b0011: target_vol_low = 4'b1110; + 4'b0100: target_vol_low = 4'b1110; + 4'b0101: target_vol_low = 4'b1101; + 4'b0110: target_vol_low = 4'b1101; + 4'b0111: target_vol_low = 4'b1100; + 4'b1000: target_vol_low = 4'b1100; + 4'b1001: target_vol_low = 4'b1011; + 4'b1010: target_vol_low = 4'b1011; + 4'b1011: target_vol_low = 4'b1010; + 4'b1100: target_vol_low = 4'b1010; + 4'b1101: target_vol_low = 4'b1001; + 4'b1110: target_vol_low = 4'b1001; + 4'b1111: target_vol_low = 4'b1000; + endcase + end + assign target_vol_high = {1'b0, target_vol[3:1]}; + + assign level = (enable) ? ((modulate) ? (target_vol_high) : (target_vol_low)) : (4'b0000);*/ + + assign level = (enable) ? ((modulate) ? (target_vol) : (4'b0000)) : (4'b0000); + +endmodule
diff --git a/verilog/rtl/sound_length_ctr.v b/verilog/rtl/sound_length_ctr.v new file mode 100644 index 0000000..3a1452a --- /dev/null +++ b/verilog/rtl/sound_length_ctr.v
@@ -0,0 +1,49 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 22:24:55 04/08/2018 +// Module Name: sound_length_ctr +// Project Name: VerilogBoy +// Description: +// Sound length control for all channels +// Dependencies: +// none +// Additional Comments: +// Channel 3 has a different length +////////////////////////////////////////////////////////////////////////////////// +module sound_length_ctr(rst, clk_length_ctr, start, single, length, enable); + parameter WIDTH = 6; // 6bit for Ch124, 8bit for Ch3 + + input rst; + input clk_length_ctr; + input start; + input single; + input [WIDTH-1:0] length; + output reg enable = 0; + + reg [WIDTH-1:0] length_left = {WIDTH{1'b1}}; // Upcounter from length to 255 + + // Length Control + always @(posedge clk_length_ctr, posedge start, posedge rst) + begin + if (rst) begin + enable <= 1'b0; + length_left <= 0; + end + else if (start) begin + enable <= 1'b1; + length_left <= (length == 0) ? ({WIDTH{1'b1}}) : (length); + end + else begin + if (single) begin + if (length_left != {WIDTH{1'b1}}) + length_left <= length_left + 1'b1; + else + enable <= 1'b0; + end + end + end + +endmodule
diff --git a/verilog/rtl/sound_noise.v b/verilog/rtl/sound_noise.v new file mode 100644 index 0000000..a836bcf --- /dev/null +++ b/verilog/rtl/sound_noise.v
@@ -0,0 +1,112 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 21:19:04 04/08/2018 +// Module Name: sound_noise +// Project Name: VerilogBoy +// Description: +// +// Dependencies: +// +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module sound_noise( + input rst, // Async reset + input clk, // CPU Clock + input clk_length_ctr, // Length control clock + input clk_vol_env, // Volume Envelope clock + input [5:0] length, // Length = (64-t1)*(1/256) second, used iff single is set + input [3:0] initial_volume, // Initial volume of envelope 0 = no sound + input envelope_increasing, // 0 = decrease, 1 = increase + input [2:0] num_envelope_sweeps, // number of envelope sweep 0 = stop + input [3:0] shift_clock_freq, // shift clock prescaler (s) + input counter_width, // 0 = 15 bits, 1 = 7 bits + input [2:0] freq_dividing_ratio, // shift clock divider 0 -> 1MHz, 1 -> 512kHz (r) + input start, // Restart sound + input single, // If set, output would stop upon reaching the length specified + output [3:0] level, + output enable + ); + + // Dividing ratio from 4MHz is (r * 8), for the divier to work, the comparator shoud + // compare with (dividing_factor / 2 - 1), so it becomes (r * 4 - 1) + reg [4:0] adjusted_freq_dividing_ratio; + reg [3:0] latched_shift_clock_freq; + + wire [3:0] target_vol; + + reg clk_div = 0; + wire clk_shift; + + reg [4:0] clk_divider = 5'b0; // First stage + always @(posedge clk) + begin + if (clk_divider == adjusted_freq_dividing_ratio) begin + clk_div <= ~clk_div; + clk_divider <= 0; + end + else + clk_divider <= clk_divider + 1'b1; + end + + reg [13:0] clk_shifter = 14'b0; // Second stage + always @(posedge clk_div) + begin + clk_shifter <= clk_shifter + 1'b1; + end + + assign clk_shift = clk_shifter[latched_shift_clock_freq]; + + reg [14:0] lfsr = {15{1'b1}}; + wire target_freq_out = ~lfsr[0]; + + wire [14:0] lfsr_next = + (counter_width == 0) ? ({(lfsr[0] ^ lfsr[1]), lfsr[14:1]}) : + ({8'b0, (lfsr[0] ^ lfsr[1]), lfsr[6:1]}); + + always@(posedge start) + begin + adjusted_freq_dividing_ratio <= + (freq_dividing_ratio == 3'b0) ? (5'd1) : ((freq_dividing_ratio * 4) - 1); + latched_shift_clock_freq <= shift_clock_freq; + end + + always@(posedge clk_shift, posedge start) + begin + if (start) begin + lfsr <= {15{1'b1}}; + end + else begin + lfsr <= lfsr_next; + end + end + + sound_vol_env sound_vol_env( + .clk_vol_env(clk_vol_env), + .start(start), + .initial_volume(initial_volume), + .envelope_increasing(envelope_increasing), + .num_envelope_sweeps(num_envelope_sweeps), + .target_vol(target_vol) + ); + + sound_length_ctr #(6) sound_length_ctr( + .rst(rst), + .clk_length_ctr(clk_length_ctr), + .start(start), + .single(single), + .length(length), + .enable(enable) + ); + + sound_channel_mix sound_channel_mix( + .enable(enable), + .modulate(target_freq_out), + .target_vol(target_vol), + .level(level) + ); + +endmodule
diff --git a/verilog/rtl/sound_square.v b/verilog/rtl/sound_square.v new file mode 100644 index 0000000..8f85838 --- /dev/null +++ b/verilog/rtl/sound_square.v
@@ -0,0 +1,139 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 16:51:12 04/07/2018 +// Module Name: sound_square +// Project Name: VerilogBoy +// Description: +// Square wave generator for channel 1 and 2 +// Dependencies: +// sound_vol_env, sound_length_ctr, sound_channel_mix +// Additional Comments: +// First, synthesize a frequency with 8X of specified frequency with any percent +// of duty cycle, then use a small FSM to synthesis it into desired duty cycle. +// +// Note: the original GameBoy process all the sound internally as unsigned +// number, and use a bypass capacitor to remove all the DC component. One drawback +// is that it do not have a constant "zero" reference: when a channel is off, the +// voltage is, naturually 0V. But when it is working, it will alter between 0 and +// Vmax(volume), means the zero becomes the half of current volume. This is also +// the design I am using here. +////////////////////////////////////////////////////////////////////////////////// +module sound_square( + input rst, // Async reset + input clk, // CPU Clock + input clk_length_ctr, // Length control clock + input clk_vol_env, // Volume Envelope clock + input clk_sweep, // Sweep clock + input clk_freq_div, // Base frequency for divider (should be 16x131072=2097152Hz) + input [2:0] sweep_time, // From 0 to 7/128Hz + input sweep_decreasing, // 0: Addition (Freq+) 1: Subtraction (Freq-) + input [2:0] num_sweep_shifts, // Number of sweep shift (n=0-7) + input [1:0] wave_duty, // 00: 87.5% HIGH 01: 75% HIGH 10: 50% HIGH 11: 25% HIGH + input [5:0] length, // Length = (64-t1)*(1/256) second, used iff single is set + input [3:0] initial_volume, // Initial volume of envelope 0 = no sound + input envelope_increasing, // 0 = decrease, 1 = increase + input [2:0] num_envelope_sweeps, // number of envelope sweep 0 = stop + input start, // Restart sound + input single, // If set, output would stop upon reaching the length specified + input [10:0] frequency, // Output frequency = 131072/(2048-x) Hz + output [3:0] level, // Sound output + output enable // Internal enable flag + ); + + //Sweep: X(t) = X(t-1) +/- X(t-1)/2^n + + reg [10:0] divider = 11'b0; + reg [10:0] target_freq; + reg octo_freq_out = 0; // 8 x target frequency with arbitrary duty cycle + wire target_freq_out; // Traget frequency with specified duty cycle + wire [3:0] target_vol; + reg [2:0] sweep_left; // Number of sweeps need to be done + + always @(posedge clk_freq_div, posedge start) + begin + if (start) begin + divider <= target_freq; + end + else begin + if (divider == 11'd2047) begin + octo_freq_out <= ~octo_freq_out; + divider <= target_freq; + end + else begin + divider <= divider + 1'b1; + end + end + end + + reg [2:0] duty_counter = 3'b0; + always @(posedge octo_freq_out) + begin + duty_counter <= duty_counter + 1'b1; + end + + assign target_freq_out = + (wave_duty == 2'b00) ? ((duty_counter != 3'b111) ? 1'b1 : 1'b0) : ( // 87.5% HIGH + (wave_duty == 2'b01) ? ((duty_counter[2:1] != 2'b11) ? 1'b1 : 1'b0) : ( // 75% HIGH + (wave_duty == 2'b10) ? ((duty_counter[2]) ? 1'b1 : 1'b0) : ( // 50% HIGH + ((duty_counter[2:1] == 2'b00) ? 1'b1 : 1'b0)))); // 25% HIGH + + // Frequency Sweep + reg overflow; + always @(posedge clk_sweep, posedge start) + begin + if (start) begin + target_freq <= frequency; + sweep_left <= sweep_time; + overflow <= 0; + end + else begin + if (sweep_left != 3'b0) begin + sweep_left <= sweep_left - 1'b1; + if (sweep_decreasing) + target_freq <= target_freq - (target_freq << num_sweep_shifts); + else + {overflow, target_freq} <= {1'b0, target_freq} + ({1'b0, target_freq} << num_sweep_shifts); + end + else begin + target_freq <= frequency; + end + end + end + /*always@(posedge start) + begin + target_freq <= frequency; + end*/ + + sound_vol_env sound_vol_env( + .clk_vol_env(clk_vol_env), + .start(start), + .initial_volume(initial_volume), + .envelope_increasing(envelope_increasing), + .num_envelope_sweeps(num_envelope_sweeps), + .target_vol(target_vol) + ); + + wire enable_length; + + sound_length_ctr #(6) sound_length_ctr( + .rst(rst), + .clk_length_ctr(clk_length_ctr), + .start(start), + .single(single), + .length(length), + .enable(enable_length) + ); + + assign enable = enable_length & ~overflow; + + sound_channel_mix sound_channel_mix( + .enable(enable), + .modulate(target_freq_out), + .target_vol(target_vol), + .level(level) + ); + +endmodule
diff --git a/verilog/rtl/sound_vol_env.v b/verilog/rtl/sound_vol_env.v new file mode 100644 index 0000000..81cc07f --- /dev/null +++ b/verilog/rtl/sound_vol_env.v
@@ -0,0 +1,56 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 22:21:41 04/08/2018 +// Module Name: sound_vol_env +// Project Name: VerilogBoy +// Description: +// Sound volume envelope control for channel 1 2 4 +// Dependencies: +// none +// Additional Comments: +// +////////////////////////////////////////////////////////////////////////////////// +module sound_vol_env( + input clk_vol_env, + input start, + input [3:0] initial_volume, + input envelope_increasing, + input [2:0] num_envelope_sweeps, + output reg [3:0] target_vol + ); + + reg [2:0] enve_left; // Number of cycles before next sweep + wire enve_enabled = (num_envelope_sweeps == 3'd0) ? 0 : 1; + + // Volume Envelope + always @(posedge clk_vol_env, posedge start) + begin + if (start) begin + target_vol <= initial_volume; + enve_left <= num_envelope_sweeps; + end + else begin + if (enve_left != 3'b0) begin + enve_left <= enve_left - 1'b1; + end + else begin + if (enve_enabled) begin + if (envelope_increasing) begin + if (target_vol != 4'b1111) + target_vol <= target_vol + 1; + end + else begin + if (target_vol != 4'b0000) + target_vol <= target_vol - 1; + end + enve_left <= num_envelope_sweeps; + end + end + end + end + + +endmodule
diff --git a/verilog/rtl/sound_wave.v b/verilog/rtl/sound_wave.v new file mode 100644 index 0000000..22bd632 --- /dev/null +++ b/verilog/rtl/sound_wave.v
@@ -0,0 +1,96 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 15:06:53 04/09/2018 +// Module Name: sound_wave +// Project Name: VerilogBoy +// Description: +// Sound wave player for channel 3 +// Dependencies: +// clk_div, sound_length_ctr +// Additional Comments: +// If Ch3 bugs are to be implemented, they should be probably implemented +// outside of this file. This file does not handle of RW to wave RAM +////////////////////////////////////////////////////////////////////////////////// +module sound_wave( + input rst, // Async reset + input clk, // Main CPU clock + input clk_length_ctr, // Length control clock + input [7:0] length, // Length = (256-t1)*(1/256) second, used iff single is set + input [1:0] volume, + input on, + input single, + input start, + input [10:0] frequency, + output [3:0] wave_a, + input [7:0] wave_d, + output [3:0] level, + output enable + ); + + // Freq = 64kHz / (2048 - frequency) + // Why???????? + + wire [3:0] current_sample; + + reg [4:0] current_pointer = 5'b0; + + assign wave_a[3:0] = current_pointer[4:1]; + assign current_sample[3:0] = (current_pointer[0]) ? + (wave_d[3:0]) : (wave_d[7:4]); + + wire clk_wave_base = clk; // base clock + /*clk_div #(.WIDTH(6), .DIV(32)) freq_div( + .i(clk), + .o(clk_wave_base) + );*/ + + + reg clk_pointer_inc = 1'b0; // Clock for pointer to increment + reg [10:0] divider = 11'b0; + always @(posedge clk_wave_base, posedge start) + begin + if (start) begin + divider <= frequency; + end + else begin + if (divider == 11'd2047) begin + clk_pointer_inc <= ~clk_pointer_inc; + divider <= frequency; + end + else begin + divider <= divider + 1'b1; + end + end + end + + always @(posedge clk_pointer_inc, posedge start) + begin + if (start) begin + current_pointer <= 5'b0; + end + else begin + if (on) + current_pointer <= current_pointer + 1'b1; + end + end + + sound_length_ctr #(8) sound_length_ctr( + .rst(rst), + .clk_length_ctr(clk_length_ctr), + .start(start), + .single(single), + .length(length), + .enable(enable) + ); + + assign level = (on) ? ( + (volume == 2'b00) ? (4'b0000) : ( + (volume == 2'b01) ? (current_sample[3:0]) : ( + (volume == 2'b10) ? ({1'b0, current_sample[3:1]}) : ( + ({2'b0, current_sample[3:2]}))))) : 4'b0000; + + +endmodule
diff --git a/verilog/rtl/timer.v b/verilog/rtl/timer.v new file mode 100644 index 0000000..9d72628 --- /dev/null +++ b/verilog/rtl/timer.v
@@ -0,0 +1,125 @@ +`timescale 1ns / 1ps +////////////////////////////////////////////////////////////////////////////////// +// Company: +// Engineer: Wenting Zhang +// +// Create Date: 17:12:01 04/13/2018 +// Module Name: timer +// Project Name: VerilogBoy +// Description: +// GameBoy internal timer +// Dependencies: +// +// Additional Comments: +// This should probably run at 1MHz domain, but currently at 4MHz. +////////////////////////////////////////////////////////////////////////////////// +module timer( + input wire clk, + input wire [1:0] ct, // certain things can only happen at 1MHz rate + input wire rst, + input wire [15:0] a, + output reg [7:0] dout, + input wire [7:0] din, + input wire rd, + input wire wr, + output reg int_tim_req, + input wire int_tim_ack + ); + + wire [7:0] reg_div; // Divider Register + reg [7:0] reg_tima; // Timer counter + reg [7:0] reg_tma; // Timer modulo + reg [7:0] reg_tac; // Timer control + + wire addr_in_timer = ((a == 16'hFF04) || + (a == 16'hFF05) || + (a == 16'hFF06) || + (a == 16'hFF07)) ? 1'b1 : 1'b0; + + reg [15:0] div; + + wire reg_timer_enable = reg_tac[2]; + wire [1:0] reg_clock_sel = reg_tac[1:0]; + + assign reg_div[7:0] = div[15:8]; + wire clk_4khz = div[9]; + wire clk_256khz = div[3]; + wire clk_64khz = div[5]; + wire clk_16khz = div[7]; + wire clk_tim; + assign clk_tim = (reg_timer_enable) ? ( + (reg_clock_sel == 2'b00) ? (clk_4khz) : ( + (reg_clock_sel == 2'b01) ? (clk_256khz) : ( + (reg_clock_sel == 2'b10) ? (clk_64khz) : + (clk_16khz)))) : (1'b0); + + reg last_clk_tim; + reg write_block; + + // Bus RW + // Bus RW - Combinational Read + always @(*) + begin + dout = 8'hFF; + if (a == 16'hFF04) dout = reg_div; else + if (a == 16'hFF05) dout = reg_tima; else + if (a == 16'hFF06) dout = reg_tma; else + if (a == 16'hFF07) dout = reg_tac; + end + + // Bus RW - Sequential Write + always @(posedge clk) begin + last_clk_tim <= clk_tim; + end + + always @(posedge clk) begin + if (rst) begin + //reg_div <= 0; + reg_tima <= 0; + reg_tma <= 0; + reg_tac <= 0; + div <= 0; + int_tim_req <= 0; + write_block <= 0; + end + else begin + div <= div + 1'b1; + if ((wr) && (a == 16'hFF04)) div <= 4; // compensate 1 cycle delay + else if ((wr) && (a == 16'hFF06)) begin + // test acceptance/timer/tma_write_reloading seems to imply + // that the reloading is done using a latch rather a FF + // writing to tma in the same cycle will fall through to tima + // as well. + reg_tma <= din; + if (write_block) + reg_tima <= din; + end + else if ((wr) && (a == 16'hFF07)) reg_tac <= din; + else if ((wr) && (a == 16'hFF05) && (!write_block)) reg_tima <= din; + else begin + if ((last_clk_tim == 1'b1)&&(clk_tim == 1'b0)) begin + reg_tima <= reg_tima + 1'b1; + if (reg_tima == 8'hFF) begin + int_tim_req <= 1'b1; // interrupt doesn't get delayed. + end + end + else begin + if ((int_tim_req)&&(int_tim_ack)) begin + int_tim_req <= 1'b0; + end + if ((ct == 2'b00)&&(reg_timer_enable)) begin + if (reg_tima == 8'd0) begin + reg_tima <= reg_tma; + //int_tim_req <= 1'b1; + write_block <= 1'b1; + end + else begin + write_block <= 1'b0; + end + end + end + end + end + end + +endmodule
diff --git a/verilog/sim/Makefile b/verilog/sim/Makefile new file mode 100644 index 0000000..415ae0a --- /dev/null +++ b/verilog/sim/Makefile
@@ -0,0 +1,88 @@ +TARGET := simulator +OBJODIR := obj +RTLDIR := . + +ifeq ($(VERILATOR_ROOT),) +VERILATOR_ROOT ?= $(shell bash -c 'verilator -V|grep VERILATOR_ROOT | head -1 | sed -e " s/^.*=\s*//"') +endif +VROOT := $(VERILATOR_ROOT) +VINCD := $(VROOT)/include +RTLOBJDIR := $(RTLDIR)/obj_dir +RTLOBJ := $(RTLOBJDIR)/Vboy__ALL.a + +CC = g++ +CXX = g++ +LD = g++ + +LIBS := $(shell pkg-config sdl2 --cflags --libs) -lm + +COMMONFLAGS := \ + -Wall -Og -g $(shell pkg-config sdl2 --cflags) + +CPPFLAGS := \ + -faligned-new \ + -std=c++17 + +LDFLAGS := + +INCLUDES += \ + -I. \ + -I$(RTLOBJDIR) \ + -I$(RTLDIR) \ + -I$(VINCD) \ + -I$(VINCD)/vltstd + +CSRCS += + +CPPSRCS += \ + ./main.cpp \ + ./dispsim.cpp \ + ./mbcsim.cpp \ + ./memsim.cpp \ + ./mmrprobe.cpp \ + verilated.cpp \ + verilated_vcd_c.cpp + +OBJS := $(CSRCS:%.c=$(OBJODIR)/%.o) \ + $(CPPSRCS:%.cpp=$(OBJODIR)/%.o) + +OBJS += $(RTLOBJ) + +$(RTLOBJ): + @echo Building RTL + make -f rtl.mk + +$(OBJODIR)/%.o: %.c $(RTLOBJ) + @echo [CC] $< + @mkdir -p $(dir $@) + @$(CC) $(COMMONFLAGS) $(CCFLAGS) $(INCLUDES) -c -o $@ $< + +$(OBJODIR)/%.o: %.cpp $(RTLOBJ) + @echo [CXX] $< + @mkdir -p $(dir $@) + @$(CXX) $(COMMONFLAGS) $(CPPFLAGS) $(INCLUDES) -c -o $@ $< + +$(OBJODIR)/%.o: $(VINCD)/%.cpp + @echo [CXX] $< + @mkdir -p $(dir $@) + @$(CXX) $(COMMONFLAGS) $(CPPFLAGS) $(INCLUDES) -c -o $@ $< + +PHONY += all +all: $(OBJS) + @$(LD) $(LDFLAGS) $(OBJS) $(LIBS) -o $(TARGET) + @echo 'all finish' + +PHONY += clean +clean: + rm -rf $(OBJODIR) + rm -f $(TARGET) + make -f rtl.mk clean + @echo 'clean finish' + +# Declare the contents of the .PHONY variable as phony. We keep that +# information in a variable so we can use it in if_changed and friends. +.PHONY: $(PHONY) + +# Set default target +.DEFAULT_GOAL:= all +
diff --git a/verilog/sim/dispsim.cpp b/verilog/sim/dispsim.cpp new file mode 100644 index 0000000..c796b5d --- /dev/null +++ b/verilog/sim/dispsim.cpp
@@ -0,0 +1,166 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// dispsim.cpp: Display simulation unit +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include <SDL.h> +#include "dispsim.h" + +DISPSIM::DISPSIM(void) { + window = SDL_CreateWindow("VerilogBoy Simulation", + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + dispWidth, dispHeight, SDL_SWSURFACE); + + if (window == NULL) { + fprintf(stderr, "Unable to create window\n"); + return; + } + + renderer = SDL_CreateRenderer(window, -1, + SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + + if (renderer == NULL) + { + fprintf(stderr, "Unable to create renderer\n"); + return; + } + + screen = SDL_CreateRGBSurface(SDL_SWSURFACE, contentWidth, contentHeight, 32, + 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + + textureRect.x = textureRect.y = 0; + textureRect.w = contentWidth; + textureRect.h = contentHeight; + + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, contentWidth, contentHeight); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + + if (screen == NULL || texture == NULL) + { + fprintf(stderr, "Unable to allocate framebuffer or texture\n"); + return; + } + + xCounter = 0; + yCounter = 0; + + SDL_FillRect(screen, &textureRect, 0xFF0000FF); + renderCopy(); + + tick = SDL_GetTicks(); +} + +DISPSIM::~DISPSIM(void) { + if (screen != NULL) + { + SDL_FreeSurface(screen); + } + + if (texture) + { + SDL_DestroyTexture(texture); + } + + if (renderer) + { + SDL_DestroyRenderer(renderer); + } + + if (window) + { + SDL_DestroyWindow(window); + } +} + +void DISPSIM::apply(const unsigned char lcd_data, const unsigned char lcd_hs, + const unsigned char lcd_vs, const unsigned char lcd_enable) { + if (!last_hs && lcd_hs) { + xCounter = 0; + yCounter ++; + } + if (!last_vs && lcd_vs) { + // Verical sync can happen at the same time. + yCounter = 0; + } + if (lcd_enable) { + xCounter ++; + setPixel(xCounter - HBP, yCounter - VBP, colorMap(lcd_data)); + } + + last_vs = lcd_vs; + last_hs = lcd_hs; + + if ((SDL_GetTicks() - tick) > REFRESH_INTERVAL) { + renderCopy(); + tick = SDL_GetTicks(); + } +} + +void DISPSIM::set_title(char *title) { + SDL_SetWindowTitle(window, title); +} + +void DISPSIM::renderCopy(void) { + void *texturePixels; + int texturePitch; + + SDL_LockTexture(texture, NULL, &texturePixels, &texturePitch); + memset(texturePixels, 0, textureRect.y * texturePitch); + uint8_t *pixels = (uint8_t *)texturePixels + textureRect.y * texturePitch; + uint8_t *src = (uint8_t *)screen->pixels; + int leftPitch = textureRect.x << 2; + int rightPitch = texturePitch - ((textureRect.x + textureRect.w) << 2); + for (int y = 0; y < textureRect.h; y++, src += screen->pitch) + { + memset(pixels, 0, leftPitch); pixels += leftPitch; + memcpy(pixels, src, contentWidth << 2); pixels += contentWidth << 2; + memset(pixels, 0, rightPitch); pixels += rightPitch; + } + memset(pixels, 0, textureRect.y * texturePitch); + SDL_UnlockTexture(texture); + + SDL_RenderClear(renderer); + SDL_RenderCopy(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); +} + +void DISPSIM::setPixel(int x, int y, unsigned long pixel) { + uint32_t *pixels = (uint32_t *)screen->pixels; + if ((x < 0) || (y < 0) || (x >= contentWidth) || (y >= contentHeight)) + return; + pixels[y * contentWidth + x] = pixel; +} + +unsigned long DISPSIM::colorMap(unsigned char pixel) { + if (pixel == 3) + return 0xff212f25; + else if (pixel == 2) + return 0xff32513a; + else if (pixel == 1) + return 0xff658635; + else if (pixel == 0) + return 0xff8b9a26; + else + // how??? + return 0xffffffff; +} \ No newline at end of file
diff --git a/verilog/sim/dispsim.h b/verilog/sim/dispsim.h new file mode 100644 index 0000000..e6ae898 --- /dev/null +++ b/verilog/sim/dispsim.h
@@ -0,0 +1,55 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// dispsim.h: Display simulation unit +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once + +class DISPSIM { +public: + const int contentWidth = 160; + const int contentHeight = 144; + const int dispWidth = 320; + const int dispHeight = 288; + DISPSIM(void); + ~DISPSIM(void); + void apply(const unsigned char lcd_data, const unsigned char lcd_hs, + const unsigned char lcd_vs, const unsigned char lcd_enable); + void set_title(char *title); +private: + static constexpr int HBP = 1; + static constexpr int VBP = 2; + static constexpr int REFRESH_INTERVAL = 20; + SDL_Surface *screen = NULL; + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + SDL_Texture *texture = NULL; + SDL_Rect textureRect; + unsigned char last_vs; + unsigned char last_hs; + int xCounter; + int yCounter; + int tick; + void renderCopy(void); + void setPixel(int x, int y, unsigned long pixel); + unsigned long colorMap(unsigned char pixel); +};
diff --git a/verilog/sim/main.cpp b/verilog/sim/main.cpp new file mode 100644 index 0000000..c181510 --- /dev/null +++ b/verilog/sim/main.cpp
@@ -0,0 +1,374 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// main.cpp: VerilogBoy main simulation unit +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include <stdio.h> +#include <stdint.h> +#include <assert.h> +#include <time.h> + +#include <SDL.h> + +#include "verilated.h" +#include "verilated_vcd_c.h" +#include "Vboy.h" + +#include "memsim.h" +#include "mbcsim.h" +#include "dispsim.h" +#include "mmrprobe.h" + +#define CLK_PERIOD_PS 250000 + +#define RAM_BASE 0x80000000 +#define RAM_SIZE 1*1024*1024 + +#define CON_BASE 0x20000000 + +// Verilator related +Vboy *core; +VerilatedVcdC *trace; + +#define CONCAT(a,b) a##b +#define SIGNAL(x) CONCAT(core->boy__DOT__,x) + +// this only applies to quiet mode. +const uint64_t CYCLE_LIMIT = 32768; + +static bool quiet = false; +static bool verbose = false; +static bool enable_trace = false; +static bool noboot = false; +static bool nostop = false; +static bool itrace = false; +static bool usembc = false; +static unsigned short breakpoint = 0xff7f; +static char result_file[127]; + +// Software simulated peripherals +MEMSIM *cartrom; +MEMSIM *cartram; +MBCSIM *mbc; +DISPSIM *dispsim; +MMRPROBE *mmrprobe; +FILE *it; + +// State +uint64_t tickcount; + +double sc_time_stamp() { + // This is in pS. Currently we use a 10ns (100MHz) clock signal. + return (double)tickcount * (double)CLK_PERIOD_PS; +} + +void tick() { + if (usembc) { + mbc->apply( + core->dout, + core->a, + core->wr, + core->rd, + core->din); + } + else { + cartrom->apply( + core->dout, + core->a, + 0, + //core->wr, + core->rd, + core->din); + + cartram->apply( + core->dout, + core->a, + core->wr, + core->rd, + core->din); + } + + if (!quiet) { + dispsim->apply( + core->pixel, + core->hs, + core->vs, + core->valid); + } + + if (verbose) { + mmrprobe->apply( + SIGNAL(cpu_dout), + SIGNAL(cpu_a), + SIGNAL(cpu_wr), + SIGNAL(cpu_rd), + SIGNAL(cpu_din), + SIGNAL(cpu__DOT__last_pc)); + } + + tickcount++; + + core->eval(); + if (enable_trace) trace->dump(tickcount * CLK_PERIOD_PS - CLK_PERIOD_PS / 4); + core->clk = 1; + core->eval(); + if (enable_trace) trace->dump(tickcount * CLK_PERIOD_PS); + core->clk = 0; + core->eval(); + if (enable_trace) trace->dump(tickcount * CLK_PERIOD_PS + CLK_PERIOD_PS / 2); + + if (itrace) { + if ((SIGNAL(cpu__DOT__ct_state == 3)) && + (SIGNAL(cpu__DOT__next == 0))) { + // Instruction just finished executing + fprintf(it, "Time %ld\nPC = %04x, F = %c%c%c%c, A = %02x, SP = %02x%02x\nB = %02x, C = %02x, D = %02x, E = %02x, H = %02x, L = %02x\n", + 10 * tickcount, + SIGNAL(cpu__DOT__pc), + ((SIGNAL(cpu__DOT__flags)) & 0x8) ? 'Z' : '-', + ((SIGNAL(cpu__DOT__flags)) & 0x4) ? 'N' : '-', + ((SIGNAL(cpu__DOT__flags)) & 0x2) ? 'H' : '-', + ((SIGNAL(cpu__DOT__flags)) & 0x1) ? 'C' : '-', + SIGNAL(cpu__DOT__acc__DOT__data), + SIGNAL(cpu__DOT__regfile__DOT__regs[6]), + SIGNAL(cpu__DOT__regfile__DOT__regs[7]), + SIGNAL(cpu__DOT__regfile__DOT__regs[0]), + SIGNAL(cpu__DOT__regfile__DOT__regs[1]), + SIGNAL(cpu__DOT__regfile__DOT__regs[2]), + SIGNAL(cpu__DOT__regfile__DOT__regs[3]), + SIGNAL(cpu__DOT__regfile__DOT__regs[4]), + SIGNAL(cpu__DOT__regfile__DOT__regs[5])); + } + } +} + +void reset() { + core->rst = 0; + tick(); + core->rst = 1; + tick(); + core->rst = 0; + if (noboot) { + SIGNAL(brom_disable) = 1; + } +} + +int main(int argc, char *argv[]) { + + // Initialize testbench + Verilated::commandArgs(argc, argv); + + core = new Vboy; + Verilated::traceEverOn(true); + + if (argc < 2) { + puts("USAGE: vb_sim <rom.gb> [--testmode] [--verbose] [--trace] [--noboot]" + "[--nostop] [--itrace] [--mbc] (verilator paramters...)\n"); + exit(0); + } + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--testmode") == 0) { + quiet = true; + strcpy(result_file, argv[1]); + char *location = strstr(result_file, "."); + if (location == NULL) + location = result_file + strlen(result_file); + strcpy(location, ".actual"); + noboot = true; + } + // Skip boot ROM + if (strcmp(argv[i], "--noboot") == 0) { + noboot = true; + } + // Enable MMR probe + if (strcmp(argv[i], "--verbose") == 0) { + verbose = true; + } + // Enable waveform trace + if (strcmp(argv[i], "--trace") == 0) { + enable_trace = true; + } + // Does not stop on STOP/HALT + if (strcmp(argv[i], "--nostop") == 0) { + nostop = true; + } + // Enable instruction level trace + if (strcmp(argv[i], "--itrace") == 0) { + itrace = true; + } + // Enable MBC emulation + if (strcmp(argv[i], "--mbc") == 0) { + usembc = true; + } + } + + if (enable_trace) { + trace = new VerilatedVcdC; + core->trace(trace, 99); + trace->open("trace.vcd"); + } + + if (usembc) { + mbc = new MBCSIM(); + } + else { + cartrom = new MEMSIM(0x0000, 32768, 0); + cartram = new MEMSIM(0xa000, 8192, 0); + } + + if (!quiet) { + dispsim = new DISPSIM(); + } + if (verbose) { + mmrprobe = new MMRPROBE(); + } + if (itrace) { + it = fopen("itrace.txt", "w"); + if (!it) { + itrace = false; + fprintf(stderr, "Fail to open output file for itrace.\n"); + } + } + + if (usembc) + mbc->load(argv[1]); + else + cartrom->load(argv[1]); + + // Start simulation + if (verbose) + printf("Simulation start.\n"); + + reset(); + + uint32_t sim_tick = 0; + uint32_t ms_tick = SDL_GetTicks(); + char window_title[63]; + bool running = true; + while (running) { + tick(); + + sim_tick++; + + // Check end condition + if (SIGNAL(cpu__DOT__last_pc) == breakpoint) { + printf("Hit breakpoint\n"); + running = false; + } + + if ((tickcount > CYCLE_LIMIT) && (quiet) && (!nostop)) { + printf("Time Limit Exceeded\n"); + running = false; + } + + if (core->fault) { + printf("Core fault condition\n"); + running = false; + } + + if (core->done && !nostop) + running = false; + + // Get the next event + if (!quiet & (sim_tick % 4096 == 0)) { + SDL_Event event; + if (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + // Break out of the loop on quit + running = false; + } + } + uint32_t ms_delta = SDL_GetTicks() - ms_tick; + int sim_freq = sim_tick / ms_delta; + sim_tick = 0; + sprintf(window_title, "VerilogBoy Sim (%d kHz)", sim_freq); + dispsim->set_title(window_title); + ms_tick = SDL_GetTicks(); + } + } + + if (quiet) { + // output result to file + FILE *result; + result = fopen(result_file, "w+"); + assert(result); + fprintf(result, "AF %02x%02x\r\n", + SIGNAL(cpu__DOT__acc__DOT__data), + SIGNAL(cpu__DOT__flags) << 4); + fprintf(result, "BC %02x%02x\r\n", + SIGNAL(cpu__DOT__regfile__DOT__regs[0]), + SIGNAL(cpu__DOT__regfile__DOT__regs[1])); + fprintf(result, "DE %02x%02x\r\n", + SIGNAL(cpu__DOT__regfile__DOT__regs[2]), + SIGNAL(cpu__DOT__regfile__DOT__regs[3])); + fprintf(result, "HL %02x%02x\r\n", + SIGNAL(cpu__DOT__regfile__DOT__regs[4]), + SIGNAL(cpu__DOT__regfile__DOT__regs[5])); + fprintf(result, "SP %02x%02x\r\n", + SIGNAL(cpu__DOT__regfile__DOT__regs[6]), + SIGNAL(cpu__DOT__regfile__DOT__regs[7])); + fprintf(result, "PC %04x\r\n", + SIGNAL(cpu__DOT__pc)); + fclose(result); + } + // print on screen + printf("PC = %04x, F = %c%c%c%c, A = %02x, SP = %02x%02x\nB = %02x, C = %02x, D = %02x, E = %02x, H = %02x, L = %02x\n", + SIGNAL(cpu__DOT__pc), + ((SIGNAL(cpu__DOT__flags)) & 0x8) ? 'Z' : '-', + ((SIGNAL(cpu__DOT__flags)) & 0x4) ? 'N' : '-', + ((SIGNAL(cpu__DOT__flags)) & 0x2) ? 'H' : '-', + ((SIGNAL(cpu__DOT__flags)) & 0x1) ? 'C' : '-', + SIGNAL(cpu__DOT__acc__DOT__data), + SIGNAL(cpu__DOT__regfile__DOT__regs[6]), + SIGNAL(cpu__DOT__regfile__DOT__regs[7]), + SIGNAL(cpu__DOT__regfile__DOT__regs[0]), + SIGNAL(cpu__DOT__regfile__DOT__regs[1]), + SIGNAL(cpu__DOT__regfile__DOT__regs[2]), + SIGNAL(cpu__DOT__regfile__DOT__regs[3]), + SIGNAL(cpu__DOT__regfile__DOT__regs[4]), + SIGNAL(cpu__DOT__regfile__DOT__regs[5]) + ); + + if (enable_trace) { + trace->close(); + } + + delete core; + if (!quiet) { + delete dispsim; + } + if (verbose) { + delete mmrprobe; + } + if (it) { + fclose(it); + } + if (usembc) { + delete mbc; + } + else { + delete cartrom; + delete cartram; + } + + return 0; +} \ No newline at end of file
diff --git a/verilog/sim/mbcsim.cpp b/verilog/sim/mbcsim.cpp new file mode 100644 index 0000000..69a836f --- /dev/null +++ b/verilog/sim/mbcsim.cpp
@@ -0,0 +1,214 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// memsim.cpp: Cartridge with memory bank controller (MBC) simulation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <assert.h> +#include "mbcsim.h" + +MBCSIM::MBCSIM(void) { + ram = new uint8_t[MBC_RAM_SIZE]; + rom = new uint8_t[MBC_ROM_SIZE]; + ram_enable = 0; // Disable by default + mbc_mode = 0; // Banking mode for MBC1 + rom_bank = 1; + ram_bank = 0; + last_wr = 0; + last_rd = 0; + last_data = 0; +} + +MBCSIM::~MBCSIM(void) { + delete[] ram; + delete[] rom; +} + +void MBCSIM::load(const char *fname) { + FILE *fp; + + fp = fopen(fname, "rb"); + assert(fp); + fseek(fp, 0, SEEK_END); + size_t fsize = ftell(fp); + fseek(fp, 0, SEEK_SET); + size_t result = fread((void *)rom, fsize, 1, fp); + assert(result == 1); + fclose(fp); + + char title[17]; + title[16] = 0; + memcpy(title, rom + 0x134, 16); + + printf("ROM Title: %s\n", title); + + char ctype = rom[0x147]; + if ((ctype == 0x00) || (ctype == 0x08) || (ctype == 0x09)) { + mbc_type = MBCNONE; + printf("MBC Type: None\n"); + } + else if ((ctype >= 0x01)&&(ctype <= 0x03)) { + mbc_type = MBC1; + printf("MBC Type: MBC1\n"); + } + else if ((ctype >= 0x05)&&(ctype <= 0x06)) { + mbc_type = MBC2; + printf("MBC Type: MBC2\n"); + } + else if ((ctype >= 0x0f)&&(ctype <= 0x13)) { + mbc_type = MBC3; + printf("MBC Type: MBC3\n"); + } + else if ((ctype >= 0x19)&&(ctype <= 0x1e)) { + mbc_type = MBC5; + printf("MBC Type: MBC5\n"); + } + else { + mbc_type = MBCUNKNOWN; + printf("Unsupported Cartridge Type: %d\n", ctype); + } + + int rom_size = rom[0x148]; + if (rom_size <= 0x08) + rom_size = (1 << rom_size) * 32; + else if (rom_size == 0x52) + rom_size = 72*16; // 72 banks + else if (rom_size == 0x53) + rom_size = 80*16; + else if (rom_size == 0x54) + rom_size = 96*16; + else + rom_size = 32; // Fallback to 32KB + printf("ROM Size: %d KB\n", rom_size); + + int ram_size = rom[0x149]; + if (ram_size == 0x00) + ram_size = 0; + else if (ram_size == 0x01) + ram_size = 2; + else if (ram_size == 0x02) + ram_size = 8; + else if (ram_size == 0x03) + ram_size = 32; + else if (ram_size == 0x04) + ram_size = 128; + else if (ram_size == 0x05) + ram_size = 64; + else + ram_size = 0; + printf("RAM Size: %d KB\n", ram_size); + + memset(ram, 0xff, MBC_RAM_SIZE); +} + +void MBCSIM::apply(const uint8_t wr_data, const uint16_t address, + const uint8_t wr_enable, const uint8_t rd_enable, uint8_t &rd_data) { + + // Address within ROM window or RAM window + if ((address <= 0x8000) || ((address >= 0xa000) && (address < 0xc000))) { + if (last_wr && !wr_enable) { + if (address >= 0xa000) { + // Write to RAM + if (ram_enable == 0x0a) { + if ((mbc_type == MBC1) && (mbc_mode == 0)) { + ram[address - 0xa000] = last_data; + } + else { + ram[address - 0xa000 + ram_bank * 0x2000] = last_data; + } + } + } + else if (address < 0x2000) { + // RAM Enable (MBC1/3/5) + ram_enable = last_data; + } + else if (address < 0x4000) { + // ROM Bank (MBC1/3/5) + if (mbc_type == MBC1) { + rom_bank &= ~0x1f; + rom_bank = (unsigned int)last_data & 0x1f; + if (last_data == 0) + rom_bank |= 0x01; + } + else if (mbc_type == MBC3) { + rom_bank &= ~0x7f; + rom_bank = (unsigned int)last_data & 0x7f; + if (last_data == 0) + rom_bank |= 0x01; + } + else if (mbc_type == MBC5) { + if (address < 0x3000) { + rom_bank &= ~0xff; + rom_bank |= (unsigned int)last_data & 0xff; + } + else { + rom_bank &= ~0x100; + rom_bank |= ((unsigned int)last_data & 0x01) << 8; + } + } + //printf("[MBC] Rom bank %d (%04x=%02x)\n", rom_bank, address, last_data); + } + else if (address < 0x6000) { + if ((mbc_type == MBC1) && (mbc_mode == 0)) { + // High ROM Bank + rom_bank &= ~0xe0; + rom_bank |= ((unsigned int)last_data & 0x03) << 5; + //printf("[MBC] Rom bank %d (%04x=%02x)\n", rom_bank, address, last_data); + } + else { + // RAM Bank + ram_bank = last_data; + //printf("[MBC] Ram bank %d (%04x=%02x)\n", ram_bank, address, last_data); + } + } + else if (address < 0x8000) { + mbc_mode = last_data; + } + } + else if (!last_rd && rd_enable) { + if (address < 0x4000) { + // LoROM + rd_data = rom[address]; + } + else if (address < 0x8000) { + // HiROM + rd_data = rom[address - 0x4000 + rom_bank * 0x4000]; + //printf("[MBC] Read from bank %d, %04x (%06x) = %02x\n", rom_bank, address, address - 0x4000 + rom_bank * 0x4000, rd_data); + } + else { + if ((mbc_type == MBC1) && (mbc_mode == 0)) { + rd_data = ram[address - 0xa000]; + } + else { + rd_data = ram[address - 0xa000 + ram_bank * 0x2000]; + } + } + } + } + last_rd = rd_enable; + last_wr = wr_enable; + last_data = wr_data; +} + +
diff --git a/verilog/sim/mbcsim.h b/verilog/sim/mbcsim.h new file mode 100644 index 0000000..87c4f36 --- /dev/null +++ b/verilog/sim/mbcsim.h
@@ -0,0 +1,57 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// memsim.h: Cartridge with memory bank controller (MBC) simulation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once + +#define MBC_RAM_SIZE (128*1024) +#define MBC_ROM_SIZE (8*1024*1024) + +class MBCSIM { +public: + MBCSIM(void); + ~MBCSIM(void); + void load(const char *fname); + void apply(const uint8_t wr_data, const uint16_t address, const uint8_t wr_enable, + const uint8_t rd_enable, uint8_t &rd_data); +private: + typedef enum { + MBCNONE, + MBC1, + MBC2, + MBC3, + MBC5, + MBCUNKNOWN + } MBCTYPE; + + uint8_t *rom; + uint8_t *ram; + uint8_t last_wr; + uint8_t last_rd; + uint8_t last_data; + MBCTYPE mbc_type; + char ram_enable; + char mbc_mode; + unsigned int rom_bank; + unsigned int ram_bank; +};
diff --git a/verilog/sim/memsim.cpp b/verilog/sim/memsim.cpp new file mode 100644 index 0000000..0cbf3b0 --- /dev/null +++ b/verilog/sim/memsim.cpp
@@ -0,0 +1,93 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// memsim.cpp: A memory simulation model with simple delay control +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <assert.h> +#include "memsim.h" + +MEMSIM::MEMSIM(uint16_t base, size_t len, size_t delay) { + this->base = base; + this->len = len; + this->delay = delay; + mem = (uint8_t *)malloc(len); + delay_count = 0; + last_wr = 0; + last_rd = 0; + last_data = 0; +} + +MEMSIM::~MEMSIM(void) { + free(mem); +} + +void MEMSIM::load(char *fname) { + FILE *fp; + + fp = fopen(fname, "rb"); + assert(fp); + fseek(fp, 0, SEEK_END); + size_t fsize = ftell(fp); + fseek(fp, 0, SEEK_SET); + size_t result = fread((void *)mem, fsize, 1, fp); + assert(result == 1); + fclose(fp); +} + +void MEMSIM::apply(uint8_t wr_data, uint16_t address, + uint8_t wr_enable, uint8_t rd_enable, uint8_t &rd_data) { + + if (delay_count == 0) { + if ((address >= base) && (address < (base + len))) { + if (last_wr && !wr_enable) { + mem[address - base] = last_data; + delay_count = delay; +#ifdef __DEBUG + printf("MEMBUS W[%04x] = %02x\n", + address, + last_data); +#endif + } + else if (!last_rd && rd_enable) { + rd_data = mem[address - base]; + delay_count = delay; +#ifdef __DEBUG + printf("MEMBUS R[%04x] = %02x\n", + address, + rd_data); +#endif + } + } + last_rd = rd_enable; + last_wr = wr_enable; + last_data = wr_data; + } + else { + delay_count --; + } +} + +
diff --git a/verilog/sim/memsim.h b/verilog/sim/memsim.h new file mode 100644 index 0000000..8ce05d2 --- /dev/null +++ b/verilog/sim/memsim.h
@@ -0,0 +1,43 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// memsim.h: A memory simulation model with simple delay control +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once + +class MEMSIM { +public: + MEMSIM(uint16_t base, size_t len, size_t delay); + ~MEMSIM(void); + void load(char *fname); + void apply(uint8_t wr_data, uint16_t address, uint8_t wr_enable, + uint8_t rd_enable, uint8_t &rd_data); +private: + uint16_t base; + uint8_t *mem; + uint16_t len; + int delay; + int delay_count; + uint8_t last_wr; + uint8_t last_rd; + uint8_t last_data; +};
diff --git a/verilog/sim/mmrprobe.cpp b/verilog/sim/mmrprobe.cpp new file mode 100644 index 0000000..5ff5fc3 --- /dev/null +++ b/verilog/sim/mmrprobe.cpp
@@ -0,0 +1,63 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// mmrprobe.cpp: A probe that prints out MMR access logs +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <assert.h> +#include "mmrprobe.h" + +MMRPROBE::MMRPROBE() { + last_wr = 0; + last_rd = 0; + last_data = 0; +} + +MMRPROBE::~MMRPROBE(void) { + +} + +void MMRPROBE::apply(uint8_t wr_data, uint16_t address, + uint8_t wr_enable, uint8_t rd_enable, uint8_t &rd_data, uint16_t pc) { + + // Ignore ROM and HRAM RW + if (last_wr && !wr_enable) { + if ((address >= 0x8000)&&(address <= 0xff7f)) { + printf("PC %04x: BUS W[%04x] = %02x\n", pc, address, last_data); + } + } + else if (last_rd && !rd_enable) { + if ((address >= 0x8000)&&(address <= 0xff7f)&&(address != 0xff44)) { + printf("PC %04x: BUS R[%04x] = %02x\n", pc, address, rd_data); + } + /*if ((address == 0xff44) && (rd_data == 0x99)) { + printf("VSYNC\n"); + }*/ + } + last_rd = rd_enable; + last_wr = wr_enable; + last_data = wr_data; +} + +
diff --git a/verilog/sim/mmrprobe.h b/verilog/sim/mmrprobe.h new file mode 100644 index 0000000..ceff7cd --- /dev/null +++ b/verilog/sim/mmrprobe.h
@@ -0,0 +1,37 @@ +// +// VerilogBoy simulator +// Copyright 2022 Wenting Zhang +// +// mmrprobe.h: A probe that prints out MMR access logs +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +#pragma once + +class MMRPROBE { +public: + MMRPROBE(void); + ~MMRPROBE(void); + void apply(uint8_t wr_data, uint16_t address, uint8_t wr_enable, + uint8_t rd_enable, uint8_t &rd_data, uint16_t pc); +private: + uint8_t last_wr; + uint8_t last_rd; + uint8_t last_data; +};
diff --git a/verilog/sim/rtl.mk b/verilog/sim/rtl.mk new file mode 100644 index 0000000..fdc9c72 --- /dev/null +++ b/verilog/sim/rtl.mk
@@ -0,0 +1,42 @@ +TARGET ?= boy +all: $(TARGET) + +VOBJ := obj_dir +CXX := g++ +FBDIR := ../rtl +CPUS ?= $(shell bash -c 'nproc --all') +VERBOSE ?= 0 + +.PHONY: all +$(TARGET): $(VOBJ)/V$(TARGET)__ALL.a + +SUBMAKE := $(MAKE) --no-print-directory --directory=$(VOBJ) -f +ifeq ($(VERILATOR_ROOT),) +VERILATOR := verilator +else +VERILATOR := $(VERILATOR_ROOT)/bin/verilator +endif +VFLAGS := -Wall -Wno-fatal -MMD --trace -cc -I../rtl +ifeq ($(VERBOSE), 1) +VFLAGS += +define+VERBOSE=1 +endif + +$(VOBJ)/V$(TARGET)__ALL.a: $(VOBJ)/V$(TARGET).cpp $(VOBJ)/V$(TARGET).h +$(VOBJ)/V$(TARGET)__ALL.a: $(VOBJ)/V$(TARGET).mk + +$(VOBJ)/V%.cpp $(VOBJ)/V%.h $(VOBJ)/V%.mk: $(FBDIR)/%.v + $(VERILATOR) $(VFLAGS) $*.v + +$(VOBJ)/V%.cpp: $(VOBJ)/V%.h +$(VOBJ)/V%.mk: $(VOBJ)/V%.h +$(VOBJ)/V%.h: $(FBDIR)/%.v + +$(VOBJ)/V%__ALL.a: $(VOBJ)/V%.mk + $(SUBMAKE) V$*.mk -j$(CPUS) + +.PHONY: clean +clean: + rm -rf $(VOBJ)/*.mk + rm -rf $(VOBJ)/*.cpp + rm -rf $(VOBJ)/*.h + rm -rf $(VOBJ)/