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) 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) 2020 Antmicro <www.antmicro.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.cores.clock import S7PLL, S7IDELAYCTRL
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.modules import MT41K128M16
@ -189,6 +189,7 @@ class PreemptiveInterface(Module, AutoCSR):
self.comb += Case(self.master_select.storage, cases)
class SPIMaster(Module):
""" Wrapper for the SPI master verilog code. """
def __init__(self, rst, miso, mosi, sck, ss,
polarity = 0,
phase = 0,
@ -198,6 +199,23 @@ class SPIMaster(Module):
spi_wid = 24,
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.region = SoCRegion(size=0x10, cached=False)
@ -237,136 +255,160 @@ class SPIMaster(Module):
#TODO: Generalize CSR stuff
class ControlLoopParameters(Module, AutoCSR):
def __init__(self):
self.cl_I = CSRStorage(32, description='Integral parameter')
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')
""" Interface for the Linux CPU to write parameters to the CPU
and for the CPU to write data back to the CPU without complex
locking mechanisms.
"""
def __init__(self):
self.cl_I = CSRStorage(32, description='Integral parameter')
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.region = SoCRegion(size=minbits(0x17), cached=False)
self.sync += [
If(self.bus.cyc == 1 and self.bus.stb == 1 and self.bus.ack == 0,
Case(self.bus.adr[0:4], {
0x0: self.bus.dat_r.eq(self.cl_I.storage),
0x4: self.bus.dat_r.eq(self.cl_P.storage),
0x8: self.bus.dat_r.eq(self.deltaT.storage),
0xC: self.bus.dat_r.eq(self.setpt.storage),
0x10: If(self.bus.we,
self.zset.status.eq(self.bus.dat_w)
).Else(
self.bus.dat_r.eq(self.zset.status)
),
0x14: If(self.bus.we,
self.zpos.status.eq(self.bus.dat_w),
).Else(
self.bus.dat_r.eq(self.zpos.status)
),
}),
self.bus.ack.eq(1),
).Else(
self.bus.ack.eq(0),
)
]
self.bus = Interface(data_width = 32, address_width = 32, addressing="word")
self.width = 0x20
self.sync += [
If(self.bus.cyc == 1 and self.bus.stb == 1 and self.bus.ack == 0,
Case(self.bus.adr[0:4], {
0x0: self.bus.dat_r.eq(self.cl_I.storage),
0x4: self.bus.dat_r.eq(self.cl_P.storage),
0x8: self.bus.dat_r.eq(self.deltaT.storage),
0xC: self.bus.dat_r.eq(self.setpt.storage),
0x10: If(self.bus.we,
self.zset.status.eq(self.bus.dat_w)
).Else(
self.bus.dat_r.eq(self.zset.status)
),
0x14: If(self.bus.we,
self.zpos.status.eq(self.bus.dat_w),
).Else(
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(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):
def __init__(self, bramwid=0x1000):
self.submodules.params = params = ControlLoopParameters()
self.submodules.ram = self.ram = SRAM(bramwid)
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),
},
)
def add_ram(self, name, width, origin):
mod = SRAM(width)
self.submodules += mod
self.ram_stb_cyc = CSRStatus(2)
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)
self.mmap.add_region(name, BasicRegion(width, origin, mod.bus))
bus.add_slave("picorv32_ram", ram_iface.buses[1], ram_region)
bus.add_slave("picorv32_params", params.bus, params.region)
bus.add_master("picorv32_master", self.masterbus)
# 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),
)
def add_params(self, origin):
self.submodules.params = params = ControlLoopParameters()
self.mmap.add_region('params', BasicRegion(origin, params.width, params.bus))
# dumb hack: "self.bus.finalize()" in do_finalize()
# should work but doesn't
self.submodules.picobus = InterconnectShared(
list(self.bus.masters.values()),
slaves = [(self.bus.regions[n].decoder(self.bus), s) for n, s in self.bus.slaves.items()],
register = self.bus.interconnect_register,
timeout_cycles = self.bus.timeout,
)
def do_finalize(self):
#self.bus.finalize()
jsondata = {}
for region in self.bus.regions:
d = self.bus.regions[region]
jsondata[region] = {
"origin": d.origin,
"size": d.size,
}
with open('picorv32.json', 'w') as f:
import json
json.dump(jsondata, f)
def __init__(self, name, start_addr=0x10000, irq_addr=0x10010):
self.mmap = MemoryMap()
self.name = name
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")
# NOTE: need to compile to these extact instructions
self.specials += Instance("picorv32_wb",
p_COMPRESSED_ISA = 1,
p_ENABLE_MUL = 1,
p_PROGADDR_RESET=start_addr,
p_PROGADDR_IRQ =irq_addr,
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),
)
def do_finalize(self):
self.mmap.dump_json(self.name)
self.submodules.decoder = self.mmap.bus_submodule(self.masterbus)
# Clock and Reset Generator
# 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):
self.add_constant(f"{ip_name}{seg_num}", int(ip_byte))
def add_picorv32(self):
siz = 0x1000
self.submodules.picorv32 = pr = PicoRV32(siz)
self.bus.add_slave("picorv32_ram", pr.ram_iface.buses[0],
SoCRegion(origin=None,size=siz, cached=True))
def add_blockram(self, name, size, connect_now=True):
assert not hasattr(self.submodules, name)
mod = SRAM(size)
setattr(self.submodules, name, mod)
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,
variant="a7-100",
@ -495,7 +562,7 @@ class UpsilonSoC(SoCCore):
)
self.bus.add_slave("spi0", self.spi0.bus, self.spi0.region)
self.add_picorv32()
self.add_picorv32("pico0")
def main():
""" Add modifications to SoC variables here """