upsilon/gateware/region.py

120 lines
4.2 KiB
Python

# 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 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 have "offset" and "width", and
optionally other parameters that help with describing the
subregion.
"""
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": self.registers}
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]
self.submodules.decoder = Decoder(self.masterbus, slaves, register=True)