|
|
@ -16,6 +16,7 @@ |
|
|
|
# 19.10.20/KQ Renamed from 'neopixelprotocol' |
|
|
|
# 27.10.20/KQ Using AutoDoc etc(improved documentation generation) |
|
|
|
# 29.10.20/KQ Memory object replaces array (due to resource consumption) |
|
|
|
# 02.01.21/KQ DRAM loader integrated |
|
|
|
# |
|
|
|
|
|
|
|
from migen import * |
|
|
@ -32,25 +33,29 @@ class NeoPixelEngine(Module, AutoCSR, AutoDoc, ModuleDoc): |
|
|
|
Usage: |
|
|
|
###### |
|
|
|
|
|
|
|
#. Fill NeoPixelEngine's local array of GRB values (Green/Red/Blue). |
|
|
|
Load ``b24Data2Load`` with a 24-bit (GRB) value. |
|
|
|
Indicate the offset (where to store) via writing to ``b8LoadOffset``. |
|
|
|
Repeat for all offsets 'til end of array ... |
|
|
|
#. Fill NeoPixelEngine's local array of GRB values (Green/Red/Blue) |
|
|
|
from DRAM location |
|
|
|
Load ``b32DRAMAddress`` with a 32-bit DRAM memory pointer. |
|
|
|
Indicate the offset (where to store) via writing to ``b6StoreOffset``. |
|
|
|
Repeat for all DRAM address 'til end of array ... |
|
|
|
|
|
|
|
#. Indicate to NeoPixelEngine the actual no. of pixels used by setting up ``b8Len``. |
|
|
|
#. Indicate no. of DRAM addresses stored by writing to ``b6NoOfTables`` |
|
|
|
|
|
|
|
#. Indicate to NeoPixelEngine the actual no. of pixels used by setting up ``b9Len``. |
|
|
|
For now, all arrays have to have the same max. length. |
|
|
|
|
|
|
|
#. Finally, enable processing by setting ``bEnable`` to true (1). |
|
|
|
|
|
|
|
Inputs: |
|
|
|
####### |
|
|
|
|
|
|
|
:b24Data2Load: New data to be loaded (24 bits) |
|
|
|
:b32DRAMAddress: New DRAM address to be loaded (32 bits) into local memory |
|
|
|
|
|
|
|
:b8LoadOffset: Offset (0..255) into b24GRBArray to load b24Data2Load to |
|
|
|
:b6StoreOffset: Offset (0..63) into local memory to load b32DRAMAddress to |
|
|
|
|
|
|
|
:b4LoadTable: Table index (0..15) where to load to via b8LoadOffset |
|
|
|
:b6NoOfTables: No. of actually used tables (0..63) |
|
|
|
|
|
|
|
:b8Len: Length (0..255) of actual 24-bit data entries (i.e. # of NeoPixels) |
|
|
|
:b9Len: Length (0..511) of actual 32-bit data entries (i.e. # of NeoPixels) |
|
|
|
|
|
|
|
:bEnable: To enable running (after data preparation) |
|
|
|
|
|
|
@ -59,30 +64,29 @@ class NeoPixelEngine(Module, AutoCSR, AutoDoc, ModuleDoc): |
|
|
|
|
|
|
|
:bDataPin: NeoPixel 'Din' pin output (wire to actual output pin ... ;) |
|
|
|
""" |
|
|
|
def __init__(self, n_TABLES=1, n_LEDs=3): |
|
|
|
def __init__(self, n_TABLES=1, n_LEDs=3, dramtransfer=None): |
|
|
|
# On Colorlight-5A-75B/Lattice ECP5-25 (@i7/4th gen.): |
|
|
|
# 16 pins simultaneously driven (w/ 256 NeoPixels each) yield 94% |
|
|
|
|
|
|
|
# Inputs |
|
|
|
self.b24Data2Load = CSRStorage(24, reset_less=True, |
|
|
|
fields=[CSRField("Data2Load", size=24, description="*Field*: 24-Bit value")], |
|
|
|
self.b32DRAMAddress = CSRStorage(32, reset_less=True, |
|
|
|
fields=[CSRField("Data2Load", size=32, description="*Field*: 32-Bit value")], |
|
|
|
description=""" |
|
|
|
Load value (24-Bit G/R/B). |
|
|
|
Use ``b8LoadOffset`` first to indicate array location where to store this value. |
|
|
|
Load value (32-bit DRAM address). |
|
|
|
Use ``b6StoreOffset`` first to indicate array location where to store new DRAM address value. |
|
|
|
""") |
|
|
|
self.b8LoadOffset = CSRStorage(8, reset_less=True, |
|
|
|
fields=[CSRField("LoadOffset", size=8, description="*Field*: 8-Bit value (0..max)")], |
|
|
|
self.b6StoreOffset = CSRStorage(6, reset_less=True, |
|
|
|
fields=[CSRField("LoadOffset", size=6, description="*Field*: 6-Bit value (0..63)")], |
|
|
|
description=""" |
|
|
|
Offset into storage array for 24-bit G/R/B values. |
|
|
|
Prepare this one second, then indicate value to store via ``b24Data2Load``. |
|
|
|
Offset into local memory for 32-bit DRAM address values. |
|
|
|
Prepare this one second, then indicate value to store via ``b32DRAMAddress``. |
|
|
|
""") |
|
|
|
self.b4LoadTable = CSRStorage(4, reset_less=True, |
|
|
|
fields=[CSRField("LoadTable", size=4, description="*Field*: 8-Bit value (0..max)")], |
|
|
|
self.b6NoOfTables = CSRStorage(6, reset_less=True, |
|
|
|
fields=[CSRField("NoOfTables", size=6, description="*Field*: 6-Bit value (0..63)")], |
|
|
|
description=""" |
|
|
|
Table index into storage array for 24-bit G/R/B values. |
|
|
|
Prepare this one first, then indicate offset value ``b8LoadOffset``. |
|
|
|
No. of actually used different DRAM address blocks |
|
|
|
""") |
|
|
|
self.b8Len = CSRStorage(8, reset_less=True, |
|
|
|
self.b9Len = CSRStorage(9, reset_less=True, |
|
|
|
fields=[CSRField("Len", size=8, description="*Field*: 8-Bit value (0..max)")], |
|
|
|
description=""" |
|
|
|
No. of active (GRB) entries. |
|
|
@ -99,39 +103,42 @@ class NeoPixelEngine(Module, AutoCSR, AutoDoc, ModuleDoc): |
|
|
|
""") |
|
|
|
|
|
|
|
# Local data |
|
|
|
self.b4Table = Signal(4) # Table rover |
|
|
|
self.b8Offset = Signal(8) # Array rover |
|
|
|
self.b24GRB = Signal(24) # Current 24-bit data to send |
|
|
|
self.b6Table = Signal(6) # Table rover (0..63) |
|
|
|
self.b9Offset = Signal(9) # Array rover (0..511) |
|
|
|
self.b32GRB = Signal(32) # Current 24-bit(GRB) or 32-bit (GRBW) data to send |
|
|
|
self.b12PulseLen = Signal(12) # Current pulse length |
|
|
|
self.b5Count24 = Signal(5) # 24-Bit counter |
|
|
|
self.b5Count24 = Signal(5) # 24-Bit (or 32-bit) counter |
|
|
|
|
|
|
|
storage = Memory(24, n_TABLES * n_LEDs) |
|
|
|
storage = Memory(32, n_TABLES) # * n_LEDs) |
|
|
|
self.specials += storage |
|
|
|
wrport = storage.get_port(write_capable=True) |
|
|
|
self.specials += wrport |
|
|
|
self.comb += [ # Write to memory |
|
|
|
wrport.adr.eq((self.b4LoadTable.storage * n_LEDs) + self.b8LoadOffset.storage), |
|
|
|
wrport.dat_w.eq(self.b24Data2Load.storage), |
|
|
|
wrport.adr.eq(self.b6StoreOffset.storage), # Index local memory for WRITE |
|
|
|
wrport.dat_w.eq(self.b32DRAMAddress.storage), # & store DRAM address |
|
|
|
wrport.we.eq(1) |
|
|
|
] |
|
|
|
rdport = storage.get_port() |
|
|
|
self.specials += rdport |
|
|
|
self.comb += [ # Read from memory |
|
|
|
rdport.adr.eq((self.b4Table * n_LEDs) + self.b8Offset) |
|
|
|
self.comb += [ # Read from DRAM addresses memory |
|
|
|
rdport.adr.eq(self.b6Table) # Index DRAM address array (READ) # TODO: Not used currently! |
|
|
|
] |
|
|
|
if dramtransfer != None: # This is for real! |
|
|
|
self.sync += dramtransfer.b9Offset.storage.eq(self.b9Offset) # Index DRAM data value storage |
|
|
|
|
|
|
|
# Output |
|
|
|
self.bDataPin = Array(Signal(1) for bit in range(16)) # To be wired to data pins ... |
|
|
|
# TODO: Expand to 60 outputs ... |
|
|
|
|
|
|
|
### |
|
|
|
fsm = FSM(reset_state="IDLETABLE") # FSM starts idling ... |
|
|
|
self.submodules += fsm |
|
|
|
|
|
|
|
fsm.act("IDLETABLE", |
|
|
|
If((self.bEnable.storage==True) and (self.b8Len.storage > 0), |
|
|
|
NextValue(self.b4Table, 0), # Start @ 1st table |
|
|
|
NextValue(self.b8Offset, 0), # Start @ 1st 24-bit data (mem will be ready next cycle) |
|
|
|
NextValue(self.b5Count24, 0), # Bit count 0..23 |
|
|
|
If((self.bEnable.storage==True) and (self.b9Len.storage > 0), |
|
|
|
NextValue(self.b6Table, 0), # Start @ 1st table |
|
|
|
NextValue(self.b9Offset, 0), # Start @ 1st 24-bit or 32-bit data (mem will be ready next cycle) |
|
|
|
NextValue(self.b5Count24, 0), # Bit count 0..23 (or 0..31) |
|
|
|
NextState("IDLE1") |
|
|
|
) |
|
|
|
) |
|
|
@ -144,14 +151,16 @@ class NeoPixelEngine(Module, AutoCSR, AutoDoc, ModuleDoc): |
|
|
|
NextState("IDLE3") |
|
|
|
) |
|
|
|
fsm.act("IDLE3", |
|
|
|
NextValue(self.b24GRB, rdport.dat_r), # Depends upon b4Table/b8Offset |
|
|
|
NextValue(self.b5Count24, 0), # Bit count 0..23 |
|
|
|
#NextValue(self.b32GRB, rdport.dat_r), # Depends upon b6Table/b9Offset |
|
|
|
#if dramtransfer != None: |
|
|
|
NextValue(self.b32GRB, dramtransfer.b32Data.storage), # Depends upon b9Offset |
|
|
|
NextValue(self.b5Count24, 0), # Bit count 0..23 (or 0..31) |
|
|
|
NextState("PREPAREBIT") |
|
|
|
) |
|
|
|
# 24-bit loop entry: |
|
|
|
# 24-bit or 32-bit loop entry: |
|
|
|
# Protocol: T0H=400ns/T0L=850ns, T1H=800ns/T1L=450ns, RST>50µs(>50000ns) |
|
|
|
fsm.act("PREPAREBIT", |
|
|
|
If(self.b24GRB[23], |
|
|
|
If(self.b32GRB[23], |
|
|
|
NextValue(self.b12PulseLen, 47), # Compensate for 1 state changes w/o action ...), |
|
|
|
NextState("T1H") |
|
|
|
).Else( |
|
|
@ -160,10 +169,10 @@ class NeoPixelEngine(Module, AutoCSR, AutoDoc, ModuleDoc): |
|
|
|
) |
|
|
|
) |
|
|
|
fsm.act("T1H", |
|
|
|
NextValue(self.bDataPin[self.b4Table], 1), |
|
|
|
NextValue(self.bDataPin[self.b6Table], 1), |
|
|
|
NextValue(self.b12PulseLen, self.b12PulseLen - 1), |
|
|
|
If(self.b12PulseLen == 0, |
|
|
|
If(self.b5Count24 < 23, # Not final pulse of word |
|
|
|
If(self.b5Count24 < 23, # Not final pulse of word (TODO: Adjust for 32-bit as well!) |
|
|
|
NextValue(self.b12PulseLen, 24) # Compensate for 3 state changes w/o action ... |
|
|
|
).Else( # Final word pulse special |
|
|
|
NextValue(self.b12PulseLen, 21) # Compensate word load cycles |
|
|
@ -172,20 +181,20 @@ class NeoPixelEngine(Module, AutoCSR, AutoDoc, ModuleDoc): |
|
|
|
) |
|
|
|
) |
|
|
|
fsm.act("T1L", |
|
|
|
NextValue(self.bDataPin[self.b4Table], 0), |
|
|
|
NextValue(self.bDataPin[self.b6Table], 0), |
|
|
|
NextValue(self.b12PulseLen, self.b12PulseLen - 1), |
|
|
|
If(self.b12PulseLen == 0, |
|
|
|
NextValue(self.b5Count24, self.b5Count24 + 1), # Next bit (of GRB) |
|
|
|
NextValue(self.b24GRB, self.b24GRB << 1), # Next bit (of GRB) |
|
|
|
NextValue(self.b32GRB, self.b32GRB << 1), # Next bit (of GRB) |
|
|
|
NextState("NEXTBIT") |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
fsm.act("T0H", |
|
|
|
NextValue(self.bDataPin[self.b4Table], 1), |
|
|
|
NextValue(self.bDataPin[self.b6Table], 1), |
|
|
|
NextValue(self.b12PulseLen, self.b12PulseLen - 1), |
|
|
|
If(self.b12PulseLen == 0, |
|
|
|
If(self.b5Count24 < 23, # Not final pulse of word? |
|
|
|
If(self.b5Count24 < 23, # Not final pulse of word? (TODO: Adjust for 32-bit as well!) |
|
|
|
NextValue(self.b12PulseLen, 48) # Compensate for 3 state changes w/o action ... |
|
|
|
).Else( # Final word load special |
|
|
|
NextValue(self.b12PulseLen, 45) # Compensate for load word cycles |
|
|
@ -194,21 +203,21 @@ class NeoPixelEngine(Module, AutoCSR, AutoDoc, ModuleDoc): |
|
|
|
) |
|
|
|
) |
|
|
|
fsm.act("T0L", |
|
|
|
NextValue(self.bDataPin[self.b4Table], 0), |
|
|
|
NextValue(self.bDataPin[self.b6Table], 0), |
|
|
|
NextValue(self.b12PulseLen, self.b12PulseLen - 1), |
|
|
|
If(self.b12PulseLen == 0, |
|
|
|
NextValue(self.b5Count24, self.b5Count24 + 1), # Next bit (of GRB) |
|
|
|
NextValue(self.b24GRB, self.b24GRB << 1), # Next bit (of GRB) |
|
|
|
NextValue(self.b32GRB, self.b32GRB << 1), # Next bit (of GRB) |
|
|
|
NextState("NEXTBIT") |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
fsm.act("NEXTBIT", |
|
|
|
If(self.b5Count24 < 24, # Not yet done? |
|
|
|
If(self.b5Count24 < 24, # Not yet done? (TODO: Adjust for 32-bit as well!) |
|
|
|
NextState("PREPAREBIT") |
|
|
|
).Else( # GRB word finished. More to come? |
|
|
|
NextValue(self.b5Count24,0), # Bit count reset for next word |
|
|
|
NextValue(self.b8Offset, self.b8Offset + 1), # Prepare offset for later use |
|
|
|
NextValue(self.b9Offset, self.b9Offset + 1), # Prepare offset for later use |
|
|
|
NextState("NEXTWORD1") |
|
|
|
) |
|
|
|
) |
|
|
@ -219,8 +228,10 @@ class NeoPixelEngine(Module, AutoCSR, AutoDoc, ModuleDoc): |
|
|
|
NextState("NEXTWORD3") # Add one cycle for read port propagation! |
|
|
|
) |
|
|
|
fsm.act("NEXTWORD3", |
|
|
|
If((self.b8Offset < self.b8Len.storage) & (self.bEnable.storage==True), # Still more words to come (& no exit request)? |
|
|
|
NextValue(self.b24GRB, rdport.dat_r), # Depends upon b4Table/b8Offset! |
|
|
|
If((self.b9Offset < self.b9Len.storage) & (self.bEnable.storage==True), # Still more words to come (& no exit request)? |
|
|
|
#NextValue(self.b32GRB, rdport.dat_r), # Depends upon b6Table/b9Offset! |
|
|
|
#if dramtransfer != None: |
|
|
|
NextValue(self.b32GRB, dramtransfer.b32Data.storage), # Depends upon b9Offset! |
|
|
|
NextState("PREPAREBIT") |
|
|
|
).Else( |
|
|
|
NextValue(self.b12PulseLen, 4095), # >50µs required (3000 not ok!) |
|
|
@ -228,17 +239,17 @@ class NeoPixelEngine(Module, AutoCSR, AutoDoc, ModuleDoc): |
|
|
|
) |
|
|
|
) |
|
|
|
fsm.act("RST", |
|
|
|
NextValue(self.bDataPin[self.b4Table], 0), |
|
|
|
NextValue(self.bDataPin[self.b6Table], 0), |
|
|
|
NextValue(self.b12PulseLen, self.b12PulseLen - 1), |
|
|
|
If(self.b12PulseLen == 0, |
|
|
|
NextValue(self.b4Table, self.b4Table + 1), |
|
|
|
NextValue(self.b6Table, self.b6Table + 1), |
|
|
|
NextState("NEXTTABLE") |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
fsm.act("NEXTTABLE", |
|
|
|
If(self.b4Table < n_TABLES, |
|
|
|
NextValue(self.b8Offset, 0), # Start @ 1st 24-bit data |
|
|
|
If(self.b6Table < self.b6NoOfTables.storage, |
|
|
|
NextValue(self.b9Offset, 0), # Start @ 1st 24-bit data (or 32-bit data) |
|
|
|
NextState("IDLE1") |
|
|
|
).Else( |
|
|
|
NextState("IDLETABLE") |
|
|
@ -250,19 +261,19 @@ def npe_testbench(npe): |
|
|
|
print("----- npe testbench -----") |
|
|
|
yield npe.b4LoadTable.storage.eq(0) |
|
|
|
yield |
|
|
|
yield npe.b8LoadOffset.storage.eq(0) |
|
|
|
yield npe.b6LoadOffset.storage.eq(0) |
|
|
|
yield |
|
|
|
yield npe.b24Data2Load.storage.eq(0x110000) |
|
|
|
yield npe.b32DRAMAddress.storage.eq(0x110000) |
|
|
|
yield |
|
|
|
yield npe.b8LoadOffset.storage.eq(1) |
|
|
|
yield npe.b6LoadOffset.storage.eq(1) |
|
|
|
yield |
|
|
|
yield npe.b24Data2Load.storage.eq(0x002200) |
|
|
|
yield npe.b32DRAMAddress.storage.eq(0x002200) |
|
|
|
yield |
|
|
|
yield npe.b8LoadOffset.storage.eq(2) |
|
|
|
yield npe.b6LoadOffset.storage.eq(2) |
|
|
|
yield |
|
|
|
yield npe.b24Data2Load.storage.eq(0x000033) |
|
|
|
yield npe.b32DRAMAddress.storage.eq(0x000033) |
|
|
|
yield |
|
|
|
yield npe.b8Len.storage.eq(3) |
|
|
|
yield npe.b9Len.storage.eq(3) |
|
|
|
yield |
|
|
|
yield npe.bEnable.storage.eq(True) |
|
|
|
yield |
|
|
|