from migen import *
from migen.build.generic_platform import *
from migen.build.platforms import papilio_pro
from migen.genlib.fifo import SyncFIFO, SyncFIFOBuffered
from migen.genlib.cdc import MultiReg
from migen.genlib.io import DDRInput
import uart
# Number of lines to buffer. Since the UART can't output the frame data as fast
# as the Gameboy is generating it make this as big as will fit
BUFFER_LINES = 7 * 144
LINE_PX = 160
LINE_BITS = LINE_PX * 2
LINE_BYTES = LINE_BITS // 8
class Input(Module):
def __init__(self, inp, out, ddr):
if ddr:
# DDR input, ignore pulses of <= 0.5 clock cycles to filter noise
def one_bit(inp_bit, out_bit):
ddr_inp = [Signal(), Signal()]
self.specials += DDRInput(inp_bit, *ddr_inp)
self.sync += If(ddr_inp[0] == ddr_inp[1], out_bit.eq(ddr_inp[0]))
else:
# Resync input through a MultiReg
# No implementation of DDRInput in sim so use this
def one_bit(inp_bit, out_bit):
self.specials += MultiReg(inp_bit, out_bit)
all_bits = []
for bidx in range(inp.nbits):
all_bits.append(Signal())
one_bit(inp[bidx], all_bits[-1])
self.comb += out.eq(Cat(*all_bits))
class PixelStream(Module):
def __init__(self, lcd, ddr_inp):
# data, eol gated by valid
# eol signals last pixel in a line
self.data = Signal(2, name='pixel_data')
self.eol = Signal()
self.valid = Signal(name='pixel_valid')
self.frame_start = Signal()
###
lcd_clk = Signal()
lcd_cpl = Signal()
lcd_hsync = Signal()
lcd_vsync = Signal()
lcd_pixel = Signal(2)
self.submodules += [
Input(lcd.clk, lcd_clk, ddr_inp),
Input(lcd.cpl, lcd_cpl, ddr_inp),
Input(lcd.hsync, lcd_hsync, ddr_inp),
Input(lcd.vsync, lcd_vsync, ddr_inp),
Input(lcd.pixel, lcd_pixel, ddr_inp),
]
old_clk = Signal()
old_cpl = Signal()
old_vsync = Signal()
old_pixel = Signal(2)
self.sync += [
# Keep old values to detect edges
old_clk.eq(lcd_clk),
old_cpl.eq(lcd_cpl),
old_vsync.eq(lcd_vsync),
# Actually sample the pixel data from the previous cycle
old_pixel.eq(lcd_pixel),
]
# Rising edge of vsync is frame start
self.sync += [
self.frame_start.eq(lcd_vsync & ~old_vsync)
]
any_pixels = Signal()
self.sync += [
self.valid.eq(0),
self.eol.eq(0),
# read pixel on rising edge of clk or cpl
# final pixel in each line is the one signalled by cpl
If((lcd_clk & ~old_clk) | (lcd_cpl & ~old_cpl),
self.data.eq(old_pixel),
self.eol.eq(lcd_cpl),
# Always generate a pixel on lcd_clk, but only for cpl if it was
# preceeded by pixels generated from lcd_clk
# cpl continues to pulse in the vblank but no pixels are
# generated (lcd_clk=0)
self.valid.eq(any_pixels | lcd_clk),
any_pixels.eq(lcd_clk),
),
]
class LCDLineInput(Module):
def __init__(self, lcd, capture, ddr_inp):
self.valid = Signal(name='line_valid')
# rightmost pixel in MSB, leftmost pixel in LSB
self.line = Signal(LINE_BITS, name='line_shiftreg')
self.frame_done = Signal()
self.capturing = Signal(reset=0)
self.submodules.pixels = PixelStream(lcd, ddr_inp)
###
self.sync += [
# Only start/stop capturing at the start of a frame
If(self.pixels.frame_start, self.capturing.eq(capture)),
self.frame_done.eq(self.pixels.frame_start & self.capturing)
]
# The logic here isn't *exactly* what the Gameboy hardware implements -
# eol doesn't actually shift the shift register, but this is simple and
# works as the PixelStream supresses eols from empty (vsync) lines.
self.sync += [
self.valid.eq(0),
If(self.capturing & self.pixels.valid,
self.line.eq(Cat(self.line[2:], self.pixels.data)),
self.valid.eq(self.pixels.eol)
)
]
class PixelsToUART(Module):
def __init__(self, lcd, serial, clk_period_ns, baud_rate, ddr_inp):
self.capture = Signal(reset=0)
self.submodules.lcd = LCDLineInput(lcd, self.capture, ddr_inp)
self.submodules.fifo = SyncFIFOBuffered(LINE_BITS, BUFFER_LINES)
self.submodules.uart = uart.RS232PHY(serial, clk_period_ns, baud_rate)
self.frame_done = self.lcd.frame_done
self.error = Signal()
###
count = Signal(max=LINE_BYTES, reset=0)
line = Signal(LINE_BITS - 8)
self.sync += [
# Start capture on frame start after UART rx
If(self.uart.rx.valid, self.capture.eq(1)),
# Stop on error (fifo overflow), and clear the error so it is
# possible to start caturing again
If(self.error,
self.capture.eq(0),
self.error.eq(0)),
]
# Lines write straight into FIFO
self.comb += [
self.fifo.din.eq(self.lcd.line),
self.fifo.we.eq(self.lcd.valid)
]
# Set error flag when FIFO overflows
self.sync += [
If(self.lcd.valid & ~self.fifo.writable, self.error.eq(1))
]
self.sync += [
self.fifo.re.eq(0),
self.uart.tx.valid.eq(0),
# Not sending anything or final byte finished sending
If((count == 0) | ((count == 1) & self.uart.tx.ready),
# More to send, get right on it
If(self.fifo.readable,
# Save all except first byte to line
self.fifo.re.eq(1),
line.eq(self.fifo.dout[8:]),
count.eq(LINE_BYTES),
# First byte of line straight to UART
self.uart.tx.valid.eq(1),
self.uart.tx.data.eq(self.fifo.dout[:8])
).Else(
# Nothing to send, reset into idle state
count.eq(0)
)
).Elif(self.uart.tx.ready,
# Next byte into UART
self.uart.tx.valid.eq(1),
self.uart.tx.data.eq(line[:8]),
count.eq(count - 1),
# Shift it off
line.eq(Cat(line[8:], 0))
)
]
if __name__ == '__main__':
import sys
if sys.argv[1] == 'build':
plat = papilio_pro.Platform()
plat.add_extension([
('gb_lcd', 0,
Subsignal('vsync', Pins('B:5')),
Subsignal('hsync', Pins('B:4')),
Subsignal('cpl', Pins('B:6')),
Subsignal('clk', Pins('B:2')),
Subsignal('pixel', Pins('B:0', 'B:1')))])
led = plat.request('user_led')
lcd = plat.request('gb_lcd')
m = Module()
m.submodules.cap = PixelsToUART(lcd, plat.request('serial'), plat.default_clk_period, baud_rate=1000000, ddr_inp=True)
m.comb += led.eq(m.cap.lcd.capturing)
plat.build(m)
elif sys.argv[1] == 'test':
import vcd_parse
import vcd2pixels
def gen(dut):
with open('6coins-title.vcd') as f:
for t, signals in vcd_parse.parse(f):
yield dut.pads.vsync.eq(signals['vsync'])
yield dut.pads.hsync.eq(signals['hsync'])
yield dut.pads.clk.eq(signals['clk'])
yield dut.pads.cpl.eq(signals['cpl'])
yield dut.pads.pixel.eq(signals['data'])
yield
def tx(dut):
# Send a byte
yield
yield dut.loopback.tx.data.eq(88)
yield dut.loopback.tx.valid.eq(1)
yield
yield dut.loopback.tx.valid.eq(0)
# Wait for capture to start (byte received)
while (yield dut.pixel_uart.capture) == 0:
yield
yield from gen(dut)
def rx(dut):
pixel_bytes = []
with open('6coins-title.vcd') as f:
packed = 0
pack_count = 0
for pixel in vcd2pixels.pixels(f):
# first (left-most) pixel in LSBs
packed |= pixel << (2 * pack_count)
pack_count += 1
if pack_count == 4:
pixel_bytes.append(packed)
pack_count = 0
packed = 0
x = 0
y = 0
for b in pixel_bytes:
while (yield dut.loopback.rx.valid) == 0:
yield
v = (yield dut.loopback.rx.data)
print('({}, {}) {:02x}'.format(x, y, v))
assert v == b, 'rx: {:02x}, expected: {:02x}'.format(v, b)
x += 1
if x == LINE_BYTES:
x = 0
y += 1
while (yield dut.loopback.rx.valid) == 1:
yield
class DUT(Module):
def __init__(self):
self.pads = Record([('vsync', 1), ('hsync', 1), ('cpl', 1), ('clk', 1), ('pixel', 2)])
self.serial_pads = Record([('tx', 1), ('rx', 1)])
self.loop_serial_pads = Record([('tx', 1), ('rx', 1)])
self.submodules.pixel_uart = PixelsToUART(self.pads, self.serial_pads, 31.25, baud_rate=1000000, ddr_inp=False)
self.submodules.loopback = uart.RS232PHY(self.loop_serial_pads, 31.25, baud_rate=1000000)
self.comb += [
self.serial_pads.rx.eq(self.loop_serial_pads.tx),
self.loop_serial_pads.rx.eq(self.serial_pads.tx)
]
dut = DUT()
run_simulation(dut, [tx(dut), rx(dut)], vcd_name='out.vcd')