283 lines
11 KiB
Python
283 lines
11 KiB
Python
# 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, addressing="word"):
|
|
"""
|
|
: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``.
|
|
:param addressing: Addressing style of the slave. Default is "word". Note
|
|
that masters may have to convert when selecting self.buses.
|
|
"""
|
|
|
|
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=addressing))
|
|
|
|
"""
|
|
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="byte")
|
|
self.width = 0x20
|
|
self.comb += [
|
|
self.bus.err.eq(0),
|
|
]
|
|
|
|
self.did_write = CSRStatus(8)
|
|
self.sync += [
|
|
If(self.bus.cyc & self.bus.stb & ~self.bus.ack,
|
|
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),
|
|
self.did_write.status.eq(self.did_write.status + 1),
|
|
).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(0xdeadc0de),
|
|
}),
|
|
self.bus.ack.eq(1),
|
|
).Elif(~self.bus.cyc,
|
|
self.bus.ack.eq(0),
|
|
)
|
|
]
|
|
|
|
class PicoRV32RegisterRead(LiteXModule):
|
|
def __init__(self):
|
|
self.regs = [Signal(32) for i in range(1,32)]
|
|
self.bus = Interface(data_width = 32, address_width = 32, addressing="byte")
|
|
self.width = 0x100
|
|
|
|
cases = {"default": self.bus.dat_r.eq(0xdeaddead)}
|
|
for i, reg in enumerate(self.regs):
|
|
cases[i*0x4] = self.bus.dat_r.eq(reg)
|
|
|
|
self.debug_addr = CSRStatus(32)
|
|
self.debug_cntr = CSRStatus(16)
|
|
|
|
# CYC -> transfer in progress
|
|
# STB -> data is valid on the input lines
|
|
self.sync += [
|
|
If(self.bus.cyc & self.bus.stb & ~self.bus.ack,
|
|
Case(self.bus.adr[0:7], cases),
|
|
self.bus.ack.eq(1),
|
|
self.debug_addr.status.eq(self.bus.adr),
|
|
self.debug_cntr.status.eq(self.debug_cntr.status + 1),
|
|
).Elif(self.bus.cyc != 1,
|
|
self.bus.ack.eq(0)
|
|
)
|
|
]
|
|
|
|
# Parts of this class come from LiteX.
|
|
#
|
|
# Copyright (c) 2016-2019 Florent Kermarrec <florent@enjoy-digital.fr>
|
|
# Copyright (c) 2018 Sergiusz Bazanski <q3k@q3k.org>
|
|
# Copyright (c) 2019 Antmicro <www.antmicro.com>
|
|
# Copyright (c) 2019 Tim 'mithro' Ansell <me@mith.ro>
|
|
# Copyright (c) 2018 William D. Jones <thor0505@comcast.net>
|
|
# SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
class PicoRV32(LiteXModule):
|
|
def add_params(self, origin):
|
|
params = ControlLoopParameters()
|
|
self.add_module("params", params)
|
|
self.mmap.add_region('params', BasicRegion(origin, params.width, params.bus))
|
|
|
|
def __init__(self, name, start_addr=0x10000, irq_addr=0x10010, stackaddr=0x100FF):
|
|
self.name = name
|
|
self.masterbus = Interface(data_width=32, address_width=32, addressing="byte")
|
|
self.mmap = MemoryMap(self.masterbus)
|
|
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.debug_reg_read = PicoRV32RegisterRead()
|
|
reg_args = {}
|
|
for i in range(1,32):
|
|
reg_args[f"o_dbg_reg_x{i}"] = self.debug_reg_read.regs[i-1]
|
|
|
|
mem_valid = Signal()
|
|
mem_instr = Signal()
|
|
mem_ready = Signal()
|
|
mem_addr = Signal(32)
|
|
mem_wdata = Signal(32)
|
|
mem_wstrb = Signal(4)
|
|
mem_rdata = Signal(32)
|
|
|
|
self.comb += [
|
|
self.masterbus.adr.eq(mem_addr),
|
|
self.masterbus.dat_w.eq(mem_wdata),
|
|
self.masterbus.we.eq(mem_wstrb != 0),
|
|
self.masterbus.sel.eq(mem_wstrb),
|
|
self.masterbus.cyc.eq(mem_valid),
|
|
self.masterbus.stb.eq(mem_valid),
|
|
self.masterbus.cti.eq(0),
|
|
self.masterbus.bte.eq(0),
|
|
mem_ready.eq(self.masterbus.ack),
|
|
mem_rdata.eq(self.masterbus.dat_r),
|
|
]
|
|
|
|
self.comb += [
|
|
self.d_adr.status.eq(mem_addr),
|
|
self.d_dat_w.status.eq(mem_wdata),
|
|
]
|
|
|
|
# NOTE: need to compile to these extact instructions
|
|
self.specials += Instance("picorv32",
|
|
p_COMPRESSED_ISA = 1,
|
|
p_ENABLE_MUL = 1,
|
|
p_REGS_INIT_ZERO = 1,
|
|
p_PROGADDR_RESET=start_addr,
|
|
p_PROGADDR_IRQ =irq_addr,
|
|
p_STACKADDR = stackaddr,
|
|
o_trap = self.trap.status,
|
|
|
|
o_mem_valid = mem_valid,
|
|
o_mem_instr = mem_instr,
|
|
i_mem_ready = mem_ready,
|
|
|
|
o_mem_addr = mem_addr,
|
|
o_mem_wdata = mem_wdata,
|
|
o_mem_wstrb = mem_wstrb,
|
|
i_mem_rdata = mem_rdata,
|
|
|
|
i_clk = ClockSignal(),
|
|
i_resetn = self.resetpin.storage,
|
|
|
|
o_mem_la_read = Signal(),
|
|
o_mem_la_write = Signal(),
|
|
o_mem_la_addr = Signal(32),
|
|
o_mem_la_wdata = Signal(32),
|
|
o_mem_la_wstrb = Signal(4),
|
|
|
|
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_dbg_insn_addr = self.dbg_insn_addr.status,
|
|
o_dbg_insn_opcode = self.dbg_insn_opcode.status,
|
|
|
|
**reg_args
|
|
)
|
|
|
|
def do_finalize(self):
|
|
self.mmap.dump_mmap(self.name + ".json")
|
|
self.mmap.finalize()
|