Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 77d98ea283b5
Choose a base ref
...
head repository: GlasgowEmbedded/glasgow
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 50ce60b666fa
Choose a head ref
  • 2 commits
  • 1 file changed
  • 1 contributor

Commits on Mar 12, 2019

  1. applet.audio.yamaha_opl: replace ~CS pin with A1.

    This applet does not really need ~CS, since the chip can be safely
    always selected. On the other hand, having A0 and A1 allows us to
    interface almost every OPx device, with the chief exception of OPL4.
    whitequark committed Mar 12, 2019
    Copy the full SHA
    d6c9e03 View commit details
  2. applet.audio.yamaha_opl: separate CPU and DAC buses. NFC.

    This helps to make clear the distinction between these interfaces,
    which are essentially unrelated and not synchronized. Also, some
    chips, like OPL3, have dual DAC outputs. (Really quad, but dual
    electrically.)
    whitequark committed Mar 12, 2019
    Copy the full SHA
    50ce60b View commit details
Showing with 66 additions and 40 deletions.
  1. +66 −40 software/glasgow/applet/audio/yamaha_opl/__init__.py
106 changes: 66 additions & 40 deletions software/glasgow/applet/audio/yamaha_opl/__init__.py
Original file line number Diff line number Diff line change
@@ -18,6 +18,10 @@
# which differs from series to series and from address to data.
# - OPLL/OPL(?)/OPL2(?): address 12 cycles, data 84 cycles. (only documented for OPLL)
# - OPL3: address 32 cycles, data 32 cycles. (documented)
# * The timing diagrams are sometimes absurd. YMF278 datasheet figure 1-5 implies that the data
# bus is stable for precisely tRDS (which is, incidentally, not defined anywhere) before
# the ~RD rising edge, which would imply time travel. YM3812 datasheet figure A-3 is actually
# physically possible.
#
# Bitstream format
# ----------------
@@ -51,6 +55,14 @@
# enable all available advanced features, zero out the registers, and then disable them back for
# compatibility with OPL clients that expect the compatibility mode to be on.
#
# Bus cycles
# ----------
#
# The CPU bus interface is asynchronous to master or DAC clock, but is heavily registered (see
# the next section). Writes are referenced to the ~WR rising edge, and require chip-specific
# wait states. Reads are referenced to ~RD falling edge, and always read exactly one register,
# although there might be undocumented registers elsewhere.
#
# Register latency
# ----------------
#
@@ -62,6 +74,10 @@
# On YM3812, the latch latency is 12 cycles and the sample takes 72 clocks, therefore each
# address/data write cycle takes 12+12+72 clocks.
#
# On some chips (e.g OPL4) the register that can be read has a busy bit, which seems to be always
# the LSB. On many others (e.g. OPL3) there is no busy bit. Whether the busy bit is available
# on any given silicon seems pretty arbitrary.
#
# VGM timeline
# ------------
#
@@ -98,13 +114,11 @@
from ... import *


class YamahaOPLBus(Module):
class YamahaCPUBus(Module):
def __init__(self, pads, master_cyc):
self.stb_m = Signal()
self.stb_sy = Signal()
self.stb_sh = Signal()
self.stb_m = Signal()

self.a = Signal(1)
self.a = Signal(2)

self.oe = Signal(reset=1)
self.di = Signal(8)
@@ -114,43 +128,52 @@ def __init__(self, pads, master_cyc):
self.rd = Signal()
self.wr = Signal()

self.sh = Signal()
self.mo = Signal()

###

self.submodules.clkgen = ClockGen(master_cyc)
self.comb += self.stb_m.eq(self.clkgen.stb_r)

clk_sy_s = Signal()
clk_sy_r = Signal()
self.sync += [
clk_sy_r.eq(clk_sy_s),
self.stb_sy.eq(~clk_sy_r & clk_sy_s)
]

sh_r = Signal()
self.sync += [
sh_r.eq(self.sh),
self.stb_sh.eq(sh_r & ~self.sh)
]

self.comb += [
pads.clk_m_t.oe.eq(1),
pads.clk_m_t.o.eq(self.clkgen.clk),
pads.a_t.oe.eq(1),
pads.a_t.o.eq(self.a),
pads.d_t.oe.eq(self.oe),
pads.d_t.o.eq(Cat((self.do))),
self.di.eq(Cat((pads.d_t.i))),
pads.a_t.oe.eq(1),
pads.a_t.o.eq(self.a),
pads.cs_t.oe.eq(1),
pads.cs_t.o.eq(~self.cs),
# handle (self.rd & (self.wr | self.oe)) == 1 safely
pads.rd_t.oe.eq(1),
pads.rd_t.o.eq(~(self.rd & ~self.wr & ~self.oe)),
pads.wr_t.oe.eq(1),
pads.wr_t.o.eq(~(self.wr & ~self.rd)),
]
if hasattr(pads, "cs_t"):
self.comb += [
pads.cs_t.oe.eq(1),
pads.cs_t.o.eq(~self.cs),
]


class YamahaDACBus(Module):
def __init__(self, pads):
self.stb_sy = Signal()
self.stb_sh = Signal()

self.sh = Signal()
self.mo = Signal()

