#!/usr/bin/env python3
|
|
|
|
# This file is Copyright (c) 2020 Wolfgang Villing <ringel@it-ringel.de>
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
import math
|
|
from migen import *
|
|
from migen.fhdl import verilog
|
|
from litex.soc.interconnect.csr import *
|
|
|
|
class ColorDim(Module):
|
|
"""Binary dimming for RGB332 Colors"""
|
|
def __init__(self):
|
|
self.pixUpper = Signal(8)
|
|
self.pixLower = Signal(8)
|
|
self.output = Signal(6)
|
|
self.resetCounter = Signal()
|
|
self.incrementCounter = Signal()
|
|
self.oen = Signal()
|
|
self.done = Signal()
|
|
self.active = Signal()
|
|
self.waitForLast = Signal()
|
|
|
|
pulseduration = Signal(22)
|
|
bitToCheck = Signal(3)
|
|
|
|
self.sync += If( self.resetCounter,
|
|
pulseduration.eq(0),
|
|
bitToCheck.eq(0b100)
|
|
).Elif(self.incrementCounter,
|
|
pulseduration.eq(bitToCheck << 5), # Multiply by 16 for Max Brightness
|
|
bitToCheck.eq(bitToCheck >> 1)
|
|
).Else(
|
|
If((pulseduration == 0),
|
|
pulseduration.eq(0)
|
|
).Else(
|
|
pulseduration.eq(pulseduration - 1)
|
|
)
|
|
)
|
|
self.comb += self.active.eq(pulseduration != 0)
|
|
self.comb += self.oen.eq(pulseduration == 0)
|
|
self.comb += self.done.eq((bitToCheck == 0) & (pulseduration == 0))
|
|
self.comb += self.waitForLast.eq((bitToCheck == 0) & (pulseduration != 0))
|
|
|
|
r1, r2, g1, g2, b1, b2 = [Signal() for _ in range(6)]
|
|
|
|
self.comb += r1.eq(self.pixUpper[5:8] & bitToCheck > 0)
|
|
self.comb += r2.eq(self.pixLower[5:8] & bitToCheck > 0)
|
|
self.comb += g1.eq(self.pixUpper[2:5] & bitToCheck > 0)
|
|
self.comb += g2.eq(self.pixLower[2:5] & bitToCheck > 0)
|
|
self.comb += b1.eq(self.pixUpper[0:2] & bitToCheck[1:3] > 0)
|
|
self.comb += b2.eq(self.pixLower[0:2] & bitToCheck[1:3] > 0)
|
|
self.comb += self.output.eq(Cat(r1, g1, b1, r2, g2, b2))
|
|
|
|
class Hub75Out(Module):
|
|
def __init__(self):
|
|
self.rgb = Signal(6)
|
|
self.adr = Signal(5)
|
|
self.clk = Signal()
|
|
self.lat = Signal()
|
|
self.oen = Signal()
|
|
|
|
class Hub75Sender(Module, AutoCSR):
|
|
"""Hub75 LED Matrix driver. Framebuffer Colorformat RGB332"""
|
|
def __init__(self, numCols=8, numRows=4, content=None, out=None):
|
|
addressOffset = Constant(numCols * numRows/2)
|
|
rowCountShift = math.ceil(math.log2(numCols))
|
|
memSize = numCols * numRows
|
|
#self.status = CSRStatus(32)
|
|
|
|
# Memory mapped to Wishbone - having second read port
|
|
self.specials.mem = Memory(8, memSize, init=content)
|
|
ram = self.mem.get_port()
|
|
self.specials += ram
|
|
|
|
pwm = ColorDim()
|
|
self.submodules += pwm
|
|
|
|
# Pixel and Row counter
|
|
self.pixCount = Signal(max=numCols+1)
|
|
self.rowCount = Signal(max=numRows)
|
|
|
|
# Output Ports
|
|
self.outAddr = Signal(5) # abcde
|
|
self.outClk = Signal()
|
|
self.outLat = Signal()
|
|
self.outRGB = Signal(6) # (6) r0 r1 g0 g1 b0 b1
|
|
self.comb += self.outRGB.eq(pwm.output),
|
|
self.frameDone = Signal() # Debug
|
|
self.rowDone = Signal() # Debug
|
|
if out:
|
|
self.comb += out.rgb.eq(self.outRGB)
|
|
self.comb += out.adr.eq(self.outAddr)
|
|
self.comb += out.lat.eq(self.outLat)
|
|
self.comb += out.clk.eq(self.outClk)
|
|
self.comb += out.oen.eq(pwm.oen)
|
|
|
|
fsm = FSM(reset_state="RESET")
|
|
self.submodules.fsm = fsm
|
|
|
|
fsm.act("RESET",
|
|
NextValue(self.pixCount, 0),
|
|
NextValue(self.rowCount, 0),
|
|
pwm.resetCounter.eq(1),
|
|
NextState("AdrUp"))
|
|
|
|
fsm.act("AdrUp", # Address upper Pixel
|
|
ram.adr.eq(self.pixCount + (self.rowCount << rowCountShift)),
|
|
NextState("AdrLo"))
|
|
|
|
fsm.act("AdrLo", # Address lower Pixel + Readout Upper Pixel data
|
|
ram.adr.eq((self.pixCount + addressOffset) + (self.rowCount << rowCountShift)),
|
|
NextValue(self.pixCount, self.pixCount+1),
|
|
NextValue(pwm.pixUpper, ram.dat_r),
|
|
NextState("ReadLow"))
|
|
|
|
fsm.act("ReadLow", # Readout Lower Pixel data
|
|
NextValue(pwm.pixLower, ram.dat_r),
|
|
NextState("ClkOn"))
|
|
|
|
fsm.act("ClkOn", # Clock the Data out
|
|
NextValue(self.outClk, 1),
|
|
NextState("ClkOff"))
|
|
|
|
fsm.act("ClkOff", # Clock the Data out
|
|
NextValue(self.outClk, 0),
|
|
If(pwm.active & (self.pixCount == numCols),
|
|
NextState("ClkOff") # Wait for last Dimming done
|
|
).Elif(self.pixCount == numCols,
|
|
NextValue(self.pixCount, 0),
|
|
NextState("RowOk") # Finished with Row
|
|
).Else(
|
|
# Skip AddrUp State and set Address here
|
|
ram.adr.eq(self.pixCount + (self.rowCount << rowCountShift)),
|
|
NextState("AdrLo")
|
|
))
|
|
|
|
fsm.act("RowOk", NextValue(self.outLat, 1), NextState("LatOn"))
|
|
fsm.act("LatOn", NextState("AdrRow"))
|
|
fsm.act("AdrRow", NextValue(self.outLat, 0), NextState("LatOff"))
|
|
fsm.act("LatOff", pwm.incrementCounter.eq(1), NextState("CkCnt"))
|
|
|
|
fsm.act("CkCnt",
|
|
If(pwm.waitForLast,
|
|
NextState("CkCnt")
|
|
).Elif(pwm.done,
|
|
If(self.rowCount == Constant(numRows/2 - 1),
|
|
NextValue(self.rowCount, 0),
|
|
self.frameDone.eq(1)
|
|
).Else(
|
|
NextValue(self.rowCount, self.rowCount + 1),
|
|
),
|
|
NextState("OeOn"),
|
|
pwm.resetCounter.eq(1)
|
|
).Else(
|
|
NextState("AdrUp")
|
|
)
|
|
)
|
|
|
|
fsm.act("OeOn",
|
|
NextValue(self.outAddr, self.rowCount),
|
|
NextState("Done"))
|
|
fsm.act("Done", self.rowDone.eq(1), NextState("AdrUp"))
|
|
|
|
def testbench():
|
|
content = (0, 1, 2, 3,
|
|
0, 0, 0, 0,
|
|
0x00, 0x04, 0x08, 0x0C, 0, 0, 0, 0)
|
|
|
|
|
|
dut = Hub75Sender(4, 4, content, Hub75Out())
|
|
|
|
def memtest():
|
|
for _ in range(1000):
|
|
yield
|
|
yield
|
|
|
|
#print(verilog.convert(dut, dut.ios))
|
|
run_simulation(dut, memtest(), vcd_name="hub75sender.vcd", clocks={"sys": 1000})
|