#!/usr/bin/env python3 # This variable defines all the external programs that this module # relies on. lxbuildenv reads this variable in order to ensure # the build will finish without exiting due to missing third-party # programs. LX_DEPENDENCIES = ["riscv", "icestorm", "yosys"] # Import lxbuildenv to integrate the deps/ directory import lxbuildenv # Disable pylint's E1101, which breaks completely on migen #pylint:disable=E1101 #from migen import * from migen import Module, Signal, Instance, ClockDomain, If from migen.genlib.resetsync import AsyncResetSynchronizer from litex.build.lattice.platform import LatticePlatform from litex.build.generic_platform import Pins, IOStandard, Misc, Subsignal from litex.soc.integration import SoCCore from litex.soc.integration.builder import Builder from litex.soc.integration.soc_core import csr_map_update from litex.soc.interconnect import wishbone from valentyusb import usbcore from valentyusb.usbcore import io as usbio from valentyusb.usbcore.cpu import epmem, unififo, epfifo from valentyusb.usbcore.endpoint import EndpointType from lxsocsupport import up5kspram, cas, spi_flash import argparse _io = [ ("serial", 0, Subsignal("rx", Pins("21")), Subsignal("tx", Pins("13"), Misc("PULLUP")), IOStandard("LVCMOS33") ), ("usb", 0, Subsignal("d_p", Pins("34")), Subsignal("d_n", Pins("37")), Subsignal("pullup", Pins("35")), IOStandard("LVCMOS33") ), ("spiflash", 0, Subsignal("cs_n", Pins("16"), IOStandard("LVCMOS33")), Subsignal("clk", Pins("15"), IOStandard("LVCMOS33")), Subsignal("miso", Pins("17"), IOStandard("LVCMOS33")), Subsignal("mosi", Pins("14"), IOStandard("LVCMOS33")), Subsignal("wp", Pins("18"), IOStandard("LVCMOS33")), Subsignal("hold", Pins("19"), IOStandard("LVCMOS33")), ), ("spiflash4x", 0, Subsignal("cs_n", Pins("16"), IOStandard("LVCMOS33")), Subsignal("clk", Pins("15"), IOStandard("LVCMOS33")), Subsignal("dq", Pins("14 17 19 18"), IOStandard("LVCMOS33")), ), ("clk48", 0, Pins("44"), IOStandard("LVCMOS33")) ] _connectors = [] class _CRG(Module): def __init__(self, platform): clk12 = Signal() # # "0b00" Sets 48MHz HFOSC output # # "0b01" Sets 24MHz HFOSC output. # # "0b10" Sets 12MHz HFOSC output. # # "0b11" Sets 6MHz HFOSC output # self.specials += Instance( # "SB_HFOSC", # i_CLKHFEN=1, # i_CLKHFPU=1, # o_CLKHF=clk12, # p_CLKHF_DIV="0b10", # 12MHz # ) self.clock_domains.cd_sys = ClockDomain() self.clock_domains.cd_usb_12 = ClockDomain() self.reset = Signal() # FIXME: Use PLL, increase system clock to 32 MHz, pending nextpnr # fixes. self.comb += self.cd_sys.clk.eq(clk12) self.comb += self.cd_usb_12.clk.eq(clk12) # POR reset logic- POR generated from sys clk, POR logic feeds sys clk # reset. self.clock_domains.cd_por = ClockDomain() reset_delay = Signal(12, reset=4095) self.comb += [ self.cd_por.clk.eq(self.cd_sys.clk), self.cd_sys.rst.eq(reset_delay != 0), self.cd_usb_12.rst.eq(reset_delay != 0) ] # Divide clk48 down to clk12, to ensure they're synchronized. clk12_counter = Signal(2) self.sync.usb_48 += [ clk12_counter.eq(clk12_counter + 1), ] self.specials += Instance( "SB_GB", i_USER_SIGNAL_TO_GLOBAL_BUFFER=clk12_counter[1], o_GLOBAL_BUFFER_OUTPUT=clk12, ) self.sync.por += \ If(reset_delay != 0, reset_delay.eq(reset_delay - 1) ) self.specials += AsyncResetSynchronizer(self.cd_por, self.reset) self.clock_domains.cd_usb_48 = ClockDomain() platform.add_period_constraint(self.cd_usb_48.clk, 1e9/48e6) self.comb += [ self.cd_usb_48.clk.eq(platform.request("clk48")), ] class RandomFirmwareROM(wishbone.SRAM): """ Seed the random data with a fixed number, so different bitstreams can all share firmware. """ def __init__(self, size, seed=2373): def xorshift32(x): x = x ^ (x << 13) & 0xffffffff x = x ^ (x >> 17) & 0xffffffff x = x ^ (x << 5) & 0xffffffff return x & 0xffffffff def get_rand(x): out = 0 for i in range(32): x = xorshift32(x) if (x & 1) == 1: out = out | (1 << i) return out & 0xffffffff data = [] seed = 1 for d in range(int(size / 4)): seed = get_rand(seed) data.append(seed) print("Firmware {} bytes of random data".format(size)) wishbone.SRAM.__init__(self, size, read_only=True, init=data) class Platform(LatticePlatform): default_clk_name = "clk48" default_clk_period = 20.833 gateware_size = 0x20000 def __init__(self, toolchain="icestorm"): LatticePlatform.__init__(self, "ice40-up5k-sg48", _io, _connectors, toolchain="icestorm") def create_programmer(self): raise ValueError("programming is not supported") # def do_finalize(self, fragment): # LatticePlatform.do_finalize(self, fragment) class BaseSoC(SoCCore): csr_peripherals = [ "cpu_or_bridge", "usb", "usb_obuf", "usb_ibuf", ] csr_map_update(SoCCore.csr_map, csr_peripherals) mem_map = { "spiflash": 0x20000000, # (default shadow @0xa0000000) } mem_map.update(SoCCore.mem_map) interrupt_map = { "usb": 3, } interrupt_map.update(SoCCore.interrupt_map) def __init__(self, platform, boot_source="random_rom", **kwargs): # Disable integrated RAM as we'll add it later self.integrated_sram_size = 0 clk_freq = int(12e6) self.submodules.crg = _CRG(platform) platform.add_period_constraint(self.crg.cd_sys.clk, 1e9/clk_freq) platform.add_period_constraint(self.crg.cd_usb_12.clk, 1e9/clk_freq) SoCCore.__init__(self, platform, clk_freq, integrated_sram_size=0, **kwargs) # SPRAM- UP5K has single port RAM, might as well use it as SRAM to # free up scarce block RAM. spram_size = 128*1024 self.submodules.spram = up5kspram.Up5kSPRAM(size=spram_size) self.register_mem("sram", 0x10000000, self.spram.bus, spram_size) if boot_source == "random_rom": kwargs['cpu_reset_address']=0 bios_size = 0x2000 self.submodules.random_rom = RandomFirmwareROM(bios_size) self.add_constant("ROM_DISABLE", 1) self.register_rom(self.random_rom.bus, bios_size) elif boot_source == "bios_rom": kwargs['cpu_reset_address']=0 bios_size = 0x2000 self.add_memory_region("rom", kwargs['cpu_reset_address'], bios_size) elif boot_source == "spi_rom": bios_size = 0x8000 kwargs['cpu_reset_address']=self.mem_map["spiflash"]+platform.gateware_size self.add_memory_region("rom", kwargs['cpu_reset_address'], bios_size) self.add_constant("ROM_DISABLE", 1) self.flash_boot_address = self.mem_map["spiflash"]+platform.gateware_size+bios_size self.add_memory_region("user_flash", self.flash_boot_address, # Leave a grace area- possible one-by-off bug in add_memory_region? # Possible fix: addr < origin + length - 1 platform.spiflash_total_size - (self.flash_boot_address - self.mem_map["spiflash"]) - 0x100) else: raise ValueError("unrecognized boot_source: {}".format(boot_source)) # Add USB pads usb_pads = platform.request("usb") usb_iobuf = usbio.IoBuf(usb_pads.d_p, usb_pads.d_n, usb_pads.pullup) self.submodules.usb = epfifo.PerEndpointFifoInterface(usb_iobuf, endpoints=[EndpointType.BIDIR]) # self.submodules.usb = epmem.MemInterface(usb_iobuf) # self.submodules.usb = unififo.UsbUniFifo(usb_iobuf) # Disable final deep-sleep power down so firmware words are loaded # onto softcore's address bus. platform.toolchain.build_template[3] = "icepack -s {build_name}.txt {build_name}.bin" platform.toolchain.nextpnr_build_template[2] = "icepack -s {build_name}.txt {build_name}.bin" def main(): platform = Platform() parser = argparse.ArgumentParser( description="Build Fomu Main Gateware", add_help=False) parser.add_argument( "--bios", help="use bios as boot source", action="store_true" ) parser.add_argument( "--rand", help="use random data as boot source", action="store_false" ) parser.add_argument( "--spi", help="boot from spi", action="store_true" ) (args, rest) = parser.parse_known_args() if args.rand: boot_source="random_rom" compile_software=False elif args.bios: boot_source="bios_rom" compile_software=True elif args.spi: boot_source = "spi_rom" compile_software = False soc = BaseSoC(platform, cpu_type="vexriscv", cpu_variant="min", boot_source=boot_source) builder = Builder(soc, output_dir="build", csr_csv="test/csr.csv", compile_software=compile_software) vns = builder.build() soc.do_exit(vns) if __name__ == "__main__": main()