From 3785e3498dfcf9a6f8ff3978608dc4b105ee1180 Mon Sep 17 00:00:00 2001 From: Peter McGoron Date: Thu, 22 Feb 2024 15:35:31 +0000 Subject: [PATCH] reorganize litex code --- doc/swic.rst | 38 +++++ gateware/extio.py | 75 ++++++++++ gateware/region.py | 83 +++++++++++ gateware/soc.py | 335 ++------------------------------------------- gateware/swic.py | 206 ++++++++++++++++++++++++++++ gateware/util.py | 11 ++ 6 files changed, 423 insertions(+), 325 deletions(-) create mode 100644 doc/swic.rst create mode 100644 gateware/extio.py create mode 100644 gateware/region.py create mode 100644 gateware/swic.py create mode 100644 gateware/util.py diff --git a/doc/swic.rst b/doc/swic.rst new file mode 100644 index 0000000..bfd3943 --- /dev/null +++ b/doc/swic.rst @@ -0,0 +1,38 @@ +Copyright 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. + +*************************************************** + +==================== +System Within a Chip +==================== + +A *system within a chip* (SWiC) is a SoC within a SoC. Upsilon has the +capability to add SWiCs that can be controlled by the main CPU. The CPU for +the SWiC is the PicoRV32, which is a RISC-V RVI32 core with optional C, M +extensions and custom-made non-branching instructions. + +---------------- +Main CPU Control +---------------- + +The main CPU controls the SWiC using two methods: + +1. LiteX CSR Registers. This is for simple things where the main CPU should + have 100% control at all times, like starting and stopping the CPU. +2. *Preemptive interfaces* (PI), which sit in front of a Wishbone slave. The + main CPU has a LiteX CSR register which selects the Wishbone master the + Wishbone slave connects to. + +In usual operation the SWiC RAM sits behind a PI. Before the SWiC starts, the +main CPU switches the PI to itself, fills the RAM with the program, and then +switches it back to the SWiC. When the SWiC starts, the SWiC has read-write +access to the RAM. PIs are also used for connecting to external IO (like SPI). + +In the future, there will be master-read-slave-write interfaces to transfer +bulk memory from the SWiC to the main CPU (for instances, raster scanning). + +See /gateware/soc.py for implementation notes. diff --git a/gateware/extio.py b/gateware/extio.py new file mode 100644 index 0000000..fe59e0e --- /dev/null +++ b/gateware/extio.py @@ -0,0 +1,75 @@ +# 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 Interface + +from util import * + +class SPIMaster(Module): + """ Wrapper for the SPI master verilog code. """ + def __init__(self, rst, miso, mosi, sck, ss, + polarity = 0, + phase = 0, + ss_wait = 1, + enable_miso = 1, + enable_mosi = 1, + 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.addr_space_size = 0x10 + + self.comb += [ + self.bus.err.eq(0), + ] + + self.specials += Instance("spi_master_ss_wb", + p_SS_WAIT = ss_wait, + p_SS_WAIT_TIMER_LEN = minbits(ss_wait), + p_CYCLE_HALF_WAIT = spi_cycle_half_wait, + p_TIMER_LEN = minbits(spi_cycle_half_wait), + p_WID = spi_wid, + p_WID_LEN = minbits(spi_wid), + p_ENABLE_MISO = enable_miso, + p_ENABLE_MOSI = enable_mosi, + p_POLARITY = polarity, + p_PHASE = phase, + + i_clk = ClockSignal(), + i_rst_L = rst, + i_miso = miso, + o_mosi = mosi, + o_sck_wire = sck, + o_ss_L = ss, + + i_wb_cyc = self.bus.cyc, + i_wb_stb = self.bus.stb, + i_wb_we = self.bus.we, + i_wb_sel = self.bus.sel, + i_wb_addr = self.bus.adr, + i_wb_dat_w = self.bus.dat_w, + o_wb_ack = self.bus.ack, + o_wb_dat_r = self.bus.dat_r, + ) diff --git a/gateware/region.py b/gateware/region.py new file mode 100644 index 0000000..eb32fb5 --- /dev/null +++ b/gateware/region.py @@ -0,0 +1,83 @@ +# 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 + +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): + """ + :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. + """ + + 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_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 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) + diff --git a/gateware/soc.py b/gateware/soc.py index 2df0adf..d233f03 100644 --- a/gateware/soc.py +++ b/gateware/soc.py @@ -60,11 +60,9 @@ from litedram.modules import MT41K128M16 from litedram.frontend.dma import LiteDRAMDMAReader from liteeth.phy.mii import LiteEthPHYMII -from math import log2, floor - -def minbits(n): - """ Return the amount of bits necessary to store n. """ - return floor(log2(n) + 1) +from util import * +from swic import * +from extio import * """ Keep this diagram up to date! This is the wiring diagram from the ADC to @@ -93,6 +91,8 @@ generator. These pins are then connected to Verilog modules. If there is more than one pin in the Pins string, the resulting name will be a vector of pins. + +TODO: generate declaratively from constraints file. """ io = [ # ("differntial_output_low", 0, Pins("J17 J18 K15 J15 U14 V14 T13 U13 B6 E5 A3"), IOStandard("LVCMOS33")), @@ -111,318 +111,6 @@ io = [ # ("test_clock", 0, Pins("P18"), IOStandard("LVCMOS33")) ] -class PreemptiveInterface(Module, AutoCSR): - """ A preemptive interface is a manually controlled Wishbone interface - that stands between multiple masters (potentially interconnects) and a - single slave. A master controls which master (or interconnect) has access - to the slave. This is to avoid bus contention by having multiple buses. """ - - def __init__(self, masters_len, slave): - """ - :param masters_len: The amount of buses accessing this slave. This number - must be greater than one. - :param slave: The slave device. This object must have an Interface object - accessable as ``bus``. - """ - - assert masters_len > 1 - self.buses = [] - self.master_select = CSRStorage(masters_len, name='master_select', description='RW bitstring of which master interconnect to connect to') - self.slave = slave - - for i in range(masters_len): - # Add the slave interface each master interconnect sees. - self.buses.append(Interface(data_width=32, address_width=32, addressing="byte")) - - """ - Construct a combinatorial case statement. In verilog, the if - statment would look like - - always @ (*) case (master_select) - 1: begin - // Bus assignments... - end - 2: begin - // Bus assignments... - end - // more cases: - default: - // assign everything to master 0 - end - - The If statement in Migen (Python HDL) is an object with a method - called "ElseIf" and "Else", that return objects with the specified - case attached. Instead of directly writing an If statement into - the combinatorial block, the If statement is constructed in a - for loop. - - Avoiding latches: - Left hand sign (assignment) is always an input. - """ - - def assign_for_case(current_case): - asn = [ ] - - for j in range(masters_len): - if current_case == j: - asn += [ - self.slave.bus.adr.eq(self.buses[j].adr), - self.slave.bus.dat_w.eq(self.buses[j].dat_w), - self.slave.bus.cyc.eq(self.buses[j].cyc), - self.slave.bus.stb.eq(self.buses[j].stb), - self.slave.bus.we.eq(self.buses[j].we), - self.slave.bus.sel.eq(self.buses[j].sel), - self.buses[j].dat_r.eq(self.slave.bus.dat_r), - self.buses[j].ack.eq(self.slave.bus.ack), - ] - else: - asn += [ - self.buses[j].dat_r.eq(0), - self.buses[j].ack.eq(self.buses[j].cyc & self.buses[j].stb), - ] - return asn - - cases = {"default": assign_for_case(0)} - for i in range(1, masters_len): - cases[i] = assign_for_case(i) - - 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, - ss_wait = 1, - enable_miso = 1, - enable_mosi = 1, - 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) - - self.comb += [ - self.bus.err.eq(0), - ] - - self.specials += Instance("spi_master_ss_wb", - p_SS_WAIT = ss_wait, - p_SS_WAIT_TIMER_LEN = minbits(ss_wait), - p_CYCLE_HALF_WAIT = spi_cycle_half_wait, - p_TIMER_LEN = minbits(spi_cycle_half_wait), - p_WID = spi_wid, - p_WID_LEN = minbits(spi_wid), - p_ENABLE_MISO = enable_miso, - p_ENABLE_MOSI = enable_mosi, - p_POLARITY = polarity, - p_PHASE = phase, - - i_clk = ClockSignal(), - i_rst_L = rst, - i_miso = miso, - o_mosi = mosi, - o_sck_wire = sck, - o_ss_L = ss, - - i_wb_cyc = self.bus.cyc, - i_wb_stb = self.bus.stb, - i_wb_we = self.bus.we, - i_wb_sel = self.bus.sel, - i_wb_addr = self.bus.adr, - i_wb_dat_w = self.bus.dat_w, - o_wb_ack = self.bus.ack, - o_wb_dat_r = self.bus.dat_r, - ) - - #TODO: Generalize CSR stuff -class ControlLoopParameters(Module, AutoCSR): - """ 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.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 add_ram(self, name, width, origin): - mod = SRAM(width) - self.submodules += mod - - self.mmap.add_region(name, BasicRegion(width, origin, mod.bus)) - - def add_params(self, origin): - self.submodules.params = params = ControlLoopParameters() - self.mmap.add_region('params', BasicRegion(origin, params.width, params.bus)) - - 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(8, name="trap", description="Trap condition") - self.d_adr = CSRStatus(32) - self.d_dat_w = CSRStatus(32) - self.dbg_insn_addr = CSRStatus(32) - self.dbg_insn_opcode = CSRStatus(32) - - self.comb += [ - self.d_adr.status.eq(self.masterbus.adr), - self.d_dat_w.status.eq(self.masterbus.dat_w), - ] - - # 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), - - o_dbg_insn_addr = self.dbg_insn_addr.status, - o_dbg_insn_opcode = self.dbg_insn_opcode.status, - ) - - def do_finalize(self): - self.mmap.dump_json(self.name + ".json") - 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. class _CRG(Module): @@ -462,9 +150,8 @@ class UpsilonSoC(SoCCore): self.add_constant(f"{ip_name}{seg_num}", int(ip_byte)) def add_blockram(self, name, size, connect_now=True): - assert not hasattr(self.submodules, name) mod = SRAM(size) - setattr(self.submodules, name, mod) + self.add_module(name, mod) if connect_now: self.bus.add_slave(name, mod.bus, @@ -472,19 +159,17 @@ class UpsilonSoC(SoCCore): 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) + self.add_module(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) + self.add_module(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", + pico.add_region("main", BasicRegion(origin=origin, size=size, bus=ram_iface.buses[1])) self.bus.add_slave(name + "_ram", ram_iface.buses[0], @@ -573,7 +258,7 @@ class UpsilonSoC(SoCCore): platform.request("dac_sck_0"), platform.request("dac_ss_L_0"), ) - self.bus.add_slave("spi0", self.spi0.bus, self.spi0.region) + self.bus.add_slave("spi0", self.spi0.bus, SoCRegion(origin=None, size=self.spi0.addr_space_size, cached=False)) self.add_picorv32("pico0") diff --git a/gateware/swic.py b/gateware/swic.py new file mode 100644 index 0000000..5d317ba --- /dev/null +++ b/gateware/swic.py @@ -0,0 +1,206 @@ +# 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.csr import CSRStorage, CSRStatus +from litex.soc.interconnect.wishbone import Interface, SRAM, Decoder +from litex.gen import LiteXModule +from region import * + +class PreemptiveInterface(LiteXModule): + """ A preemptive interface is a manually controlled Wishbone interface + that stands between multiple masters (potentially interconnects) and a + single slave. A master controls which master (or interconnect) has access + to the slave. This is to avoid bus contention by having multiple buses. """ + + def __init__(self, masters_len, slave): + """ + :param masters_len: The amount of buses accessing this slave. This number + must be greater than one. + :param slave: The slave device. This object must have an Interface object + accessable as ``bus``. + """ + + assert masters_len > 1 + self.buses = [] + self.master_select = CSRStorage(masters_len, name='master_select', description='RW bitstring of which master interconnect to connect to') + self.slave = slave + + for i in range(masters_len): + # Add the slave interface each master interconnect sees. + self.buses.append(Interface(data_width=32, address_width=32, addressing="byte")) + + """ + Construct a combinatorial case statement. In verilog, the case + statment would look like + + always @ (*) case (master_select) + 1: begin + // Assign slave to master 1, + // Assign all other masters to dummy ports + end + 2: begin + // Assign slave to master 2, + // Assign all other masters to dummy ports + end + // more cases: + default: begin + // assign slave to master 0 + // Assign all other masters to dummy ports + end + + Case statement is a dictionary, where each key is either the + number to match or "default", which is the default case. + + Avoiding latches: + Left hand sign (assignment) is always an input. + """ + + def assign_for_case(current_case): + asn = [ ] + + for j in range(masters_len): + if current_case == j: + """ Assign all inputs (for example, slave reads addr + from master) to outputs. """ + asn += [ + self.slave.bus.adr.eq(self.buses[j].adr), + self.slave.bus.dat_w.eq(self.buses[j].dat_w), + self.slave.bus.cyc.eq(self.buses[j].cyc), + self.slave.bus.stb.eq(self.buses[j].stb), + self.slave.bus.we.eq(self.buses[j].we), + self.slave.bus.sel.eq(self.buses[j].sel), + self.buses[j].dat_r.eq(self.slave.bus.dat_r), + self.buses[j].ack.eq(self.slave.bus.ack), + ] + else: + """ Master bus will always read 0 when they are not + selected, and writes are ignored. They will still + get a response to avoid timeouts. """ + asn += [ + self.buses[j].dat_r.eq(0), + self.buses[j].ack.eq(self.buses[j].cyc & self.buses[j].stb), + ] + return asn + + cases = {"default": assign_for_case(0)} + for i in range(1, masters_len): + cases[i] = assign_for_case(i) + + self.comb += Case(self.master_select.storage, cases) + +#TODO: Generalize CSR stuff +class ControlLoopParameters(LiteXModule): + """ 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. + + This is a hack and will be replaced later with a more general + memory region. + """ + 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.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 PicoRV32(LiteXModule, MemoryMap): + def add_params(self, origin): + params = ControlLoopParameters() + self.add_module("params", params) + self.add_region('params', BasicRegion(origin, params.width, params.bus)) + + def __init__(self, name, start_addr=0x10000, irq_addr=0x10010): + MemoryMap.__init__(self) + 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(8, name="trap", description="Trap condition") + self.d_adr = CSRStatus(32) + self.d_dat_w = CSRStatus(32) + self.dbg_insn_addr = CSRStatus(32) + self.dbg_insn_opcode = CSRStatus(32) + + self.comb += [ + self.d_adr.status.eq(self.masterbus.adr), + self.d_dat_w.status.eq(self.masterbus.dat_w), + ] + + # 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_w, + i_wbm_dat_i = self.masterbus.dat_r, + 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), + + o_dbg_insn_addr = self.dbg_insn_addr.status, + o_dbg_insn_opcode = self.dbg_insn_opcode.status, + ) + + def do_finalize(self): + self.dump_mmap(self.name + ".json") + self.submodules.decoder = self.bus_submodule(self.masterbus) diff --git a/gateware/util.py b/gateware/util.py new file mode 100644 index 0000000..f342889 --- /dev/null +++ b/gateware/util.py @@ -0,0 +1,11 @@ +# 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. + +def minbits(n): + from math import log2, floor + """ Return the amount of bits necessary to store n. """ + return floor(log2(n) + 1) +