Compare commits

...

2 commits

Author SHA1 Message Date
saji d42e227c4d add wip sdram controller, start multistring hub75
Some checks failed
Verilator Unit Tests / Test (push) Failing after 3m28s
2024-08-23 20:06:29 -05:00
saji bf35262640 add pytest 2024-08-23 18:02:26 -05:00
4 changed files with 280 additions and 21 deletions

125
pdm.lock
View file

@ -2,10 +2,10 @@
# It is not intended for manual editing.
[metadata]
groups = ["default"]
groups = ["default", "dev"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:711e8d9f3db580c3fd719dc08025858852c340c17bd0ec68be953ed3a4a18d6d"
content_hash = "sha256:7b1a11a64e435bfbced61bd2b5314e78a56b93e0bac7dcb6b043529bc66fc060"
[[metadata.targets]]
requires_python = "==3.12.*"
@ -27,6 +27,43 @@ files = [
{file = "amaranth-0.5.1.tar.gz", hash = "sha256:58b01efcec24c3696a7465e97a6e62f46466afab1f956a6eb00bda4b791c8345"},
]
[[package]]
name = "basedpyright"
version = "1.17.0"
requires_python = ">=3.8"
summary = "static type checking for Python (but based)"
groups = ["dev"]
dependencies = [
"nodejs-wheel-binaries>=20.13.1",
]
files = [
{file = "basedpyright-1.17.0-py3-none-any.whl", hash = "sha256:a7e070be5d3930223df0a435590932fbc114242b7f8d1723c5d717754e7a3b4e"},
{file = "basedpyright-1.17.0.tar.gz", hash = "sha256:79e2740156a040fdc68e4372940f40694375ef7e4a0ca70bacf8910af08717e0"},
]
[[package]]
name = "colorama"
version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text."
groups = ["dev"]
marker = "sys_platform == \"win32\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "iniconfig"
version = "2.0.0"
requires_python = ">=3.7"
summary = "brain-dead simple config-ini parsing"
groups = ["dev"]
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "jinja2"
version = "3.1.4"
@ -75,6 +112,63 @@ files = [
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
]
[[package]]
name = "nodejs-wheel-binaries"
version = "20.17.0"
requires_python = ">=3.7"
summary = "unoffical Node.js package"
groups = ["dev"]
files = [
{file = "nodejs_wheel_binaries-20.17.0-py2.py3-none-macosx_10_15_x86_64.whl", hash = "sha256:a5eaffb0327d751360e5f01e92f0adeb3b297a9196751bf1dda910b92c9f6f83"},
{file = "nodejs_wheel_binaries-20.17.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:a661d2894b11dc886ae8c25727c3cdf1d54db982cdccfcddbb2dfe20dd249f3b"},
{file = "nodejs_wheel_binaries-20.17.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0144c91e9816cda969d78ba0903acc405c99e0a4bfffbdabac6b48b237335e7"},
{file = "nodejs_wheel_binaries-20.17.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87ee7e88bfece2d325949e4f0dedb942b7478c401fc4e6726c5270f5835a02d"},
{file = "nodejs_wheel_binaries-20.17.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5b5fb7c51204ad30e9f83ab503f11b17de4bcc1c248314f83ca3302fda24b1a1"},
{file = "nodejs_wheel_binaries-20.17.0-py2.py3-none-win_amd64.whl", hash = "sha256:931558d528b976eb67ae1f68253df089e9d7d7267b6980fc580bfd31d67a0f5c"},
{file = "nodejs_wheel_binaries-20.17.0.tar.gz", hash = "sha256:827a3297a99764adfaeb0cd1e9e36d8fb79c5b074de916a2a31b8a61b8d208f7"},
]
[[package]]
name = "packaging"
version = "24.1"
requires_python = ">=3.8"
summary = "Core utilities for Python packages"
groups = ["dev"]
files = [
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
]
[[package]]
name = "pluggy"
version = "1.5.0"
requires_python = ">=3.8"
summary = "plugin and hook calling mechanisms for python"
groups = ["dev"]
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[[package]]
name = "pytest"
version = "8.3.2"
requires_python = ">=3.8"
summary = "pytest: simple powerful testing with Python"
groups = ["dev"]
dependencies = [
"colorama; sys_platform == \"win32\"",
"exceptiongroup>=1.0.0rc8; python_version < \"3.11\"",
"iniconfig",
"packaging",
"pluggy<2,>=1.5",
"tomli>=1; python_version < \"3.11\"",
]
files = [
{file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"},
{file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"},
]
[[package]]
name = "pyvcd"
version = "0.4.0"
@ -96,3 +190,30 @@ files = [
{file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"},
{file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"},
]
[[package]]
name = "ruff"
version = "0.6.2"
requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust."
groups = ["dev"]
files = [
{file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"},
{file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"},
{file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"},
{file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"},
{file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"},
{file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"},
{file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"},
{file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"},
{file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"},
{file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"},
{file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"},
{file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"},
{file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"},
{file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"},
{file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"},
{file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"},
{file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"},
{file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"},
]

View file

@ -19,7 +19,8 @@ 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"
"ruff",
"pytest>=8.3.2",
]

View file

@ -1,7 +1,8 @@
from amaranth import Module, Cat, Signal, Assert, unsigned
from amaranth import Array, 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, WritePort
class RGBLayout(data.StructLayout):
@ -55,9 +56,10 @@ class Hub75Ctrl(wiring.Signature):
class Hub75Data(wiring.Signature):
""" Data lines for HUB75 displays. When combined with Hub75Ctrl, this forms a complete HUB75 interface.
"""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__(
{
@ -65,31 +67,91 @@ class Hub75Data(wiring.Signature):
"rgb1": Out(rgb111_hub75),
}
)
def get_lineram() -> Memory:
return Memory(shape=rgb888_layout, depth=512, init=[])
class Hub75StringDriver(wiring.Component):
""" A data driver for Hub75 panels. This accesses the line memory and feeds out the data
"""
"""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)
start: In(1)
active_bank: In(1) # the bank to use
bram_port: WritePort.Signature(addr_width=9, shape=rgb888_layout)
out: Out(Hub75Data())
def elaborate(self, platform: Platform) -> Module:
m = Module()
bitplane_bit = Signal(3)
# add two memories
m.submodules.bram0 = bram0 = Memory(shape=rgb888_layout, depth=512, init=[])
m.submodules.bram1 = bram1 = Memory(shape=rgb888_layout, depth=512, init=[])
# We use two brams here so we can swap between each - that way we can update the data
# while displaying the other line. Switching is controlled by the coordinator.
readports = Array([bram0.read_port(), bram1.read_port()])
active_readport = readports[self.active_bank]
m.d.comb += self.active_readport.eq(readports[self.active_bank])
writeports = Array([bram0.write_port(), bram1.write_port()])
m.d.comb += self.bram_port.eq(writeports[~self.active_bank])
counter = Signal(32)
m.d.sync += counter.eq(counter + 1)
ram_rgb_slice = Cat(
active_readport.data['red'].bit_select(self.bcm_shift, 1),
active_readport.data['blue'].bit_select(self.bcm_shift, 1),
active_readport.data['green'].bit_select(self.bcm_shift, 1),
)
pixnum = Signal(8, reset=127)
pixrow = Signal(1, reset=0)
m.d.comb += self.buf_addr.eq(Cat(pixrow, pixnum))
with m.FSM():
with m.State("init"):
m.d.sync += bitplane_bit.eq(0)
m.d.sync += self.done.eq(0)
m.d.sync += [
self.done.eq(0),
counter.eq(0),
pixnum.eq(127),
pixrow.eq(0),
]
with m.If(self.start == 1):
m.d.sync += active_readport.en.eq(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"
return m
class Hub75Coordinator(wiring.Component):
"""A shared-control hub75 driver"""
def __init__(self, n_strings=1):
self.n_strings = n_strings
super().__init__()
def elaborate(self, platform: Platform) -> Module:
m = Module()
return m
@ -208,3 +270,8 @@ class Hub75EDriver(wiring.Component):
m.next = "init"
return m
if __name__ == "__main__":
m = Hub75EDriver()
from amaranth.cli import main
main(m)

70
src/groovylight/sdram.py Normal file
View file

@ -0,0 +1,70 @@
# SDRAM controller for the Colorlight 5a-75b
from amaranth import Module, Cat, Signal, Assert, unsigned
from amaranth.build import Platform
from amaranth.lib import wiring, data, enum
from amaranth.lib.wiring import In, Out
# RAM Specs:
# 4 banks, 2048 rows, 256 columns.
# 11 row bits, 8 column bits.
# word size = 32
# 8 megabytes data.
sdram_control_layout = data.StructLayout(
{
"nCS": 1,
"nRAS": 1,
"nCAS": 1,
"nWE": 1,
}
)
class _WriteBurstLength(enum.Enum, shape=1):
"""MRS field"""
BURST = 0
SINGLE_BIT = 1
class _TestMode(enum.Enum, shape=2):
""" The "test mode" of the sdram. This is always zero pretty much"""
MODE_REGISTER_SET = 0
RESERVED0 = 1
RESERVED1 = 2
RESERVED2 = 3
class _CASLatency(enum.Enum, shape=3):
""" How many cycles of latency for the column address select to complete """
CYCL2 = 2
CYCL3 = 3
class _BurstType(enum.Enum, shape=1):
SEQUENTIAL = 0
INTERLEAVED = 1
class _BurstLength(enum.IntEnum, shape=3):
""" The size of the burst """
SINGLE = 0
DUAL = 1
QUAD = 2
OCT = 3
FULL_PAGE = 7
class _Command(enum.Enum):
""" Command set for SDRAM """
MRS_WRITE = 0
ACTIVATE = 1
PRECHARGE = 2
WRITE = 3
READ = 4
CBR = 5 # auto refresh
SELF_REFRESH = 6
BRST_STOP = 7
NOP = 8