Compare commits

..

No commits in common. "79fce1afc1afd547269f99cc959ada062a523b02" and "ba1aa9181e9411d648f246b170e2ddb2349b754d" have entirely different histories.

5 changed files with 122 additions and 290 deletions

View file

@ -1,15 +0,0 @@
# WS2812 driver gateware for blinkenwall v3
At 100 FPS, a single strand of WS2812 can only be just over 300 LEDs long:
```
MAX_LENGTH = (1 / FPS - RESET_TIME) / BIT_TIME / BITS_PER_PIXEL
318 = (1 / 100 - 50e-6) / 1.3e-6 / 24
```
Because blinkenwall v3 has `64 * 96 = 6144` pixels, driving it at 100 FPS requires at least
`6144 / 318 = 20` parallel drivers. For simplicity, the wall is devided into 24 strands of
256 LEDs. Unfortunately, most microcontrollers have at most a couple WS2812-capable interfaces
(typically SPI), so this would require coordinating several microcontrollers in parallel. A
much more integrated solution is to instantiate as many WS2812 drivers as desired in an FPGA,
then point the entire video firehose at the FPGA.

View file

@ -222,7 +222,7 @@ set_property IOSTANDARD LVCMOS33 [get_ports {ck_dig_h[13]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ck_dig_h[14]}] set_property IOSTANDARD LVCMOS33 [get_ports {ck_dig_h[14]}]
set_property IOSTANDARD LVCMOS33 [get_ports {ck_dig_h[15]}] set_property IOSTANDARD LVCMOS33 [get_ports {ck_dig_h[15]}]
create_clock -period 12.5 [get_nets sys_clk] create_clock -period 12.5 [get_nets clk_sys]
create_clock -period 40.0 [get_nets liteeth_inst.eth_rx_clk] create_clock -period 40.0 [get_nets liteeth_inst.eth_rx_clk]
create_clock -period 40.0 [get_nets liteeth_inst.eth_tx_clk] create_clock -period 40.0 [get_nets liteeth_inst.eth_tx_clk]

View file

