Compare commits

..

No commits in common. "0c1d9241666fa4ff7741718934e7ad5a31be5bb9" and "7e41e6e2f3c1acbe57b30b3a3bba0ec42e559e5f" have entirely different histories.

15 changed files with 56 additions and 191 deletions

View file

@ -1,52 +0,0 @@
# Groovylight configuration base file.
# make your edits using this, at the moment the defaults are not stored
# in the program so every value must be specified.
[hardware]
# The target platform.
# Valid options: "cxxrtl", "colorlight"
type = "cxxrtl"
# hardware-specific options, these are gently parsed i.e extra options are ignored.
# colorlight options
# Maps the strings array onto the colorlight connectors.
# it's fine (but will raise a warning) if the list is longer than the number of strings.
stringmapping = [ "J1", "J2", "J3", "J4", "J5", "J6", "J7", "J8" ]
# cxxrtl options
[network]
# Network port to listen on for UDP datagrams.
port = 9999
# IPv4 address to use for the Ethernet port.
# This setting is ignored for CXXRTL.
# 'dhcp' will get the IP at runtime.
ip = "dhcp" # Can also be e.g. "192.168.0.123"
[display]
strict = true # allows some wacky configurations, like panels that overlap.
[[display.strings]]
position = { x = 31, y = 0 }
dimensions = { width = 256, height = 64 }
rotation = "UPDOWN"
[[display.strings]]
position = { x = 32, y = 0 }
dimensions = { width = 256, height = 64 }
rotation = "LEFTRIGHT"
[[display.strings]]
position = { x = 32, y = 64 }
dimensions = { width = 256, height = 64 }
rotation = "LEFTRIGHT"
[[display.strings]]
position = { x = 32, y = 128 }
dimensions = { width = 256, height = 64 }
rotation = "LEFTRIGHT"
[[display.strings]]
position = { x = 32, y = 192 }
dimensions = { width = 256, height = 64 }
rotation = "LEFTRIGHT"

View file

@ -8,6 +8,7 @@ from amaranth.utils import ceil_log2
from .common import Rgb666Layout, Hub75Stream, Hub75Ctrl, Hub75Data from .common import Rgb666Layout, Hub75Stream, Hub75Ctrl, Hub75Data
class SwapBuffer(wiring.Component): class SwapBuffer(wiring.Component):
"""A pair of BRAMs for holdling line data that are swapped between using an external signal. """A pair of BRAMs for holdling line data that are swapped between using an external signal.
@ -163,7 +164,8 @@ class Hub75Coordinator(wiring.Component):
self.n_strings = n_strings self.n_strings = n_strings
super().__init__( super().__init__(
{ {
"hub75": Out(Hub75Ctrl(n_strings)), "ctrl": Out(Hub75Ctrl),
"data": data.ArrayLayout(Hub75Data, n_strings),
# TODO: fetching routine? maybe it's passed through. # TODO: fetching routine? maybe it's passed through.
} }
) )

View file

@ -1,4 +1,4 @@
from amaranth import Array, unsigned from amaranth import unsigned
from amaranth.lib import wiring, data from amaranth.lib import wiring, data
from amaranth.lib.wiring import Out from amaranth.lib.wiring import Out
@ -42,14 +42,13 @@ class Hub75Ctrl(wiring.Signature):
""" """
def __init__(self, n_strings: int = 1): def __init__(self):
super().__init__( super().__init__(
{ {
"latch": Out(1), "latch": Out(1),
"oe": Out(1), "oe": Out(1),
"addr": Out(5), "addr": Out(5),
"display_clk": Out(1), "display_clk": Out(1),
"data": Out(Hub75Data()).array(n_strings),
} }
) )

View file

@ -1,25 +0,0 @@
import tomllib
from pathlib import Path
from groovylight.geom import (
Coord,
DisplayDimensions,
DisplayGeometry,
DisplayRotation,
DisplayString,
)
class Config:
def __init__(self, file: Path) -> None:
with file.open("rb") as f:
self.conf = tomllib.load(f)
self.geom = DisplayGeometry(self.conf.display.strict)
for string in self.conf.display.strings:
pos = Coord(**string.position)
dims = DisplayDimensions(**string.dimensions)
rot = DisplayRotation[string.rotation]
disp = DisplayString(pos, dims, rot)
self.geom.add_string(disp)
self.network = self.conf.network # FIXME: parse this properly.

View file

@ -24,30 +24,8 @@
# This file contains code to generate these timing adjustments and # This file contains code to generate these timing adjustments and
# control/quantify them. # control/quantify them.
# FIXME: This doesn't work. As much as I wish it did. Because the numbers are still
# added linearly. values that are between the powers of two (which are fitted to the log
# curve) are not even remotely close to matching.
# thankfully a gamma LUT is only 64 elements. we will need 3 muxes per color (1 per channel).
from math import pow from math import pow
from amaranth import Module, Cat, Mux, ShapeLike, Signal, Assert
from amaranth.build import Platform
from amaranth.lib import wiring, data
from amaranth.lib.wiring import In, Out
from amaranth.lib.memory import Memory, ReadPort, WritePort
from amaranth.utils import ceil_log2
class GammaLUT(wiring.Component):
def __init__(self, n_bits: int, gamma: float = 2.2):
self.gamma = gamma
self.n_bits = n_bits
self.lut = [pow(x, gamma) for x in range(2**n_bits)]
# TODO: we can combo it maybe with the variable timing method.
super().__init__(wiring.Signature({}))
def _gammavec(vals: [float], g: float) -> [float]: def _gammavec(vals: [float], g: float) -> [float]:
return [pow(x, g) for x in vals] return [pow(x, g) for x in vals]

View file

@ -173,8 +173,7 @@ class DisplayGeometry:
will overlap with an existing string. will overlap with an existing string.
""" """
if self.strict: for e in self._strings:
for e in self._strings: if e.intersects(s):
if e.intersects(s): pass
raise RuntimeError(f"node {e} intersects with {s}")
self._strings.append(s)

View file

@ -1,8 +1,10 @@
# main entry point for CLI applications. # main entry point for CLI applications.
import logging import logging
import argparse import argparse
logger = logging.getLogger(__loader__.name) logger = logging.getLogger(__loader__.name)
@ -10,7 +12,9 @@ def setup_logger(args):
root_logger = logging.getLogger() root_logger = logging.getLogger()
handler = logging.StreamHandler() handler = logging.StreamHandler()
formatter = logging.Formatter(style="{", fmt="{levelname:s}: {name:s}: {message:s}") formatter = logging.Formatter(
style="{", fmt="{levelname:s}: {name:s}: {message:s}"
)
handler.setFormatter(formatter) handler.setFormatter(formatter)
root_logger.addHandler(handler) root_logger.addHandler(handler)
@ -29,7 +33,6 @@ def main():
const=logging.DEBUG, const=logging.DEBUG,
default=logging.INFO, default=logging.INFO,
) )
parser.add_argument( parser.add_argument(
"-L", "-L",
"--log-file", "--log-file",

View file

@ -23,9 +23,7 @@ class Colorlight_5A75B_R82Platform(LatticeECP5Platform):
Subsignal("copi", Pins("T8", dir="o")), Subsignal("copi", Pins("T8", dir="o")),
Attrs(IO_TYPE="LVCMOS33"), Attrs(IO_TYPE="LVCMOS33"),
), ),
*LEDResources( *LEDResources(pins="T6", invert=True, attrs=Attrs(IO_TYPE="LVCMOS33", DRIVE="4")),
pins="T6", invert=True, attrs=Attrs(IO_TYPE="LVCMOS33", DRIVE="4")
),
*ButtonResources( *ButtonResources(
pins="R7", invert=True, attrs=Attrs(IO_TYPE="LVCMOS33", PULLMODE="UP") pins="R7", invert=True, attrs=Attrs(IO_TYPE="LVCMOS33", PULLMODE="UP")
), ),

View file

@ -1,9 +0,0 @@
# CXXRTL-based simulator.
# Uses SDL2 + black-box network stack to simulate the entire design.
# Tasks:
# Generate header with network port information
# black box the UDP streaming.
# provide code for display outputs to render onto SDL2 canvas.
# compile code (optionally)

View file

@ -13,80 +13,67 @@ from amaranth.lib.wiring import In, Out
# word size = 32 # word size = 32
# 8 megabytes data. # 8 megabytes data.
class SDRAMSignature(wiring.Signature): class SDRAMSignature(wiring.Signature):
"""Signature of a variable-size sdram. Data is split between in/out and has out_en""" """ Signature of a variable-size sdram. Data is split between in/out and has out_en"""
def __init__(self, addr_width, data_width=32, bank_width=2): def __init__(self, addr_width, data_width=32, bank_width=2):
super().__init__( super().__init__({
{ "nCS": Out(1),
"nCS": Out(1), "cke": Out(1),
"cke": Out(1), "nRAS": Out(1),
"nRAS": Out(1), "nCAS": Out(1),
"nCAS": Out(1), "nWE": Out(1),
"nWE": Out(1), "addr": Out(addr_width),
"addr": Out(addr_width), "data_out": Out(data_width),
"data_out": Out(data_width), "data_in": In(data_width),
"data_in": In(data_width), # todo: use dqm
# todo: use dqm "data_wren": Out(1),
"data_wren": Out(1), "bank_cs": Out(bank_width),
"bank_cs": Out(bank_width), })
}
)
class _WriteBurstLength(enum.Enum, shape=1): class _WriteBurstLength(enum.Enum, shape=1):
"""MRS Write burst mode""" """MRS Write burst mode"""
BURST = 0 BURST = 0
SINGLE_BIT = 1 SINGLE_BIT = 1
class _TestMode(enum.Enum, shape=2): class _TestMode(enum.Enum, shape=2):
"""The "test mode" of the sdram. This is always zero pretty much""" """ The "test mode" of the sdram. This is always zero pretty much"""
MODE_REGISTER_SET = 0 MODE_REGISTER_SET = 0
RESERVED0 = 1 RESERVED0 = 1
RESERVED1 = 2 RESERVED1 = 2
RESERVED2 = 3 RESERVED2 = 3
class _CASLatency(enum.Enum, shape=3): class _CASLatency(enum.Enum, shape=3):
"""How many cycles of latency for the column address select to complete""" """ How many cycles of latency for the column address select to complete """
CYCL2 = 2 CYCL2 = 2
CYCL3 = 3 CYCL3 = 3
class _BurstType(enum.Enum, shape=1): class _BurstType(enum.Enum, shape=1):
SEQUENTIAL = 0 SEQUENTIAL = 0
INTERLEAVED = 1 INTERLEAVED = 1
class _BurstLength(enum.IntEnum, shape=3): class _BurstLength(enum.IntEnum, shape=3):
"""The size of the burst""" """ The size of the burst """
SINGLE = 0 SINGLE = 0
DUAL = 1 DUAL = 1
QUAD = 2 QUAD = 2
OCT = 3 OCT = 3
FULL_PAGE = 7 # this is 256 words? FULL_PAGE = 7 # this is 256 words?
class _Command(enum.Enum): class _Command(enum.Enum):
"""Command set for SDRAM""" """ Command set for SDRAM """
MRS_WRITE = 0 MRS_WRITE = 0
ACTIVATE = 1 ACTIVATE = 1
PRECHARGE = 2 PRECHARGE = 2
WRITE = 3 WRITE = 3
READ = 4 READ = 4
CBR = 5 # auto refresh CBR = 5 # auto refresh
SELF_REFRESH = 6 SELF_REFRESH = 6
BRST_STOP = 7 BRST_STOP = 7
NOP = 8 NOP = 8
class BankController(wiring.Component): class BankController(wiring.Component):
"""Manages a single Bank. Has a bank locking/state tracker, """Manages a single Bank. Has a bank locking/state tracker,
can issue commands""" can issue commands"""

View file

@ -2,12 +2,11 @@ from ..geom import Coord, BBox
import pytest import pytest
def test_coord_comparison(): def test_coord_comparison():
c1 = Coord(0, 0) c1 = Coord(0,0)
c2 = Coord(0, 1) c2 = Coord(0,1)
c3 = Coord(1, 1) c3 = Coord(1,1)
c3_other = Coord(1, 1) c3_other = Coord(1,1)
assert c1 < c3 assert c1 < c3
assert not c1 < c2, "both x,y must be greater/lt/eq" assert not c1 < c2, "both x,y must be greater/lt/eq"
@ -16,24 +15,23 @@ def test_coord_comparison():
assert c3 == c3_other, "Coords with same numbers should equal each other" assert c3 == c3_other, "Coords with same numbers should equal each other"
assert c3 != c2 assert c3 != c2
def test_coord_construction(): def test_coord_construction():
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
Coord(0, -1) Coord(0,-1)
def test_bbox(): def test_bbox():
b = BBox(Coord(1, 1), Coord(3, 2)) b = BBox(Coord(1,1), Coord(3,2))
assert b.width == 2 assert b.width == 2
assert b.height == 1 assert b.height == 1
assert b.contains(Coord(1, 2)) assert b.contains(Coord(1,2))
assert not b.contains(Coord(0, 0)) assert not b.contains(Coord(0,0))
# TODO: test .intersect(other) # TODO: test .intersect(other)
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
BBox(Coord(0, 0), Coord(1, 0)) BBox(Coord(0,0), Coord(1,0))
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
BBox(Coord(1, 1), Coord(0, 0)) BBox(Coord(1,1), Coord(0,0))

View file

@ -5,7 +5,7 @@ from amaranth.lib.wiring import In, Out
from amaranth.lib.memory import Memory, WritePort from amaranth.lib.memory import Memory, WritePort
from amaranth.sim import Simulator from amaranth.sim import Simulator
from ..hub75 import Hub75Coordinator, Hub75StringDriver, Rgb666Layout from ..bitslicer import Hub75StringDriver, Rgb666Layout
def test_stringdriver(): def test_stringdriver():
@ -36,8 +36,3 @@ def test_stringdriver():
with sim.write_vcd("output.vcd"): with sim.write_vcd("output.vcd"):
sim.run_until(1e-6 * 1000) sim.run_until(1e-6 * 1000)
def test_hub75():
m = Module()
m.submodules.dut = dut = Hub75Coordinator(1)

View file

@ -1,6 +1,11 @@
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
from amaranth.sim import Simulator from amaranth.sim import Simulator
from ..hub75 import Hub75StringDriver, Rgb666Layout, SwapBuffer from ..bitslicer import Hub75StringDriver, Rgb666Layout, SwapBuffer
def test_swapbuffer(): def test_swapbuffer():
@ -20,11 +25,9 @@ def test_swapbuffer():
assert ctx.get(dut.read_port.data) == init_color assert ctx.get(dut.read_port.data) == init_color
# swap buffer # swap buffer
ctx.set(dut.selector, 1) ctx.set(dut.selector, 1)
await ctx.tick().repeat( await ctx.tick().repeat(2) # takes two clocks after switching selector to output data.
2
) # takes two clocks after switching selector to output data.
assert ctx.get(dut.read_port.data) == test_color assert ctx.get(dut.read_port.data) == test_color
# TODO: add more assertions/verification # TODO: add more assertions/verification
sim.add_testbench(testbench) sim.add_testbench(testbench)
with sim.write_vcd("output.vcd"): with sim.write_vcd("output.vcd"):

View file

@ -1,16 +1,8 @@
import shutil
import pytest
from amaranth import Elaboratable, Module, Signal from amaranth import Elaboratable, Module, Signal
from amaranth.lib.io import Buffer from amaranth.lib.io import Buffer
from groovylight.platforms.colorlight_5a75b_v8_2 import Colorlight_5A75B_R82Platform from groovylight.platforms.colorlight_5a75b_v8_2 import Colorlight_5A75B_R82Platform
def progs_exist(programs) -> bool:
for p in programs:
if shutil.which(p) is None:
return False
return True
class Blinky(Elaboratable): class Blinky(Elaboratable):
def elaborate(self, platform): def elaborate(self, platform):
m = Module() m = Module()
@ -30,8 +22,5 @@ class Blinky(Elaboratable):
def test_platform(): def test_platform():
if not progs_exist(["yosys", "nextpnr-ecp5", "openFPGALoader"]):
pytest.skip("missing toolchain programs")
plat = Colorlight_5A75B_R82Platform() plat = Colorlight_5A75B_R82Platform()
plat.build(Blinky()) plat.build(Blinky())