gary/tiler/Tiler.bsv

415 lines
15 KiB
Plaintext

package Tiler;
import FIFOF::*;
import SpecialFIFOs::*;
import StmtFSM::*;
import ConfigReg::*;
import GetPut::*;
import ClientServer::*;
import VRAM::*;
/////////////////////
// Coordinates and coordinate transforms
// Within the tiler, the universe is a grid of tiles. The screen is a
// viewport that is placed onto that grid for rendering. If the
// viewport overflows the tile grid, the tile grid is wrapped and
// repeated until the entire viewport is on solid ground.
// PixelCoord is the X or Y coordinate of a pixel on screen.
typedef UInt#(10) PixelCoord;
// Scanline is the vertical offset of one display scanline. Scanline 0
// is the first line of pixels drawn at the top of a frame, scanline
// 479 is the final line at the bottom of the screen (assuming a
// display mode that uses full resolution, otherwise the final
// scanline will be a lower number).
typedef UInt#(9) Scanline;
// TileCoord is the X or Y coordinate of a tile.
typedef UInt#(8) TileCoord;
// TilePixelCoord is the X or Y coordinate of a pixel within a tile.
typedef UInt#(4) TilePixelCoord;
// TileSubcoord is like a TilePixelCoord, but expressed as a TileCoord
// plus a pixel offset within the tile.
typedef struct {
TileCoord tile;
TilePixelCoord pixel;
} TileSubcoord deriving (Bits, Eq, FShow);
// ExtendedPixelCoord is the X or Y coordinate of a pixel in tile
// space. This is a much larger space than PixelCoord, because in the
// maximal configuration the tiler can be working with 256 tiles of 16
// pixels, 4096 pixels compared to the screen's 640x480.
typedef UInt#(TAdd#(SizeOf#(TileCoord), SizeOf#(TilePixelCoord))) ExtendedPixelCoord;
// TileCount is a number of tiles. It exists as a distinct type from
// TileCoord because it needs to be 1 bit larger to be able to
// represent the value of zero tiles (which in TileCoord is just the
// identifier of the first tile).
typedef UInt#(TAdd#(SizeOf#(TileCoord), 1)) TileCount;
typedef UInt#(TAdd#(SizeOf#(TilePixelCoord), 1)) TilePixelCount;
typedef PixelCoord PixelCount;
// pixelcoord_to_tilesubcoord translates a screen pixel X or Y
// coordinate to the corresponding tile and pixel offset within the
// tile.
//
// This can be used for both row and column computations. When
// screen_pixel is a row number and the other parameters describe
// heights (tile height, map height, vertical scroll offset), the
// returned value identifies the corresponding tile row, as well as
// the line offset within the tile.
function TileSubcoord pixelcoord_to_tilesubcoord(PixelCoord screen_pixel, TilePixelCount pixels_per_tile, TileCount num_tiles, ExtendedPixelCoord scroll_offset);
ExtendedPixelCoord scrolled_pixel = scroll_offset + extend(screen_pixel);
TileCoord scrolled_tile = truncate(scrolled_pixel / extend(pixels_per_tile));
TileCoord wrapped_tile = truncate(extend(scrolled_tile) % num_tiles);
TilePixelCoord line_in_tile = truncate(scrolled_pixel % extend(pixels_per_tile));
return TileSubcoord{tile: wrapped_tile, pixel: line_in_tile};
endfunction
// PaletteIndex is an index into the screen palette, describing the
// colour of one pixel.
typedef UInt#(8) PaletteIndex;
typedef UInt#(10) TileIndex;
// TilerMode is the tiling engine's output mode.
typedef enum {
// In Tile mode, the tiler operates on a tile map: for each
// {8,16}x{8,16} chunk of screen space, the map points to the tile
// to be rendered. The map entry combined with the corresponding
// tile descriptor defines the PaletteIndex to output for each
// pixel.
Tile,
// In Bitmap mode, the tiler operates on a simple bitmap array,
// where each array element directly describes the PaletteIndex for
// one pixel.
Bitmap
} TilerMode deriving (Bits, Eq, FShow);
// ColorDepth describes the number of bits used to encode a
// PaletteIndex in the tile or bitmap data.
typedef enum {
// 1bpp, each pixel can be on (foreground PaletteIndex) or off
// (background or transparent PaletteIndex, depending on other mode
// settings).
Bpp1,
// 2bpp, PaletteIndex values 0-3
Bpp2,
// 4bpp, PaletteIndex value 0-15
Bpp4,
// 8bpp, PaletteIndex value 0-255 (full range)
Bpp8
} ColorDepth deriving (Bits, Eq);
instance FShow#(ColorDepth);
function Fmt fshow(ColorDepth val);
case (val)
Bpp1: return $format("1bpp");
Bpp2: return $format("2bpp");
Bpp4: return $format("4bpp");
Bpp8: return $format("8bpp");
endcase
endfunction
endinstance
function UInt#(4) pixels_per_byte(ColorDepth bpp);
case (bpp)
Bpp1: return 8;
Bpp2: return 4;
Bpp4: return 2;
Bpp8: return 1;
endcase
endfunction
function UInt#(4) bits_per_pixel(ColorDepth bpp);
case (bpp)
Bpp1: return 1;
Bpp2: return 2;
Bpp4: return 4;
Bpp8: return 8;
endcase
endfunction
// MapSize is the X or Y dimension of a tile map.
typedef enum {
Tiles32,
Tiles64,
Tiles128,
Tiles256
} MapSize deriving (Bits, Eq);
instance FShow#(MapSize);
function Fmt fshow(MapSize sz);
case (sz)
Tiles32: return $format("32 tiles");
Tiles64: return $format("64 tiles");
Tiles128: return $format("128 tiles");
Tiles256: return $format("256 tiles");
endcase
endfunction
endinstance
// // num_tiles returns the number of tiles for the given MapSize.
// function TileCount num_tiles(MapSize m);
// return 32 << pack(m);
// endfunction
// // tile_bytes returns the number of bytes needed to store cnt map tile
// // descriptors in memory.
// function VRAMAddr map_tile_bytes(TileCount cnt);
// return 2 * extend(cnt);
// endfunction
// function VRAMAddr tile_addr(MapSize cols, TileCoord row, TileCoord col);
// return (extend(row) * fromInteger(tile_bytes_per_row(cols))) + (2*extend(col));
// endfunction
/////////////////////
// The tiler hardware itself.
(* always_ready *)
interface TilerConfig;
interface Reg#(TilerMode) mode;
interface Reg#(ColorDepth) depth; // 1/2/4/8bpp
interface Reg#(Bool) text_hicolor; // in 1bpp tile mode, 256 foreground colors and transparent background
interface Reg#(VRAMAddr) tile_base; // or bitmap data in bitmap mode
// Tile mode settings
interface Reg#(VRAMAddr) map_base;
interface Reg#(MapSize) map_width; // 32/64/128/256
interface Reg#(MapSize) map_height; // 32/64/128/256
interface Reg#(Bool) tile_double_width; // 8b or 16b tiles
interface Reg#(Bool) tile_double_height; // 8b or 16b tiles
interface Reg#(ExtendedPixelCoord) horizontal_scroll; // pixels from left screen edge
interface Reg#(ExtendedPixelCoord) vertical_scroll; // pixels from top screen edge
endinterface
module mkTilerConfig(TilerConfig);
Reg#(TilerMode) mode <- mkConfigReg(Tile);
Reg#(ColorDepth) depth <- mkConfigReg(Bpp1);
Reg#(Bool) text_hicolor <- mkConfigReg(False);
Reg#(VRAMAddr) tile_base <- mkConfigReg(0);
Reg#(VRAMAddr) map_base <- mkConfigReg(0);
Reg#(MapSize) map_width <- mkConfigReg(Tiles32);
Reg#(MapSize) map_height <- mkConfigReg(Tiles32);
Reg#(Bool) tile_double_width <- mkConfigReg(False);
Reg#(Bool) tile_double_height <- mkConfigReg(False);
Reg#(ExtendedPixelCoord) horizontal_scroll <- mkConfigReg(0);
Reg#(ExtendedPixelCoord) vertical_scroll <- mkConfigReg(0);
return (interface TilerConfig
interface mode = mode;
interface depth = depth;
interface text_hicolor = text_hicolor;
interface tile_base = tile_base;
interface map_base = map_base;
interface map_width = map_width;
interface map_height = map_height;
interface tile_double_width = tile_double_width;
interface tile_double_height = tile_double_height;
interface horizontal_scroll = horizontal_scroll;
interface vertical_scroll = vertical_scroll;
endinterface);
endmodule
interface PaletteExpander;
method Action expand(Bit#(8) val, UInt#(4) num_pixels, ColorDepth bpp);
method ActionValue#(PaletteIndex) pixel();
method Bool done();
endinterface
module mkPaletteExpander(PaletteExpander);
Reg#(Bit#(8)) val[2] <- mkCReg(2, 0);
Reg#(UInt#(4)) num_pixels[2] <- mkCReg(2, 0);
Reg#(ColorDepth) bpp <- mkReg(Bpp1);
method Action expand(val_in, num_pixels_in, bpp_in) if (num_pixels[1] == 0);
val[1] <= val_in;
num_pixels[1] <= num_pixels_in;
bpp <= bpp_in;
endmethod
method ActionValue#(PaletteIndex) pixel() if (num_pixels[0] > 0);
PaletteIndex ret = case (bpp)
Bpp1: extend(unpack(val[0][7]));
Bpp2: extend(unpack(val[0][7:6]));
Bpp4: extend(unpack(val[0][7:4]));
Bpp8: unpack(val[0]);
endcase;
val[0] <= val[0] << bits_per_pixel(bpp);
num_pixels[0] <= num_pixels[0] - 1;
return ret;
endmethod
method Bool done();
return num_pixels[0] == 0;
endmethod
endmodule
typedef struct {
VRAMAddr tile_row_addr;
TilePixelCoord start_col;
TilePixelCoord end_col;
ColorDepth bpp;
Bool flip;
} TileRequest deriving (Bits, Eq, FShow);
instance DefaultValue#(TileRequest);
defaultValue = TileRequest{
tile_row_addr: 0,
start_col: 0,
end_col: 0,
bpp: Bpp1,
flip: False
};
endinstance
interface TileRenderer;
interface Server#(TileRequest, PaletteIndex) tiles;
interface Client#(VRAMRequest, VRAMResponse) vram;
method Bool done();
endinterface
module mkTileRenderer(TileRenderer ifc);
// Render output. Memory handler below feeds bytes in, pixel data
// comes out.
PaletteExpander expander <- mkPaletteExpander();
// Memory fetcher state: number of bytes remaining to issue, issue
// address, whether rendering is going backwards through memory.
Reg#(UInt#(5)) bytes_to_issue <- mkReg(0); // 0 to 16
Reg#(VRAMAddr) next_byte_addr <- mkReg(0);
Reg#(Bool) flip <- mkReg(False);
// Memory reader state: number of responses remaining to process,
// initial and final shifts to apply.
Reg#(UInt#(5)) bytes_to_retire <- mkReg(0); // 0 to 16
Reg#(UInt#(4)) initial_skip_pixels <- mkReg(0);
Reg#(UInt#(4)) final_skip_pixels <- mkReg(0);
Reg#(ColorDepth) bpp <- mkReg(Bpp1);
function Bit#(8) reverse_pixels(Bit#(8) px);
case (bpp)
Bpp1: return reverseBits(px);
Bpp2: return {px[1:0], px[3:2], px[5:4], px[7:6]};
Bpp4: return {px[3:0], px[7:4]};
Bpp8: return px;
endcase
endfunction
function Bool mem_done();
return bytes_to_issue == 0 && bytes_to_retire == 0;
endfunction
function VRAMAddr byte_for_pixel(TilePixelCoord pixel, ColorDepth depth);
return extend(pixel / pixels_per_byte(depth));
endfunction
interface Client vram;
interface Get request;
method ActionValue#(VRAMRequest) get() if (bytes_to_issue > 0);
next_byte_addr <= flip ? next_byte_addr - 1 : next_byte_addr + 1;
bytes_to_issue <= bytes_to_issue - 1;
return VRAMRequest{
addr: next_byte_addr,
data: tagged Invalid
};
endmethod
endinterface
interface Put response;
method Action put(VRAMResponse resp) if (bytes_to_retire > 0);
let tile_byte = resp.data;
if (flip)
tile_byte = reverse_pixels(tile_byte);
let pixels_to_render = pixels_per_byte(bpp);
if (initial_skip_pixels != 0) begin
tile_byte = tile_byte << (initial_skip_pixels * bits_per_pixel(bpp));
pixels_to_render = pixels_to_render - initial_skip_pixels;
end
else if (bytes_to_retire == 1) // Final byte for this tile
pixels_to_render = pixels_to_render - final_skip_pixels;
expander.expand(tile_byte, pixels_to_render, bpp);
bytes_to_retire <= bytes_to_retire - 1;
initial_skip_pixels <= 0;
endmethod
endinterface
endinterface
interface Server tiles;
interface Put request;
method Action put(tile_req) if (mem_done());
UInt#(4) px_per_byte = pixels_per_byte(tile_req.bpp);
// Depending on color depth and tile width, one row of a
// tile can span multiple bytes. Combined with horizontal
// scrolling, we may not be starting the render on the
// first byte of the tile data.
//
// Likewise when rendering the last tile in a scanline, we
// may cut off early, and thus need to calculate the
// ending byte as well.
VRAMAddr start_col_byte = byte_for_pixel(tile_req.start_col, tile_req.bpp);
VRAMAddr end_col_byte = byte_for_pixel(tile_req.end_col, tile_req.bpp);
// start and end are inclusive, hence the +1.
UInt#(5) bytes_to_read = truncate(end_col_byte - start_col_byte + 1);
// Memory operations happen on a byte basis, but again
// depending on color depth we may not start/end the tile
// render on a byte boundary. In practice, the tile render
// breaks down into 3 phases: render the first byte
// (potentially partially), then zero or more whole bytes,
// then the final byte (potentially partially).
//
// These values are the number of pixels to omit from
// those first and last bytes.
UInt#(4) start_skip_pixels = tile_req.start_col % px_per_byte;
UInt#(4) end_skip_pixels = px_per_byte - 1 - (tile_req.end_col % px_per_byte);
// One final wrinkle is that the tile may be flipped
// horizontally. If so, we need to start rendering from
// the final byte and move backwards, with the skipped
// pixel counts swapped appropriately.
//
// Here we're flipping the order in which we'll read the
// bytes. The retire_read rule above handles swapping the
// pixels within each byte, if applicable.
bytes_to_issue <= bytes_to_read;
bytes_to_retire <= bytes_to_read;
flip <= tile_req.flip;
bpp <= tile_req.bpp;
initial_skip_pixels <= start_skip_pixels;
final_skip_pixels <= end_skip_pixels;
if (tile_req.flip) begin
next_byte_addr <= tile_req.tile_row_addr + end_col_byte;
//initial_skip_pixels <= end_skip_pixels;
//final_skip_pixels <= start_skip_pixels;
end
else begin
next_byte_addr <= tile_req.tile_row_addr + start_col_byte;
end
endmethod
endinterface
interface response = toGet(expander.pixel);
endinterface
method Bool done();
return mem_done() && expander.done();
endmethod
endmodule
endpackage