Litex System on Colorlight Board. ECP5 FPGA runs Risc-V Core and custom Hardware
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

179 lines
6.6 KiB

#!/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})