/
goblin_alt_audio.py
324 lines (304 loc) · 21 KB
/
goblin_alt_audio.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
from migen import *
from migen.genlib.fifo import *
from litex.soc.interconnect.csr import *
from litex.soc.interconnect import wishbone
class GoblinAudio(Module, AutoCSR):
def __init__(self, soc, audio_clk_freq):
self.hdmiext_audio_clk = hdmiext_audio_clk = Signal() # should be audio_clk_freq/44.1 kHz clock
self.hdmiext_audio_word_0 = hdmiext_audio_word_0 = Signal(16) # channel 0 of stereo audio (L-PCM)
self.hdmiext_audio_word_1 = hdmiext_audio_word_1 = Signal(16) # channel 1 of stereo audio (L-PCM)
self.irq = Signal() # active LOW
self.busmaster = busmaster = wishbone.Interface()
# # #
self.irqctrl = CSRStorage(write_from_dev=True, fields = [CSRField("irq_enable", 1, description = "Enable interrupt"),
CSRField("irq_clear", 1, description = "Clear interrupt"),
CSRField("reserved", 30, description = "Reserved"),
])
self.irqstatus = CSRStatus(fields = [ CSRField("irq", 1, description = "There's a pending interrupt"),
CSRField("reserved", 31, description = "Reserved")
])
# The SW should specify the buffer to use (currently 0 or 1), autostop (if not set, keep playing the next buffer in RR order), and start play
# buffers are definied in buf0_* and buf1_*, and can be anywhere accessible through Wishbone
# the type of data is defined in buf_dec, and must be homogeneous accross buffers
# an interrupt is generated each time a buffer is emptied
# the theory of operations is
# (a) if the audio is small enough to fit in one buffer, then the SW starts the buffer with autostop (short sound or without (for continuous looping)
# (b) if the audio is larger, then some goes in the first buffer, next part in the second buffer, and the SW starts the first buffer without autostop.
# Once the first buffer has been played, the HW will emit the interrupt and play the second buffer. The SW can then fill out the first buffer with new data. When the second buffer is played, a new interrupt is emmitted and the first buffer starts playing. This enables uninterrupted play. Once the SW determines the next buffer is the last, it just turns on autostop.
self.ctrl = CSRStorage(write_from_dev=True, fields = [CSRField("buffer_num", 1, description = "Buffer to use"),
CSRField("reserved0", 7, description = "Reserved"),
CSRField("play", 1, description = "Play (start with specified buffer and keel looping over all buffers)"),
CSRField("reserved1", 7, description = "Reserved"),
CSRField("autostop", 1, description = "Autostop when currently playing buffer is empty"),
CSRField("reserved2", 7, description = "Reserved"),
CSRField("reserved3", 8, description = "Reserved"),
])
self.bufstatus = CSRStatus(fields = [CSRField("buffer_num", 1, description = "Buffer in use"),
CSRField("reserved0", 7, description = "Reserved"),
CSRField("play", 1, description = "Playing"),
CSRField("reserved1", 7, description = "Reserved"),
CSRField("buffer_left", 16, description = "samples left"),
])
self.buf0_addr = CSRStorage(32, description = "Wishbone base address for audio buffer 0")
self.buf0_size = CSRStorage(fields = [CSRField("size", 16, description = "Number of samples in audio buffer 0"),
CSRField("reserved", 16, description = "Reserved"),
])
self.buf1_addr = CSRStorage(32, description = "Wishbone base address for audio buffer 1")
self.buf1_size = CSRStorage(fields = [CSRField("size", 16, description = "Number of samples in audio buffer 1"),
CSRField("reserved", 16, description = "Reserved"),
])
self.buf_desc = CSRStorage(fields = [CSRField("width", 1, description = "Sample width (0 = 16, 1 = 8)"),
CSRField("reserved0", 5, description = "Reserved"),
CSRField("mono", 1, description = "Mono (0 = Stereo, 1 = Mono)"),
CSRField("signedness", 1, description = "Signedness (0 = signed, 1 = unsigned)"),
CSRField("freq", 1, description = "Sample frequency (0 = 44.100 KHz, 1 = 22.050 KHz)"),
CSRField("reserved1", 7, description = "Reserved"),
CSRField("reserved2", 16, description = "Reserved"),
])
# handle IRQ
self.sync += If(self.irqctrl.fields.irq_clear, ## auto-reset irq_clear
self.irqctrl.we.eq(1),
self.irqctrl.dat_w.eq(self.irqctrl.storage & 0xFFFFFFFD)).Else(
self.irqctrl.we.eq(0),
)
temp_irq = Signal() # long internal irq (can be masked)
set_irq = Signal() # short transient irq (1 cycle ping from FSM)
self.sync += temp_irq.eq(set_irq | # transient irq signal
(temp_irq & ~self.irqctrl.fields.irq_clear)) # keep irq until cleared
self.comb += self.irq.eq(~(temp_irq & self.irqctrl.fields.irq_enable)) # only notify irq to the host if not disabled # self.irq active low
self.comb += self.irqstatus.fields.irq.eq(~self.irq) # self.irq active low
# basic 44.1 KHz clock based on system clock
# FIXME: what if this should be derived from the pixel clock ???
audio_max = int((soc.sys_clk_freq / audio_clk_freq) + 0.5)
audio_max_bits = log2_int(audio_max, False)
audio_counter = Signal(audio_max_bits)
self.sync += [
If((audio_counter == (audio_max - 1)),
hdmiext_audio_clk.eq(1),
audio_counter.eq(0),
).Else(
hdmiext_audio_clk.eq(0),
audio_counter.eq(audio_counter + 1),
),
]
next_adr = Signal(30) # wishbone!
sample_cnt = Signal(16)
cur_buf = Signal(1)
next_buf = Signal(1)
self.comb += [
self.bufstatus.fields.buffer_num.eq(cur_buf),
self.bufstatus.fields.buffer_left.eq(sample_cnt),
]
need_data = Signal()
# self-reset need_data, whenever we consume a sample in hdmi (audio_clk) we request more
self.sync += [
If(~need_data & hdmiext_audio_clk,
need_data.eq(1),
)
]
# autostop
self.sync += [
If((sample_cnt == 0) & self.ctrl.fields.autostop & hdmiext_audio_clk, # we just consumed the last sample in autostop mode
self.ctrl.we.eq(1),
self.ctrl.dat_w.eq(self.ctrl.storage & 0xFFFFFEFF), # reset 'play'
).Else(
self.ctrl.we.eq(0),
),
]
self.submodules.play_fsm = play_fsm = FSM(reset_state="Reset")
play_fsm.act("Reset",
NextState("Silent")
)
play_fsm.act("Silent",
NextValue(hdmiext_audio_word_0, 0),
NextValue(hdmiext_audio_word_1, 0),
If(self.ctrl.fields.play,
NextValue(cur_buf, self.ctrl.fields.buffer_num),
NextValue(next_buf, self.ctrl.fields.buffer_num + 1),
NextValue(need_data, 1),
Case(self.ctrl.fields.buffer_num, {
0x0: [
NextValue(next_adr, self.buf0_addr.storage[2:32]),
NextValue(sample_cnt, self.buf0_size.fields.size),
],
0x1: [
NextValue(next_adr, self.buf1_addr.storage[2:32]),
NextValue(sample_cnt, self.buf1_size.fields.size),
],
}),
NextState("Play"),
)
)
play_fsm.act("Play",
If(~self.ctrl.fields.play,
NextValue(hdmiext_audio_word_0, 0),
NextValue(hdmiext_audio_word_1, 0),
NextState("Silent"),
).Elif((sample_cnt == 0), # ran out of sample in this buffer
set_irq.eq(self.irqctrl.fields.irq_enable), # transient notify to the host we emptied a buffer # only raised when enabled
If(~self.ctrl.fields.autostop,
NextValue(cur_buf, next_buf),
NextValue(next_buf, next_buf + 1),
Case(next_buf, {
0x0: [
NextValue(next_adr, self.buf0_addr.storage[2:32]),
NextValue(sample_cnt, self.buf0_size.fields.size),
],
0x1: [
NextValue(next_adr, self.buf1_addr.storage[2:32]),
NextValue(sample_cnt, self.buf1_size.fields.size),
],
}),
)
# stay in "Play"
)
)
#led0 = soc.platform.request("user_led", 0)
#led1 = soc.platform.request("user_led", 1)
self.comb += [
self.bufstatus.fields.play.eq(play_fsm.ongoing("Play")),
#led0.eq(self.buf_desc.fields.signedness),
#led1.eq(self.buf_desc.fields.mono),
]
# intermediate storage when playing less than stereo 16-bits
nextSample = Signal(24)
# auto-reload
self.submodules.req_fsm = req_fsm = FSM(reset_state="Reset")
req_fsm.act("Reset",
NextState("Idle")
)
req_fsm.act("Idle",
If(need_data & self.ctrl.fields.play & play_fsm.ongoing("Play") & (sample_cnt != 0),
NextValue(busmaster.cyc, 1),
NextValue(busmaster.stb, 1),
NextValue(busmaster.sel, 2**len(busmaster.sel)-1),
NextValue(busmaster.we, 0),
NextValue(busmaster.adr, next_adr),
If(~self.buf_desc.fields.width,
NextState("WaitForAck16")
).Else(
NextState("WaitForAck8")
),
),
)
req_fsm.act("WaitForAck16",
If(busmaster.ack,
NextValue(busmaster.cyc, 0),
NextValue(busmaster.stb, 0),
If(~self.buf_desc.fields.mono,
NextValue(hdmiext_audio_word_0, Cat(busmaster.dat_r[ 8:16], busmaster.dat_r[ 0: 8])),
NextState("Idle"),
).Else(
NextValue(hdmiext_audio_word_0, Cat(busmaster.dat_r[24:32], busmaster.dat_r[16:24])),
NextValue(nextSample[ 0:24], busmaster.dat_r[ 0:24]), # only 16 are needed, what's most efficient ?
NextState("SecondSample16"),
),
NextValue(hdmiext_audio_word_1, Cat(busmaster.dat_r[24:32], busmaster.dat_r[16:24])),
NextValue(next_adr, next_adr + 1),
NextValue(sample_cnt, sample_cnt - 1),
NextValue(need_data, 0), # this will self-reset to 1 above after the next audio_clk cycle, i.e. when hdmi consume the data
)
)
req_fsm.act("SecondSample16",
If(~self.ctrl.fields.play | ~play_fsm.ongoing("Play") | (sample_cnt == 0),
NextState("Idle"),
).Elif(need_data,
NextValue(hdmiext_audio_word_0, Cat(nextSample[ 8:16], nextSample[ 0: 8])),
NextValue(hdmiext_audio_word_1, Cat(nextSample[ 8:16], nextSample[ 0: 8])),
NextValue(sample_cnt, sample_cnt - 1),
NextValue(need_data, 0), # this will self-reset to 1 above after the next audio_clk cycle, i.e. when hdmi consume the data
NextState("Idle"),
)
)
offset8bits = 7 # by how many bits to shift 8-bits audio to create 16-bits ; 0 to 8
req_fsm.act("WaitForAck8",
If(busmaster.ack,
NextValue(busmaster.cyc, 0),
NextValue(busmaster.stb, 0),
If(~self.buf_desc.fields.mono,
NextValue(hdmiext_audio_word_0, Cat(Replicate(0, offset8bits),
busmaster.dat_r[16:24],
Replicate(~self.buf_desc.fields.signedness & busmaster.dat_r[23], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
).Else(
NextValue(hdmiext_audio_word_0, Cat(Replicate(0, offset8bits),
busmaster.dat_r[24:32],
Replicate(~self.buf_desc.fields.signedness & busmaster.dat_r[31], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
),
NextValue(hdmiext_audio_word_1, Cat(Replicate(0, offset8bits),
busmaster.dat_r[24:32],
Replicate(~self.buf_desc.fields.signedness & busmaster.dat_r[31], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
NextValue(nextSample[ 0:24], busmaster.dat_r[ 0:24]), # only 16 are needed if ~mono, what's more efficient ?
NextValue(next_adr, next_adr + 1),
NextValue(sample_cnt, sample_cnt - 1),
NextValue(need_data, 0), # this will self-reset to 1 above after the next audio_clk cycle, i.e. when hdmi consume the data
NextState("SecondSample8"),
)
)
req_fsm.act("SecondSample8",
If(~self.ctrl.fields.play | ~play_fsm.ongoing("Play") | (sample_cnt == 0),
NextState("Idle"),
).Elif(need_data,
If(~self.buf_desc.fields.mono,
NextValue(hdmiext_audio_word_0, Cat(Replicate(0, offset8bits),
nextSample[ 0: 8],
Replicate(~self.buf_desc.fields.signedness & nextSample[ 7], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
NextValue(hdmiext_audio_word_1, Cat(Replicate(0, offset8bits),
nextSample[ 8:16],
Replicate(~self.buf_desc.fields.signedness & nextSample[15], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
NextState("Idle"),
).Else(
NextValue(hdmiext_audio_word_0, Cat(Replicate(0, offset8bits),
nextSample[16:24],
Replicate(~self.buf_desc.fields.signedness & nextSample[ 7], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
NextValue(hdmiext_audio_word_1, Cat(Replicate(0, offset8bits),
nextSample[16:24],
Replicate(~self.buf_desc.fields.signedness & nextSample[15], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
NextState("ThirdSample8"),
),
NextValue(sample_cnt, sample_cnt - 1),
NextValue(need_data, 0), # this will self-reset to 1 above after the next audio_clk cycle, i.e. when hdmi consume the data
)
)
req_fsm.act("ThirdSample8",
If(~self.ctrl.fields.play | ~play_fsm.ongoing("Play") | (sample_cnt == 0),
NextState("Idle"),
).Elif(need_data,
# mono only here
NextValue(hdmiext_audio_word_0, Cat(Replicate(0, offset8bits),
nextSample[ 8:16],
Replicate(~self.buf_desc.fields.signedness & nextSample[ 7], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
NextValue(hdmiext_audio_word_1, Cat(Replicate(0, offset8bits),
nextSample[ 8:16],
Replicate(~self.buf_desc.fields.signedness & nextSample[15], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
NextState("FourthSample8"),
NextValue(sample_cnt, sample_cnt - 1),
NextValue(need_data, 0), # this will self-reset to 1 above after the next audio_clk cycle, i.e. when hdmi consume the data
)
)
req_fsm.act("FourthSample8",
If(~self.ctrl.fields.play | ~play_fsm.ongoing("Play") | (sample_cnt == 0),
NextState("Idle"),
).Elif(need_data,
# mono only here
NextValue(hdmiext_audio_word_0, Cat(Replicate(0, offset8bits),
nextSample[ 0: 8],
Replicate(~self.buf_desc.fields.signedness & nextSample[ 7], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
NextValue(hdmiext_audio_word_1, Cat(Replicate(0, offset8bits),
nextSample[ 0: 8],
Replicate(~self.buf_desc.fields.signedness & nextSample[15], 8-offset8bits)) -
Cat(Replicate(0, 7+offset8bits), self.buf_desc.fields.signedness, Replicate(0, 8-offset8bits))), # fixme: endianess
NextState("Idle"),
NextValue(sample_cnt, sample_cnt - 1),
NextValue(need_data, 0), # this will self-reset to 1 above after the next audio_clk cycle, i.e. when hdmi consume the data
)
)