Compare commits

..

No commits in common. "1ccc0b3d3989403d77a700c8cbe0cca966f3b37f" and "f3789b64326eedb2703125da7736bc47b593c2b5" have entirely different histories.

12 changed files with 366 additions and 355 deletions

1
.gitignore vendored
View file

@ -198,7 +198,6 @@ dmypy.json
# Cython debug symbols
cython_debug/
.ruff_cache
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore

View file

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1723637854,
"narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=",
"lastModified": 1712791164,
"narHash": "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
"rev": "1042fd8b148a9105f3c0aca3a6177fd1d9360ba5",
"type": "github"
},
"original": {

View file

@ -23,6 +23,7 @@
inherit system;
config.allowUnfree = true;
overlays = [
litex-overlay
]; # patches, version pins, new pkgs here.
}
));
@ -35,6 +36,16 @@
packages = with pkgs; [
(python3.withPackages (ps: with ps; [
cocotb
cocotb-bus
litex
litedram
liteeth
litescope
litespi
liteiclink
pythondata-cpu-vexriscv
pythondata-software-compiler_rt
pythondata-software-picolibc
amaranth
]))
yosys

114
groovylight/hub75.py Normal file
View file

@ -0,0 +1,114 @@
# r0 g0 b0 gnd r1 g1 b1 e a b c d clk stb oe gnd
from litex.build.generic_platform import Signal, Subsignal, Pins
from litex.build.io import FSM, Module, Cat, Replicate
from litex.gen import ClockSignal, If, Instance, NextState, NextValue
def make_hub75_iodevice(index, basename):
b = basename
signals = ("hub75_iodev", index,
Subsignal("r0", Pins(f"{b}:0")),
Subsignal("g0", Pins(f"{b}:1")),
Subsignal("b0", Pins(f"{b}:2")),
Subsignal("r1", Pins(f"{b}:4")),
Subsignal("g1", Pins(f"{b}:5")),
Subsignal("b1", Pins(f"{b}:6")),
Subsignal("addr", Pins(f"{b}:8 {b}:9 {b}:10 {b}:11 {b}:7")),
Subsignal("clk", Pins(f"{b}:12")),
Subsignal("stb", Pins(f"{b}:13")),
Subsignal("oe", Pins(f"{b}:14")),
)
return [signals]
class Hub75Driver(Module):
def __init__(self, base_freq=60e6, linedepth=128):
if base_freq // 2 > 30e6:
raise RuntimeError("hi")
self.addr = Signal(5)
self.latch = Signal()
self.output_en = Signal()
color = Signal(24, reset=0xA0FF00)
self.rgb = Signal(6)
self.clock_out = Signal()
self.fsm = fsm = FSM()
self.submodules += self.fsm
bcm_value = Signal(3)
counter = Signal(32)
should_expose = (counter < (16 << bcm_value)) & (bcm_value != 0)
# this state both sets the OE low to drive the display with the previous frame
# while also loading the next row
# FIXME: there's a bug on the starting conditions right now, we are losing the lowest bit.
fsm.act("WRITEROW",
self.output_en.eq(~should_expose),
self.rgb[0].eq((color >> bcm_value) & 1),
self.rgb[3].eq((color >> bcm_value) & 1),
self.rgb[1].eq((color >> (bcm_value + 8)) & 1),
self.rgb[4].eq((color >> (bcm_value + 8)) & 1),
self.rgb[2].eq((color >> (bcm_value + 16)) & 1),
self.rgb[5].eq((color >> (bcm_value + 16)) & 1),
NextValue(counter, counter + 1),
If(counter < linedepth * 2,
self.clock_out.eq(counter[0]),
),
If(~(counter < linedepth * 2) & ~should_expose,
NextValue(counter, 0),
NextState("LATCH"),
),
)
fsm.act("EXPOSE",
self.output_en.eq(0),
If(counter < (16 << bcm_value),
NextValue(counter, counter + 1),
).Else(
NextValue(counter, 0),
NextState("LATCH"),
),
)
fsm.act("LATCH",
self.latch.eq(1),
self.output_en.eq(1),
NextValue(counter, 0),
If(bcm_value == 7,
NextValue(bcm_value, 0),
NextValue(self.addr, self.addr + 1),
).Else(
NextValue(bcm_value, bcm_value + 1),
),
NextState("WRITEROW"),
)
class Hub75VerilogDriver(Module):
def __init__(self):
self.o_addr = Signal(5)
self.o_display_clk = Signal()
self.o_latch = Signal()
self.o_out_enable = Signal()
self.o_panel_rgb0 = Signal(3)
self.o_panel_rgb1 = Signal(3)
inst = Instance("coordinator",
i_clk = ClockSignal(),
o_panel_rgb0 = self.o_panel_rgb0,
o_panel_rgb1 = self.o_panel_rgb1,
o_latch = self.o_latch,
o_display_clk = self.o_display_clk,
o_out_enable = self.o_out_enable,
o_display_addr = self.o_addr,
)
self.specials += inst

View file

@ -0,0 +1,141 @@
# Board definition file for the Colorlight 5A-75B.
# Mostly copied from the litex-boards repo, but we didn't pull that in
from litex.build.generic_platform import Pins, IOStandard, Subsignal, Misc
from litex.build.lattice import LatticeECP5Platform
from litex.build.openfpgaloader import OpenFPGALoader
from litex.gen import LiteXModule, ClockDomain, ClockSignal
from litex.build.io import DDROutput
from litex.soc.cores.clock import ECP5PLL
_io = [
# Clk
("clk25", 0, Pins("P6"), IOStandard("LVCMOS33")),
# Led
("user_led_n", 0, Pins("T6"), IOStandard("LVCMOS33")),
# Button
("user_btn_n", 0, Pins("R7"), IOStandard("LVCMOS33")),
# serial
("serial", 0,
Subsignal("tx", Pins("T6")), # led (J19 DATA_LED-)
Subsignal("rx", Pins("R7")), # btn (J19 KEY+)
IOStandard("LVCMOS33")
),
# SDR SDRAM (M12L64322A)
("sdram_clock", 0, Pins("C8"), IOStandard("LVCMOS33")),
("sdram", 0,
Subsignal("a", Pins(
"A9 B9 B10 C10 D9 C9 E9 D8",
"E8 C7 B8")),
Subsignal("dq", Pins(
"B2 A2 C3 A3 B3 A4 B4 A5",
"E7 C6 D7 D6 E6 D5 C5 E5",
"A11 B11 B12 A13 B13 A14 B14 D14",
"D13 E11 C13 D11 C12 E10 C11 D10")),
Subsignal("we_n", Pins("B5")),
Subsignal("ras_n", Pins("B6")),
Subsignal("cas_n", Pins("A6")),
#Subsignal("cs_n", Pins("")), # gnd
#Subsignal("cke", Pins("")), # 3v3
Subsignal("ba", Pins("B7 A8")),
#Subsignal("dm", Pins("")), # gnd
IOStandard("LVCMOS33"),
Misc("SLEWRATE=FAST")
),
# RGMII Ethernet (RTL8211FD)
("eth_clocks", 0,
Subsignal("tx", Pins("L1")),
Subsignal("rx", Pins("J1")),
IOStandard("LVCMOS33")
),
("eth", 0,
#Subsignal("rst_n", Pins("R6")),
Subsignal("mdio", Pins("T4")),
Subsignal("mdc", Pins("R5")),
Subsignal("rx_ctl", Pins("J2")),
Subsignal("rx_data", Pins("K2 J3 K1 K3")),
Subsignal("tx_ctl", Pins("L2")),
Subsignal("tx_data", Pins("M2 M1 P1 R1")),
IOStandard("LVCMOS33")
),
("eth_clocks", 1,
Subsignal("tx", Pins("J16")),
Subsignal("rx", Pins("M16")),
IOStandard("LVCMOS33")
),
("eth", 1,
#Subsignal("rst_n", Pins("R6")),
Subsignal("mdio", Pins("T4")),
Subsignal("mdc", Pins("R5")),
Subsignal("rx_ctl", Pins("P16")),
Subsignal("rx_data", Pins("M15 R16 L15 L16")),
Subsignal("tx_ctl", Pins("K14")),
Subsignal("tx_data", Pins("K16 J15 J14 K15")),
IOStandard("LVCMOS33")
),
]
_connectors = [
("j1", "C4 D4 E4 - D3 F5 E3 N4 N5 N3 P3 P4 M3 N1 M4 -"),
("j2", "F1 F2 G2 - G1 H2 H3 N4 N5 N3 P3 P4 M3 N1 M4 -"),
("j3", "B1 C2 C1 - D1 E2 E1 N4 N5 N3 P3 P4 M3 N1 M4 -"),
("j4", "P5 R3 P2 - R2 T2 N6 N4 N5 N3 P3 P4 M3 N1 M4 -"),
("j5", "T13 R12 R13 - R14 T14 P12 N4 N5 N3 P3 P4 M3 N1 M4 -"),
("j6", "R15 T15 P13 - P14 N14 H15 N4 N5 N3 P3 P4 M3 N1 M4 -"),
("j7", "G16 H14 G15 - F15 F16 E16 N4 N5 N3 P3 P4 M3 N1 M4 -"),
("j8", "D16 E15 C16 - B16 C15 B15 N4 N5 N3 P3 P4 M3 N1 M4 -"),
]
class _CRG(LiteXModule):
def __init__(self, platform, sys_clk_freq, with_reset = False):
self.cd_sys = ClockDomain("sys")
# self.cd_hub = ClockDomain("hub")
self.cd_sys_ps = ClockDomain("sys_ps")
# self.cd_sys2x = ClockDomain()
# self.cd_sys2x_ps = ClockDomain()
# Clk / Rst.
clk25 = platform.request("clk25")
# PLL.
self.pll = pll = ECP5PLL()
rst_n = platform.request("user_btn_n", 0) if with_reset else 1
self.comb += pll.reset.eq(~rst_n)
pll.register_clkin(clk25, 25e6)
pll.create_clkout(self.cd_sys, sys_clk_freq)
# for the sdram
pll.create_clkout(self.cd_sys_ps, sys_clk_freq, phase=180)
# pll.create_clkout(self.cd_sys2x, 2*sys_clk_freq)
# pll.create_clkout(self.cd_sys2x_ps, 2*sys_clk_freq, phase=180)
# sdram_clk = ClockSignal("sys2x_ps")
sdram_clk = ClockSignal("sys_ps")
self.specials += DDROutput(1,0, platform.request("sdram_clock"), sdram_clk)
class Groovy1Platform(LatticeECP5Platform):
default_clk_name = "clk25"
default_clk_period = 1e9/25e6
def __init__(self, toolchain='trellis', **kwargs):
self.device = "LFE5U-25F-8BG256C"
LatticeECP5Platform.__init__(self, self.device, _io, connectors=_connectors, toolchain=toolchain, **kwargs)
def create_programmer(self):
return OpenFPGALoader(cable="cmsisdap")
def get_crg(self, sys_clk_freq) -> _CRG:
crg = _CRG(self, sys_clk_freq)
return crg
def do_finalize(self, fragment, *args, **kwargs):
LatticeECP5Platform.do_finalize(self, fragment, *args, **kwargs)
self.add_period_constraint(self.lookup_request("clk25", loose=True), 1e9/25e6)
self.add_period_constraint(self.lookup_request("eth_clocks:rx", 0, loose=True), 1e9/125e6)
self.add_period_constraint(self.lookup_request("eth_clocks:rx", 1, loose=True), 1e9/125e6)

97
groovylight/soc.py Normal file
View file

@ -0,0 +1,97 @@
from migen import *
from litex.gen import LiteXModule, ClockDomain, ClockSignal
from litex.soc.cores.cpu import vexriscv
from litex.soc.integration.soc_core import SoCCore
from litex.soc.integration.builder import Builder, builder_argdict, builder_args
from litex.soc.integration.soc_core import soc_core_argdict, soc_core_args
from litex.build.lattice.trellis import trellis_argdict, trellis_args
from litedram.modules import M12L64322A
from litedram.phy import GENSDRPHY, HalfRateGENSDRPHY
from liteeth.phy.ecp5rgmii import LiteEthPHYRGMII
from platform.colorlight_5a_75b_8_0 import Groovy1Platform
from hub75 import Hub75Driver, make_hub75_iodevice, Hub75VerilogDriver
class GroovySoC(SoCCore):
def __init__(self, platform, sys_clk_freq,
use_spi = False,
**kwargs):
SoCCore.__init__(self, platform, sys_clk_freq, ident="LiteX SoC for GroovyLight", **kwargs)
self.crg = platform.get_crg(sys_clk_freq)
self.submodules += self.crg
print(kwargs)
# if not self.integrated_main_ram_size:
# self.sdrphy = GENSDRPHY(platform.request("sdram"), sys_clk_freq)
# self.add_sdram("sdram",
# phy = self.sdrphy,
# module = M12L64322A(sys_clk_freq, "1:1"),
# l2_cache_size = kwargs.get("l2_size", 8192),
# )
#
#
# self.submodules.ethphy = LiteEthPHYRGMII(
# clock_pads= self.platform.request("eth_clocks", 0),
# pads = self.platform.request("eth", 0),
# tx_delay = 0e-9, # not sure what this is
# )
# self.add_csr("ethphy")
# # self.add_etherbone(phy=self.ethphy, ip_address="192.168.0.36", mac_address = 0x10e2d5000001, data_width=32)
# if use_spi:
# from litespi.modules import W25Q32JV as SpiFlashModule
# from litespi.opcodes import SpiNorFlashOpCodes
# self.mem_map["spiflash"] = 0x20000000
# mod = SpiFlashModule(SpiNorFlashOpCodes.READ_1_1_1)
# self.add_spi_flash(mode="1x", module=SpiFlashModule, with_master=False)
self.platform.add_extension(make_hub75_iodevice(0, "j8"))
hub_io = self.platform.request("hub75_iodev", 0)
self.submodules.hub75 = hub75 = Hub75VerilogDriver()
self.comb += [
hub_io.r0.eq(hub75.o_panel_rgb0[0]),
hub_io.g0.eq(hub75.o_panel_rgb0[1]),
hub_io.b0.eq(hub75.o_panel_rgb0[2]),
hub_io.r1.eq(hub75.o_panel_rgb1[0]),
hub_io.g1.eq(hub75.o_panel_rgb1[1]),
hub_io.b1.eq(hub75.o_panel_rgb1[2]),
hub_io.clk.eq(hub75.o_display_clk),
hub_io.addr.eq(hub75.o_addr),
hub_io.oe.eq(hub75.o_out_enable),
hub_io.stb.eq(hub75.o_latch),
]
platform.add_sources("./verilog/", "bitslicer.sv", "coordinator.sv", "hub75e.sv", "lineram.v", "pixgen.sv")
import argparse
def main():
parser = argparse.ArgumentParser(description="Groovylight builder")
builder_arggroup = parser.add_argument_group("builder options")
soc_arggroup = parser.add_argument_group('SoC core options')
soc_arggroup.add_argument("--with-spiflash", action="store_true", help="Use built in SPI flash")
builder_args(builder_arggroup)
soc_core_args(soc_arggroup)
trellis_args(parser.add_argument_group('Trellis options'))
args = parser.parse_args()
platform = Groovy1Platform()
soc = GroovySoC(platform, 120e6, **soc_core_argdict(args))
builder = Builder(soc, **builder_argdict(args))
builder.build()
if __name__ == "__main__":
main()

View file

@ -1,98 +0,0 @@
# This file is @generated by PDM.
# It is not intended for manual editing.
[metadata]
groups = ["default"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:711e8d9f3db580c3fd719dc08025858852c340c17bd0ec68be953ed3a4a18d6d"
[[metadata.targets]]
requires_python = "==3.12.*"
[[package]]
name = "amaranth"
version = "0.5.1"
requires_python = "~=3.8"
summary = "Amaranth hardware definition language"
groups = ["default"]
dependencies = [
"Jinja2~=3.0",
"importlib-resources; python_version < \"3.9\"",
"jschon~=0.11.1",
"pyvcd<0.5,>=0.2.2",
]
files = [
{file = "amaranth-0.5.1-py3-none-any.whl", hash = "sha256:2d370cc5b97e2472aab0a4eca515ab7f5116274550bd454132520eaec6068fb6"},
{file = "amaranth-0.5.1.tar.gz", hash = "sha256:58b01efcec24c3696a7465e97a6e62f46466afab1f956a6eb00bda4b791c8345"},
]
[[package]]
name = "jinja2"
version = "3.1.4"
requires_python = ">=3.7"
summary = "A very fast and expressive template engine."
groups = ["default"]
dependencies = [
"MarkupSafe>=2.0",
]
files = [
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
]
[[package]]
name = "jschon"
version = "0.11.1"
requires_python = "~=3.8"
summary = "A JSON toolkit for Python developers."
groups = ["default"]
dependencies = [
"rfc3986",
]
files = [
{file = "jschon-0.11.1-py3-none-any.whl", hash = "sha256:2350e8b6747b17358022960f91208bea70de448b914827af3184d30e20500f0f"},
{file = "jschon-0.11.1.tar.gz", hash = "sha256:c0ca0beab1f1694a03d726b91ed75ec604a7787af3ae91ead765f78215bf149f"},
]
[[package]]
name = "markupsafe"
version = "2.1.5"
requires_python = ">=3.7"
summary = "Safely add untrusted strings to HTML/XML markup."
groups = ["default"]
files = [
{file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"},
{file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"},
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"},
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"},
{file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"},
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"},
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"},
{file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"},
{file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"},
{file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"},
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
]
[[package]]
name = "pyvcd"
version = "0.4.0"
requires_python = ">=3.7"
summary = "Python VCD file support"
groups = ["default"]
files = [
{file = "pyvcd-0.4.0-py2.py3-none-any.whl", hash = "sha256:a21b10e5018b7940c8f2c20ef83d97496e86f15e215afed134ee115166035e17"},
{file = "pyvcd-0.4.0.tar.gz", hash = "sha256:31be3f501441a9b8c5dc72660ff7b9cfef9b43b2121a23d96f586d2863270290"},
]
[[package]]
name = "rfc3986"
version = "2.0.0"
requires_python = ">=3.7"
summary = "Validating URI References per RFC 3986"
groups = ["default"]
files = [
{file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"},
{file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"},
]

View file

@ -1,31 +0,0 @@
[project]
name = "groovylight"
version = "0.1.0"
description = "Default template for PDM package"
authors = [
{name = "saji", email = "saji@saji.dev"},
]
dependencies = [
"amaranth>=0.5.1",
]
requires-python = "==3.12.*"
readme = "README.md"
license = {text = "MIT"}
[tool.pdm]
distribution = false
[tool.pdm.dev-dependencies] # or the poetry equivalent
dev = [
"basedpyright", # you can pin the version here if you want, or just rely on the lockfile
"ruff"
]
[tool.ruff.lint]
[tool.basedpyright]
reportInvalidTypeForm = false
typeCheckingMode = "off"

View file

@ -1,210 +0,0 @@
from amaranth import Module, Cat, Signal, Assert, unsigned
from amaranth.build import Platform
from amaranth.lib import wiring, data
from amaranth.lib.wiring import In, Out
class RGBLayout(data.StructLayout):
def __init__(self, r_bits, g_bits, b_bits):
super().__init__(
{
"red": unsigned(r_bits),
"green": unsigned(g_bits),
"blue": unsigned(b_bits),
}
)
rgb888_layout = RGBLayout(8, 8, 8)
rgb111_hub75 = RGBLayout(1, 1, 1)
class Hub75Stream(wiring.Signature):
"""A Hub75E Driver for a single string of panels."""
def __init__(self):
super().__init__(
{
"latch": Out(1),
"oe": Out(1),
"addr": Out(5),
"rgb0": Out(3),
"rgb1": Out(3),
"display_clk": Out(1),
}
)
class Hub75Ctrl(wiring.Signature):
"""Hub75E control/addressing signals.
This is separated because certain designs keep these common
between all panel outputs (e.g Colorlight 5A-75b boards).
"""
def __init__(self):
super().__init__(
{
"latch": Out(1),
"oe": Out(1),
"addr": Out(5),
"display_clk": Out(1),
}
)
class Hub75Data(wiring.Signature):
""" Data lines for HUB75 displays. When combined with Hub75Ctrl, this forms a complete HUB75 interface.
These are kept separate as some devices have a single set of control signals.
"""
def __init__(self):
super().__init__(
{
"rgb0": Out(rgb111_hub75),
"rgb1": Out(rgb111_hub75),
}
)
class Hub75StringDriver(wiring.Component):
""" A data driver for Hub75 panels. This accesses the line memory and feeds out the data
"""
bcm_select: In(3)
class Hub75Coordinator(wiring.Component):
""" A shared-control hub75 driver"""
pass
class Bitslicer(wiring.Component):
start_write: In(1)
done: Out(1)
bitplane_addr: Out(11)
bitplane_wren: Out(1)
bitplane_data: Out(6)
def elaborate(self, platform: Platform) -> Module:
m = Module()
bitplane_bit = Signal(3)
with m.FSM():
with m.State("init"):
m.d.sync += bitplane_bit.eq(0)
m.d.sync += self.done.eq(0)
return m
class Hub75EDriver(wiring.Component):
"""An optimized driver for hub75 strings.
This version is faster than most implementations by merging the exposure
period and the data-write period to happen simultaneously. As a result,
the display can be brighter due to higher duty cycle.
"""
start: In(1)
done: Out(1)
out: Out(Hub75Stream())
buf_addr: Out(9)
buf_data: In(36)
row_depth = 128
bit_depth = 8
bcm_len = 32
def elaborate(self, platform: Platform) -> Module:
m = Module()
counter = Signal(32)
bcm_shift: Signal = Signal(4, init=7)
m.d.sync += counter.eq(counter + 1)
# construct helper signals.
ram_r = self.buf_data[16:23]
ram_b = self.buf_data[8:15]
ram_g = self.buf_data[0:7]
ram_rgb_slice = Cat(
ram_r.bit_select(bcm_shift, 1),
ram_g.bit_select(bcm_shift, 1),
ram_b.bit_select(bcm_shift, 1),
)
pixnum = Signal(8, reset=127)
pixrow = Signal(1, reset=0)
m.d.comb += self.buf_addr.eq(Cat(pixrow, pixnum))
should_clock = counter < (128 * 2 + 1)
should_expose = (counter < (32 << (bcm_shift + 1) + 1)).bool() & (
bcm_shift != 7
).bool()
with m.FSM():
with m.State("init"):
m.d.sync += [
bcm_shift.eq(7),
counter.eq(0),
self.done.eq(0),
pixnum.eq(127),
pixrow.eq(0),
]
with m.If(self.start == 1):
m.next = "prefetch"
with m.State("prefetch"):
with m.If(counter == 0):
m.d.sync += pixrow.eq(0)
with m.Elif(counter == 1):
m.d.sync += pixrow.eq(1)
with m.Elif(counter == 2):
m.d.sync += self.out.rgb0.eq(ram_rgb_slice)
with m.Elif(counter == 3):
m.d.sync += [self.out.rgb1.eq(ram_rgb_slice), counter.eq(0)]
m.next = "writerow"
with m.State("writerow"):
# expose if we haven't done it for long enough yet.
m.d.sync += self.out.oe.eq(~(should_expose))
with m.If(should_clock):
# clock is high on entry
m.d.sync += self.out.display_clk.eq(counter[1] == 0)
with m.If(counter[0:1] == 0):
# rising edge of the clock
m.d.sync += pixrow.eq(0)
with m.If(counter[0:1] == 1):
m.d.sync += [
pixrow.eq(1),
self.out.rgb0.eq(ram_rgb_slice),
]
with m.If(counter[0:1] == 2):
m.d.sync += [
pixnum.eq(pixnum - 1),
pixrow.eq(0),
self.out.rgb1.eq(ram_rgb_slice),
]
with m.Elif(~(should_expose)):
# we are done both feeding in the new data and exposing the previous.
m.d.sync += [counter.eq(0), self.out.display_clk.eq(0)]
m.next = "latchout"
with m.State("latchout"):
m.d.sync += [
pixnum.eq(127),
self.out.latch.eq(1),
]
with m.If(counter > 3):
m.d.sync += self.out.latch.eq(0)
m.d.sync += counter.eq(0)
with m.If(bcm_shift == 0):
m.next = "finish"
with m.Else():
m.d.sync += bcm_shift.eq(bcm_shift - 1)
m.next = "prefetch"
with m.State("finish"):
m.d.sync += Assert(bcm_shift == 0, "finish without bcm shift 0")
with m.If(counter < (32)):
m.d.sync += self.out.oe.eq(0)
with m.Else():
m.d.sync += [self.out.oe.eq(1), self.done.eq(1)]
m.next = "init"
return m

View file

@ -1,12 +0,0 @@
from amaranth import Module, Cat, Signal, Assert, unsigned
from amaranth.build import Platform
from amaranth.lib import wiring, data
from amaranth.lib.wiring import In, Out
from amaranth.lib.memory import Memory
# file to wrap line ram using PDPw16KD
# this is tricky.
def lineram():
return Memory(shape=unsigned(24), depth=512)

View file