initial effort

This commit is contained in:
saji 2024-08-21 16:20:40 -05:00
parent f3789b6432
commit 83bb38ba6d
8 changed files with 354 additions and 14 deletions

View file

@ -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": {

View file

@ -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

98
pdm.lock Normal file
View 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
View 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"

View file

View 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

View 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
View file