generated from saji/ecp5-template
Compare commits
2 commits
f3789b6432
...
1ccc0b3d39
Author | SHA1 | Date | |
---|---|---|---|
saji | 1ccc0b3d39 | ||
saji | 83bb38ba6d |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -198,6 +198,7 @@ dmypy.json
|
||||||
# Cython debug symbols
|
# Cython debug symbols
|
||||||
cython_debug/
|
cython_debug/
|
||||||
|
|
||||||
|
.ruff_cache
|
||||||
# PyCharm
|
# PyCharm
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
# 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
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1712791164,
|
"lastModified": 1723637854,
|
||||||
"narHash": "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=",
|
"narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1042fd8b148a9105f3c0aca3a6177fd1d9360ba5",
|
"rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
11
flake.nix
11
flake.nix
|
@ -23,7 +23,6 @@
|
||||||
inherit system;
|
inherit system;
|
||||||
config.allowUnfree = true;
|
config.allowUnfree = true;
|
||||||
overlays = [
|
overlays = [
|
||||||
litex-overlay
|
|
||||||
]; # patches, version pins, new pkgs here.
|
]; # patches, version pins, new pkgs here.
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
@ -36,16 +35,6 @@
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
(python3.withPackages (ps: with ps; [
|
(python3.withPackages (ps: with ps; [
|
||||||
cocotb
|
cocotb
|
||||||
cocotb-bus
|
|
||||||
litex
|
|
||||||
litedram
|
|
||||||
liteeth
|
|
||||||
litescope
|
|
||||||
litespi
|
|
||||||
liteiclink
|
|
||||||
pythondata-cpu-vexriscv
|
|
||||||
pythondata-software-compiler_rt
|
|
||||||
pythondata-software-picolibc
|
|
||||||
amaranth
|
amaranth
|
||||||
]))
|
]))
|
||||||
yosys
|
yosys
|
||||||
|
|
|
@ -1,114 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
# 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
|
|
|
@ -1,141 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,97 +0,0 @@
|
||||||
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()
|
|
98
pdm.lock
Normal file
98
pdm.lock
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
# 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"},
|
||||||
|
]
|
31
pyproject.toml
Normal file
31
pyproject.toml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
[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"
|
0
src/groovylight/__init__.py
Normal file
0
src/groovylight/__init__.py
Normal file
210
src/groovylight/bitslicer.py
Normal file
210
src/groovylight/bitslicer.py
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
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
|
12
src/groovylight/lineram.py
Normal file
12
src/groovylight/lineram.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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)
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
Loading…
Reference in a new issue