2024-02-22 10:35:31 -05:00
|
|
|
# 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.
|
|
|
|
|
2024-02-28 08:28:06 -05:00
|
|
|
# XXX: PicoRV32 code only handles word-sized registers correctly. Memory
|
2024-03-06 16:17:51 -05:00
|
|
|
# registers made up of multiple words are not supported right now.
|
2024-02-28 08:28:06 -05:00
|
|
|
|
2024-02-22 10:35:31 -05:00
|
|
|
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):
|
2024-03-06 16:17:51 -05:00
|
|
|
""" 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.
|
2024-02-26 01:02:48 -05:00
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
To use this module, instantiate it. Then connect the controlling master
|
|
|
|
to ``self.buses[0]``. Connect the other masters to ``self.buses[1]``, etc.
|
|
|
|
Since the buses are seperate, origins don't have to be the same, but the
|
|
|
|
size of the region will be the same as the slave interface.
|
|
|
|
"""
|
|
|
|
|
2024-03-07 10:59:08 -05:00
|
|
|
def __init__(self, slave_bus, addressing="word", name=None):
|
2024-03-06 16:17:51 -05:00
|
|
|
"""
|
|
|
|
:param slave_bus: Instance of Wishbone.Interface that connects to the
|
|
|
|
slave's bus.
|
|
|
|
:param addressing: Addressing style of the slave. Default is "word". Note
|
|
|
|
that masters may have to convert when selecting self.buses. This conversion
|
|
|
|
is not done in this module.
|
2024-03-07 10:59:08 -05:00
|
|
|
:param name: Name for debugging purposes.
|
2024-03-06 16:17:51 -05:00
|
|
|
"""
|
|
|
|
|
|
|
|
self.slave_bus = slave_bus
|
|
|
|
self.addressing=addressing
|
|
|
|
self.buses = []
|
2024-03-07 10:59:08 -05:00
|
|
|
self.name = name
|
|
|
|
|
2024-03-11 00:31:30 -04:00
|
|
|
self.master_names = []
|
|
|
|
|
2024-03-07 10:59:08 -05:00
|
|
|
self.pre_finalize_done = False
|
2024-03-06 16:17:51 -05:00
|
|
|
|
2024-03-11 00:31:30 -04:00
|
|
|
def add_master(self, name):
|
2024-03-06 16:17:51 -05:00
|
|
|
""" Adds a new master bus to the PI.
|
|
|
|
|
|
|
|
:return: The interface to the bus.
|
2024-03-11 00:31:30 -04:00
|
|
|
:param name: Name associated with this master.
|
2024-03-06 16:17:51 -05:00
|
|
|
"""
|
2024-03-07 10:59:08 -05:00
|
|
|
if self.pre_finalize_done:
|
|
|
|
raise Exception(self.name + ": Attempted to modify PreemptiveInterface after pre-finalize")
|
|
|
|
|
2024-03-11 00:31:30 -04:00
|
|
|
self.master_names.append(name)
|
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
iface = Interface(data_width=32, address_width=32, addressing=self.addressing)
|
|
|
|
self.buses.append(iface)
|
|
|
|
return iface
|
2024-03-07 10:59:08 -05:00
|
|
|
|
2024-03-11 00:31:30 -04:00
|
|
|
def pre_finalize(self, dump_name):
|
2024-03-07 10:59:08 -05:00
|
|
|
# NOTE: DUMB HACK! CSR bus logic is NOT generated when inserted at do_finalize time!
|
|
|
|
|
|
|
|
if self.pre_finalize_done:
|
|
|
|
raise Exception(self.name + ": Cannot pre-finalize twice")
|
|
|
|
self.pre_finalize_done = True
|
|
|
|
|
|
|
|
masters_len = len(self.buses)
|
|
|
|
if masters_len > 1:
|
|
|
|
self.master_select = CSRStorage(masters_len, name='master_select', description='RW bitstring of which master interconnect to connect to')
|
2024-03-11 00:31:30 -04:00
|
|
|
|
|
|
|
# FIXME: Implement PreemptiveInterfaceController module to limit proliferation
|
|
|
|
# of JSON files
|
|
|
|
with open(dump_name, 'wt') as f:
|
|
|
|
import json
|
|
|
|
json.dump(self.master_names, f)
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
def do_finalize(self):
|
2024-03-07 10:59:08 -05:00
|
|
|
if not self.pre_finalize_done:
|
|
|
|
raise Exception(self.name + ": PreemptiveInterface needs a manual call to pre_finalize()")
|
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
masters_len = len(self.buses)
|
|
|
|
if masters_len == 0:
|
|
|
|
return None
|
2024-03-07 10:59:08 -05:00
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
"""
|
|
|
|
Construct a combinatorial case statement. In verilog, the case
|
|
|
|
statment would look like
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
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
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
Case statement is a dictionary, where each key is either the
|
|
|
|
number to match or "default", which is the default case.
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
Avoiding latches:
|
|
|
|
Left hand sign (assignment) is always an input.
|
|
|
|
"""
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
def assign_for_case(current_case):
|
|
|
|
asn = [ ]
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
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
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
cases = {"default": assign_for_case(0)}
|
|
|
|
# This loop only executes cases when there is more than one master.
|
|
|
|
for i in range(1, masters_len):
|
|
|
|
cases[i] = assign_for_case(i)
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-03-06 16:17:51 -05:00
|
|
|
# If there is only one case, just connect the two interfaces as usual.
|
|
|
|
if masters_len == 1:
|
|
|
|
self.comb += cases["default"]
|
|
|
|
else:
|
|
|
|
self.comb += Case(self.master_select.storage, cases)
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-02-28 08:28:06 -05:00
|
|
|
def gen_pico_header(pico_name):
|
|
|
|
""" Generate PicoRV32 C header for this CPU from JSON file. """
|
|
|
|
import json
|
|
|
|
with open(pico_name + "_mmio.h", "wt") as out:
|
|
|
|
print('#pragma once', file=out)
|
|
|
|
|
|
|
|
with open(pico_name + ".json") as f:
|
|
|
|
js = json.load(f)
|
|
|
|
|
|
|
|
for region in js:
|
|
|
|
if js[region]["registers"] is None:
|
|
|
|
continue
|
|
|
|
origin = js[region]["origin"]
|
|
|
|
for reg in js[region]["registers"]:
|
|
|
|
macname = f"{region}_{reg}".upper()
|
|
|
|
loc = origin + js[region]["registers"][reg]["origin"]
|
|
|
|
print(f"#define {macname} (volatile uint32_t *)({loc})", file=out)
|
|
|
|
|
2024-02-25 13:58:34 -05:00
|
|
|
# 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):
|
2024-03-11 00:31:30 -04:00
|
|
|
def add_cl_params(self):
|
2024-02-26 01:02:48 -05:00
|
|
|
""" Add parameter region for control loop variables. Dumps the
|
|
|
|
region information to a JSON file `dumpname`.
|
|
|
|
"""
|
2024-03-11 00:31:30 -04:00
|
|
|
|
|
|
|
self.params.add_register("cl_I", "1", 32)
|
|
|
|
self.params.add_register("cl_P", "1", 32)
|
|
|
|
self.params.add_register("deltaT", "1", 32)
|
|
|
|
self.params.add_register("setpt", "1", 32)
|
|
|
|
self.params.add_register("zset", "2", 32)
|
|
|
|
self.params.add_register("zpos", "2", 32)
|
|
|
|
|
|
|
|
def __init__(self, name, start_addr=0x10000, irq_addr=0x10010, stackaddr=0x100FF, param_origin=0x100000):
|
2024-02-22 10:35:31 -05:00
|
|
|
self.name = name
|
|
|
|
self.masterbus = Interface(data_width=32, address_width=32, addressing="byte")
|
2024-02-25 13:58:34 -05:00
|
|
|
self.mmap = MemoryMap(self.masterbus)
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-03-11 00:31:30 -04:00
|
|
|
self.params = PeekPokeInterface()
|
|
|
|
self.param_origin = param_origin
|
|
|
|
|
|
|
|
self.params.add_register("enable", "1", 1)
|
|
|
|
self.params.add_register("trap", "", 8)
|
|
|
|
self.params.add_register("debug_adr", "", 32)
|
|
|
|
self.params.add_register("dat_w", "", 32)
|
|
|
|
self.params.add_register("pc", "", 32)
|
|
|
|
self.params.add_register("opcode", "", 32)
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-02-25 13:58:34 -05:00
|
|
|
reg_args = {}
|
2024-03-11 00:31:30 -04:00
|
|
|
for num, reg in enumerate(["ra", "sp", "gp", "tp", "t0", "t1", "t2",
|
|
|
|
"s0_fp", "s1", "a0",
|
|
|
|
"a1", "a2", "a3", "a4", "a5", "a6", "a7",
|
|
|
|
"s2", "s3", "s4", "s5", "s6", "s7", "t3",
|
|
|
|
"t4", "t5", "t6",], start=1):
|
|
|
|
self.params.add_register(reg, "", 32)
|
|
|
|
reg_args[f"o_dbg_reg_x{num}"] = self.params.signals[reg]
|
2024-02-25 13:58:34 -05:00
|
|
|
|
|
|
|
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),
|
2024-03-11 00:31:30 -04:00
|
|
|
self.params.signals["debug_adr"].eq(mem_addr),
|
|
|
|
self.params.signals["dat_w"].eq(mem_wdata),
|
2024-02-22 10:35:31 -05:00
|
|
|
]
|
|
|
|
|
2024-02-25 13:58:34 -05:00
|
|
|
self.specials += Instance("picorv32",
|
2024-02-22 10:35:31 -05:00
|
|
|
p_COMPRESSED_ISA = 1,
|
|
|
|
p_ENABLE_MUL = 1,
|
2024-02-25 13:58:34 -05:00
|
|
|
p_REGS_INIT_ZERO = 1,
|
2024-02-22 10:35:31 -05:00
|
|
|
p_PROGADDR_RESET=start_addr,
|
|
|
|
p_PROGADDR_IRQ =irq_addr,
|
2024-02-25 13:58:34 -05:00
|
|
|
p_STACKADDR = stackaddr,
|
2024-03-11 00:31:30 -04:00
|
|
|
o_trap = self.params.signals["trap"],
|
2024-02-22 10:35:31 -05:00
|
|
|
|
2024-02-25 13:58:34 -05:00
|
|
|
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(),
|
2024-03-11 00:31:30 -04:00
|
|
|
i_resetn = self.params.signals["enable"],
|
2024-02-25 13:58:34 -05:00
|
|
|
|
|
|
|
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),
|
2024-02-22 10:35:31 -05:00
|
|
|
|
|
|
|
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),
|
|
|
|
|
2024-03-11 00:31:30 -04:00
|
|
|
o_dbg_insn_addr = self.params.signals["pc"],
|
|
|
|
o_dbg_insn_opcode = self.params.signals["opcode"],
|
2024-02-25 13:58:34 -05:00
|
|
|
|
|
|
|
**reg_args
|
2024-02-22 10:35:31 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
def do_finalize(self):
|
2024-03-11 00:31:30 -04:00
|
|
|
self.mmap.finalize()
|
2024-02-25 13:58:34 -05:00
|
|
|
self.mmap.dump_mmap(self.name + ".json")
|
2024-02-28 08:28:06 -05:00
|
|
|
gen_pico_header(self.name)
|