From ae9080302614a921a3748fd613630e11b738041b Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sat, 21 Sep 2024 14:33:11 -0700 Subject: [PATCH] tiler/Tiler: start of tiling engine. Some internal modules are written, with tests. Top-level tiler module still TODO. --- tiler/Tiler.bsv | 414 +++++++++++++++++++++++++++++++ tiler/Tiler_Test.bsv | 569 +++++++++++++++++++++++++++++++++++++++++++ vram/VRAM.bsv | 4 + 3 files changed, 987 insertions(+) create mode 100644 tiler/Tiler.bsv create mode 100644 tiler/Tiler_Test.bsv diff --git a/tiler/Tiler.bsv b/tiler/Tiler.bsv new file mode 100644 index 0000000..4e9f811 --- /dev/null +++ b/tiler/Tiler.bsv @@ -0,0 +1,414 @@ +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 diff --git a/tiler/Tiler_Test.bsv b/tiler/Tiler_Test.bsv new file mode 100644 index 0000000..741240f --- /dev/null +++ b/tiler/Tiler_Test.bsv @@ -0,0 +1,569 @@ +package Tiler_Test; + +import Connectable::*; +import GetPut::*; +import ClientServer::*; +import Assert::*; +import StmtFSM::*; +import LFSR::*; +import Probe::*; + +import Testing::*; +import Tiler::*; +import VRAM::*; + +interface Test; + method Action start(); + method Bool done(); +endinterface + +(* synthesize *) +module mkTestPixelcoordToTileSubcoord(Test); + let testflags <- mkTestFlags(); + Reg#(Bool) run <- mkReg(False); + Reg#(Bool) finished <- mkReg(False); + + function Action check_pxc_to_tsc(PixelCoord screen_pixel, TilePixelCount pixels_per_tile, TileCount num_tiles, ExtendedPixelCoord scroll_offset, TileCoord want_tile, TilePixelCoord want_subtile); + return action + let got = pixelcoord_to_tilesubcoord(screen_pixel, pixels_per_tile, num_tiles, scroll_offset); + let want = TileSubcoord{tile: want_tile, pixel: want_subtile}; + if (testflags.verbose) + $display("pixelcoord_to_tilesubcoord(", screen_pixel, ", ", pixels_per_tile, ", ", num_tiles, ", ", scroll_offset, ") = ", fshow(got), ", want ", fshow(want)); + dynamicAssert(got == want, "wrong output from pixelcoord_to_tilesubcoord"); + endaction; + endfunction + + rule test (run && !finished); + // Basic conversion with 32x8px tiles + check_pxc_to_tsc(0, 8, 32, 0, + 0, 0); + check_pxc_to_tsc(5, 8, 32, 0, + 0, 5); + check_pxc_to_tsc(7, 8, 32, 0, + 0, 7); + check_pxc_to_tsc(8, 8, 32, 0, + 1, 0); + check_pxc_to_tsc(9, 8, 32, 0, + 1, 1); + + // 32x16px tiles + check_pxc_to_tsc(9, 16, 32, 0, + 0, 9); + check_pxc_to_tsc(15, 16, 32, 0, + 0, 15); + check_pxc_to_tsc(16, 16, 32, 0, + 1, 0); + + // Tile wraparound at 32x8 + check_pxc_to_tsc(255, 8, 32, 0, + 31, 7); + check_pxc_to_tsc(256, 8, 32, 0, + 0, 0); + check_pxc_to_tsc(511, 8, 32, 0, + 31, 7); + check_pxc_to_tsc(512, 8, 32, 0, + 0, 0); + + // Tile wraparound at 64x8 + check_pxc_to_tsc(256, 8, 64, 0, + 32, 0); + check_pxc_to_tsc(511, 8, 64, 0, + 63, 7); + check_pxc_to_tsc(512, 8, 64, 0, + 0, 0); + + // Scroll offset + check_pxc_to_tsc(0, 8, 32, 42, + 5, 2); + check_pxc_to_tsc(1, 8, 32, 42, + 5, 3); + check_pxc_to_tsc(5, 8, 32, 42, + 5, 7); + check_pxc_to_tsc(6, 8, 32, 42, + 6, 0); + check_pxc_to_tsc(6, 8, 32, 41, + 5, 7); + + // Scroll offset vs. wraparound + check_pxc_to_tsc(255, 8, 32, 2, + 0, 1); + check_pxc_to_tsc(0, 8, 32, 3072, + 0, 0); + check_pxc_to_tsc(0, 8, 32, 3071, + 31, 7); + + finished <= True; + endrule + + method Action start() if (!run); + run <= True; + endmethod + method done = finished._read; +endmodule + +(* synthesize *) +module mkTestPaletteExpander(Test); + let testflags <- mkTestFlags(); + let cycles <- mkCycleCounter(); + + let dut <- mkPaletteExpander(); + let got_probe <- mkProbe(); + let got_done_probe <- mkProbe(); + (* no_implicit_conditions,fire_when_enabled *) + rule probe_done; + got_done_probe <= dut.done; + endrule + + function Action check_pixel(PaletteIndex want); + return action + let got <- dut.pixel(); + got_probe <= got; + if (testflags.verbose) + $display("PaletteExpander.pixel() = %0d, want %0d", got, want); + dynamicAssert(got == want, "wrong PaletteExpander output"); + + let got_done = dut.done(); + let want_done = False; + if (testflags.verbose) + $display("PaletteExpander.done() = ", fshow(got_done), ", want ", fshow(want_done)); + dynamicAssert(got_done == want_done, "wrong PaletteExpander done()"); + + dynamicAssert(cycles == 1, "output took more than 1 cycle"); + cycles.reset(); + endaction; + endfunction + + let fsm <- mkFSM(par + seq + dut.expand('h8e, 8, Bpp1); + dut.expand('ha4, 4, Bpp2); + dut.expand('h6c, 2, Bpp4); + dut.expand('h4d, 1, Bpp8); + + dut.expand('h8e, 5, Bpp1); + dut.expand('ha4, 3, Bpp2); + dut.expand('h6c, 1, Bpp4); + endseq + + seq + cycles.reset(); + + // 0x8e, 1bpp + check_pixel(1); + check_pixel(0); + check_pixel(0); + check_pixel(0); + check_pixel(1); + check_pixel(1); + check_pixel(1); + check_pixel(0); + + // 0xa4, 2bpp + check_pixel(2); + check_pixel(2); + check_pixel(1); + check_pixel(0); + + // 0x6c, 4bpp + check_pixel(6); + check_pixel(12); + + // 0x4d, 8bpp + check_pixel('h4d); + + // 0x8d, 1bpp, first 5 only + check_pixel(1); + check_pixel(0); + check_pixel(0); + check_pixel(0); + check_pixel(1); + + // 0xa4, 2bpp, first 3 only + check_pixel(2); + check_pixel(2); + check_pixel(1); + + // 0x6c, 4bpp, first only + check_pixel(6); + + action + dynamicAssert(dut.done, "PaletteExpander not done after processing all requests"); + dynamicAssert(cycles == 1, "done flag took too long to assert"); + endaction + endseq + + endpar); + + method start = fsm.start; + method done = fsm.done; +endmodule + +(* synthesize *) +module mkTestTileRenderer(Test); + let testflags <- mkTestFlags(); + let cycles <- mkCycleCounter(); + + let vram <- mkVRAM(4); + let dut <- mkTileRenderer(); + mkConnection(dut.vram, vram.tile1); + + let got_probe <- mkProbe(); + let want_probe <- mkProbe(); + let done_probe <- mkProbe(); + (* no_implicit_conditions,fire_when_enabled *) + rule set_done_probe; + done_probe <= dut.done(); + endrule + + function Action check_pixel(PaletteIndex want); + return action + let got <- dut.tiles.response.get(); + got_probe <= got; + want_probe <= want; + if (testflags.verbose) + $display("TileRenderer.get() = %02x, want %02x", got, want); + dynamicAssert(got == want, "wrong pixel rendered"); + endaction; + endfunction + + Reg#(VRAMAddr) i <- mkReg(0); + LFSR#(Bit#(8)) rnd <- mkLFSR_8(); + let populate_vram = seq + for (i <= 0; i < 32; i <= i+1) action + let val = rnd.value(); + if (testflags.verbose) + $display("VRAM.write(%0d, 0x%02x) (%0d, %08b)", i, val, val, val); + vram.cpu.request.put(VRAMRequest{ + addr: i, + data: tagged Valid rnd.value + }); + rnd.next(); + endaction + endseq; + + let test_basic_render = seq + dut.tiles.request.put(TileRequest{ + tile_row_addr: 1, + start_col: 0, + end_col: 7, + bpp: Bpp1, + flip: False + }); + check_pixel(1); + check_pixel(0); + check_pixel(0); + check_pixel(0); + check_pixel(1); + check_pixel(1); + check_pixel(1); + check_pixel(0); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 0, + start_col: 0, + end_col: 7, + bpp: Bpp1, + flip: False + }); + check_pixel(0); + check_pixel(0); + check_pixel(0); + check_pixel(0); + check_pixel(0); + check_pixel(0); + check_pixel(0); + check_pixel(1); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 2, + start_col: 0, + end_col: 7, + bpp: Bpp2, + flip: False + }); + check_pixel(1); + check_pixel(0); + check_pixel(1); + check_pixel(3); + check_pixel(2); + check_pixel(2); + check_pixel(3); + check_pixel(1); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 2, + start_col: 0, + end_col: 7, + bpp: Bpp4, + flip: False + }); + check_pixel(4); + check_pixel(7); + check_pixel(10); + check_pixel(13); + check_pixel(13); + check_pixel(8); + check_pixel(6); + check_pixel(12); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 4, + start_col: 0, + end_col: 7, + bpp: Bpp8, + flip: False + }); + check_pixel('hd8); + check_pixel('h6c); + check_pixel('h36); + check_pixel('h1b); + check_pixel('h83); + check_pixel('hcf); + check_pixel('he9); + check_pixel('hfa); + endseq; + + let test_stalled_consumer = par + seq + dut.tiles.request.put(TileRequest{ + tile_row_addr: 12, + start_col: 0, + end_col: 7, + bpp: Bpp8, + flip: False + }); + dut.tiles.request.put(TileRequest{ + tile_row_addr: 4, + start_col: 0, + end_col: 7, + bpp: Bpp8, + flip: False + }); + endseq + + seq + check_pixel('h7d); + check_pixel('hb0); + check_pixel('h58); + check_pixel('h2c); + repeat (10) noAction; + check_pixel('h16); + check_pixel('h0b); + check_pixel('h8b); + check_pixel('hcb); + + repeat (5) noAction; + check_pixel('hd8); + check_pixel('h6c); + check_pixel('h36); + repeat (5) noAction; + check_pixel('h1b); + check_pixel('h83); + check_pixel('hcf); + check_pixel('he9); + check_pixel('hfa); + endseq + endpar; + + let test_scrolled_start = seq + dut.tiles.request.put(TileRequest{ + tile_row_addr: 1, + start_col: 3, + end_col: 7, + bpp: Bpp1, + flip: False + }); + check_pixel(0); + check_pixel(1); + check_pixel(1); + check_pixel(1); + check_pixel(0); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 2, + start_col: 1, + end_col: 7, + bpp: Bpp4, + flip: False + }); + check_pixel(7); + check_pixel(10); + check_pixel(13); + check_pixel(13); + check_pixel(8); + check_pixel(6); + check_pixel(12); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 4, + start_col: 3, + end_col: 7, + bpp: Bpp2, + flip: False + }); + check_pixel(0); + check_pixel(1); + check_pixel(2); + check_pixel(3); + check_pixel(0); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 12, + start_col: 3, + end_col: 7, + bpp: Bpp8, + flip: False + }); + check_pixel('h2c); + check_pixel('h16); + check_pixel('h0b); + check_pixel('h8b); + check_pixel('hcb); + endseq; + + let test_scrolled_end = seq + dut.tiles.request.put(TileRequest{ + tile_row_addr: 1, + start_col: 0, + end_col: 5, + bpp: Bpp1, + flip: False + }); + check_pixel(1); + check_pixel(0); + check_pixel(0); + check_pixel(0); + check_pixel(1); + check_pixel(1); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 2, + start_col: 0, + end_col: 4, + bpp: Bpp4, + flip: False + }); + check_pixel(4); + check_pixel(7); + check_pixel(10); + check_pixel(13); + check_pixel(13); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 4, + start_col: 0, + end_col: 4, + bpp: Bpp2, + flip: False + }); + check_pixel(3); + check_pixel(1); + check_pixel(2); + check_pixel(0); + check_pixel(1); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 12, + start_col: 0, + end_col: 4, + bpp: Bpp8, + flip: False + }); + check_pixel('h7d); + check_pixel('hb0); + check_pixel('h58); + check_pixel('h2c); + check_pixel('h16); + endseq; + + let test_flipped_tile = seq + dut.tiles.request.put(TileRequest{ + tile_row_addr: 1, + start_col: 0, + end_col: 7, + bpp: Bpp1, + flip: True + }); + check_pixel(0); + check_pixel(1); + check_pixel(1); + check_pixel(1); + check_pixel(0); + check_pixel(0); + check_pixel(0); + check_pixel(1); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 2, + start_col: 0, + end_col: 7, + bpp: Bpp2, + flip: True + }); + check_pixel(1); + check_pixel(3); + check_pixel(2); + check_pixel(2); + check_pixel(3); + check_pixel(1); + check_pixel(0); + check_pixel(1); + + dut.tiles.request.put(TileRequest{ + tile_row_addr: 2, + start_col: 1, + end_col: 5, + bpp: Bpp2, + flip: True + }); + // pixels in VRAM order: 1 0 1 3 2 2 3 1 + // unflipped slice : _ 0 1 3 2 2 _ _ + // flipped pixels : 1 3 2 2 3 1 0 1 + // flipped slice : _ 3 2 2 3 1 _ _ + check_pixel(3); + check_pixel(2); + check_pixel(2); + check_pixel(3); + check_pixel(1); + endseq; + + let fsm <- mkFSM(seq + populate_vram; + + test_basic_render; + test_stalled_consumer; + test_scrolled_start; + test_scrolled_end; + test_flipped_tile; + + repeat (10) noAction; + endseq); + + method start = fsm.start; + method done = fsm.done; +endmodule + +module mkTB(); + let test_pxc_to_tsc <- mkTestPixelcoordToTileSubcoord(); + let test_palette_expander <- mkTestPaletteExpander(); + let test_tile_renderer <- mkTestTileRenderer(); + + Reg#(Scanline) line <- mkReg(0); + runTest(2000, + mkTest("Tiler", seq + mkTest("Tiler/PixelcoordToTilesubcoord", seq + test_pxc_to_tsc.start(); + await(test_pxc_to_tsc.done); + endseq); + mkTest("Tiler/PaletteExpander", seq + test_palette_expander.start(); + await(test_palette_expander.done); + endseq); + mkTest("Tiler/TileRenderer", seq + test_tile_renderer.start(); + await(test_tile_renderer.done); + endseq); + endseq)); +endmodule + +endpackage diff --git a/vram/VRAM.bsv b/vram/VRAM.bsv index 407bcd4..ce7399f 100644 --- a/vram/VRAM.bsv +++ b/vram/VRAM.bsv @@ -14,6 +14,7 @@ import VRAMCore::*; export VRAMAddr, VRAMData, VRAMRequest(..), VRAMResponse(..); export VRAMServer(..); +export VRAMClient(..); export VRAM(..), mkVRAM; export mkArbitratedVRAMServers; @@ -21,6 +22,9 @@ export mkArbitratedVRAMServers; // A VRAMServer is a memory port. typedef Server#(VRAMRequest, VRAMResponse) VRAMServer; +// A VRAMClient is a user of a memory port. +typedef Client#(VRAMRequest, VRAMResponse) VRAMClient; + // mkArbitratedVRAMServers expands a VRAMServer port into multiple // ports through the use of a MemArbiter. module mkArbitratedVRAMServers(VRAMServer ram, MemArbiter#(n, VRAMAddr) arb, Vector#(n, VRAMServer) ifc)