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