mirror of
https://github.com/rdolbeau/VintageBusFPGA_Common.git
synced 2026-02-16 12:02:50 +00:00
first draft at audio-in-hdmi
This commit is contained in:
188
goblin_alt_audio.py
Normal file
188
goblin_alt_audio.py
Normal file
@@ -0,0 +1,188 @@
|
||||
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")
|
||||
])
|
||||
|
||||
|
||||
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"),
|
||||
])
|
||||
|
||||
# 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
|
||||
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)
|
||||
self.comb += [
|
||||
self.bufstatus.fields.play.eq(play_fsm.ongoing("Play")),
|
||||
led0.eq(play_fsm.ongoing("Play")),
|
||||
]
|
||||
|
||||
# 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),
|
||||
NextState("WaitForAck")
|
||||
)
|
||||
)
|
||||
req_fsm.act("WaitForAck",
|
||||
If(busmaster.ack,
|
||||
NextValue(busmaster.cyc, 0),
|
||||
NextValue(busmaster.stb, 0),
|
||||
NextValue(hdmiext_audio_word_0, Cat(busmaster.dat_r[ 8:16], busmaster.dat_r[ 0: 8])), # fixme: endianess
|
||||
NextValue(hdmiext_audio_word_1, Cat(busmaster.dat_r[24:32], busmaster.dat_r[16:24])), # fixme: endianess
|
||||
NextValue(next_adr, next_adr + 1), # fixme
|
||||
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"),
|
||||
)
|
||||
)
|
||||
@@ -15,6 +15,8 @@ from litex.soc.cores.video import *
|
||||
from VintageBusFPGA_Common.fb_video import *
|
||||
from VintageBusFPGA_Common.fb_dma import LiteDRAMFBDMAReader
|
||||
|
||||
from VintageBusFPGA_Common.goblin_alt_audio import GoblinAudio
|
||||
|
||||
from math import ceil
|
||||
|
||||
cmap_layout = [
|
||||
@@ -674,6 +676,13 @@ class GoblinAlt(Module, AutoCSR):
|
||||
hdmiext_rgb = Signal(24) # three colors at 8 bits each, fixme
|
||||
hdmiext_audio_word_0 = Signal(16) # channel 0 of stereo audio, fixme
|
||||
hdmiext_audio_word_1 = Signal(16) # channel 1 of stereo audio, fixme
|
||||
|
||||
self.submodules.goblin_audio = GoblinAudio(soc, 44.1e3)
|
||||
self.comb += [
|
||||
hdmiext_audio_clk.eq(self.goblin_audio.hdmiext_audio_clk),
|
||||
hdmiext_audio_word_0.eq(self.goblin_audio.hdmiext_audio_word_0),
|
||||
hdmiext_audio_word_1.eq(self.goblin_audio.hdmiext_audio_word_1),
|
||||
]
|
||||
|
||||
## OUTPUTS from the HDMI module
|
||||
hdmiext_tmds = Signal(3) # high-speed colors to the TMDS bits
|
||||
@@ -693,8 +702,8 @@ class GoblinAlt(Module, AutoCSR):
|
||||
hdmiext_rgb[ 0: 8].eq(vfb.source.b),
|
||||
hdmiext_rgb[ 8:16].eq(vfb.source.g),
|
||||
hdmiext_rgb[16:24].eq(vfb.source.r),
|
||||
hdmiext_audio_word_0.eq(0), # fixme: implement
|
||||
hdmiext_audio_word_1.eq(0), # fixme: implement
|
||||
## hdmiext_audio_word_0.eq(0), # fixme: implement
|
||||
## hdmiext_audio_word_1.eq(0), # fixme: implement
|
||||
# use VTG enable to generate reset, to we have the same relationship to the DMA
|
||||
hdmiext_reset.eq(~vtg_enable),
|
||||
]
|
||||
@@ -757,19 +766,19 @@ class GoblinAlt(Module, AutoCSR):
|
||||
vfb.inframe.eq(hinframe & vinframe),
|
||||
]
|
||||
|
||||
# basic 44.1 KHz clock based on the HDMI clock
|
||||
audio_max = int((vtg.video_timings["pix_clk"] / 44100.0) + 0.5)
|
||||
audio_max_bits = log2_int(audio_max, False)
|
||||
audio_counter = Signal(audio_max_bits)
|
||||
hdmi_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),
|
||||
),
|
||||
]
|
||||
### basic 44.1 KHz clock based on the HDMI clock
|
||||
##audio_max = int((vtg.video_timings["pix_clk"] / 44100.0) + 0.5)
|
||||
##audio_max_bits = log2_int(audio_max, False)
|
||||
##audio_counter = Signal(audio_max_bits)
|
||||
##hdmi_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),
|
||||
## ),
|
||||
##]
|
||||
|
||||
self.specials += Instance("hdmi",
|
||||
p_VIDEO_ID_CODE = 16, # CEA-861-D, "4.15 1920x1080p @ 59.94/60Hz (Format 16)", FIXME
|
||||
|
||||
Reference in New Issue
Block a user