From 67c670cb5c887da8c52f0e51c7015f9ae59a5c45 Mon Sep 17 00:00:00 2001 From: Peter McGoron Date: Wed, 21 Feb 2024 23:39:21 +0000 Subject: [PATCH] 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. --- gateware/soc.py | 331 +++++++++++++++++++++++++++++------------------- 1 file changed, 199 insertions(+), 132 deletions(-) diff --git a/gateware/soc.py b/gateware/soc.py index ad3f97b..2c8dde5 100644 --- a/gateware/soc.py +++ b/gateware/soc.py @@ -5,7 +5,7 @@ # # Copyright (c) 2014-2022 Florent Kermarrec # Copyright (c) 2013-2014 Sebastien Bourdeauducq -# Copyright (c) 2019 Gabriel L. Somlo + # Copyright (c) 2015-2019 Florent Kermarrec # Copyright (c) 2020 Antmicro # Copyright (c) 2022 Victor Suarez Rovere @@ -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 + :param polarity: See . + :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 """