Livt packages
Eccelerators.Matrix
Version 1.0.1
Reusable RGB matrix drawing, color conversion, animation pacing, and HUB75 display components for Livt.
Published files (48)
src/Hub75/Hub75Framebuffer.lvt
namespace Eccelerators.Matrix.Hub75
using Eccelerators.Matrix
/**
* Own two packed RGB image banks and expose a synchronous scan source plus a
* blocking drawing API. Use one scheduled drawing client in the same context;
* Clear must complete before each Present. Reset invalidates both images and
* outputs black without clearing block RAM. Never overlap drawing calls.
*/
component Hub75Framebuffer<SHIFT_COLUMNS: int = 64, ROW_BITS: int = 5, COLOR_BITS: int = 1>
{
static assert SHIFT_COLUMNS >= 2 && SHIFT_COLUMNS <= 1024, "Shift columns must be 2..1024"
static assert ROW_BITS > 0 && ROW_BITS <= 5, "Row bits must be 1..5"
static assert COLOR_BITS >= 1 && COLOR_BITS <= 6, "Color bits must be 1..6"
// Public pixel-coordinate bounds.
const PIXEL_BITS: int = 3 * COLOR_BITS
const COLUMN_BITS: int = Bits.RequiredFor(SHIFT_COLUMNS - 1)
public const WIDTH: int = SHIFT_COLUMNS
public const HEIGHT: int = 2 << ROW_BITS
// Storage geometry: one lane contains two image banks.
const LANE_SIZE: int = SHIFT_COLUMNS * (1 << ROW_BITS)
const CAPACITY: int = 2 * LANE_SIZE
// Upper-lane image banks; contents remain unspecified until Clear.
@Memory(Style = MemoryStyle.Block)
@UninitializedStorage
upperMemory: logic[CAPACITY, PIXEL_BITS]
// Lower-lane image banks, with the same ownership and initialization contract.
@Memory(Style = MemoryStyle.Block)
@UninitializedStorage
lowerMemory: logic[CAPACITY, PIXEL_BITS]
// Scan-source requests, sampled in the shared clock/reset context.
public readColumn: in logic[COLUMN_BITS]
public readRow: in logic[ROW_BITS]
public frameBoundary: in logic
// Packed RGB scan responses, black until the first completed presentation.
public rgbUpper: out logic[PIXEL_BITS]
public rgbLower: out logic[PIXEL_BITS]
// Registered RAM responses published without an extra output clock.
responseUpper: logic[PIXEL_BITS]
responseLower: logic[PIXEL_BITS]
// Front-bank selection and presentation request/acknowledgment toggles.
frontBank: int
request: bool
acknowledged: bool
frontValid: bool
backInitialized: bool
// Single-client write payload and completion handshake.
writeAddress: int
writeData: logic[PIXEL_BITS]
writeLower: bool
writeRequest: bool
writeAcknowledged: bool
/**
* Write packed R,G,B channels to the back buffer at zero-based (x,y).
* Return false outside [0,WIDTH) x [0,HEIGHT), without a write. Return true
* only after the memory write is acknowledged. Requires the sole drawing
* client; the front image stays unchanged until Present completes.
*/
public fn SetPixel(x: int, y: int, rgb: logic[PIXEL_BITS]) bool
{
if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) {
return false
}
state {
// Address = back-bank offset + row within this RGB lane + column.
writeAddress = (1 - frontBank) * LANE_SIZE + (y & (HEIGHT / 2 - 1)) * WIDTH + x
writeData = rgb
writeLower = y >= HEIGHT / 2
writeRequest = !writeRequest
}
while (writeAcknowledged != writeRequest) {
}
return true
}
/**
* Fill every back-buffer pixel and wait for all writes to complete.
* Mark this drawing transaction initialized for Present. Call once before
* each new image; each R,G,B channel has COLOR_BITS bits. Requires the sole
* drawing client.
*/
public fn Clear(rgb: logic[PIXEL_BITS])
{
for (var y = 0; y < HEIGHT; y++) {
for (var x = 0; x < WIDTH; x++) {
SetPixel(x, y, rgb)
}
}
backInitialized = true
}
/**
* Return false immediately if Clear has not completed for this image.
* Otherwise wait for a frame boundary and return true after the banks swap.
* The old front bank becomes the back bank and requires another Clear.
* The scan source must keep supplying frameBoundary; reset cancels pending
* work and invalidates the image. Requires the sole drawing client.
*/
public fn Present() bool
{
if (!backInitialized) {
return false
}
request = !request
while (acknowledged != request) {
}
backInitialized = false
return true
}
/**
* Publish registered RAM responses without adding another accessor clock.
*/
process Outputs[]()
{
rgbUpper = responseUpper
rgbLower = responseLower
}
/**
* Own all RAM writes, front-bank swaps and scan reads in one clocked state.
* A pending swap precedes the read on a frame boundary; acknowledged writes
* target the back bank. Before the first presentation, scan outputs are black.
*/
process MemoryAccess()
{
state {
if (frameBoundary == 0b1 && request != acknowledged) {
frontBank = 1 - frontBank
frontValid = true
acknowledged = request
}
if (frontValid) {
// Both lane reads use front-bank offset + unsigned scan row/column.
responseUpper = upperMemory[frontBank * LANE_SIZE +
((readRow as uint) as int) * WIDTH +
((readColumn as uint) as int)] as logic[PIXEL_BITS]
responseLower = lowerMemory[frontBank * LANE_SIZE +
((readRow as uint) as int) * WIDTH +
((readColumn as uint) as int)] as logic[PIXEL_BITS]
} else {
responseUpper = 0 as logic[PIXEL_BITS]
responseLower = 0 as logic[PIXEL_BITS]
}
if (writeRequest != writeAcknowledged) {
if (writeLower) {
lowerMemory[writeAddress] = writeData
} else {
upperMemory[writeAddress] = writeData
}
writeAcknowledged = writeRequest
}
}
}
}