# Copyright 2023-2024 (C) Peter McGoron # # This file is a part of Upsilon, a free and open source software project. # For license terms, refer to the files in `doc/copying` in the Upsilon # source distribution. from migen import * from litex.soc.interconnect.wishbone import Decoder, Interface from litex.gen import LiteXModule from util import * """ LiteX has an automatic Wishbone bus generator that has a lot of quality of life features, like overlap checking, relocation, multiple masters, etc. It doesn't work when the main SoC bus is also using the bus generator, so this module implements a basic Wishbone bus generator. All locations have to be added manually and there is no sanity checking. """ class Register: """ Register describes a register in a memory region. It must have an origin and a bit width. Register stores all fields as attributes. """ def __init__(self, origin, bitwidth, **kwargs): self.origin = origin self.bitwidth = bitwidth # Assign all values in kwargs as attributes. self.__dict__.update(kwargs) def _to_dict(self): """ This function has an underscore in front of it in order for it to not get picked up in this comprehension. """ return {k: getattr(self,k) for k in dir(self) if not k.startswith("_")} class BasicRegion: """ Simple class for storing a RAM region. """ def __init__(self, origin, size, bus=None, registers=None): """ :param origin: Positive integer denoting the start location of the memory region. :param size: Size of the memory region. This must be of the form (2**N - 1). :param bus: Instance of a wishbone bus interface. :param registers: Dictionary where keys are names of addressable areas in the region, values are instances of Register. """ self.origin = origin self.size = size self.bus = bus self.registers = registers def decoder(self): """ Wishbone decoder generator. The decoder looks at the high bits of the address to check what bits are passed to the slave device. Examples: Location 0x10000 has 0xFFFF of address space. origin = 0x10000, rightbits = 16. Location 0x10000 has 0xFFF of address space. origin = 0x10000, rightbits = 12. Location 0x100000 has 0x1F of address space. origin = 0x100000, rightbits = 5. """ rightbits = minbits(self.size-1) print(self.origin, self.origin >> rightbits) return lambda addr: addr[rightbits:32] == (self.origin >> rightbits) def to_dict(self): return { "origin" : self.origin, "width": self.size, "registers": {k:v._to_dict() for k,v in self.registers.items()} if self.registers is not None else None } def __str__(self): return str(self.to_dict()) class MemoryMap(LiteXModule): """ Stores the memory map of an embedded core. """ def __init__(self, masterbus): self.regions = {} self.masterbus = masterbus def add_region(self, name, region): assert name not in self.regions self.regions[name] = region def dump_mmap(self, jsonfile): with open(jsonfile, 'wt') as f: import json json.dump({k : self.regions[k].to_dict() for k in self.regions}, f) def adapt(self, target_bus): """ When a slave is "word" addressed (like SRAM), it accepts an index into a array with 32-bit elements. It DOES NOT accept a byte index. When a byte-addressed master (like the CPU) interacts with a word addressed slave, there must be an adapter in between that converts between the two. PicoRV32 will read the word that contains a byte/halfword and extract the word from it (see code assigning mem_rdata_word). """ assert target_bus.addressing in ["byte", "word"] if target_bus.addressing == "byte": return target_bus adapter = Interface(data_width=32, address_width=32, addressing="byte") self.comb += [ target_bus.adr.eq(adapter.adr >> 2), target_bus.dat_w.eq(adapter.dat_w), target_bus.we.eq(adapter.we), target_bus.sel.eq(adapter.sel), target_bus.cyc.eq(adapter.cyc), target_bus.stb.eq(adapter.stb), target_bus.cti.eq(adapter.cti), target_bus.bte.eq(adapter.bte), adapter.ack.eq(target_bus.ack), adapter.dat_r.eq(target_bus.dat_r), ] return adapter def do_finalize(self): slaves = [(self.regions[n].decoder(), self.adapt(self.regions[n].bus)) for n in self.regions] # TODO: timeout using InterconnectShared? self.submodules.decoder = Decoder(self.masterbus, slaves, register=True) class PeekPokeInterface(LiteXModule): """ Module that exposes registers to two Wishbone masters. Registers can be written to by at most one CPU. Some of them are read-only for both. NOTE: The interface only accepts up to 32 bit registers and does not respect wstrb. All writes will be interpreted as word writes. """ def __init__(self): self.firstbus = Interface(data_width = 32, address_width = 32, addressing="byte") self.secondbus = Interface(data_width = 32, address_width = 32, addressing="byte") # If an address is added, this is the next memory location self.next_register_loc = 0 # Register description self.public_registers = {} # Migen signals self.signals = {} self.has_pre_finalize = False def mmio(self, origin): r = "" for name, reg in self.public_registers.items(): can_write = True if reg.can_write == "1" else False r += f'{name} = Register(loc={origin + reg.origin}, bitwidth={reg.bitwidth}, rw={can_write}),' return r def add_register(self, name, can_write, bitwidth, sig=None): """ Add a register to the memory area. :param name: Name of the register in the description. :param bitwidth: Width of the register in bits. :param can_write: Which CPU can write to it. One of "1", "2" or empty (none). """ if self.has_pre_finalize: raise Exception("Cannot add register after pre finalization") if sig is None: sig = Signal(bitwidth) if name in self.public_registers: raise NameError(f"Register {name} already allocated") self.public_registers[name] = Register( origin=self.next_register_loc, bitwidth=bitwidth, can_write=can_write, ) self.signals[name] = sig # Each location is padded in memory space to 32 bits. # Push every 32 bits to a new memory location. while bitwidth > 0: self.next_register_loc += 0x4 bitwidth -= 32 def pre_finalize(self): second_case = {"default": self.secondbus.dat_r.eq(0xFACADE)} first_case = {"default": self.firstbus.dat_r.eq(0xEDACAF)} if self.has_pre_finalize: raise Exception("Cannot pre_finalize twice") self.has_pre_finalize = True for name in self.public_registers: sig = self.signals[name] reg = self.public_registers[name] if reg.bitwidth > 32: raise Exception("Registers larger than 32 bits are not supported") def write_case(bus): return If(bus.we, sig.eq(bus.dat_w), ).Else( bus.dat_r.eq(sig) ) def read_case(bus): return bus.dat_r.eq(sig) if reg.can_write == "2": second_case[reg.origin] = write_case(self.secondbus) first_case[reg.origin] = read_case(self.firstbus) elif reg.can_write == "1": second_case[reg.origin] = read_case(self.secondbus) first_case[reg.origin] = write_case(self.firstbus) elif reg.can_write == "": second_case[reg.origin] = read_case(self.secondbus) first_case[reg.origin] = read_case(self.firstbus) else: raise Exception("Invalid can_write: ", reg.can_write) self.width = round_up_to_pow_2(self.next_register_loc) # The width is a power of 2 (0b1000...). This bitlen is the # number of bits to read, starting from 0. bitlen = (self.width - 1).bit_length() def bus_logic(bus, cases): self.sync += If(bus.cyc & bus.stb & ~bus.ack, Case(bus.adr[0:bitlen], cases), bus.ack.eq(1) ).Elif(~bus.cyc, bus.ack.eq(0)) bus_logic(self.firstbus, first_case) bus_logic(self.secondbus, second_case) def do_finalize(self): if not self.has_pre_finalize: raise Exception("pre_finalize required")