blob: e2c13c76c9f70323cf00d339d07f48fb6bb44551 [file] [log] [blame]
// vdp_sprite_core.v
//
// Copyright (C) 2020 Dan Rodrigues <danrr.gh.oss@gmail.com>
//
// SPDX-License-Identifier: Apache-2.0
`default_nettype none
module vdp_sprite_core #(
parameter INIT_X_BLOCK = "",
parameter INIT_Y_BLOCK = "",
parameter INIT_G_BLOCK = "",
parameter SPRITES_TOTAL = 31,
parameter [0:0] REGSTER_RENDER_X = 0
) (
input clk,
input start_new_line,
input [9:0] render_x,
input [8:0] render_y,
input line_buffer_hold,
input [4:0] meta_address,
input [15:0] meta_write_data,
input [2:0] meta_block_select,
input meta_we,
input [13:0] vram_base_address,
output vram_read_data_needs_x_flip,
output [13:0] vram_read_address,
input [31:0] vram_read_data,
input vram_data_valid,
output [7:0] pixel,
output [1:0] pixel_priority
);
localparam SPRITES_ADDR_BITS = $clog2(SPRITES_TOTAL);
reg line_buffer_hold_r;
always @(posedge clk) begin
if (start_new_line) begin
line_buffer_hold_r <= line_buffer_hold;
end
end
reg start_new_line_r;
always @(posedge clk) begin
start_new_line_r <= start_new_line;
end
// yosys hangs on the "make count" target with x_r removed:
// > 28.39. Executing DFF2DFFE pass (transform $dff to $dffe where applicable).
// ...
// need to investigate this, leaving this commented out until then
// It would save on LCs though
// SPRITE_X_INITIAL needs to have an additional -1 if this change is made
reg [9:0] render_x_r;
generate
if (REGSTER_RENDER_X) begin
always @(posedge clk) begin
render_x_r <= render_x;
end
end else begin
always @* begin
render_x_r = render_x;
end
end
endgenerate
// --- Metadata block writing ---
assign x_block_we = meta_block_select[0] && meta_we;
assign y_block_we = meta_block_select[1] && meta_we;
assign g_block_we = meta_block_select[2] && meta_we;
// --- y_block ---
// ----whYy yyyyyyyy
// y: sprite y (9 bits, could be extended to 10bits on another platform)
// h: height select (8 or 16)
// w: width select (8 or 16)
// Y: Y flip - the advantage of doing it here is that it frees up bits in other attribute blocks
// -: unused
wire [7:0] y_block_read_address;
wire [15:0] y_block_data_out;
wire y_block_we;
ffram #(
.INIT_FILE(INIT_Y_BLOCK),
.DATA_WIDTH(16),
.ADDR_WIDTH(SPRITES_ADDR_BITS)
) y_block (
.clk(clk),
.wdata0(meta_write_data),
.we0(y_block_we),
.addr0(meta_address),
.addr1(y_block_read_address[4:0]),
.rdata1(y_block_data_out)
);
// --- g_block ---
// ppppPPgg gggggggg
// g: character #
// p: palette
// P: priority
wire [7:0] g_block_read_address;
wire [15:0] g_block_data_out;
wire g_block_we;
ffram #(
.INIT_FILE(INIT_G_BLOCK),
.DATA_WIDTH(16),
.ADDR_WIDTH(SPRITES_ADDR_BITS)
) g_block (
.clk(clk),
.wdata0(meta_write_data),
.we0(g_block_we),
.addr0(meta_address),
.addr1(g_block_read_address[4:0]),
.rdata1(g_block_data_out)
);
// --- x_block ---
// -----Xxx xxxxxxxx
// x: x position
// X: flip
// -: unused
wire [7:0] x_block_read_address;
wire [15:0] x_block_data_out;
wire x_block_we;
ffram #(
.INIT_FILE(INIT_X_BLOCK),
.DATA_WIDTH(16),
.ADDR_WIDTH(SPRITES_ADDR_BITS)
) x_block (
.clk(clk),
.wdata0(meta_write_data),
.we0(x_block_we),
.addr0(meta_address),
.addr1(x_block_read_address[4:0]),
.rdata1(x_block_data_out)
);
// --- Hit list (private) ---
// layout:
// T--wcccc iiiiiiii
// i: sprite ID
// c: collision Y within sprite (0-15, 16px sprite tall is the max)
// w: width select (8 or 16)
// T: terminator bit
// -: unused
wire hit_list_select = render_y[0];
wire hit_list_ended = hit_list_render_read_data[15];
wire [15:0] hit_list_render_read_data = hit_list_select ?
hit_list_read_data_1 : hit_list_read_data_0;
wire [7:0] hit_list_read_address;
wire [7:0] hit_list_write_address;
wire hit_list_write_en;
wire [15:0] hit_list_data_in;
wire [31:0] hit_list_read_data;
wire [15:0] hit_list_read_data_0 = hit_list_read_data[15:0];
wire [15:0] hit_list_read_data_1 = hit_list_read_data[31:16];
generate
genvar i;
for (i = 0; i < 2; i = i + 1) begin : hit_list_gen
wire write_en = hit_list_write_en & (hit_list_select ^ i);
wire [15:0] read_data;
assign hit_list_read_data[i * 16 + 15: i * 16] = read_data;
wire [7:0] address = write_en ? hit_list_write_address : hit_list_read_address;
ffram #(
.DATA_WIDTH(16),
.ADDR_WIDTH(SPRITES_ADDR_BITS)
) hit_list (
.clk(clk),
.wdata0(hit_list_data_in),
.rdata0(read_data),
.we0(write_en),
.addr0(address[4:0])
);
end
endgenerate
// --- Line buffers ---
// selected buffer toggles every line
wire line_buffer_select = !render_y[0];
// there is an offscreen and onscreen buffer at any given time
// on: onscreen - being read
// off: offscreen - being rendered to
wire [9:0] line_buffer_write_address;
wire [11:0] line_buffer_data_in;
wire line_buffer_write_en;
wire [9:0] line_buffer_clear_write_address;
wire line_buffer_clear_en;
// Reduced line buffer width means writes must be gated according to write address
// The original BRAM setup means there was "spillover" RAM and this check wasn't needed
wire line_buffer_write_gate = line_buffer_write_address < 256;
wire line_buffer_gated_write = line_buffer_write_en && line_buffer_write_gate;
// --- Line buffers ---
wire [23:0] line_buffer_data_out;
wire [11:0] line_buffer_data_out_0 = line_buffer_data_out[11:0];
wire [11:0] line_buffer_data_out_1 = line_buffer_data_out[23:12];
generate
for (i = 0; i < 2; i = i + 1) begin : line_buffer_gen
wire select = line_buffer_select ^ i;
wire [9:0] write_address = select ? line_buffer_write_address : line_buffer_clear_write_address;
wire [11:0] write_data = select ? line_buffer_data_in : line_buffer_clear_data_in;
wire write_en = (select ? line_buffer_gated_write : line_buffer_clear_en) && !line_buffer_hold_r;
wire [11:0] read_data;
// Line buffer has reduced size and color index bitwidth
// Originally it was 1024px x 10bit words
// It is reduced to 256px (double-width rendered) and 2bit words
wire [1:0] pixel_2bpp;
assign read_data = {10'b0, pixel_2bpp};
ffram #(
.DATA_WIDTH(2),
.ADDR_WIDTH(8)
) line_buffer (
.clk(clk),
.wdata0(write_data[1:0]),
.we0(write_en),
.addr0(write_address[7:0]),
.addr1(line_buffer_display_read_address[7:0]),
.rdata1(pixel_2bpp)
);
assign line_buffer_data_out[i * 12 + 11 : i * 12] = read_data;
end
endgenerate
// --- Line buffer clearing ---
reg [9:0] line_buffer_previous_read_address;
assign line_buffer_clear_write_address = line_buffer_previous_read_address;
wire [11:0] line_buffer_clear_data_in = 12'h000;
assign line_buffer_clear_en = line_buffer_clear_write_address < 9'h100;
always @(posedge clk) begin
line_buffer_previous_read_address <= line_buffer_display_read_address;
end
// --- Line buffer reading ---
wire [9:0] line_buffer_display_read_address;
wire [9:0] line_buffer_display_data;
// data to read from active buffer
assign line_buffer_display_data = line_buffer_select ?
line_buffer_data_out_1 : line_buffer_data_out_0;
assign line_buffer_display_read_address = render_x_r;
// These are registered in the sprite_line_buffer module
// 8bit palette index
assign pixel = line_buffer_display_data[7:0];
// 2bit priority
assign pixel_priority = line_buffer_display_data[9:8];
// there is no competing access from blitter / prefetch
assign hit_list_read_address = hit_list_blitter_read_address;
// --- Sprite raster-collision testing ---
// attribute extraction from y_block data
wire [8:0] sprite_y_read = y_block_data_out[8:0];
wire [4:0] sprite_selected_height = y_block_data_out[10] ? 16 : 8;
wire sprite_flip_y = y_block_data_out[9];
wire sprite_width_select = y_block_data_out[11];
assign hit_list_data_in[14:13] = 0;
vdp_sprite_raster_collision #(
.SPRITES_TOTAL(SPRITES_TOTAL)
) collision (
.clk(clk),
.restart(start_new_line_r),
.render_y(render_y),
// reading
.sprite_y(sprite_y_read),
.sprite_test_id(y_block_read_address),
.sprite_height(sprite_selected_height),
.flip_y(sprite_flip_y),
.width_select_in(sprite_width_select),
// writing
.sprite_y_intersect(hit_list_data_in[11:8]),
.sprite_id(hit_list_data_in[7:0]),
.hit_list_index(hit_list_write_address),
.finished(hit_list_data_in[15]),
.width_select_out(hit_list_data_in[12]),
.hit_list_write_en(hit_list_write_en)
);
// --- Sprite render ---
wire [7:0] hit_list_blitter_read_address;
wire [7:0] blitter_sprite_meta_read_address;
// these happen to be the same read address, at the same time
assign g_block_read_address = blitter_sprite_meta_read_address;
assign x_block_read_address = blitter_sprite_meta_read_address;
vdp_sprite_render render(
.clk(clk),
.restart(start_new_line_r),
.vram_base_address(vram_base_address),
.vram_read_address(vram_read_address),
.vram_read_data_needs_x_flip(vram_read_data_needs_x_flip),
.vram_read_data(vram_read_data),
.vram_data_valid(vram_data_valid),
.sprite_meta_address(blitter_sprite_meta_read_address),
.character(g_block_data_out[9:0]),
.palette(g_block_data_out[15:12]),
.pixel_priority(g_block_data_out[11:10]),
.target_x(x_block_data_out[9:0]),
.flip_x(x_block_data_out[10]),
.line_buffer_write_address(line_buffer_write_address),
.line_buffer_write_data(line_buffer_data_in),
.line_buffer_write_en(line_buffer_write_en),
.hit_list_read_address(hit_list_blitter_read_address),
.sprite_id(hit_list_render_read_data[7:0]),
.line_offset(hit_list_render_read_data[11:8]),
.width_select(hit_list_render_read_data[12]),
.hit_list_ended(hit_list_ended)
);
endmodule