def elaborate(self, platform): # Constants pixel_f = self.timing.pixel_freq hsync_front_porch = self.timing.h_front_porch hsync_pulse_width = self.timing.h_sync_pulse hsync_back_porch = self.timing.h_back_porch vsync_front_porch = self.timing.v_front_porch vsync_pulse_width = self.timing.v_sync_pulse vsync_back_porch = self.timing.v_back_porch clk25 = platform.request("clk25") m = Module() # Clock generator. m.domains.sync = cd_sync = ClockDomain("sync") m.domains.pixel = cd_pixel = ClockDomain("pixel") m.domains.shift = cd_shift = ClockDomain("shift") m.submodules.ecp5pll = pll = ECP5PLL() pll.register_clkin(clk25, platform.default_clk_frequency) pll.create_clkout(cd_sync, platform.default_clk_frequency) pll.create_clkout(cd_pixel, pixel_f) pll.create_clkout(cd_shift, pixel_f * 5.0 * (1.0 if self.ddr else 2.0)) platform.add_clock_constraint(cd_sync.clk, platform.default_clk_frequency) platform.add_clock_constraint(cd_pixel.clk, pixel_f) platform.add_clock_constraint( cd_shift.clk, pixel_f * 5.0 * (1.0 if self.ddr else 2.0)) led = [platform.request("led", i) for i in range(8)] leds = Cat([i.o for i in led]) ov7670 = platform.request("ov7670") btn1 = platform.request("button_fire", 0) btn2 = platform.request("button_fire", 1) up = platform.request("button_up", 0) down = platform.request("button_down", 0) sw0 = platform.request("switch", 0) sw1 = platform.request("switch", 1) sw2 = platform.request("switch", 2) sw3 = platform.request("switch", 3) sw8 = platform.request("sw8") # Add CamRead submodule camread = CamRead() m.submodules.camread = camread # Camera config camconfig = CamConfig() m.submodules.camconfig = camconfig # Configure and read the camera m.d.comb += [ ov7670.cam_RESET.eq(1), ov7670.cam_PWON.eq(0), ov7670.cam_XCLK.eq(clk25.i), ov7670.cam_SIOC.eq(camconfig.sioc), ov7670.cam_SIOD.eq(camconfig.siod), camconfig.start.eq(btn1), camread.p_data.eq(Cat([ov7670.cam_data[i] for i in range(8)])), camread.href.eq(ov7670.cam_HREF), camread.vsync.eq(ov7670.cam_VSYNC), camread.p_clock.eq(ov7670.cam_PCLK) ] # Frame buffer buffer = Memory(width=16, depth=320 * 480) m.submodules.r = r = buffer.read_port() m.submodules.w = w = buffer.write_port() # Buttons and val debup = Debouncer() m.submodules.debup = debup val = Signal(signed(6)) up_down = Signal() debdown = Debouncer() m.submodules.debdown = debdown debres = Debouncer() m.submodules.debres = debres m.d.comb += [ debup.btn.eq(up), debdown.btn.eq(down), debres.btn.eq(btn2) ] with m.If(debup.btn_down): m.d.sync += val.eq(val + 1) with m.If(debdown.btn_down): m.d.sync += val.eq(val - 1) with m.If(debres.btn_down): m.d.sync += val.eq(0) # Image stream max_r = Signal(5) max_g = Signal(6) max_b = Signal(5) ims = ImageStream() m.submodules.image_stream = ims m.d.comb += [ ims.valid.eq(camread.pixel_valid), ims.i_x.eq(camread.row[1:]), ims.i_y.eq(camread.col), ims.i_r.eq(camread.pixel_data[11:]), ims.i_g.eq(camread.pixel_data[5:11]), ims.i_b.eq(camread.pixel_data[0:5]), ims.edge.eq(sw8.sw1), ims.red.eq(sw8.sw2), ims.green.eq(sw8.sw3), ims.blue.eq(sw8.sw4), ims.invert.eq(sw8.sw5), ims.border.eq(sw8.sw6), ims.gamma.eq(sw8.sw7), ims.filter.eq(sw8.sw8), ims.mono.eq(sw0), ims.bright.eq(sw1), ims.x_flip.eq(sw2), ims.y_flip.eq(sw3), ims.val.eq(val) ] with m.If(ims.i_r > max_r): m.d.sync += max_r.eq(ims.i_r) with m.If(ims.i_g > max_g): m.d.sync += max_g.eq(ims.i_g) with m.If(ims.i_b > max_b): m.d.sync += max_b.eq(ims.i_b) with m.If(camread.frame_done): m.d.sync += [max_r.eq(0), max_g.eq(0), max_b.eq(0)] # Show value on leds m.d.comb += leds.eq(ims.p_x) # VGA signal generator. vga_r = Signal(8) vga_g = Signal(8) vga_b = Signal(8) vga_hsync = Signal() vga_vsync = Signal() vga_blank = Signal() psum = Signal(8) # Add VGA generator m.submodules.vga = vga = VGA( resolution_x=self.timing.x, hsync_front_porch=hsync_front_porch, hsync_pulse=hsync_pulse_width, hsync_back_porch=hsync_back_porch, resolution_y=self.timing.y, vsync_front_porch=vsync_front_porch, vsync_pulse=vsync_pulse_width, vsync_back_porch=vsync_back_porch, bits_x=16, # Play around with the sizes because sometimes bits_y=16 # a smaller/larger value will make it pass timing. ) # Connect frame buffer m.d.comb += [ w.en.eq(ims.ready), w.addr.eq(ims.o_y * 320 + ims.o_x), w.data.eq(Cat(ims.o_b, ims.o_g, ims.o_r)), r.addr.eq(vga.o_beam_y * 320 + vga.o_beam_x[1:]) ] # Generate VGA signals m.d.comb += [ vga.i_clk_en.eq(1), vga.i_test_picture.eq(0), vga.i_r.eq(Cat(Const(0, unsigned(3)), r.data[11:16])), vga.i_g.eq(Cat(Const(0, unsigned(2)), r.data[5:11])), vga.i_b.eq(Cat(Const(0, unsigned(3)), r.data[0:5])), vga_r.eq(vga.o_vga_r), vga_g.eq(vga.o_vga_g), vga_b.eq(vga.o_vga_b), vga_hsync.eq(vga.o_vga_hsync), vga_vsync.eq(vga.o_vga_vsync), vga_blank.eq(vga.o_vga_blank), ] # VGA to digital video converter. tmds = [Signal(2) for i in range(4)] m.submodules.vga2dvid = vga2dvid = VGA2DVID( ddr=self.ddr, shift_clock_synchronizer=False) m.d.comb += [ vga2dvid.i_red.eq(vga_r), vga2dvid.i_green.eq(vga_g), vga2dvid.i_blue.eq(vga_b), vga2dvid.i_hsync.eq(vga_hsync), vga2dvid.i_vsync.eq(vga_vsync), vga2dvid.i_blank.eq(vga_blank), tmds[3].eq(vga2dvid.o_clk), tmds[2].eq(vga2dvid.o_red), tmds[1].eq(vga2dvid.o_green), tmds[0].eq(vga2dvid.o_blue), ] if (self.ddr): # Vendor specific DDR modules. # Convert SDR 2-bit input to DDR clocked 1-bit output (single-ended) # onboard GPDI. m.submodules.ddr0_clock = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[3][0], i_D1=tmds[3][1], o_Q=self.o_gpdi_dp[3]) m.submodules.ddr0_red = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[2][0], i_D1=tmds[2][1], o_Q=self.o_gpdi_dp[2]) m.submodules.ddr0_green = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[1][0], i_D1=tmds[1][1], o_Q=self.o_gpdi_dp[1]) m.submodules.ddr0_blue = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[0][0], i_D1=tmds[0][1], o_Q=self.o_gpdi_dp[0]) else: m.d.comb += [ self.o_gpdi_dp[3].eq(tmds[3][0]), self.o_gpdi_dp[2].eq(tmds[2][0]), self.o_gpdi_dp[1].eq(tmds[1][0]), self.o_gpdi_dp[0].eq(tmds[0][0]), ] return m
def elaborate(self, platform): # VGA constants pixel_f = self.timing.pixel_freq hsync_front_porch = self.timing.h_front_porch hsync_pulse_width = self.timing.h_sync_pulse hsync_back_porch = self.timing.h_back_porch vsync_front_porch = self.timing.v_front_porch vsync_pulse_width = self.timing.v_sync_pulse vsync_back_porch = self.timing.v_back_porch # Pins clk25 = platform.request("clk25") ov7670 = platform.request("ov7670") led = [platform.request("led", i) for i in range(8)] leds = Cat([i.o for i in led]) led8_2 = platform.request("led8_2") leds8_2 = Cat([led8_2.leds[i] for i in range(8)]) led8_3 = platform.request("led8_3") leds8_3 = Cat([led8_3.leds[i] for i in range(8)]) leds16 = Cat(leds8_3, leds8_2) btn1 = platform.request("button_fire", 0) btn2 = platform.request("button_fire", 1) up = platform.request("button_up", 0) down = platform.request("button_down", 0) pwr = platform.request("button_pwr", 0) left = platform.request("button_left", 0) right = platform.request("button_right", 0) sw = Cat([platform.request("switch", i) for i in range(4)]) uart = platform.request("uart") divisor = int(platform.default_clk_frequency // 460800) esp32 = platform.request("esp32_spi") csn = esp32.csn sclk = esp32.sclk copi = esp32.copi cipo = esp32.cipo m = Module() # Clock generator. m.domains.sync = cd_sync = ClockDomain("sync") m.domains.pixel = cd_pixel = ClockDomain("pixel") m.domains.shift = cd_shift = ClockDomain("shift") m.submodules.ecp5pll = pll = ECP5PLL() pll.register_clkin(clk25, platform.default_clk_frequency) pll.create_clkout(cd_sync, platform.default_clk_frequency) pll.create_clkout(cd_pixel, pixel_f) pll.create_clkout(cd_shift, pixel_f * 5.0 * (1.0 if self.ddr else 2.0)) # Add CamRead submodule camread = CamRead() m.submodules.camread = camread # Camera config cam_x_res = 640 cam_y_res = 480 camconfig = CamConfig() m.submodules.camconfig = camconfig # Connect the camera pins and config and read modules m.d.comb += [ ov7670.cam_RESET.eq(1), ov7670.cam_PWON.eq(0), ov7670.cam_XCLK.eq(clk25.i), ov7670.cam_SIOC.eq(camconfig.sioc), ov7670.cam_SIOD.eq(camconfig.siod), camconfig.start.eq(btn1), camread.p_data.eq(Cat([ov7670.cam_data[i] for i in range(8)])), camread.href.eq(ov7670.cam_HREF), camread.vsync.eq(ov7670.cam_VSYNC), camread.p_clock.eq(ov7670.cam_PCLK) ] # Create the uart m.submodules.serial = serial = AsyncSerial(divisor=divisor, pins=uart) # Frame buffer x_res = cam_x_res // 2 y_res = cam_y_res buffer = Memory(width=16, depth=x_res * y_res) m.submodules.r = r = buffer.read_port() m.submodules.w = w = buffer.write_port() # Button debouncers m.submodules.debup = debup = Debouncer() m.submodules.debdown = debdown = Debouncer() m.submodules.debosd = debosd = Debouncer() m.submodules.debsel = debsel = Debouncer() m.submodules.debsnap = debsnap = Debouncer() m.submodules.debhist = debhist = Debouncer() # Connect the buttons to debouncers m.d.comb += [ debup.btn.eq(up), debdown.btn.eq(down), debosd.btn.eq(pwr), debsel.btn.eq(right), debsnap.btn.eq(left), debhist.btn.eq(btn2) ] # Image processing configuration registers flip = Signal(2, reset=1) # Flip the image horizontally or vertically mono_en = Signal(reset=0) # Convert to monochrome invert = Signal(reset=0) # Invert monochrome image thresh_en = Signal(reset=0) # Apply threshold to monochrome image threshold = Signal(8, reset=0) # Threshold value border = Signal(reset=0) # Use OSD to show a border filt_en = Signal(reset=0) # Apply a color filter l = Rgb565(reset=(18, 12, 6)) # Image filter low values h = Rgb565(reset=(21, 22, 14)) # Image filter high values grid = Signal(reset=0) # Use OSD to show a grid hist_view = Signal(reset=1) # Switch to histogram view hist_chan = Signal(2, reset=0) # The histogram channel to calculate ccr = CC(reset=(0, 0, 18, 12, 16)) # Color control record sharpness = Signal( unsigned(4), reset=0 ) # Used to select image convolution kernel for blur/sharpness roi = Roi() # Region on interest frozen = Signal(reset=1) # Freeze/unfreeze video display sat_en = Signal() saturation = Signal(5, reset=16) # Control synchronization of camera with fifo sync_fifo = Signal(reset=0) # OSD control signals osd_val = Signal( 4, reset=0) # Account for spurious start-up button pushes osd_on = Signal(reset=1) osd_sel = Signal(reset=1) # Snapshot signals snap = Signal(reset=0) writing = Signal(reset=0) written = Signal(reset=0) byte = Signal(reset=0) w_addr = Signal(18) # Signals for calculating histogram hist_val = Signal(6) # Signals for displaying histogram hist_color = Signal(8) hbin = Signal(6, reset=0) bin_cnt = Signal(5, reset=0) old_x = Signal(10) # Frame buffer coordinates frame_x = Signal(10) frame_y = Signal(9) # VGA signals vga_r = Signal(8) vga_g = Signal(8) vga_b = Signal(8) vga_hsync = Signal() vga_vsync = Signal() vga_blank = Signal() # Pixel from camera pix = Rgb565() # Fifo stream m.submodules.fifo_stream = fs = FifoStream() # SPI memory for remote configuration m.submodules.spimem = spimem = SpiMem(addr_bits=32) # Color Control m.submodules.cc = cc = ColorControl() # Image convolution m.submodules.imc = imc = ImageConv() # Statistics m.submodules.stats = stats = Stats() # Histogram m.submodules.hist = hist = Hist() # Filter m.submodules.fil = fil = Filt() # Monochrome m.submodules.mon = mon = Mono() # Saturation m.submodules.sat = sat = Saturation() # Sync the fifo with the camera with m.If(~sync_fifo & (camread.col == cam_x_res - 1) & (camread.row == cam_y_res - 1)): m.d.sync += sync_fifo.eq(1) with m.If(btn1): m.d.sync += sync_fifo.eq(0) # Set histogram value to the data for the chosen channel with m.Switch(hist_chan): with m.Case(0): m.d.comb += hist_val.eq(cc.o.r) with m.Case(1): m.d.comb += hist_val.eq(cc.o.g) with m.Case(2): m.d.comb += hist_val.eq(cc.o.b) with m.Case(3): m.d.comb += hist_val.eq(mon.o_m) # Copy camera data to Rgb565 record m.d.comb += [ pix.r.eq(camread.pixel_data[11:]), pix.g.eq(camread.pixel_data[5:11]), pix.b.eq(camread.pixel_data[:5]) ] # Input image processing pipeline pipeline = [ [ fs, { "i": pix, # Fifo stream "i_valid": camread.pixel_valid & camread.col[0], "i_ready": cc.o_ready, "i_en": sync_fifo }, True ], [sat, { "i_en": sat_en, "i_saturation": saturation }, True], [cc, { "i_cc": ccr }, True], # Color control [ fil, { "i_en": filt_en, # Color filter "i_frame_done": fs.o_eof, "i_l": l, "i_h": h }, True ], [ mon, { "i_en": mono_en | invert | thresh_en, # Monochrome, invert and threshold "i_invert": invert, "i_thresh": thresh_en, "i_threshold": threshold }, True ], [ imc, { "i_ready": 1, # Image convolution "i_reset": ~fs.i_en, "i_sel": sharpness }, True ], [ stats, { "i": cc.o, # Statistics "i_valid": cc.o_valid, "i_avg_valid": (fs.o_x >= 32) & (fs.o_x < 288) & (fs.o_y >= 112) & (fs.o_y < 368), "i_frame_done": fs.o_eof, "i_x": fs.o_x, "i_y": fs.o_y, "i_roi": roi }, False ], [ hist, { "i_p": hist_val, # Histogram "i_valid": mon.o_valid, "i_clear": fs.o_eof, "i_x": fs.o_x, "i_y": fs.o_y, "i_roi": roi, "i_bin": hbin }, False ] ] def execute(pl): us = None # Upstream for p in pl: mod = p[0] d = p[1] st = p[2] # Stream or Sink if st and us is not None: m.d.comb += mod.i.eq(us.o) m.d.comb += mod.i_valid.eq(us.o_valid) m.d.comb += us.i_ready.eq(mod.o_ready) if st: us = mod for k in d: m.d.comb += mod.__dict__[k].eq(d[k]) execute(pipeline) # Take a snapshot, freeze the camera, and write the framebuffer to the uart # Note that this suspends video output with m.If(debsnap.btn_down | (spimem.wr & (spimem.addr == 22))): with m.If(frozen): m.d.sync += frozen.eq(0) with m.Else(): m.d.sync += [ snap.eq(1), frozen.eq(0), w_addr.eq(0), written.eq(0), byte.eq(0) ] # Wait to end of frame after requesting snapshot, before start of writing to uart with m.If(imc.o_eof & snap): m.d.sync += [frozen.eq(1), snap.eq(0)] with m.If(~written): m.d.sync += writing.eq(1) # Connect the uart m.d.comb += [ serial.tx.data.eq(Mux(byte, r.data[8:], r.data[:8])), serial.tx.ack.eq(writing) ] # Write to the uart from frame buffer (affects video output) with m.If(writing): with m.If(w_addr == x_res * y_res): m.d.sync += [writing.eq(0), written.eq(1)] with m.Elif(serial.tx.ack & serial.tx.rdy): m.d.sync += byte.eq(~byte) with m.If(byte): m.d.sync += w_addr.eq(w_addr + 1) # Connect spimem m.d.comb += [ spimem.csn.eq(~csn), spimem.sclk.eq(sclk), spimem.copi.eq(copi), cipo.eq(spimem.cipo), ] # Writable configuration registers spi_wr_vals = Array([ ccr.brightness, ccr.redness, ccr.greenness, ccr.blueness, l.r, h.r, l.g, h.g, l.b, h.b, sharpness, filt_en, border, mono_en, invert, grid, hist_view, roi.x[1:], roi.y[1:], roi.w[1:], roi.h[1:], roi.en, None, None, None, threshold, thresh_en, hist_chan, flip, None, None, None, None, None, None, None, None, None, frozen, None, None, sat_en, saturation, ccr.offset ]) with m.If(spimem.wr): with m.Switch(spimem.addr): for i in range(len(spi_wr_vals)): if spi_wr_vals[i] is not None: with m.Case(i): m.d.sync += spi_wr_vals[i].eq(spimem.dout) # Readable configuration registers spi_rd_vals = Array([ ccr.brightness, ccr.redness, ccr.greenness, ccr.blueness, l.r, h.r, l.g, h.g, l.b, h.b, sharpness, filt_en, border, mono_en, invert, grid, hist_view, roi.x[1:], roi.y[1:], roi.w[1:], roi.h[1:], roi.en, fil.o_nz[16:], fil.o_nz[8:16], fil.o_nz[:8], threshold, thresh_en, hist_chan, flip, stats.o_min.r, stats.o_min.g, stats.o_min.b, stats.o_max.r, stats.o_max.g, stats.o_max.b, stats.o_avg.r, stats.o_avg.g, stats.o_avg.b, frozen, writing, written, sat_en, saturation, ccr.offset ]) with m.If(spimem.rd): with m.Switch(spimem.addr): for i in range(len(spi_rd_vals)): with m.Case(i): m.d.sync += spimem.din.eq(spi_rd_vals[i]) # Add VGA generator m.submodules.vga = vga = VGA( resolution_x=self.timing.x, hsync_front_porch=hsync_front_porch, hsync_pulse=hsync_pulse_width, hsync_back_porch=hsync_back_porch, resolution_y=self.timing.y, vsync_front_porch=vsync_front_porch, vsync_pulse=vsync_pulse_width, vsync_back_porch=vsync_back_porch, bits_x=16, # Play around with the sizes because sometimes bits_y=16 # a smaller/larger value will make it pass timing. ) # Fetch histogram for display m.d.sync += old_x.eq(vga.o_beam_x) with m.If(vga.o_beam_x == 0): m.d.sync += [hbin.eq(0), bin_cnt.eq(0)] with m.Elif(vga.o_beam_x != old_x): m.d.sync += bin_cnt.eq(bin_cnt + 1) with m.If(bin_cnt == 19): m.d.sync += [bin_cnt.eq(0), hbin.eq(hbin + 1)] # Switch between camera and histogram view with m.If(debhist.btn_down): m.d.sync += hist_view.eq(~hist_view) # Connect frame buffer, with optional x and y flip m.d.comb += [ frame_x.eq( Mux(flip[0], x_res - 1 - vga.o_beam_x[1:], vga.o_beam_x[1:])), frame_y.eq(Mux(flip[1], y_res - 1 - vga.o_beam_y, vga.o_beam_y)), w.en.eq(imc.o_valid & ~frozen), w.addr.eq(imc.o_y * x_res + imc.o_x), w.data.eq(imc.o.as_data()), r.addr.eq(Mux(writing, w_addr, frame_y * x_res + frame_x)) ] # Apply the On-Screen Display (OSD) m.submodules.osd = osd = OSD() m.d.comb += [ osd.x.eq(vga.o_beam_x), osd.y.eq(vga.o_beam_y), hist_color.eq(Mux((479 - osd.y) < hist.o_val[8:], 0xff, 0x00)), osd.i_r.eq( Mux(hist_view, Mux((hist_chan == 0) | (hist_chan == 3), hist_color, 0), Cat(Const(0, unsigned(3)), r.data[11:16]))), osd.i_g.eq( Mux(hist_view, Mux((hist_chan == 1) | (hist_chan == 3), hist_color, 0), Cat(Const(0, unsigned(2)), r.data[5:11]))), osd.i_b.eq( Mux(hist_view, Mux((hist_chan == 2) | (hist_chan == 3), hist_color, 0), Cat(Const(0, unsigned(3)), r.data[0:5]))), osd.on.eq(osd_on), osd.osd_val.eq(osd_val), osd.sel.eq(osd_sel), osd.grid.eq(grid), osd.border.eq(border), osd.roi.eq(roi.en & ~hist_view), osd.roi_x.eq(roi.x), osd.roi_y.eq(roi.y), osd.roi_w.eq(roi.w), osd.roi_h.eq(roi.h) ] # OSD control dummy = Signal() osd_vals = Array([ ccr.offset, ccr.brightness, ccr.redness, ccr.greenness, ccr.blueness, sharpness, sat_en, saturation, mono_en, invert, thresh_en, threshold, hist_chan, Cat(border, grid), flip, filt_en ]) with m.If(debosd.btn_down): m.d.sync += osd_on.eq(~osd_on) with m.If(osd_on): with m.If(debsel.btn_down): m.d.sync += osd_sel.eq(~osd_sel) with m.If(debup.btn_down): with m.If(~osd_sel): m.d.sync += osd_val.eq(osd_val - 1) with m.Else(): with m.Switch(osd_val): for i in range(len(osd_vals)): with m.Case(i): if (len(osd_vals[i]) == 1): m.d.sync += osd_vals[i].eq(1) else: m.d.sync += osd_vals[i].eq(osd_vals[i] + 1) with m.If(debdown.btn_down): with m.If(~osd_sel): m.d.sync += osd_val.eq(osd_val + 1) with m.Else(): with m.Switch(osd_val): for i in range(len(osd_vals)): with m.Case(i): if (len(osd_vals[i]) == 1): m.d.sync += osd_vals[i].eq(0) else: m.d.sync += osd_vals[i].eq(osd_vals[i] - 1) # Show configuration values on leds with m.Switch(osd_val): for i in range(len(osd_vals)): with m.Case(i): m.d.comb += leds.eq(osd_vals[i]) # Generate VGA signals m.d.comb += [ vga.i_clk_en.eq(1), vga.i_test_picture.eq(0), vga.i_r.eq(osd.o_r), vga.i_g.eq(osd.o_g), vga.i_b.eq(osd.o_b), vga_r.eq(vga.o_vga_r), vga_g.eq(vga.o_vga_g), vga_b.eq(vga.o_vga_b), vga_hsync.eq(vga.o_vga_hsync), vga_vsync.eq(vga.o_vga_vsync), vga_blank.eq(vga.o_vga_blank), ] # VGA to digital video converter. tmds = [Signal(2) for i in range(4)] m.submodules.vga2dvid = vga2dvid = VGA2DVID( ddr=self.ddr, shift_clock_synchronizer=False) m.d.comb += [ vga2dvid.i_red.eq(vga_r), vga2dvid.i_green.eq(vga_g), vga2dvid.i_blue.eq(vga_b), vga2dvid.i_hsync.eq(vga_hsync), vga2dvid.i_vsync.eq(vga_vsync), vga2dvid.i_blank.eq(vga_blank), tmds[3].eq(vga2dvid.o_clk), tmds[2].eq(vga2dvid.o_red), tmds[1].eq(vga2dvid.o_green), tmds[0].eq(vga2dvid.o_blue), ] # GPDI pins if (self.ddr): # Vendor specific DDR modules. # Convert SDR 2-bit input to DDR clocked 1-bit output (single-ended) # onboard GPDI. m.submodules.ddr0_clock = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[3][0], i_D1=tmds[3][1], o_Q=self.o_gpdi_dp[3]) m.submodules.ddr0_red = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[2][0], i_D1=tmds[2][1], o_Q=self.o_gpdi_dp[2]) m.submodules.ddr0_green = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[1][0], i_D1=tmds[1][1], o_Q=self.o_gpdi_dp[1]) m.submodules.ddr0_blue = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[0][0], i_D1=tmds[0][1], o_Q=self.o_gpdi_dp[0]) else: m.d.comb += [ self.o_gpdi_dp[3].eq(tmds[3][0]), self.o_gpdi_dp[2].eq(tmds[2][0]), self.o_gpdi_dp[1].eq(tmds[1][0]), self.o_gpdi_dp[0].eq(tmds[0][0]), ] return m
def elaborate(self, platform: Platform) -> Module: m = Module() if platform: clk_in = platform.request(platform.default_clk, dir='-')[0] leds = Cat([platform.request("led", i) for i in range(8)]) btn = Cat([platform.request("button", i) for i in range(6)]) pwr = platform.request("button_pwr") usb = platform.request("usb") ps2_pullup = platform.request("ps2_pullup") stereo = platform.request("stereo", 0) led8_0 = platform.request("led8_0") leds8_0 = Cat([led8_0.leds[i] for i in range(8)]) led8_1 = platform.request("led8_1") leds8_1 = Cat([led8_1.leds[i] for i in range(8)]) leds16 = Cat(leds8_0, leds8_1) led8_2 = platform.request("led8_2") leds8_2 = Cat([led8_2.leds[i] for i in range(8)]) led8_3 = platform.request("led8_3") leds8_3 = Cat([led8_3.leds[i] for i in range(8)]) leds16_2 = Cat(leds8_3, leds8_2) esp32 = platform.request("esp32_spi") csn = esp32.csn sclk = esp32.sclk copi = esp32.copi cipo = esp32.cipo irq = esp32.irq # Constants pixel_f = self.timing.pixel_freq hsync_front_porch = self.timing.h_front_porch hsync_pulse_width = self.timing.h_sync_pulse hsync_back_porch = self.timing.h_back_porch vsync_front_porch = self.timing.v_front_porch vsync_pulse_width = self.timing.v_sync_pulse vsync_back_porch = self.timing.v_back_porch # Clock generator. m.domains.sync = cd_sync = ClockDomain("sync") m.domains.pixel = cd_pixel = ClockDomain("pixel") m.domains.shift = cd_shift = ClockDomain("shift") m.submodules.ecp5pll = pll = ECP5PLL() pll.register_clkin(clk_in, platform.default_clk_frequency) pll.create_clkout(cd_sync, platform.default_clk_frequency) pll.create_clkout(cd_pixel, pixel_f) pll.create_clkout(cd_shift, pixel_f * 5.0 * (1.0 if self.ddr else 2.0)) platform.add_clock_constraint(cd_sync.clk, platform.default_clk_frequency) platform.add_clock_constraint(cd_pixel.clk, pixel_f) platform.add_clock_constraint( cd_shift.clk, pixel_f * 5.0 * (1.0 if self.ddr else 2.0)) m.domains.ph1 = ph1 = ClockDomain("ph1") m.domains.ph2 = ph2 = ClockDomain("ph2") # CPU clock domains clk_freq = platform.default_clk_frequency timer = Signal(range(0, int(7)), reset=int(7 - 1)) tick = Signal() sync = ClockDomain() cpu_control = Signal(8) spi_load = Signal() pia_cra = Signal(8) pia_dra = Signal(8) pia_crb = Signal(8) pia_drb = Signal(8) r_btn = Signal(6) m.d.sync += r_btn.eq(btn) with m.If(timer == 0): m.d.sync += timer.eq(timer.reset) m.d.sync += tick.eq(~tick) with m.Else(): m.d.sync += timer.eq(timer - 1) m.d.comb += [ ph1.rst.eq(sync.rst), ph2.rst.eq(sync.rst), ph1.clk.eq(tick), ph2.clk.eq(~tick), ] # Add CPU cpu = Core() m.submodules += cpu # VGA signal generator. vga_r = Signal(8) vga_g = Signal(8) vga_b = Signal(8) vga_hsync = Signal() vga_vsync = Signal() vga_blank = Signal() r_vsync = Signal() # Save previous value of vsync m.d.sync += r_vsync.eq(vga_vsync) # Vsync sets IRQ with m.If(vga_vsync & ~r_vsync): m.d.sync += cpu.IRQ.eq(1) # Reading PIA Data Register B clears the interrupt with m.If(cpu.RW & (cpu.Addr == 0x2002)): m.d.sync += cpu.IRQ.eq(0) m.submodules.vga = vga = VGA( resolution_x=self.timing.x, hsync_front_porch=hsync_front_porch, hsync_pulse=hsync_pulse_width, hsync_back_porch=hsync_back_porch, resolution_y=self.timing.y, vsync_front_porch=vsync_front_porch, vsync_pulse=vsync_pulse_width, vsync_back_porch=vsync_back_porch, bits_x=16, # Play around with the sizes because sometimes bits_y=16 # a smaller/larger value will make it pass timing. ) # Use 1Kb of RAM ram = Memory(width=8, depth=1024) m.submodules.dr = dr = ram.read_port() m.submodules.vr = vr = ram.read_port() m.submodules.dw = dw = ram.write_port() # And 2kb of ROM rom_data = readhex("roms/apf_4000.mem") rom = Memory(width=8, depth=2048, init=rom_data) m.submodules.rr = rr = rom.read_port() # And 8kb of cartridge rom cart = Memory(width=8, depth=8192) m.submodules.cr = cr = rom.read_port() m.submodules.cw = cw = rom.write_port() # Add SpiRamBtn for OSD control m.submodules.rambtn = rambtn = SpiRamBtn() # Add PS/2 keyboard controller m.submodules.ps2 = ps2 = PS2() # Add character rom and video controller font = readhex("roms/charrom.mem") charrom = Memory(width=8, depth=512, init=font) m.submodules.fr = fr = charrom.read_port() m.submodules.video = video = Video() m.d.comb += [ # Connect rambtn rambtn.csn.eq(~csn), rambtn.sclk.eq(sclk), rambtn.copi.eq(copi), rambtn.btn.eq(Cat(~pwr, btn)), cipo.eq(rambtn.cipo), irq.eq(~rambtn.irq), # Connect memory dr.addr.eq(cpu.Addr), rambtn.din.eq(vr.data), cw.data.eq(rambtn.dout), cw.addr.eq(rambtn.addr), cw.en.eq(rambtn.wr & (rambtn.addr[24:] == 0)), rr.addr.eq(cpu.Addr), cpu.Din.eq( Mux( cpu.Addr == 0xffff, 0x00, Mux( cpu.Addr == 0xfffe, 0x40, # reset vector Mux( cpu.Addr == 0xfff9, 0xA4, Mux( cpu.Addr == 0xfff8, 0x42, # irq vector Mux( cpu.Addr[13:] == 0, dr.data, Mux( cpu.Addr[13:] == 2, rr.data, # ram or system rom Mux( cpu.Addr[14:] == 1, cr.data, Mux( cpu.Addr == 0x2002, pia_drb, Mux( cpu.Addr == 0x2000, # PIA_DRA has keyboard row Mux( pia_drb[:4] == C( 0b1110, 4), Cat([ ~r_btn[0], Repl(1, 7) ]), Mux( pia_drb[:4] == C(0b1101, 4), Cat([ Repl(1, 1), ~r_btn[5], Repl(1, 1), ~r_btn[4], Repl(1, 1), ~r_btn[5], Repl(1, 1), ~r_btn[4] ]), Mux( pia_drb[:4] == C( 0b1011, 4), Cat([ Repl( 1, 8) ]), Mux( pia_drb[: 4] == C( 0b0111, 4), Cat([ Repl( 1, 1 ), ~r_btn[ 2], Repl( 1, 2 ), ~r_btn[ 1: 3], Repl( 1, 2 ) ]), 0xff))) ), 0xff)))))))))), dw.addr.eq(cpu.Addr), dw.data.eq(cpu.Dout), dw.en.eq(~cpu.RW & cpu.VMA & (cpu.Addr[13:] == 0)), vr.addr.eq(Mux(spi_load, rambtn.addr, video.c_addr)), # PS/2 keyboard usb.pullup.eq(1), ps2_pullup.eq(1), ps2.ps2_clk.eq(usb.d_p), ps2.ps2_data.eq(usb.d_n), spi_load.eq(cpu_control[1]), stereo.r.eq(stereo.l) ] # CPU control from ESP32 with m.If(rambtn.wr & (rambtn.addr[24:] == 0xFF)): m.d.sync += cpu_control.eq(rambtn.dout) # PIA control and data registers with m.If(~cpu.RW & cpu.VMA & cpu.Addr[13]): with m.Switch(cpu.Addr[:2]): with m.Case(0): m.d.sync += pia_dra.eq(cpu.Dout) with m.Case(1): m.d.sync += pia_cra.eq(cpu.Dout) with m.Case(2): m.d.sync += pia_drb.eq(cpu.Dout) with m.Case(3): m.d.sync += pia_crb.eq(cpu.Dout) m.d.sync += stereo.l.eq(Mux(cpu.Dout[3], 0x7, 0x0)) mode = Signal(2) with m.If(~cpu.RW & (cpu.Addr == 0x1fc) & (cpu.Dout != 0)): m.d.sync += mode.eq(0b11) # OSD m.submodules.osd = osd = SpiOsd(start_x=62, start_y=80, chars_x=64, chars_y=20) m.d.comb += [ # Connect video video.x.eq(vga.o_beam_x), video.y.eq(vga.o_beam_y), video.din.eq(vr.data), video.fin.eq(fr.data), video.mode.eq(mode), fr.addr.eq(video.f_addr), # Connect osd osd.i_csn.eq(~csn), osd.i_sclk.eq(sclk), osd.i_copi.eq(copi), osd.clk_ena.eq(1), osd.i_hsync.eq(vga.o_vga_hsync), osd.i_vsync.eq(vga.o_vga_vsync), osd.i_blank.eq(vga.o_vga_blank), osd.i_r.eq(video.r), osd.i_g.eq(video.g), osd.i_b.eq(video.b), # led diagnostics leds.eq(pia_drb), leds16_2.eq(cpu.sp), leds16.eq(cpu.Addr) ] m.d.comb += [ vga.i_clk_en.eq(1), vga.i_test_picture.eq(0), vga_r.eq(osd.o_r), vga_g.eq(osd.o_g), vga_b.eq(osd.o_b), vga_hsync.eq(osd.o_hsync), vga_vsync.eq(osd.o_vsync), vga_blank.eq(osd.o_blank), ] # VGA to digital video converter. tmds = [Signal(2) for i in range(4)] m.submodules.vga2dvid = vga2dvid = VGA2DVID( ddr=self.ddr, shift_clock_synchronizer=False) m.d.comb += [ vga2dvid.i_red.eq(vga_r), vga2dvid.i_green.eq(vga_g), vga2dvid.i_blue.eq(vga_b), vga2dvid.i_hsync.eq(vga_hsync), vga2dvid.i_vsync.eq(vga_vsync), vga2dvid.i_blank.eq(vga_blank), tmds[3].eq(vga2dvid.o_clk), tmds[2].eq(vga2dvid.o_red), tmds[1].eq(vga2dvid.o_green), tmds[0].eq(vga2dvid.o_blue), ] if (self.ddr): # Vendor specific DDR modules. # Convert SDR 2-bit input to DDR clocked 1-bit output (single-ended) # onboard GPDI. m.submodules.ddr0_clock = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[3][0], i_D1=tmds[3][1], o_Q=self.o_gpdi_dp[3]) m.submodules.ddr0_red = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[2][0], i_D1=tmds[2][1], o_Q=self.o_gpdi_dp[2]) m.submodules.ddr0_green = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[1][0], i_D1=tmds[1][1], o_Q=self.o_gpdi_dp[1]) m.submodules.ddr0_blue = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[0][0], i_D1=tmds[0][1], o_Q=self.o_gpdi_dp[0]) else: m.d.comb += [ self.o_gpdi_dp[3].eq(tmds[3][0]), self.o_gpdi_dp[2].eq(tmds[2][0]), self.o_gpdi_dp[1].eq(tmds[1][0]), self.o_gpdi_dp[0].eq(tmds[0][0]), ] return m
def elaborate(self, platform: Platform) -> Module: m = Module() if platform: clk_in = platform.request(platform.default_clk, dir='-')[0] # Constants pixel_f = self.timing.pixel_freq hsync_front_porch = self.timing.h_front_porch hsync_pulse_width = self.timing.h_sync_pulse hsync_back_porch = self.timing.h_back_porch vsync_front_porch = self.timing.v_front_porch vsync_pulse_width = self.timing.v_sync_pulse vsync_back_porch = self.timing.v_back_porch # o_wifi_gpio0 = 1 keeps board from rebooting # Hold btn0 to let ESP32 take control of the board. m.d.comb += self.o_wifi_gpio0.eq(self.i_btn[0]) # Press btn0 to exit this bitstream. R_delay_reload = Signal(20, reset=0) with m.If(R_delay_reload[19] == 0): m.d.sync += R_delay_reload.eq(R_delay_reload + 1) m.d.comb += self.o_user_programn.eq((~self.i_btn[0]) | (~R_delay_reload[19])) # Clock generator. m.domains.sync = cd_sync = ClockDomain("sync") m.domains.pixel = cd_pixel = ClockDomain("pixel") m.domains.shift = cd_shift = ClockDomain("shift") m.submodules.ecp5pll = pll = ECP5PLL() pll.register_clkin(clk_in, platform.default_clk_frequency) pll.create_clkout(cd_sync, platform.default_clk_frequency) pll.create_clkout(cd_pixel, pixel_f) pll.create_clkout(cd_shift, pixel_f * 5.0 * (1.0 if self.ddr else 2.0)) platform.add_clock_constraint(cd_sync.clk, platform.default_clk_frequency) platform.add_clock_constraint(cd_pixel.clk, pixel_f) platform.add_clock_constraint( cd_shift.clk, pixel_f * 5.0 * (1.0 if self.ddr else 2.0)) # VGA signal generator. vga_r = Signal(8) vga_g = Signal(8) vga_b = Signal(8) vga_hsync = Signal() vga_vsync = Signal() vga_blank = Signal() m.submodules.vga = vga = VGA( resolution_x=self.timing.x, hsync_front_porch=hsync_front_porch, hsync_pulse=hsync_pulse_width, hsync_back_porch=hsync_back_porch, resolution_y=self.timing.y, vsync_front_porch=vsync_front_porch, vsync_pulse=vsync_pulse_width, vsync_back_porch=vsync_back_porch, bits_x=16, # Play around with the sizes because sometimes bits_y=16 # a smaller/larger value will make it pass timing. ) with m.If(vga.o_beam_y < 400): m.d.comb += [vga.i_r.eq(0xff), vga.i_g.eq(0), vga.i_b.eq(0)] with m.Else(): m.d.comb += [vga.i_r.eq(0), vga.i_g.eq(0xff), vga.i_b.eq(0)] m.d.comb += [ vga.i_clk_en.eq(1), vga.i_test_picture.eq(0), vga_r.eq(vga.o_vga_r), vga_g.eq(vga.o_vga_g), vga_b.eq(vga.o_vga_b), vga_hsync.eq(vga.o_vga_hsync), vga_vsync.eq(vga.o_vga_vsync), vga_blank.eq(vga.o_vga_blank), ] # VGA to digital video converter. tmds = [Signal(2) for i in range(4)] m.submodules.vga2dvid = vga2dvid = VGA2DVID( ddr=self.ddr, shift_clock_synchronizer=False) m.d.comb += [ vga2dvid.i_red.eq(vga_r), vga2dvid.i_green.eq(vga_g), vga2dvid.i_blue.eq(vga_b), vga2dvid.i_hsync.eq(vga_hsync), vga2dvid.i_vsync.eq(vga_vsync), vga2dvid.i_blank.eq(vga_blank), tmds[3].eq(vga2dvid.o_clk), tmds[2].eq(vga2dvid.o_red), tmds[1].eq(vga2dvid.o_green), tmds[0].eq(vga2dvid.o_blue), ] # LED blinky counter_width = 28 countblink = Signal(8) m.submodules.blink = blink = Blink(counter_width) m.d.comb += [ countblink.eq(blink.o_led), self.o_led[6:8].eq(countblink[6:8]), self.o_led[0].eq(vga_vsync), self.o_led[1].eq(vga_hsync), self.o_led[2].eq(vga_blank), ] if (self.ddr): # Vendor specific DDR modules. # Convert SDR 2-bit input to DDR clocked 1-bit output (single-ended) # onboard GPDI. m.submodules.ddr0_clock = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[3][0], i_D1=tmds[3][1], o_Q=self.o_gpdi_dp[3]) m.submodules.ddr0_red = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[2][0], i_D1=tmds[2][1], o_Q=self.o_gpdi_dp[2]) m.submodules.ddr0_green = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[1][0], i_D1=tmds[1][1], o_Q=self.o_gpdi_dp[1]) m.submodules.ddr0_blue = Instance("ODDRX1F", i_SCLK=ClockSignal("shift"), i_RST=0b0, i_D0=tmds[0][0], i_D1=tmds[0][1], o_Q=self.o_gpdi_dp[0]) else: m.d.comb += [ self.o_gpdi_dp[3].eq(tmds[3][0]), self.o_gpdi_dp[2].eq(tmds[2][0]), self.o_gpdi_dp[1].eq(tmds[1][0]), self.o_gpdi_dp[0].eq(tmds[0][0]), ] return m
def elaborate(self, platform): # VGA constants pixel_f = self.timing.pixel_freq hsync_front_porch = self.timing.h_front_porch hsync_pulse_width = self.timing.h_sync_pulse hsync_back_porch = self.timing.h_back_porch vsync_front_porch = self.timing.v_front_porch vsync_pulse_width = self.timing.v_sync_pulse vsync_back_porch = self.timing.v_back_porch # Pins clk25 = platform.request("clk25") ov7670 = platform.request("ov7670") led = [platform.request("led", i) for i in range(8)] leds = Cat([i.o for i in led]) led8_2 = platform.request("led8_2") leds8_2 = Cat([led8_2.leds[i] for i in range(8)]) led8_3 = platform.request("led8_3") leds8_3 = Cat([led8_3.leds[i] for i in range(8)]) leds16 = Cat(leds8_3, leds8_2) btn1 = platform.request("button_fire", 0) btn2 = platform.request("button_fire", 1) up = platform.request("button_up", 0) down = platform.request("button_down", 0) pwr = platform.request("button_pwr", 0) left = platform.request("button_left", 0) right = platform.request("button_right", 0) sw = Cat([platform.request("switch",i) for i in range(4)]) uart = platform.request("uart") divisor = int(platform.default_clk_frequency // 460800) esp32 = platform.request("esp32_spi") csn = esp32.csn sclk = esp32.sclk copi = esp32.copi cipo = esp32.cipo m = Module() # Clock generator. m.domains.sync = cd_sync = ClockDomain("sync") m.domains.pixel = cd_pixel = ClockDomain("pixel") m.domains.shift = cd_shift = ClockDomain("shift") m.submodules.ecp5pll = pll = ECP5PLL() pll.register_clkin(clk25, platform.default_clk_frequency) pll.create_clkout(cd_sync, platform.default_clk_frequency) pll.create_clkout(cd_pixel, pixel_f) pll.create_clkout(cd_shift, pixel_f * 5.0 * (1.0 if self.ddr else 2.0)) # Add CamRead submodule camread = CamRead() m.submodules.camread = camread # Camera config cam_x_res = 640 cam_y_res = 480 camconfig = CamConfig() m.submodules.camconfig = camconfig # Connect the camera pins and config and read modules m.d.comb += [ ov7670.cam_RESET.eq(1), ov7670.cam_PWON.eq(0), ov7670.cam_XCLK.eq(clk25.i), ov7670.cam_SIOC.eq(camconfig.sioc), ov7670.cam_SIOD.eq(camconfig.siod), camconfig.start.eq(btn1), camread.p_data.eq(Cat([ov7670.cam_data[i] for i in range(8)])), camread.href.eq(ov7670.cam_HREF), camread.vsync.eq(ov7670.cam_VSYNC), camread.p_clock.eq(ov7670.cam_PCLK) ] # Create the uart m.submodules.serial = serial = AsyncSerial(divisor=divisor, pins=uart) # Input fifo fifo_depth=1024 m.submodules.fifo = fifo = SyncFIFOBuffered(width=16,depth=fifo_depth) # Frame buffer x_res= cam_x_res // 2 y_res= cam_y_res buffer = Memory(width=16, depth=x_res * y_res) m.submodules.r = r = buffer.read_port() m.submodules.w = w = buffer.write_port() # Button debouncers m.submodules.debup = debup = Debouncer() m.submodules.debdown = debdown = Debouncer() m.submodules.debosd = debosd = Debouncer() m.submodules.debsel = debsel = Debouncer() m.submodules.debsnap = debsnap = Debouncer() m.submodules.debhist = debhist = Debouncer() # Connect the buttons to debouncers m.d.comb += [ debup.btn.eq(up), debdown.btn.eq(down), debosd.btn.eq(pwr), debsel.btn.eq(right), debsnap.btn.eq(left), debhist.btn.eq(btn2) ] # Image processing options flip = Signal(2, reset=1) mono = Signal(reset=0) invert = Signal(reset=0) gamma = Signal(reset=0) border = Signal(reset=0) filt = Signal(reset=0) grid = Signal(reset=0) histo = Signal(reset=1) hbin = Signal(6, reset=0) bin_cnt = Signal(5, reset=0) thresh = Signal(reset=0) threshold = Signal(8, reset=0) hist_chan = Signal(2, reset=0) ccc = CC(reset=(0,18,12,16)) sharpness = Signal(unsigned(4), reset=0) osd_val = Signal(4, reset=0) # Account for spurious start-up button pushes osd_on = Signal(reset=1) osd_sel = Signal(reset=1) snap = Signal(reset=0) frozen = Signal(reset=1) writing = Signal(reset=0) written = Signal(reset=0) byte = Signal(reset=0) w_addr = Signal(18) # Color filter l = Rgb565(reset=(18,12,6)) # Initialised to red LEGO filter h = Rgb565(reset=(21,22,14)) # Region of interest roi = Roi() # VGA signals vga_r = Signal(8) vga_g = Signal(8) vga_b = Signal(8) vga_hsync = Signal() vga_vsync = Signal() vga_blank = Signal() # Fifo co-ordinates f_x = Signal(9) f_y = Signal(9) f_frame_done = Signal() # Pixel from fifo pix = Rgb565() # SPI memory for remote configuration m.submodules.spimem = spimem = SpiMem(addr_bits=32) # Color Control m.submodules.cc = cc = ColorControl() # Image convolution m.submodules.imc = imc = ImageConv() # Statistics m.submodules.stats = stats = Stats() # Histogram m.submodules.hist = hist = Hist() # Filter m.submodules.fil = fil = Filt() # Monochrome m.submodules.mon = mon = Mono() # Sync the fifo with the camera sync_fifo = Signal(reset=0) with m.If(~sync_fifo & ~fifo.r_rdy & (camread.col == cam_x_res - 1) & (camread.row == cam_y_res -1)): m.d.sync += [ sync_fifo.eq(1), f_x.eq(0), f_y.eq(0) ] with m.If(btn1): m.d.sync += sync_fifo.eq(0) # Connect the fifo m.d.comb += [ fifo.w_en.eq(camread.pixel_valid & camread.col[0] & sync_fifo), # Only write every other pixel fifo.w_data.eq(camread.pixel_data), fifo.r_en.eq(fifo.r_rdy & ~imc.o_stall) ] # Calculate fifo co-ordinates m.d.sync += f_frame_done.eq(0) with m.If(fifo.r_en & sync_fifo): m.d.sync += f_x.eq(f_x + 1) with m.If(f_x == x_res - 1): m.d.sync += [ f_x.eq(0), f_y.eq(f_y + 1) ] with m.If(f_y == y_res - 1): m.d.sync += [ f_y.eq(0), f_frame_done.eq(1) ] # Extract pixel from fifo data m.d.comb += [ pix.r.eq(fifo.r_data[11:]), pix.g.eq(fifo.r_data[5:11]), pix.b.eq(fifo.r_data[:5]) ] # Connect color control m.d.comb += [ cc.i.eq(pix), cc.i_cc.eq(ccc) ] # Calculate per-frame statistics, after applying color correction m.d.comb += [ stats.i.eq(cc.o), stats.i_valid.eq(fifo.r_rdy), # This is not valid when a region of interest is active stats.i_avg_valid.eq((f_x >= 32) & (f_x < 288) & (f_y >= 112) & (f_y < 368)), stats.i_frame_done.eq(f_frame_done), stats.i_x.eq(f_x), stats.i_y.eq(f_y), stats.i_roi.eq(roi) ] # Produce histogram, after applying color correction, and after monochrome, for monochrome histogram with m.Switch(hist_chan): with m.Case(0): m.d.comb += hist.i_p.eq(cc.o.r) with m.Case(1): m.d.comb += hist.i_p.eq(cc.o.g) with m.Case(2): m.d.comb += hist.i_p.eq(cc.o.b) with m.Case(3): m.d.comb += hist.i_p.eq(mon.o_m) m.d.comb += [ hist.i_valid.eq(fifo.r_rdy), hist.i_clear.eq(f_frame_done), hist.i_x.eq(f_x), hist.i_y.eq(f_y), hist.i_roi.eq(roi), hist.i_bin.eq(hbin) # Used when displaying histogram ] # Apply filter, after color correction m.d.comb += [ fil.i.eq(cc.o), fil.i_valid.eq(fifo.r_en), fil.i_en.eq(filt), fil.i_frame_done.eq(f_frame_done), fil.i_l.eq(l), fil.i_h.eq(h) ] # Apply mono, after color correction and filter m.d.comb += [ mon.i.eq(fil.o), mon.i_en.eq(mono), mon.i_invert.eq(invert), mon.i_thresh.eq(thresh), mon.i_threshold.eq(threshold) ] # Apply image convolution, after other transformations m.d.comb += [ imc.i.eq(mon.o), imc.i_valid.eq(fifo.r_rdy), imc.i_reset.eq(~sync_fifo), # Select image convolution imc.i_sel.eq(sharpness) ] # Take a snapshot, freeze the camera, and write the framebuffer to the uart # Note that this suspends video output with m.If(debsnap.btn_down | (spimem.wr & (spimem.addr == 22))): with m.If(frozen): m.d.sync += frozen.eq(0) with m.Else(): m.d.sync += [ snap.eq(1), frozen.eq(0), w_addr.eq(0), written.eq(0), byte.eq(0) ] # Wait to end of frame after requesting snapshot, before start of writing to uart with m.If(imc.o_frame_done & snap): m.d.sync += [ frozen.eq(1), snap.eq(0) ] with m.If(~written): m.d.sync += writing.eq(1) # Connect the uart m.d.comb += [ serial.tx.data.eq(Mux(byte, r.data[8:], r.data[:8])), serial.tx.ack.eq(writing) ] # Write to the uart from frame buffer (affects video output) with m.If(writing): with m.If(w_addr == x_res * y_res): m.d.sync += [ writing.eq(0), written.eq(1) ] with m.Elif(serial.tx.ack & serial.tx.rdy): m.d.sync += byte.eq(~byte) with m.If(byte): m.d.sync += w_addr.eq(w_addr+1) # Connect spimem m.d.comb += [ spimem.csn.eq(~csn), spimem.sclk.eq(sclk), spimem.copi.eq(copi), cipo.eq(spimem.cipo), ] # Writable configuration registers spi_wr_vals = Array([ccc.brightness, ccc.redness, ccc.greenness, ccc.blueness, l.r, h.r, l.g, h.g, l.b, h.b, sharpness, filt, border, mono, invert, grid, histo, roi.x[1:], roi.y[1:], roi.w[1:], roi.h[1:], roi.en, None, None, None, threshold, thresh, hist_chan, flip, None, None, None, None, None, None, None, None, None, frozen]) with m.If(spimem.wr): with m.Switch(spimem.addr): for i in range(len(spi_wr_vals)): if spi_wr_vals[i] is not None: with m.Case(i): m.d.sync += spi_wr_vals[i].eq(spimem.dout) # Readable configuration registers spi_rd_vals = Array([ccc.brightness, ccc.redness, ccc.greenness, ccc.blueness, l.r, h.r, l.g, h.g, l.b, h.b, sharpness, filt, border, mono, invert, grid, histo, roi.x[1:], roi.y[1:], roi.w[1:], roi.h[1:], roi.en, fil.o_nz[16:], fil.o_nz[8:16], fil.o_nz[:8], threshold, thresh, hist_chan, flip, stats.o_min.r, stats.o_min.g, stats.o_min.b, stats.o_max.r, stats.o_max.g, stats.o_max.b, stats.o_avg.r, stats.o_avg.g, stats.o_avg.b, frozen, writing, written]) with m.If(spimem.rd): with m.Switch(spimem.addr): for i in range(len(spi_rd_vals)): with m.Case(i): m.d.sync += spimem.din.eq(spi_rd_vals[i]) # Add VGA generator m.submodules.vga = vga = VGA( resolution_x = self.timing.x, hsync_front_porch = hsync_front_porch, hsync_pulse = hsync_pulse_width, hsync_back_porch = hsync_back_porch, resolution_y = self.timing.y, vsync_front_porch = vsync_front_porch, vsync_pulse = vsync_pulse_width, vsync_back_porch = vsync_back_porch, bits_x = 16, # Play around with the sizes because sometimes bits_y = 16 # a smaller/larger value will make it pass timing. ) # Fetch histogram for display old_x = Signal(10) m.d.sync += old_x.eq(vga.o_beam_x) with m.If(vga.o_beam_x == 0): m.d.sync += [ hbin.eq(0), bin_cnt.eq(0) ] with m.Elif(vga.o_beam_x != old_x): m.d.sync += bin_cnt.eq(bin_cnt+1) with m.If(bin_cnt == 19): m.d.sync += [ bin_cnt.eq(0), hbin.eq(hbin+1) ] # Switch between camera and histogram view with m.If(debhist.btn_down): m.d.sync += histo.eq(~histo) # Connect frame buffer, with optional x and y flip x = Signal(10) y = Signal(9) m.d.comb += [ w.en.eq(imc.o_valid & ~frozen), w.addr.eq(imc.o_y * x_res + imc.o_x), w.data.eq(Cat(imc.o.b, imc.o.g, imc.o.r)), y.eq(Mux(flip[1], y_res - 1 - vga.o_beam_y, vga.o_beam_y)), x.eq(Mux(flip[0], x_res - 1 - vga.o_beam_x[1:], vga.o_beam_x[1:])), r.addr.eq(Mux(writing, w_addr, y * x_res + x)) ] # Apply the On-Screen Display (OSD) m.submodules.osd = osd = OSD() hist_col = Signal(8) m.d.comb += [ osd.x.eq(vga.o_beam_x), osd.y.eq(vga.o_beam_y), hist_col.eq(Mux((479 - osd.y) < hist.o_val[8:], 0xff, 0x00)), osd.i_r.eq(Mux(histo, Mux((hist_chan == 0) | (hist_chan == 3), hist_col, 0), Cat(Const(0, unsigned(3)), r.data[11:16]))), osd.i_g.eq(Mux(histo, Mux((hist_chan == 1) | (hist_chan == 3), hist_col, 0), Cat(Const(0, unsigned(2)), r.data[5:11]))), osd.i_b.eq(Mux(histo, Mux((hist_chan == 2) | (hist_chan == 3), hist_col, 0), Cat(Const(0, unsigned(3)), r.data[0:5]))), osd.on.eq(osd_on), osd.osd_val.eq(osd_val), osd.sel.eq(osd_sel), osd.grid.eq(grid), osd.border.eq(border), osd.roi.eq(roi.en & ~histo), osd.roi_x.eq(roi.x), osd.roi_y.eq(roi.y), osd.roi_w.eq(roi.w), osd.roi_h.eq(roi.h) ] # OSD control osd_vals = Array([ccc.brightness, ccc.redness, ccc.greenness, ccc.blueness, mono, flip[0], flip[1], border, sharpness, invert, grid, filt]) with m.If(debosd.btn_down): m.d.sync += osd_on.eq(~osd_on) with m.If(osd_on): with m.If(debsel.btn_down): m.d.sync += osd_sel.eq(~osd_sel) with m.If(debup.btn_down): with m.If(~osd_sel): m.d.sync += osd_val.eq(Mux(osd_val == 0, 11, osd_val-1)) with m.Else(): with m.Switch(osd_val): for i in range(len(osd_vals)): with m.Case(i): if (len(osd_vals[i]) == 1): m.d.sync += osd_vals[i].eq(1) else: m.d.sync += osd_vals[i].eq(osd_vals[i]+1) with m.If(debdown.btn_down): with m.If(~osd_sel): m.d.sync += osd_val.eq(Mux(osd_val == 11, 0, osd_val+1)) with m.Else(): with m.Switch(osd_val): for i in range(len(osd_vals)): with m.Case(i): if (len(osd_vals[i]) == 1): m.d.sync += osd_vals[i].eq(0) else: m.d.sync += osd_vals[i].eq(osd_vals[i]-1) # Show configuration values on leds with m.Switch(osd_val): for i in range(len(osd_vals)): with m.Case(i): m.d.comb += leds.eq(osd_vals[i]) # Generate VGA signals m.d.comb += [ vga.i_clk_en.eq(1), vga.i_test_picture.eq(0), vga.i_r.eq(osd.o_r), vga.i_g.eq(osd.o_g), vga.i_b.eq(osd.o_b), vga_r.eq(vga.o_vga_r), vga_g.eq(vga.o_vga_g), vga_b.eq(vga.o_vga_b), vga_hsync.eq(vga.o_vga_hsync), vga_vsync.eq(vga.o_vga_vsync), vga_blank.eq(vga.o_vga_blank), ] # VGA to digital video converter. tmds = [Signal(2) for i in range(4)] m.submodules.vga2dvid = vga2dvid = VGA2DVID(ddr=self.ddr, shift_clock_synchronizer=False) m.d.comb += [ vga2dvid.i_red.eq(vga_r), vga2dvid.i_green.eq(vga_g), vga2dvid.i_blue.eq(vga_b), vga2dvid.i_hsync.eq(vga_hsync), vga2dvid.i_vsync.eq(vga_vsync), vga2dvid.i_blank.eq(vga_blank), tmds[3].eq(vga2dvid.o_clk), tmds[2].eq(vga2dvid.o_red), tmds[1].eq(vga2dvid.o_green), tmds[0].eq(vga2dvid.o_blue), ] # GPDI pins if (self.ddr): # Vendor specific DDR modules. # Convert SDR 2-bit input to DDR clocked 1-bit output (single-ended) # onboard GPDI. m.submodules.ddr0_clock = Instance("ODDRX1F", i_SCLK = ClockSignal("shift"), i_RST = 0b0, i_D0 = tmds[3][0], i_D1 = tmds[3][1], o_Q = self.o_gpdi_dp[3]) m.submodules.ddr0_red = Instance("ODDRX1F", i_SCLK = ClockSignal("shift"), i_RST = 0b0, i_D0 = tmds[2][0], i_D1 = tmds[2][1], o_Q = self.o_gpdi_dp[2]) m.submodules.ddr0_green = Instance("ODDRX1F", i_SCLK = ClockSignal("shift"), i_RST = 0b0, i_D0 = tmds[1][0], i_D1 = tmds[1][1], o_Q = self.o_gpdi_dp[1]) m.submodules.ddr0_blue = Instance("ODDRX1F", i_SCLK = ClockSignal("shift"), i_RST = 0b0, i_D0 = tmds[0][0], i_D1 = tmds[0][1], o_Q = self.o_gpdi_dp[0]) else: m.d.comb += [ self.o_gpdi_dp[3].eq(tmds[3][0]), self.o_gpdi_dp[2].eq(tmds[2][0]), self.o_gpdi_dp[1].eq(tmds[1][0]), self.o_gpdi_dp[0].eq(tmds[0][0]), ] return m