clk_sy_s = Signal()
clk_sy_r = Signal()
self.sync += [
clk_sy_r.eq(clk_sy_s),
self.stb_sy.eq(~clk_sy_r & clk_sy_s)
]

sh_r = Signal()
self.sync += [
sh_r.eq(self.sh),
self.stb_sh.eq(sh_r & ~self.sh)
]

self.specials += [
MultiReg(pads.clk_sy_t.i, clk_sy_s),
@@ -170,7 +193,8 @@ class YamahaOPLSubtarget(Module):
def __init__(self, pads, in_fifo, out_fifo,
master_cyc, read_pulse_cyc, write_pulse_cyc,
latch_clocks, sample_clocks):
self.submodules.bus = bus = YamahaOPLBus(pads, master_cyc)
self.submodules.cpu_bus = cpu_bus = YamahaCPUBus(pads, master_cyc)
self.submodules.dac_bus = dac_bus = YamahaDACBus(pads)

# Control

@@ -183,15 +207,15 @@ def __init__(self, pads, in_fifo, out_fifo,
# to explicitly satisfy setup/hold timings.
self.submodules.control_fsm = FSM()
self.control_fsm.act("IDLE",
NextValue(bus.oe, 1),
NextValue(cpu_bus.oe, 1),
If(out_fifo.readable,
out_fifo.re.eq(1),
Case(out_fifo.dout & OP_MASK, {
OP_ENABLE: [
NextValue(enabled, out_fifo.dout & ~OP_MASK),
],
OP_WRITE: [
NextValue(bus.a, out_fifo.dout & ~OP_MASK),
NextValue(cpu_bus.a, out_fifo.dout & ~OP_MASK),
NextState("WRITE-DATA")
],
# OP_READ: NextState("READ"),
@@ -204,18 +228,18 @@ def __init__(self, pads, in_fifo, out_fifo,
self.control_fsm.act("WRITE-DATA",
If(out_fifo.readable,
out_fifo.re.eq(1),
NextValue(bus.do, out_fifo.dout),
NextValue(bus.cs, 1),
NextValue(bus.wr, 1),
NextValue(cpu_bus.do, out_fifo.dout),
NextValue(cpu_bus.cs, 1),
NextValue(cpu_bus.wr, 1),
NextValue(pulse_timer, write_pulse_cyc - 1),
NextState("WRITE-PULSE")
)
)
self.control_fsm.act("WRITE-PULSE",
If(pulse_timer == 0,
NextValue(bus.cs, 0),
NextValue(bus.wr, 0),
If(bus.a == 0b0,
NextValue(cpu_bus.cs, 0),
NextValue(cpu_bus.wr, 0),
If(cpu_bus.a == 0b0,
NextValue(wait_timer, latch_clocks - 1)
).Else(
NextValue(wait_timer, latch_clocks + sample_clocks - 1)
@@ -243,7 +267,7 @@ def __init__(self, pads, in_fifo, out_fifo,
If(wait_timer == 0,
NextState("IDLE")
).Else(
If(bus.stb_m,
If(cpu_bus.stb_m,
NextValue(wait_timer, wait_timer - 1)
)
)
@@ -265,13 +289,13 @@ def __init__(self, pads, in_fifo, out_fifo,

data_r = Signal(16)
data_l = Signal(16)
self.sync += If(bus.stb_sy, data_r.eq(Cat(data_r[1:], bus.mo)))
self.sync += If(dac_bus.stb_sy, data_r.eq(Cat(data_r[1:], dac_bus.mo)))
self.comb += xfer_i.raw_bits().eq(data_l)

self.submodules.data_fsm = FSM()
self.data_fsm.act("WAIT-SH",
NextValue(in_fifo.flush, ~enabled),
If(bus.stb_sh & enabled,
If(dac_bus.stb_sh & enabled,
NextState("SAMPLE")
)
)
@@ -649,6 +673,9 @@ class AudioYamahaOPLApplet(GlasgowApplet, name="audio-yamaha-opl"):
Send commands and record digital output from Yamaha OPL* series FM synthesizers. Currently,
only OPL2 is supported, but this applet is easy to extend to other similar chips.
The ~CS input should always be grounded, since there is only one chip on the bus in the first
place.
The digital output is losslessly converted to 16-bit unsigned PCM samples. (The Yamaha DACs
only have 16 bit of dynamic range, and there is a direct mapping between the on-wire floating
point sample format and ordinary 16-bit PCM.)
@@ -664,7 +691,7 @@ class AudioYamahaOPLApplet(GlasgowApplet, name="audio-yamaha-opl"):
"""

__pin_sets = ("d", "a")
__pins = ("clk_m", "cs", "rd", "wr",
__pins = ("clk_m", "rd", "wr",
"clk_sy", "sh", "mo")

@classmethod
@@ -673,8 +700,7 @@ def add_build_arguments(cls, parser, access):

access.add_pin_set_argument(parser, "d", width=8, default=True)
access.add_pin_argument(parser, "clk_m", default=True)
access.add_pin_set_argument(parser, "a", width=1, default=True)
access.add_pin_argument(parser, "cs", default=True)
access.add_pin_set_argument(parser, "a", width=2, default=True)
access.add_pin_argument(parser, "rd", default=True)
access.add_pin_argument(parser, "wr", default=True)
access.add_pin_argument(parser, "clk_sy", default=True)