This compiles and runson the Arty A7-100

This commit is contained in:
Peter McGoron 2024-02-18 02:34:37 +00:00
parent 4e3df09bb8
commit 0cfa172a89
4 changed files with 230 additions and 241 deletions

View File

@ -8,9 +8,10 @@
DEVICETREE_GEN_DIR=.
all: build/digilent_arty/digilent_arty.bit arty.dtb# mmio.py
all: build/digilent_arty/digilent_arty.bit arty.dtb
csr.json build/digilent_arty/digilent_arty.bit: soc.py
cd rtl && make
python3 soc.py
clean:
@ -21,6 +22,3 @@ arty.dts: csr.json
arty.dtb: arty.dts
dtc -O dtb -o arty.dtb arty.dts
#mmio.py: csr2mp.py csr.json
# python3 csr2mp.py csr.json > mmio.py

View File

@ -3,10 +3,11 @@
# For license terms, refer to the files in `doc/copying` in the Upsilon
# source distribution.
#all: make_bram
all: make_spi
make_spi:
cd spi && make codegen
dummy:
@echo empty
#make_bram:
# cd bram && make codegen

View File

@ -15,7 +15,7 @@
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Commit 29102c00a82ffd08f1e0b3c9cbac1c95c17f573b
* Derived from commit 29102c00a82ffd08f1e0b3c9cbac1c95c17f573b
*
*/
@ -2167,7 +2167,6 @@ module picorv32 #(
`endif
endmodule
`ifdef 0
// This is a simple example implementation of PICORV32_REGS.
// Use the PICORV32_REGS mechanism if you want to use custom
// memory resources to implement the processor register file.
@ -2316,9 +2315,7 @@ module picorv32_pcpi_mul #(
end
end
endmodule
`endif
`ifdef 0
module picorv32_pcpi_fast_mul #(
parameter EXTRA_MUL_FFS = 0,
parameter EXTRA_INSN_FFS = 0,
@ -2421,7 +2418,6 @@ endmodule
* picorv32_pcpi_div
***************************************************************/
`ifdef 0
module picorv32_pcpi_div (
input clk, resetn,
@ -2514,13 +2510,10 @@ module picorv32_pcpi_div (
end
endmodule
`endif
/***************************************************************
* picorv32_axi
***************************************************************/
`ifdef 0
module picorv32_axi #(
parameter [ 0:0] ENABLE_COUNTERS = 1,
parameter [ 0:0] ENABLE_COUNTERS64 = 1,
@ -2729,13 +2722,11 @@ module picorv32_axi #(
.trace_data (trace_data)
);
endmodule
`endif
/***************************************************************
* picorv32_axi_adapter
***************************************************************/
`ifdef 0
module picorv32_axi_adapter (
input clk, resetn,
@ -2814,7 +2805,6 @@ module picorv32_axi_adapter (
end
end
endmodule
`endif
/***************************************************************
* picorv32_wb

View File

@ -108,76 +108,76 @@ 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.
#
# The "assign_for_case" function constructs the body of the If
# statement. It assigns all output ports to avoid latches.
# """
#
# def assign_for_case(i):
# asn = [ ]
#
# for j in range(masters_len):
# asn += [
# self.buses[i].cyc.eq(self.slave.bus.cyc if i == j else 0),
# self.buses[i].stb.eq(self.slave.bus.stb if i == j else 0),
# self.buses[i].we.eq(self.slave.bus.we if i == j else 0),
# self.buses[i].sel.eq(self.slave.bus.sel if i == j else 0),
# self.buses[i].adr.eq(self.slave.bus.adr if i == j else 0),
# self.buses[i].dat_w.eq(self.slave.bus.dat_w if i == j else 0),
# self.buses[i].ack.eq(self.slave.bus.ack if i == j else 0),
# self.buses[i].dat_r.eq(self.slave.bus.dat_r if i == j else 0),
# ]
# 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 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.
The "assign_for_case" function constructs the body of the If
statement. It assigns all output ports to avoid latches.
"""
def assign_for_case(i):
asn = [ ]
for j in range(masters_len):
asn += [
self.buses[i].cyc.eq(self.slave.bus.cyc if i == j else 0),
self.buses[i].stb.eq(self.slave.bus.stb if i == j else 0),
self.buses[i].we.eq(self.slave.bus.we if i == j else 0),
self.buses[i].sel.eq(self.slave.bus.sel if i == j else 0),
self.buses[i].adr.eq(self.slave.bus.adr if i == j else 0),
self.buses[i].dat_w.eq(self.slave.bus.dat_w if i == j else 0),
self.buses[i].ack.eq(self.slave.bus.ack if i == j else 0),
self.buses[i].dat_r.eq(self.slave.bus.dat_r if i == j else 0),
]
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):
def __init__(self, rst, miso, mosi, sck, ss,
@ -226,151 +226,151 @@ class SPIMaster(Module):
o_wb_dat_r = self.bus.dat_r,
)
# TODO: Generalize CSR stuff
# class ControlLoopParameters(Module, AutoCSR):
# 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.region = SoCRegion(size=minbits(0x17), cached=False)
# 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)
# ),
# }),
# self.bus.ack.eq(1),
# ).Else(
# self.bus.ack.eq(0),
# )
# ]
#
# class BRAM(Module):
# """ A BRAM (block ram) is a memory store that is completely separate from
# the system RAM. They are much smaller.
# """
# def __init__(self, addr_mask, origin=None):
# """
# :param addr_mask: Mask which defines the amount of bytes accessable
# by the BRAM.
# :param origin: Origin of the BRAM module region. This is seen by the
# subordinate master, not the usual master.
# """
# self.bus = Interface(data_width=32, address_width=32, addressing="byte")
#
# # Non-IO (i.e. MMIO) regions need to be cached
# self.region = SoCRegion(origin=origin, size=addr_mask+1, cached=True)
#
# self.specials += Instance("bram",
# p_ADDR_MASK = addr_mask,
# i_clk = ClockSignal(),
# 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,
# )
#
# class PicoRV32(Module, AutoCSR):
# def __init__(self, bramwid=0x1000):
# self.submodules.params = params = ControlLoopParameters()
# self.submodules.bram = self.bram = bram = BRAM(bramwid-1, origin=0x10000)
# self.submodules.bram_iface = self.bram_iface = bram_iface = PreemptiveInterface(2, bram)
#
# # This is the PicoRV32 master
# self.masterbus = Interface(data_width=32, address_width=32, addressing="byte")
#
# self.resetpin = CSRStorage(1, name="picorv32_reset", description="PicoRV32 reset")
# self.trap = CSRStatus(1, name="picorv32_trap", description="Trap bit")
#
# self.ic = ic = SoCBusHandler(
# standard="wishbone",
# data_width=32,
# address_width=32,
# timeout=1e6,
# bursting=False,
# interconnect="shared",
# interconnect_register=True,
# reserved_regions={
# "picorv32_null_region": SoCRegion(origin=0,size=0x10000, mode="ro", cached=True),
# "picorv32_io": SoCIORegion(origin=0x100000, size=0x100, mode="rw", cached=False),
# },
# )
#
# ic.add_slave("picorv32_bram", bram_iface.buses[1], bram.region)
# ic.add_slave("picorv32_params", params.bus, params.region)
# ic.add_master("picorv32_master", self.masterbus)
#
# # NOTE: need to compile to these extact instructions
# self.specials += Instance("picorv32_wb",
# p_COMPRESSED_ISA = 1,
# p_ENABLE_MUL = 1,
# p_PROGADDR_RESET=0x10000,
# p_PROGADDR_IRQ=0x100010,
# 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),
# )
#
# def do_finalize(self):
# self.ic.finalize()
# jsondata = {}
#
# for region in self.ic.regions:
# d = self.ic.regions[region]
# jsondata[region] = {
# "origin": d.origin,
# "size": d.size,
# }
#
# with open('picorv32.json', 'w') as f:
# import json
# json.dump(jsondata, f)
#TODO: Generalize CSR stuff
class ControlLoopParameters(Module, AutoCSR):
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.region = SoCRegion(size=minbits(0x17), cached=False)
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)
),
}),
self.bus.ack.eq(1),
).Else(
self.bus.ack.eq(0),
)
]
class BRAM(Module):
""" A BRAM (block ram) is a memory store that is completely separate from
the system RAM. They are much smaller.
"""
def __init__(self, addr_mask, origin=None):
"""
:param addr_mask: Mask which defines the amount of bytes accessable
by the BRAM.
:param origin: Origin of the BRAM module region. This is seen by the
subordinate master, not the usual master.
"""
self.bus = Interface(data_width=32, address_width=32, addressing="byte")
# Non-IO (i.e. MMIO) regions need to be cached
self.region = SoCRegion(origin=origin, size=addr_mask+1, cached=True)
self.specials += Instance("bram",
p_ADDR_MASK = addr_mask,
i_clk = ClockSignal(),
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,
)
class PicoRV32(Module, AutoCSR):
def __init__(self, bramwid=0x1000):
self.submodules.params = params = ControlLoopParameters()
self.submodules.bram = self.bram = bram = BRAM(bramwid-1, origin=0x10000)
self.submodules.bram_iface = self.bram_iface = bram_iface = PreemptiveInterface(2, bram)
# This is the PicoRV32 master
self.masterbus = Interface(data_width=32, address_width=32, addressing="byte")
self.resetpin = CSRStorage(1, name="picorv32_reset", description="PicoRV32 reset")
self.trap = CSRStatus(1, name="picorv32_trap", description="Trap bit")
self.ic = ic = SoCBusHandler(
standard="wishbone",
data_width=32,
address_width=32,
timeout=1e6,
bursting=False,
interconnect="shared",
interconnect_register=True,
reserved_regions={
"picorv32_null_region": SoCRegion(origin=0,size=0x10000, mode="ro", cached=True),
"picorv32_io": SoCIORegion(origin=0x100000, size=0x100, mode="rw", cached=False),
},
)
ic.add_slave("picorv32_bram", bram_iface.buses[1], bram.region)
ic.add_slave("picorv32_params", params.bus, params.region)
ic.add_master("picorv32_master", self.masterbus)
# NOTE: need to compile to these extact instructions
self.specials += Instance("picorv32_wb",
p_COMPRESSED_ISA = 1,
p_ENABLE_MUL = 1,
p_PROGADDR_RESET=0x10000,
p_PROGADDR_IRQ=0x100010,
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),
)
def do_finalize(self):
self.ic.finalize()
jsondata = {}
for region in self.ic.regions:
d = self.ic.regions[region]
jsondata[region] = {
"origin": d.origin,
"size": d.size,
}
with open('picorv32.json', 'w') as f:
import json
json.dump(jsondata, f)
# Clock and Reset Generator
# I don't know how this works, I only know that it does.
@ -441,11 +441,11 @@ class UpsilonSoC(SoCCore):
Since Yosys doesn't support modern Verilog, only put preprocessed
(if applicable) files here.
"""
#platform.add_source("rtl/picorv32/picorv32.v")
#platform.add_source("rtl/spi/spi_master.v")
#platform.add_source("rtl/spi/spi_master_ss.v")
#platform.add_source("rtl/spi/spi_master_ss_wb.v")
#platform.add_source("rtl/bram/bram.v")
platform.add_source("rtl/picorv32/picorv32.v")
platform.add_source("rtl/spi/spi_master_preprocessed.v")
platform.add_source("rtl/spi/spi_master_ss.v")
platform.add_source("rtl/spi/spi_master_ss_wb.v")
platform.add_source("rtl/bram/bram.v")
# SoCCore does not have sane defaults (no integrated rom)
SoCCore.__init__(self,
@ -503,12 +503,12 @@ class UpsilonSoC(SoCCore):
)
self.bus.add_slave("spi0", self.spi0.bus, self.spi0.region)
#self.add_bram()
#self.add_picorv32()
self.add_bram()
self.add_picorv32()
def main():
""" Add modifications to SoC variables here """
soc =UpsilonSoC(variant="a7-35")
soc =UpsilonSoC(variant="a7-100")
builder = Builder(soc, csr_json="csr.json", compile_software=True)
builder.build()