reorganize litex code

This commit is contained in:
Peter McGoron 2024-02-22 15:35:31 +00:00
parent f5b14d51ab
commit 3785e3498d
6 changed files with 423 additions and 325 deletions

38
doc/swic.rst Normal file
View File

@ -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.

75
gateware/extio.py Normal file
View File

@ -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 <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.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,
)

83
gateware/region.py Normal file
View File

@ -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)

View File

@ -60,11 +60,9 @@ from litedram.modules import MT41K128M16
from litedram.frontend.dma import LiteDRAMDMAReader from litedram.frontend.dma import LiteDRAMDMAReader
from liteeth.phy.mii import LiteEthPHYMII from liteeth.phy.mii import LiteEthPHYMII
from math import log2, floor from util import *
from swic import *
def minbits(n): from extio import *
""" Return the amount of bits necessary to store n. """
return floor(log2(n) + 1)
""" """
Keep this diagram up to date! This is the wiring diagram from the ADC to 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 If there is more than one pin in the Pins string, the resulting
name will be a vector of pins. name will be a vector of pins.
TODO: generate declaratively from constraints file.
""" """
io = [ io = [
# ("differntial_output_low", 0, Pins("J17 J18 K15 J15 U14 V14 T13 U13 B6 E5 A3"), IOStandard("LVCMOS33")), # ("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")) # ("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 <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)
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 # 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.
class _CRG(Module): class _CRG(Module):
@ -462,9 +150,8 @@ class UpsilonSoC(SoCCore):
self.add_constant(f"{ip_name}{seg_num}", int(ip_byte)) self.add_constant(f"{ip_name}{seg_num}", int(ip_byte))
def add_blockram(self, name, size, connect_now=True): def add_blockram(self, name, size, connect_now=True):
assert not hasattr(self.submodules, name)
mod = SRAM(size) mod = SRAM(size)
setattr(self.submodules, name, mod) self.add_module(name, mod)
if connect_now: if connect_now:
self.bus.add_slave(name, mod.bus, self.bus.add_slave(name, mod.bus,
@ -472,19 +159,17 @@ class UpsilonSoC(SoCCore):
return mod return mod
def add_preemptive_interface(self, name, size, slave): def add_preemptive_interface(self, name, size, slave):
assert not hasattr(self.submodules, name)
mod = PreemptiveInterface(size, slave) mod = PreemptiveInterface(size, slave)
setattr(self.submodules, name, mod) self.add_module(name, mod)
return mod return mod
def add_picorv32(self, name, size=0x1000, origin=0x10000, param_origin=0x100000): def add_picorv32(self, name, size=0x1000, origin=0x10000, param_origin=0x100000):
assert not hasattr(self.submodules, name)
pico = PicoRV32(name, origin, origin+0x10) 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 = self.add_blockram(name + "_ram", size=size, connect_now=False)
ram_iface = self.add_preemptive_interface(name + "ram_iface", 2, ram) 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])) BasicRegion(origin=origin, size=size, bus=ram_iface.buses[1]))
self.bus.add_slave(name + "_ram", ram_iface.buses[0], 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_sck_0"),
platform.request("dac_ss_L_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") self.add_picorv32("pico0")

206
gateware/swic.py Normal file
View File

@ -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)

11
gateware/util.py Normal file
View File

@ -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)