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 """