@ -28,7 +28,6 @@ from liteeth.common import *
from liteeth import phy as liteeth_phys from liteeth import phy as liteeth_phys
from liteeth.mac import LiteEthMAC from liteeth.mac import LiteEthMAC
from liteeth.core import LiteEthUDPIPCore from liteeth.core import LiteEthUDPIPCore
from liteeth.core.udp import LiteEthUDP
# IOs ---------------------------------------------------------------------------------------------- # IOs ----------------------------------------------------------------------------------------------
@ -41,8 +40,12 @@ _io = [
("sys_reset", 1, Pins(1)), ("sys_reset", 1, Pins(1)),
# IP/MAC Address. # IP/MAC Address.
("mac_address", 0, Pins(48)),
("ip_address", 0, Pins(32)), ("ip_address", 0, Pins(32)),
# Interrupt
("interrupt", 0, Pins(1)),
# MII PHY Pads # MII PHY Pads
("mii_eth_clocks", 0, ("mii_eth_clocks", 0,
Subsignal("tx", Pins(1)), Subsignal("tx", Pins(1)),
@ -60,10 +63,61 @@ _io = [
Subsignal("col", Pins(1)), Subsignal("col", Pins(1)),
Subsignal("crs", Pins(1)) Subsignal("crs", Pins(1))
), ),
# RMII PHY Pads
("rmii_eth_clocks", 0,
Subsignal("ref_clk", Pins(1))
),
("rmii_eth", 0,
Subsignal("rst_n", Pins(1)),
Subsignal("rx_data", Pins(2)),
Subsignal("crs_dv", Pins(1)),
Subsignal("tx_en", Pins(1)),
Subsignal("tx_data", Pins(2)),
Subsignal("mdc", Pins(1)),
Subsignal("mdio", Pins(1)),
),
# GMII PHY Pads
("gmii_eth_clocks", 0,
Subsignal("tx", Pins(1)),
Subsignal("gtx", Pins(1)),
Subsignal("rx", Pins(1))
),
("gmii_eth", 0,
Subsignal("rst_n", Pins(1)),
Subsignal("int_n", Pins(1)),
Subsignal("mdio", Pins(1)),
Subsignal("mdc", Pins(1)),
Subsignal("rx_dv", Pins(1)),
Subsignal("rx_er", Pins(1)),
Subsignal("rx_data", Pins(8)),
Subsignal("tx_en", Pins(1)),
Subsignal("tx_er", Pins(1)),
Subsignal("tx_data", Pins(8)),
Subsignal("col", Pins(1)),
Subsignal("crs", Pins(1))
),
# RGMII PHY Pads
("rgmii_eth_clocks", 0,
Subsignal("tx", Pins(1)),
Subsignal("rx", Pins(1))
),
("rgmii_eth", 0,
Subsignal("rst_n", Pins(1)),
Subsignal("int_n", Pins(1)),
Subsignal("mdio", Pins(1)),
Subsignal("mdc", Pins(1)),
Subsignal("rx_ctl", Pins(1)),
Subsignal("rx_data", Pins(4)),
Subsignal("tx_ctl", Pins(1)),
Subsignal("tx_data", Pins(4))
),
] ]
def get_udp_streamer_port_ios(name, data_width): def get_udp_port_ios(name, data_width):
return [ return [
(f"{name}", 0, (f"{name}", 0,
Subsignal("ip_address", Pins(32)), Subsignal("ip_address", Pins(32)),
@ -83,36 +137,6 @@ def get_udp_streamer_port_ios(name, data_width):
] ]
def get_udp_port_ios(name, data_width):
return [
(f"{name}", 0,
Subsignal("bind_port", Pins(16)),
# Sink.
Subsignal("sink_dst_ip_address", Pins(32)),
Subsignal("sink_dst_port", Pins(16)),
Subsignal("sink_length", Pins(16)),
Subsignal("sink_valid", Pins(1)),
Subsignal("sink_last", Pins(1)),
Subsignal("sink_last_be", Pins(data_width // 8)),
Subsignal("sink_ready", Pins(1)),
Subsignal("sink_data", Pins(data_width)),
# Source.
Subsignal("source_src_ip_address", Pins(32)),
Subsignal("source_src_port", Pins(16)),
Subsignal("source_length", Pins(16)),
Subsignal("source_valid", Pins(1)),
Subsignal("source_last", Pins(1)),
Subsignal("source_last_be", Pins(data_width // 8)),
Subsignal("source_ready", Pins(1)),
Subsignal("source_data", Pins(data_width)),
),
]
class PHYCore(SoCMini): class PHYCore(SoCMini):
def __init__(self, platform): def __init__(self, platform):
super().__init__(platform, clk_freq=CLK_FREQ) super().__init__(platform, clk_freq=CLK_FREQ)
@ -154,16 +178,6 @@ class PHYCore(SoCMini):
) )
class UDPPort(Module, AutoCSR):
def __init__(self, udp_core: LiteEthUDP, bind_port, data_width=8, cd="sys"):
udp_port = udp_core.crossbar.get_port(bind_port, dw=data_width, cd=cd)
self.sink = udp_port.sink
self.comb += [
self.sink.src_port.eq(bind_port),
]
self.source = udp_port.source
class UDPCore(PHYCore): class UDPCore(PHYCore):
def __init__(self, platform): def __init__(self, platform):
from liteeth.frontend.stream import LiteEthUDPStreamer from liteeth.frontend.stream import LiteEthUDPStreamer
@ -185,7 +199,7 @@ class UDPCore(PHYCore):
# DHCP port # DHCP port
data_width = 32 data_width = 32
platform.add_extension(get_udp_streamer_port_ios( platform.add_extension(get_udp_port_ios(
"dhcp", "dhcp",
data_width=data_width, data_width=data_width,
)) ))
@ -215,46 +229,6 @@ class UDPCore(PHYCore):
dhcp_ios.source_data.eq(dhcp_streamer.source.data), dhcp_ios.source_data.eq(dhcp_streamer.source.data),
] ]
# Pixel port
data_width = 32
platform.add_extension(get_udp_port_ios(
"pixel",
data_width=data_width,
))
pixel_ios = platform.request("pixel")
pixel_port = UDPPort(
self.core.udp,
bind_port=pixel_ios.bind_port,
data_width=data_width,
)
self.submodules += pixel_port
self.comb += [
# Connect UDP Sink IOs to UDPPort
pixel_port.sink.ip_address.eq(pixel_ios.sink_dst_ip_address),
pixel_port.sink.dst_port.eq(pixel_ios.sink_dst_port),
pixel_port.sink.length.eq(pixel_ios.sink_length),
pixel_port.sink.valid.eq(pixel_ios.sink_valid),
pixel_port.sink.last.eq(pixel_ios.sink_last),
pixel_port.sink.last_be.eq(pixel_ios.sink_last_be),
pixel_ios.sink_ready.eq(pixel_port.sink.ready),
pixel_port.sink.data.eq(pixel_ios.sink_data),
# Connect UDPPort to UDP Source IOs.
pixel_ios.source_src_ip_address.eq(pixel_port.source.ip_address),
pixel_ios.source_src_port.eq(pixel_port.source.src_port),
pixel_ios.source_length.eq(pixel_port.source.length),
pixel_ios.source_valid.eq(pixel_port.source.valid),
pixel_ios.source_last.eq(pixel_port.source.last),
pixel_ios.source_last_be.eq(pixel_port.source.last_be),
pixel_port.source.ready.eq(pixel_ios.source_ready),
pixel_ios.source_data.eq(pixel_port.source.data),
]
# Build -------------------------------------------------------------------------------------------- # Build --------------------------------------------------------------------------------------------
@ -270,7 +244,7 @@ def main():
platform = XilinxPlatform( platform = XilinxPlatform(
"xc7a35ticsg324-1L", "xc7a35ticsg324-1L",
io=[], io=[],
toolchain="yosys+nextpnr" toolchain="symbiflow"
) )
platform.add_extension(_io) platform.add_extension(_io)

View file

@ -16,8 +16,8 @@ entity arty_a7 is
-- Pmod connectors - A+D standard, B+C high-speed. -- Pmod connectors - A+D standard, B+C high-speed.
-- Defined as inputs by default for safety, change -- Defined as inputs by default for safety, change
-- when necessary -- when necessary
pmod_a : out std_logic_vector(7 downto 0); pmod_a : in std_logic_vector(7 downto 0);
pmod_b : out std_logic_vector(7 downto 0); pmod_b : in std_logic_vector(7 downto 0);
pmod_c : in std_logic_vector(7 downto 0); pmod_c : in std_logic_vector(7 downto 0);
pmod_d : out std_logic_vector(7 downto 0); pmod_d : out std_logic_vector(7 downto 0);
@ -46,14 +46,13 @@ entity arty_a7 is
end arty_a7; end arty_a7;
architecture a of arty_a7 is architecture a of arty_a7 is
constant NUM_STRANDS: positive := 24; constant NUM_DRIVERS: positive := 16;
signal drivers: std_logic_vector(NUM_STRANDS-1 downto 0); signal drivers: std_logic_vector(NUM_DRIVERS-1 downto 0);
component liteeth_core is component liteeth_core is
port ( port (
sys_clock : in std_logic; sys_clock : in std_logic;
sys_reset : in std_logic; sys_reset : in std_logic;
mii_eth_clocks_tx : in std_logic; mii_eth_clocks_tx : in std_logic;
mii_eth_clocks_rx : in std_logic; mii_eth_clocks_rx : in std_logic;
mii_eth_rst_n : out std_logic; mii_eth_rst_n : out std_logic;
@ -66,65 +65,20 @@ architecture a of arty_a7 is
mii_eth_tx_data : out std_logic_vector(3 downto 0); mii_eth_tx_data : out std_logic_vector(3 downto 0);
mii_eth_col : in std_logic; mii_eth_col : in std_logic;
mii_eth_crs : in std_logic; mii_eth_crs : in std_logic;
ip_address : in std_logic_vector(31 downto 0); ip_address : in std_logic_vector(31 downto 0);
--== DHCP PORT ==--
dhcp_ip_address : in std_logic_vector(31 downto 0); dhcp_ip_address : in std_logic_vector(31 downto 0);
dhcp_sink_valid : in std_logic; dhcp_sink_valid : in std_logic;
dhcp_sink_last : in std_logic; dhcp_sink_last : in std_logic;
dhcp_sink_ready : out std_logic; dhcp_sink_ready : out std_logic;
dhcp_sink_data : in std_logic_vector(31 downto 0); dhcp_sink_data : in std_logic_vector(31 downto 0);
dhcp_source_valid : out std_logic; dhcp_source_valid : out std_logic;
dhcp_source_last : out std_logic; dhcp_source_last : out std_logic;
dhcp_source_ready : in std_logic; dhcp_source_ready : in std_logic;
dhcp_source_data : out std_logic_vector(31 downto 0); dhcp_source_data : out std_logic_vector(31 downto 0)
--== PIXEL DATA PORT ==--
pixel_bind_port : in std_logic_vector(15 downto 0);
-- sink
pixel_sink_dst_ip_address : in std_logic_vector(31 downto 0);
pixel_sink_dst_port : in std_logic_vector(15 downto 0);
pixel_sink_length : in std_logic_vector(15 downto 0);
pixel_sink_valid : in std_logic;
pixel_sink_last : in std_logic;
pixel_sink_last_be : in std_logic_vector(3 downto 0);
pixel_sink_ready : out std_logic;
pixel_sink_data : in std_logic_vector(31 downto 0);
-- source
pixel_source_src_ip_address : out std_logic_vector(31 downto 0);
pixel_source_src_port : out std_logic_vector(15 downto 0);
pixel_source_length : out std_logic_vector(15 downto 0);
pixel_source_valid : out std_logic;
pixel_source_last : out std_logic;
pixel_source_last_be : out std_logic_vector(3 downto 0);
pixel_source_ready : in std_logic;
pixel_source_data : out std_logic_vector(31 downto 0)
); );
end component; end component;
signal pixel_sink_length : std_logic_vector(15 downto 0); signal dhcp_source_valid : std_logic;
signal pixel_sink_valid : std_logic;
signal pixel_sink_last : std_logic;
signal pixel_sink_last_be : std_logic_vector(3 downto 0);
signal pixel_sink_ready : std_logic;
signal pixel_sink_data : std_logic_vector(31 downto 0);
signal pixel_source_src_ip_address : std_logic_vector(31 downto 0);
signal pixel_source_src_port : std_logic_vector(15 downto 0);
signal pixel_source_length : std_logic_vector(15 downto 0);
signal pixel_source_valid : std_logic;
signal pixel_source_last : std_logic;
signal pixel_source_last_be : std_logic_vector(3 downto 0);
signal pixel_source_ready : std_logic;
signal pixel_source_data : std_logic_vector(31 downto 0);
component PLLE2_BASE component PLLE2_BASE
generic ( generic (
@ -178,8 +132,8 @@ architecture a of arty_a7 is
signal pll_feedback : std_logic; signal pll_feedback : std_logic;
signal pll_locked : std_logic; signal pll_locked : std_logic;
signal unbuf_sys_clk : std_logic; signal unbuf_clk_sys : std_logic;
signal sys_clk : std_logic; signal clk_sys : std_logic;
component BUFG component BUFG
port ( port (
@ -190,7 +144,7 @@ architecture a of arty_a7 is
signal sys_reset : std_logic; signal sys_reset : std_logic;
begin begin
leds_simple <= (others => '0'); --leds_simple <= (others => '0');
led0 <= (others => '0'); led0 <= (others => '0');
led1 <= (others => '0'); led1 <= (others => '0');
led2 <= (others => '0'); led2 <= (others => '0');
@ -201,8 +155,10 @@ begin
ck_dig_l <= (others => 'Z'); ck_dig_l <= (others => 'Z');
ck_dig_h <= (others => 'Z'); ck_dig_h <= (others => 'Z');
leds_simple <= drivers(3 downto 0);
liteeth_inst: liteeth_core port map ( liteeth_inst: liteeth_core port map (
sys_clock => sys_clk, sys_clock => clk_sys,
sys_reset => sys_reset, sys_reset => sys_reset,
mii_eth_clocks_tx => mii_tx_clk, mii_eth_clocks_tx => mii_tx_clk,
@ -225,32 +181,8 @@ begin
dhcp_sink_last => '1', dhcp_sink_last => '1',
dhcp_sink_data => x"cafebebe", dhcp_sink_data => x"cafebebe",
dhcp_source_ready => '1', dhcp_source_valid => dhcp_source_valid,
dhcp_source_ready => '1'
--== PIXEL DATA PORT ==--
pixel_bind_port => x"effd", -- port 61437 - "PIXEL"
-- sink
pixel_sink_dst_ip_address => x"0a141e29",
pixel_sink_dst_port => x"303a", -- port 12346
pixel_sink_length => pixel_sink_length,
pixel_sink_valid => pixel_sink_valid,
pixel_sink_last => pixel_sink_last,
pixel_sink_last_be => pixel_sink_last_be,
pixel_sink_ready => pixel_sink_ready,
pixel_sink_data => pixel_sink_data,
-- source
pixel_source_src_ip_address => pixel_source_src_ip_address,
pixel_source_src_port => pixel_source_src_port,
pixel_source_length => pixel_source_length,
pixel_source_valid => pixel_source_valid,
pixel_source_last => pixel_source_last,
pixel_source_last_be => pixel_source_last_be,
pixel_source_ready => pixel_source_ready,
pixel_source_data => pixel_source_data
); );
-- 800 MHz VCO -- 800 MHz VCO
@ -280,69 +212,61 @@ begin
CLKFBIN => pll_feedback, CLKFBIN => pll_feedback,
CLKFBOUT => pll_feedback, CLKFBOUT => pll_feedback,
CLKOUT0 => unbuf_sys_clk, CLKOUT0 => unbuf_clk_sys,
CLKOUT1 => mii_clk_25mhz CLKOUT1 => mii_clk_25mhz
); );
bufg_sys_clk: BUFG bufg_clk_sys: BUFG
port map ( port map (
I => unbuf_sys_clk, I => unbuf_clk_sys,
O => sys_clk O => clk_sys
); );
sys_reset <= not pll_locked or not n_reset; sys_reset <= not pll_locked or not n_reset;
pmod_a <= drivers(7 downto 0); ws2812_inst: entity work.ws2812
pmod_b <= drivers(15 downto 8); generic map (
pmod_d <= drivers(23 downto 16); NUM_LEDS => 20,
COLOR_ORDER => "GRB",
T_CLK => 12.5 ns,
sender: process(sys_clk) T0H => 0.35 us,
constant COUNTER_MAX: natural := 80000000; T0L => 0.8 us,
variable counter: natural range 0 to COUNTER_MAX; T1H => 0.7 us,
T1L => 0.6 us,
constant NUM_WORDS: natural := 10; T_RES => 80 us
variable words_sent: natural range 0 to NUM_WORDS; )
begin port map (
if rising_edge(sys_clk) then n_reset => not sys_reset,
if counter = COUNTER_MAX then clk => clk_sys,
pixel_sink_length <= std_logic_vector(to_unsigned(NUM_WORDS, 16));
pixel_sink_data <= std_logic_vector(to_unsigned(16#30# + words_sent, 32));
--pixel_sink_valid <= '1';
pixel_sink_last <= '1' when words_sent = NUM_WORDS-1 else '0'; led_addr => open,
if words_sent = NUM_WORDS then led_red => x"ff",
pixel_sink_valid <= '0'; led_green => x"00",
counter := 0; led_blue => x"ff",
words_sent := 0;
elsif pixel_sink_ready then dout => pmod_d(3)
words_sent := words_sent + 1; );
end if;
else -- https://github.com/YosysHQ/yosys/issues/3360
counter := counter + 1; pmod_d(0) <= '0';
end if; pmod_d(1) <= '0';
end if; pmod_d(2) <= '0';
end process; pmod_d(4) <= '0';
pixel_sink_last_be <= (pixel_sink_last, others => '0'); pmod_d(5) <= '0';
pmod_d(6) <= '0';
pmod_d(7) <= '0';
splink: entity work.splink splink: entity work.splink
generic map ( generic map (
NUM_STRANDS => NUM_STRANDS NUM_DRIVERS => NUM_DRIVERS
) )
port map ( port map (
clk => sys_clk, clk => clk_sys,
reset => sys_reset, reset => sys_reset,
udp_src_ip_address => pixel_source_src_ip_address,
udp_src_port => pixel_source_src_port,
udp_length => pixel_source_length,
udp_valid => pixel_source_valid,
udp_last => pixel_source_last,
udp_last_be => pixel_source_last_be,
udp_ready => pixel_source_ready,
udp_data => pixel_source_data,
drivers => drivers drivers => drivers
); );
end architecture; end architecture;

View file

@ -4,84 +4,33 @@ use ieee.numeric_std.all;
entity splink is entity splink is
generic ( generic (
NUM_STRANDS : positive; NUM_DRIVERS : positive := 16;
MAX_STRAND_LEN : positive := 256 ROWS : positive := 100;
COLS : positive := 100
); );
port ( port (
clk : in std_logic; clk : in std_logic;
reset : in std_logic; reset : in std_logic;
udp_src_ip_address : in std_logic_vector(31 downto 0); drivers : out std_logic_vector(NUM_DRIVERS-1 downto 0)
udp_src_port : in std_logic_vector(15 downto 0);
udp_length : in std_logic_vector(15 downto 0);
udp_valid : in std_logic;
udp_last : in std_logic;
udp_last_be : in std_logic_vector(3 downto 0);
udp_ready : out std_logic;
udp_data : in std_logic_vector(31 downto 0);
drivers : out std_logic_vector(NUM_STRANDS-1 downto 0)
); );
end entity; end entity;
architecture a of splink is architecture a of splink is
signal driver_out : std_logic; signal count2: unsigned(3 downto 0);
signal count: natural range 0 to 10000000;
constant BITS_PER_LED: natural := 24;
subtype color_t is std_logic_vector(BITS_PER_LED-1 downto 0);
type strand_store_t is array(0 to MAX_STRAND_LEN) of color_t;
signal strand_store: strand_store_t;
signal led_addr : std_logic_vector(7 downto 0);
signal current_color : color_t;
begin begin
ws2812_inst: entity work.ws2812 process(clk)
generic map (
NUM_LEDS => MAX_STRAND_LEN,
COLOR_ORDER => "GRB",
T_CLK => 12.5 ns,
T0H => 0.35 us,
T0L => 0.8 us,
T1H => 0.7 us,
T1L => 0.6 us,
T_RES => 80 us
)
port map (
n_reset => not reset,
clk => clk,
led_addr => led_addr,
led_red => current_color(7 downto 0),
led_green => current_color(15 downto 8),
led_blue => current_color(23 downto 16),
dout => driver_out
);
-- https://github.com/YosysHQ/yosys/issues/3360
drivers <= (19 => driver_out, others => '0');
writer: process(clk)
variable store_counter: natural range 0 to MAX_STRAND_LEN-1;
begin begin
if rising_edge(clk) then if rising_edge(clk) then
current_color <= strand_store(to_integer(unsigned(led_addr))); if count = 10000000 then
count <= 0;
if udp_valid then count2 <= count2 + 1;
strand_store(store_counter) <= udp_data(23 downto 0); else
count <= count + 1;
if udp_last then
store_counter := 0;
elsif store_counter /= MAX_STRAND_LEN-1 then
store_counter := store_counter + 1;
end if;
end if; end if;
end if; end if;
end process; end process;
udp_ready <= '1'; drivers <= (15 downto 4 => '0') & std_logic_vector(count2);
end architecture; end architecture;