Use Decoder and custom region code for PicoRV32

The PicoRV32 SoC bus generator conflicts with the main SoC bus
generator, which causes the address locations in the generated verilog file
to be different from the set locations. This code uses custom region
classes in soc.py and the Decoder class directly, which is similar to
what the finalization of the SoC class uses, and is based on the LiteEth
code does.
This commit is contained in:
Peter McGoron 2024-02-21 23:39:21 +00:00
parent 06cf8807c3
commit 67c670cb5c
1 changed files with 199 additions and 132 deletions

View File

@ -5,7 +5,7 @@
# #
# Copyright (c) 2014-2022 Florent Kermarrec <florent@enjoy-digital.fr> # Copyright (c) 2014-2022 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2013-2014 Sebastien Bourdeauducq <sb@m-labs.hk> # Copyright (c) 2013-2014 Sebastien Bourdeauducq <sb@m-labs.hk>
# Copyright (c) 2019 Gabriel L. Somlo <somlo@cmu.edu>
# Copyright (c) 2015-2019 Florent Kermarrec <florent@enjoy-digital.fr> # Copyright (c) 2015-2019 Florent Kermarrec <florent@enjoy-digital.fr>
# Copyright (c) 2020 Antmicro <www.antmicro.com> # Copyright (c) 2020 Antmicro <www.antmicro.com>
# Copyright (c) 2022 Victor Suarez Rovere <suarezvictor@gmail.com> # Copyright (c) 2022 Victor Suarez Rovere <suarezvictor@gmail.com>
@ -53,7 +53,7 @@ from litex.soc.integration.soc_core import SoCCore
from litex.soc.integration.soc import SoCRegion, SoCBusHandler, SoCIORegion from litex.soc.integration.soc import SoCRegion, SoCBusHandler, SoCIORegion
from litex.soc.cores.clock import S7PLL, S7IDELAYCTRL from litex.soc.cores.clock import S7PLL, S7IDELAYCTRL
from litex.soc.interconnect.csr import AutoCSR, Module, CSRStorage, CSRStatus from litex.soc.interconnect.csr import AutoCSR, Module, CSRStorage, CSRStatus
from litex.soc.interconnect.wishbone import Interface, SRAM, InterconnectShared from litex.soc.interconnect.wishbone import Interface, SRAM, Decoder
from litedram.phy import s7ddrphy from litedram.phy import s7ddrphy
from litedram.modules import MT41K128M16 from litedram.modules import MT41K128M16
@ -189,6 +189,7 @@ class PreemptiveInterface(Module, AutoCSR):
self.comb += Case(self.master_select.storage, cases) self.comb += Case(self.master_select.storage, cases)
class SPIMaster(Module): class SPIMaster(Module):
""" Wrapper for the SPI master verilog code. """
def __init__(self, rst, miso, mosi, sck, ss, def __init__(self, rst, miso, mosi, sck, ss,
polarity = 0, polarity = 0,
phase = 0, phase = 0,
@ -198,6 +199,23 @@ class SPIMaster(Module):
spi_wid = 24, spi_wid = 24,
spi_cycle_half_wait = 1, spi_cycle_half_wait = 1,
): ):
"""
:param rst: Reset signal.
:param miso: MISO signal.
:param mosi: MOSI signal.
:param sck: SCK signal.
:param ss: SS signal.
:param phase: Phase of SPI master. This phase is not the standard
SPI phase because it is defined in terms of the rising edge, not
the leading edge. See <https://software.mcgoron.com/peter/spi>
:param polarity: See <https://software.mcgoron.com/peter/spi>.
:param enable_miso: If ``False``, the module does not read data
from MISO into a register.
:param enable_mosi: If ``False``, the module does not write data
to MOSI from a register.
:param spi_wid: Verilog parameter: see file.
:param spi_cycle_half_wait: Verilog parameter: see file.
"""
self.bus = Interface(data_width = 32, address_width=32, addressing="word") self.bus = Interface(data_width = 32, address_width=32, addressing="word")
self.region = SoCRegion(size=0x10, cached=False) self.region = SoCRegion(size=0x10, cached=False)
@ -237,136 +255,160 @@ class SPIMaster(Module):
#TODO: Generalize CSR stuff #TODO: Generalize CSR stuff
class ControlLoopParameters(Module, AutoCSR): class ControlLoopParameters(Module, AutoCSR):
def __init__(self): """ Interface for the Linux CPU to write parameters to the CPU
self.cl_I = CSRStorage(32, description='Integral parameter') and for the CPU to write data back to the CPU without complex
self.cl_P = CSRStorage(32, description='Proportional parameter') locking mechanisms.
self.deltaT = CSRStorage(32, description='Wait parameter') """
self.setpt = CSRStorage(32, description='Setpoint') def __init__(self):
self.zset = CSRStatus(32, description='Set Z position') self.cl_I = CSRStorage(32, description='Integral parameter')
self.zpos = CSRStatus(32, description='Measured Z position') self.cl_P = CSRStorage(32, description='Proportional parameter')
self.deltaT = CSRStorage(32, description='Wait parameter')
self.setpt = CSRStorage(32, description='Setpoint')
self.zset = CSRStatus(32, description='Set Z position')
self.zpos = CSRStatus(32, description='Measured Z position')
self.bus = Interface(data_width = 32, address_width = 32, addressing="word") self.bus = Interface(data_width = 32, address_width = 32, addressing="word")
self.region = SoCRegion(size=minbits(0x17), cached=False) self.width = 0x20
self.sync += [ self.sync += [
If(self.bus.cyc == 1 and self.bus.stb == 1 and self.bus.ack == 0, If(self.bus.cyc == 1 and self.bus.stb == 1 and self.bus.ack == 0,
Case(self.bus.adr[0:4], { Case(self.bus.adr[0:4], {
0x0: self.bus.dat_r.eq(self.cl_I.storage), 0x0: self.bus.dat_r.eq(self.cl_I.storage),
0x4: self.bus.dat_r.eq(self.cl_P.storage), 0x4: self.bus.dat_r.eq(self.cl_P.storage),
0x8: self.bus.dat_r.eq(self.deltaT.storage), 0x8: self.bus.dat_r.eq(self.deltaT.storage),
0xC: self.bus.dat_r.eq(self.setpt.storage), 0xC: self.bus.dat_r.eq(self.setpt.storage),
0x10: If(self.bus.we, 0x10: If(self.bus.we,
self.zset.status.eq(self.bus.dat_w) self.zset.status.eq(self.bus.dat_w)
).Else( ).Else(
self.bus.dat_r.eq(self.zset.status) self.bus.dat_r.eq(self.zset.status)
), ),
0x14: If(self.bus.we, 0x14: If(self.bus.we,
self.zpos.status.eq(self.bus.dat_w), self.zpos.status.eq(self.bus.dat_w),
).Else( ).Else(
self.bus.dat_r.eq(self.zpos.status) self.bus.dat_r.eq(self.zpos.status)
), ),
}), "default": self.bus.dat_r.eq(0xdeadbeef),
self.bus.ack.eq(1), }),
).Else( self.bus.ack.eq(1),
self.bus.ack.eq(0), ).Else(
) self.bus.ack.eq(0),
] )
]
class BasicRegion:
""" Simple class for storing a RAM region. """
def __init__(self, origin, size, bus=None):
self.origin = origin
self.size = size
self.bus = bus
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, "size": self.size}
def __str__(self):
return str(self.to_dict())
class MemoryMap:
""" Stores the memory map of an embedded core. """
def __init__(self):
self.regions = {}
def add_region(self, name, region):
assert name not in self.regions
self.regions[name] = region
def dump_json(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 bus_submodule(self, masterbus):
""" Return a module that decodes the masterbus into the
slave devices according to their origin and start positions. """
slaves = [(self.regions[n].decoder(), self.regions[n].bus)
for n in self.regions]
return Decoder(masterbus, slaves, register=False)
class PicoRV32(Module, AutoCSR): class PicoRV32(Module, AutoCSR):
def __init__(self, bramwid=0x1000): def add_ram(self, name, width, origin):
self.submodules.params = params = ControlLoopParameters() mod = SRAM(width)
self.submodules.ram = self.ram = SRAM(bramwid) self.submodules += mod
ram_region = SoCRegion(size=bramwid, origin=0x10000, cached=True)
self.submodules.ram_iface = self.ram_iface = ram_iface = PreemptiveInterface(2, self.ram)
# This is the PicoRV32 master
self.masterbus = Interface(data_width=32, address_width=32, addressing="byte")
self.resetpin = CSRStorage(1, name="enable", description="PicoRV32 enable")
self.trap = CSRStatus(1, name="trap", description="Trap bit")
self.bus = bus = SoCBusHandler(
standard="wishbone",
data_width=32,
address_width=32,
timeout=1e6,
bursting=False,
interconnect="shared",
interconnect_register=True,
reserved_regions={
"picorv32_null_region": SoCRegion(origin=0,size=0x10000, mode="ro", cached=True),
"picorv32_io": SoCIORegion(origin=0x100000, size=0x100, mode="rw", cached=False),
},
)
self.ram_stb_cyc = CSRStatus(2) self.mmap.add_region(name, BasicRegion(width, origin, mod.bus))
self.ram_adr = CSRStatus(32)
self.comb += self.ram_stb_cyc.status.eq(ram_iface.buses[1].stb << 1 | ram_iface.buses[1].cyc)
self.comb += self.ram_adr.status.eq(ram_iface.buses[1].adr)
bus.add_slave("picorv32_ram", ram_iface.buses[1], ram_region) def add_params(self, origin):
bus.add_slave("picorv32_params", params.bus, params.region) self.submodules.params = params = ControlLoopParameters()
bus.add_master("picorv32_master", self.masterbus) self.mmap.add_region('params', BasicRegion(origin, params.width, params.bus))
# NOTE: need to compile to these extact instructions
self.specials += Instance("picorv32_wb",
p_COMPRESSED_ISA = 1,
p_ENABLE_MUL = 1,
p_PROGADDR_RESET=0x10000,
p_PROGADDR_IRQ=0x100010,
p_REGS_INIT_ZERO = 1,
o_trap = self.trap.status,
i_wb_rst_i = ~self.resetpin.storage,
i_wb_clk_i = ClockSignal(),
o_wbm_adr_o = self.masterbus.adr,
o_wbm_dat_o = self.masterbus.dat_r,
i_wbm_dat_i = self.masterbus.dat_w,
o_wbm_we_o = self.masterbus.we,
o_wbm_sel_o = self.masterbus.sel,
o_wbm_stb_o = self.masterbus.stb,
i_wbm_ack_i = self.masterbus.ack,
o_wbm_cyc_o = self.masterbus.cyc,
o_pcpi_valid = Signal(),
o_pcpi_insn = Signal(32),
o_pcpi_rs1 = Signal(32),
o_pcpi_rs2 = Signal(32),
i_pcpi_wr = 0,
i_pcpi_wait = 0,
i_pcpi_rd = 0,
i_pcpi_ready = 0,
i_irq = 0,
o_eoi = Signal(32),
o_trace_valid = Signal(),
o_trace_data = Signal(36),
o_debug_state = Signal(2),
)
# dumb hack: "self.bus.finalize()" in do_finalize() def __init__(self, name, start_addr=0x10000, irq_addr=0x10010):
# should work but doesn't self.mmap = MemoryMap()
self.submodules.picobus = InterconnectShared( self.name = name
list(self.bus.masters.values()),
slaves = [(self.bus.regions[n].decoder(self.bus), s) for n, s in self.bus.slaves.items()], self.masterbus = Interface(data_width=32, address_width=32, addressing="byte")
register = self.bus.interconnect_register,
timeout_cycles = self.bus.timeout, self.resetpin = CSRStorage(1, name="enable", description="PicoRV32 enable")
) self.trap = CSRStatus(1, name="trap", description="Trap bit")
def do_finalize(self): # NOTE: need to compile to these extact instructions
#self.bus.finalize() self.specials += Instance("picorv32_wb",
jsondata = {} p_COMPRESSED_ISA = 1,
p_ENABLE_MUL = 1,
for region in self.bus.regions: p_PROGADDR_RESET=start_addr,
d = self.bus.regions[region] p_PROGADDR_IRQ =irq_addr,
jsondata[region] = { p_REGS_INIT_ZERO = 1,
"origin": d.origin, o_trap = self.trap.status,
"size": d.size,
} i_wb_rst_i = ~self.resetpin.storage,
i_wb_clk_i = ClockSignal(),
with open('picorv32.json', 'w') as f: o_wbm_adr_o = self.masterbus.adr,
import json o_wbm_dat_o = self.masterbus.dat_r,
json.dump(jsondata, f) i_wbm_dat_i = self.masterbus.dat_w,
o_wbm_we_o = self.masterbus.we,
o_wbm_sel_o = self.masterbus.sel,
o_wbm_stb_o = self.masterbus.stb,
i_wbm_ack_i = self.masterbus.ack,
o_wbm_cyc_o = self.masterbus.cyc,
o_pcpi_valid = Signal(),
o_pcpi_insn = Signal(32),
o_pcpi_rs1 = Signal(32),
o_pcpi_rs2 = Signal(32),
i_pcpi_wr = 0,
i_pcpi_wait = 0,
i_pcpi_rd = 0,
i_pcpi_ready = 0,
i_irq = 0,
o_eoi = Signal(32),
o_trace_valid = Signal(),
o_trace_data = Signal(36),
o_debug_state = Signal(2),
)
def do_finalize(self):
self.mmap.dump_json(self.name)
self.submodules.decoder = self.mmap.bus_submodule(self.masterbus)
# Clock and Reset Generator # Clock and Reset Generator
# I don't know how this works, I only know that it does. # I don't know how this works, I only know that it does.
@ -406,11 +448,36 @@ class UpsilonSoC(SoCCore):
for seg_num, ip_byte in enumerate(ip_str.split('.'),start=1): for seg_num, ip_byte in enumerate(ip_str.split('.'),start=1):
self.add_constant(f"{ip_name}{seg_num}", int(ip_byte)) self.add_constant(f"{ip_name}{seg_num}", int(ip_byte))
def add_picorv32(self): def add_blockram(self, name, size, connect_now=True):
siz = 0x1000 assert not hasattr(self.submodules, name)
self.submodules.picorv32 = pr = PicoRV32(siz) mod = SRAM(size)
self.bus.add_slave("picorv32_ram", pr.ram_iface.buses[0], setattr(self.submodules, name, mod)
SoCRegion(origin=None,size=siz, cached=True))
if connect_now:
self.bus.add_slave(name, mod.bus,
SoCRegion(origin=None, size=size, cached=True))
return mod
def add_preemptive_interface(self, name, size, slave):
assert not hasattr(self.submodules, name)
mod = PreemptiveInterface(size, slave)
setattr(self.submodules, name, mod)
return mod
def add_picorv32(self, name, size=0x1000, origin=0x10000, param_origin=0x100000):
assert not hasattr(self.submodules, name)
pico = PicoRV32(name, origin, origin+0x10)
setattr(self.submodules, name, pico)
ram = self.add_blockram(name + "_ram", size=size, connect_now=False)
ram_iface = self.add_preemptive_interface(name + "ram_iface", 2, ram)
pico.mmap.add_region("main",
BasicRegion(origin=origin, size=size, bus=ram_iface.buses[1]))
self.bus.add_slave(name + "_ram", ram_iface.buses[0],
SoCRegion(origin=None, size=size, cached=True))
pico.add_params(param_origin)
def __init__(self, def __init__(self,
variant="a7-100", variant="a7-100",
@ -495,7 +562,7 @@ class UpsilonSoC(SoCCore):
) )
self.bus.add_slave("spi0", self.spi0.bus, self.spi0.region) self.bus.add_slave("spi0", self.spi0.bus, self.spi0.region)
self.add_picorv32() self.add_picorv32("pico0")
def main(): def main():
""" Add modifications to SoC variables here """ """ Add modifications to SoC variables here """