216 lines
4.8 KiB
Systemverilog
216 lines
4.8 KiB
Systemverilog
module tb_top;
|
|
|
|
logic gb_clk;
|
|
logic vga_clk;
|
|
|
|
logic gb_nreset;
|
|
logic vga_nreset;
|
|
logic nreset;
|
|
|
|
wire gb_cart_clk;
|
|
wire gb_cart_nreset;
|
|
wire gb_cart_nrd;
|
|
wire gb_cart_nwr;
|
|
wire gb_cart_ncs;
|
|
wire [15:0] gb_cart_addr;
|
|
wire [ 7:0] gb_cart_data;
|
|
|
|
wire display_enable;
|
|
wire [ 1:0] ppu_pixel;
|
|
wire ppu_hsync;
|
|
wire ppu_vsync;
|
|
|
|
logic [7:0] vga_r;
|
|
logic [7:0] vga_g;
|
|
logic [7:0] vga_b;
|
|
logic vga_hsync;
|
|
logic vga_vsync;
|
|
|
|
// 4.19MHz
|
|
clkgen #(
|
|
.PERIOD_NS (238),
|
|
.RESET_DELAY_NS (400)
|
|
) gb_clkgen_inst (
|
|
.clk (gb_clk),
|
|
.nreset(gb_nreset)
|
|
);
|
|
|
|
// 25MHz
|
|
clkgen #(
|
|
.PERIOD_NS (40),
|
|
.RESET_DELAY_NS (70)
|
|
) vga_clkgen_inst (
|
|
.clk (vga_clk),
|
|
.nreset(vga_nreset)
|
|
);
|
|
|
|
assign nreset = gb_nreset & vga_nreset;
|
|
|
|
gb gb_inst (
|
|
.clk (gb_clk),
|
|
.nreset(nreset),
|
|
|
|
.cart_clk_o (gb_cart_clk),
|
|
.cart_nreset_o(gb_cart_nreset),
|
|
.cart_nrd_o (gb_cart_nrd),
|
|
.cart_nwr_o (gb_cart_nwr),
|
|
.cart_ncs_o (gb_cart_ncs),
|
|
.cart_addr_o (gb_cart_addr),
|
|
.cart_data_io (gb_cart_data),
|
|
|
|
.ppu_display_enable_o(display_enable),
|
|
.ppu_pixel_o (ppu_pixel),
|
|
.ppu_vsync_o (ppu_vsync),
|
|
.ppu_hsync_o (ppu_hsync)
|
|
);
|
|
|
|
cart cart_inst (
|
|
.clk (gb_cart_clk),
|
|
.nreset (gb_cart_nreset),
|
|
.nrd_i (gb_cart_nrd),
|
|
.nwr_i (gb_cart_nwr),
|
|
.ncs_i (gb_cart_ncs),
|
|
.addr_i (gb_cart_addr),
|
|
.data_io (gb_cart_data)
|
|
);
|
|
|
|
display display_inst (
|
|
.nreset (nreset),
|
|
.VGA_PALETTE(
|
|
// RR GG BB
|
|
'{24'h00_00_00, // colour 00
|
|
24'h60_60_60, // colour 01
|
|
24'hA0_A0_A0, // colour 10
|
|
24'hFF_FF_FF} // colour 11
|
|
),
|
|
|
|
.display_enable_i(display_enable),
|
|
|
|
.ppu_clk (gb_clk),
|
|
.ppu_pixel_i(ppu_pixel),
|
|
.ppu_vsync_i(ppu_vsync),
|
|
.ppu_hsync_i(ppu_hsync),
|
|
|
|
.vga_clk (vga_clk),
|
|
.vga_r_o (vga_r),
|
|
.vga_g_o (vga_g),
|
|
.vga_b_o (vga_b),
|
|
.vga_hsync_o(vga_hsync),
|
|
.vga_vsync_o(vga_vsync)
|
|
);
|
|
|
|
vgasim vgasim_inst (
|
|
.clk (vga_clk),
|
|
.nreset(nreset),
|
|
|
|
.r_i (vga_r),
|
|
.g_i (vga_g),
|
|
.b_i (vga_b),
|
|
.hsync_i (vga_hsync),
|
|
.vsync_i (vga_vsync)
|
|
);
|
|
|
|
`ifdef SVA_ENABLE
|
|
`include "sva_common.svh"
|
|
`SVA_DEF_CLK(gb_clk);
|
|
`SVA_DEF_NRESET(nreset);
|
|
|
|
logic instr_valid;
|
|
logic instr_undef;
|
|
logic halted;
|
|
logic [15:0] current_pc;
|
|
logic [ 7:0] current_opcode[2:0];
|
|
|
|
logic we;
|
|
logic [15:0] last_write_address;
|
|
logic [ 7:0] last_write_value;
|
|
|
|
logic vram_sel;
|
|
logic hiram_sel;
|
|
logic ppu_sel;
|
|
|
|
assign halted = gb_inst.cpu_inst.ctrl_inst.halted_r;
|
|
assign instr_valid = gb_inst.cpu_inst.instr_valid;
|
|
assign instr_undef = gb_inst.cpu_inst.ctrl_inst.instr_undef;
|
|
assign current_opcode = gb_inst.cpu_inst.instr;
|
|
|
|
assign we = gb_inst.cpu_we;
|
|
|
|
assign vram_sel = gb_inst.vram_sel;
|
|
assign hiram_sel = gb_inst.hiram_sel;
|
|
assign ppu_sel = gb_inst.ppu_inst.ppu_ioregs_inst.sva_sel;
|
|
|
|
always_ff @(posedge gb_clk or negedge nreset) begin
|
|
if (!nreset)
|
|
current_pc <= '0;
|
|
else if (instr_valid)
|
|
current_pc <= gb_inst.cpu_inst.pc_r;
|
|
end
|
|
|
|
always_ff @(posedge gb_clk or negedge nreset) begin
|
|
if (!nreset) begin
|
|
last_write_address <= '0;
|
|
last_write_value <= '0;
|
|
end else if (we) begin
|
|
last_write_address <= gb_inst.cpu_addr;
|
|
last_write_value <= gb_inst.cpu_wdata;
|
|
end
|
|
end
|
|
|
|
`SVA_ASSERT_PROP_FATAL(undefined_opcode_pushed,
|
|
halted |-> !instr_undef,
|
|
$sformatf("PC: 0x%X | Undefined opcode pushed: 0x%X (0x%X, 0x%X)", current_pc, current_opcode[0], current_opcode[1], current_opcode[2])
|
|
);
|
|
|
|
logic selected_memory_implemented;
|
|
|
|
assign selected_memory_implemented = hiram_sel | vram_sel | ppu_sel;
|
|
|
|
`SVA_ASSERT_PROP(write_to_unimplemented_memory,
|
|
we |-> selected_memory_implemented,
|
|
$sformatf("PC: 0x%X | Write to unimplemented memory: 0x%X <= 0x%X",
|
|
current_pc, last_write_address, last_write_value)
|
|
);
|
|
|
|
// Watchdog timer
|
|
localparam HIST_SIZE = 8;
|
|
localparam MAX_COUNT = 10000;
|
|
logic [15:0] pc_history [$];
|
|
int unsigned pc_history_count [$];
|
|
|
|
always @(posedge gb_clk iff instr_valid) begin
|
|
automatic int unsigned current_pc_count = 0;
|
|
|
|
foreach (pc_history[i]) begin
|
|
if (current_pc == pc_history[i]) begin
|
|
// History contains current PC, remove from history but keep counter
|
|
current_pc_count = pc_history_count[i];
|
|
pc_history.delete(i);
|
|
pc_history_count.delete(i);
|
|
break;
|
|
end
|
|
end
|
|
|
|
// Put in front of history queue
|
|
pc_history.push_front(current_pc);
|
|
pc_history_count.push_front(current_pc_count + 1);
|
|
|
|
// Prune back of history
|
|
if (pc_history.size() > HIST_SIZE) begin
|
|
void'(pc_history.pop_back());
|
|
void'(pc_history_count.pop_back());
|
|
end
|
|
|
|
assert (current_pc_count < MAX_COUNT) else begin
|
|
$display($sformatf("[%0t] Watchdog timeout", $time));
|
|
$display("History:");
|
|
foreach (pc_history[i]) begin
|
|
$display($sformatf("0x%X: %d", pc_history[i], pc_history_count[i]));
|
|
end
|
|
$fatal();
|
|
end
|
|
end
|
|
|
|
`endif /* SVA_ENABLE */
|
|
endmodule : tb_top
|