reorganize litex code
This commit is contained in:
parent
f5b14d51ab
commit
3785e3498d
|
@ -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.
|
|
@ -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,
|
||||||
|
)
|
|
@ -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)
|
||||||
|
|
335
gateware/soc.py
335
gateware/soc.py
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue