12
12
from amaranth_soc import csr , wishbone
13
13
14
14
from ..video .framebuffer import DMAFramebuffer
15
+ from ..video .types import Pixel
15
16
16
17
17
18
class Persistance (wiring .Component ):
18
19
19
20
"""
20
21
Read pixels from a framebuffer in PSRAM and apply gradual intensity reduction to simulate oscilloscope glow.
21
- Pixels are DMA'd from PSRAM as a wishbone master in bursts of 'fifo_depth // 2 ' in the 'sync' clock domain.
22
+ Pixels are DMA'd from PSRAM as a wishbone master in bursts of 'fifo_depth' in the 'sync' clock domain.
22
23
The block of pixels has its intensity reduced and is then DMA'd back to the bus.
23
24
24
25
'holdoff' is used to keep this core from saturating the bus between bursts.
25
26
"""
26
27
27
28
def __init__ (self , * , fb : DMAFramebuffer ,
28
- fifo_depth = 32 , holdoff_default = 256 ):
29
+ fifo_depth = 16 , holdoff_default = 256 ):
29
30
30
31
self .fb = fb
31
32
self .fifo_depth = fifo_depth
32
33
33
34
# FIFO to cache pixels from PSRAM.
34
- self .fifo = SyncFIFOBuffered (width = 32 , depth = fifo_depth )
35
-
36
- # Current addresses in the framebuffer (read and write sides)
37
- self .dma_addr_in = Signal (32 , init = 0 )
38
- self .dma_addr_out = Signal (32 )
35
+ self .fifo = SyncFIFOBuffered (width = fb .bus .data_width , depth = fifo_depth )
39
36
40
37
super ().__init__ ({
41
38
# Tweakables
@@ -50,107 +47,124 @@ def __init__(self, *, fb: DMAFramebuffer,
50
47
def elaborate (self , platform ) -> Module :
51
48
m = Module ()
52
49
53
- # Length of framebuffer in 32-bit words
54
- fb_len_words = (self .fb .timings .active_pixels * self .fb .bytes_per_pixel ) // 4
55
-
56
- holdoff_count = Signal (32 )
57
- pnext = Signal (32 )
58
- wr_source = Signal (32 )
59
-
60
50
m .submodules .fifo = self .fifo
61
51
bus = self .bus
62
- dma_addr_in = self .dma_addr_in
63
- dma_addr_out = self .dma_addr_out
64
52
65
- decay_latch = Signal (4 )
53
+ # Length of framebuffer in bus words
54
+ fb_len_words = ((self .fb .timings .active_pixels * self .fb .bytes_per_pixel ) //
55
+ (self .fb .bus .data_width // Pixel .as_shape ().size ))
56
+
57
+ # Track framebuffer position by tracking fifo reads/writes
58
+ dma_offs_in = Signal (self .fb .bus .addr_width , init = 0 )
59
+ with m .If (self .fifo .w_en & self .fifo .w_rdy ):
60
+ with m .If (dma_offs_in < (fb_len_words - 1 )):
61
+ m .d .sync += dma_offs_in .eq (dma_offs_in + 1 )
62
+ with m .Else ():
63
+ m .d .sync += dma_offs_in .eq (0 )
64
+
65
+ dma_offs_out = Signal .like (dma_offs_in )
66
+ with m .If (self .fifo .r_en & self .fifo .r_rdy ):
67
+ with m .If (dma_offs_out < (fb_len_words - 1 )):
68
+ m .d .sync += dma_offs_out .eq (dma_offs_out + 1 )
69
+ with m .Else ():
70
+ m .d .sync += dma_offs_out .eq (0 )
71
+
72
+ # Latched version of decay speed control input
73
+ decay_latch = Signal .like (self .decay )
74
+ # Track delay between read/write bursts
75
+ holdoff_count = Signal (32 )
76
+ # Incoming pixel array (read from FIFO)
77
+ pixels_r = Signal (data .ArrayLayout (Pixel , 4 ))
78
+
79
+ m .d .comb += self .fifo .w_data .eq (bus .dat_r )
80
+
81
+ # Used for fastpath when all pixels are zero
82
+ any_nonzero_reads = Signal ()
83
+ pixels_peek = Signal (data .ArrayLayout (Pixel , 4 ))
84
+ m .d .comb += pixels_peek .eq (self .fifo .w_data )
85
+ with m .If (self .fifo .w_en ):
86
+ with m .If ((pixels_peek [0 ].intensity != 0 ) |
87
+ (pixels_peek [1 ].intensity != 0 ) |
88
+ (pixels_peek [2 ].intensity != 0 ) |
89
+ (pixels_peek [3 ].intensity != 0 )):
90
+ m .d .sync += any_nonzero_reads .eq (1 )
66
91
67
- # Persistance state machine in 'sync' domain.
68
92
with m .FSM () as fsm :
69
93
with m .State ('OFF' ):
70
94
with m .If (self .enable ):
71
95
m .next = 'BURST-IN'
72
96
73
97
with m .State ('BURST-IN' ):
74
- m .d .sync += holdoff_count .eq (0 )
75
98
m .d .sync += decay_latch .eq (self .decay )
76
99
m .d .comb += [
77
100
bus .stb .eq (1 ),
78
101
bus .cyc .eq (1 ),
79
102
bus .we .eq (0 ),
80
103
bus .sel .eq (2 ** (bus .data_width // 8 )- 1 ),
81
- bus .adr .eq (self .fb .fb_base + dma_addr_in ),
104
+ bus .adr .eq (self .fb .fb_base + dma_offs_in ),
82
105
self .fifo .w_en .eq (bus .ack ),
83
- self .fifo .w_data .eq (bus .dat_r ),
84
106
bus .cti .eq (
85
107
wishbone .CycleType .INCR_BURST ),
86
108
]
87
- with m .If (self .fifo .w_level > = (self .fifo_depth - 1 )):
109
+ with m .If (self .fifo .w_level = = (self .fifo_depth - 1 )):
88
110
m .d .comb += bus .cti .eq (
89
111
wishbone .CycleType .END_OF_BURST )
90
- with m .If (bus .stb & bus .ack & self .fifo .w_rdy ): # WARN: drops last word
91
- with m .If (dma_addr_in < (fb_len_words - 1 )):
92
- m .d .sync += dma_addr_in .eq (dma_addr_in + 1 )
93
- with m .Else ():
94
- m .d .sync += dma_addr_in .eq (0 )
95
- with m .Elif (~ self .fifo .w_rdy ):
96
- m .next = 'WAIT1'
97
-
98
- with m .State ('WAIT1' ):
99
- m .d .sync += holdoff_count .eq (holdoff_count + 1 )
100
- with m .If (holdoff_count > self .holdoff ):
101
- m .d .sync += pnext .eq (self .fifo .r_data )
102
- m .d .comb += self .fifo .r_en .eq (1 )
103
- m .next = 'BURST-OUT'
112
+ m .next = 'PREFETCH'
104
113
105
- with m .State ('BURST-OUT' ):
114
+ with m .State ('PREFETCH' ):
115
+ # Do not permit bus arbitration between burst in and out.
116
+ m .d .comb += bus .cyc .eq (1 )
117
+ m .d .comb += self .fifo .w_en .eq (bus .ack )
106
118
m .d .sync += holdoff_count .eq (0 )
119
+ with m .If (~ bus .ack ):
120
+ with m .If (any_nonzero_reads ):
121
+ # Prefetch first FIFO entry before burst
122
+ m .d .comb += self .fifo .r_en .eq (1 )
123
+ m .d .sync += pixels_r .eq (self .fifo .r_data )
124
+ m .next = 'BURST-OUT'
125
+ with m .Else ():
126
+ # Fastpath: all pixels have zero intensity -
127
+ # no write is needed, skip it. This saves a lot
128
+ # of bandwidth as the screen is mostly black.
129
+ m .next = 'DRAIN'
107
130
108
- # Incoming pixel array (read from FIFO)
109
- pixa = Signal (data .ArrayLayout (unsigned (8 ), 4 ))
110
- # Outgoing pixel array (write to bus)
111
- pixb = Signal (data .ArrayLayout (unsigned (8 ), 4 ))
112
-
113
- m .d .sync += [
114
- pixa .eq (wr_source ),
115
- ]
116
-
131
+ with m .State ('BURST-OUT' ):
117
132
# The actual persistance calculation. 4 pixels at a time.
133
+ pixels_w = Signal (data .ArrayLayout (Pixel , 4 ))
118
134
for n in range (4 ):
119
135
# color
120
- m .d .comb += pixb [n ][ 0 : 4 ]. eq (pixa [n ][ 0 : 4 ] )
136
+ m .d .comb += pixels_w [n ]. color . eq (pixels_r [n ]. color )
121
137
# intensity
122
- with m .If (pixa [n ][ 4 : 8 ] >= decay_latch ):
123
- m .d .comb += pixb [n ][ 4 : 8 ]. eq (pixa [n ][ 4 : 8 ] - decay_latch )
138
+ with m .If (pixels_r [n ]. intensity >= decay_latch ):
139
+ m .d .comb += pixels_w [n ]. intensity . eq (pixels_r [n ]. intensity - decay_latch )
124
140
with m .Else ():
125
- m .d .comb += pixb [n ][4 :8 ].eq (0 )
126
-
141
+ m .d .comb += pixels_w [n ].intensity .eq (0 )
127
142
128
143
m .d .comb += [
129
144
bus .stb .eq (1 ),
130
145
bus .cyc .eq (1 ),
131
146
bus .we .eq (1 ),
132
147
bus .sel .eq (2 ** (bus .data_width // 8 )- 1 ),
133
- bus .adr .eq (self .fb .fb_base + dma_addr_out ),
134
- wr_source .eq (pnext ),
135
- bus .dat_w .eq (pixb ),
148
+ bus .adr .eq (self .fb .fb_base + dma_offs_out - 1 ),
149
+ bus .dat_w .eq (pixels_w ),
136
150
bus .cti .eq (
137
151
wishbone .CycleType .INCR_BURST )
138
152
]
139
- with m .If (bus .stb & bus . ack ):
153
+ with m .If (bus .ack ):
140
154
m .d .comb += self .fifo .r_en .eq (1 )
141
- m .d .comb += wr_source .eq (self .fifo .r_data ),
142
- with m .If (dma_addr_out < (fb_len_words - 1 )):
143
- m .d .sync += dma_addr_out .eq (dma_addr_out + 1 )
144
- m .d .comb += bus .adr .eq (self .fb .fb_base + dma_addr_out + 1 ),
145
- with m .Else ():
146
- m .d .sync += dma_addr_out .eq (0 )
147
- m .d .comb += bus .adr .eq (self .fb .fb_base + 0 ),
155
+ m .d .sync += pixels_r .eq (self .fifo .r_data )
148
156
with m .If (~ self .fifo .r_rdy ):
149
157
m .d .comb += bus .cti .eq (
150
158
wishbone .CycleType .END_OF_BURST )
151
- m .next = 'WAIT2'
159
+ m .next = 'HOLDOFF'
160
+
161
+ with m .State ('DRAIN' ):
162
+ m .d .comb += self .fifo .r_en .eq (1 )
163
+ with m .If (~ self .fifo .r_rdy ):
164
+ m .next = 'HOLDOFF'
152
165
153
- with m .State ('WAIT2' ):
166
+ with m .State ('HOLDOFF' ):
167
+ m .d .sync += any_nonzero_reads .eq (0 )
154
168
m .d .sync += holdoff_count .eq (holdoff_count + 1 )
155
169
with m .If (holdoff_count > self .holdoff ):
156
170
m .next = 'BURST-IN'
@@ -197,4 +211,4 @@ def elaborate(self, platform):
197
211
with m .If (self ._decay .f .decay .w_stb ):
198
212
m .d .sync += self .persist .decay .eq (self ._decay .f .decay .w_data )
199
213
200
- return m
214
+ return m
0 commit comments