diff --git a/gateware/Makefile b/gateware/Makefile index 7769f13..02365fa 100644 --- a/gateware/Makefile +++ b/gateware/Makefile @@ -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 diff --git a/gateware/rtl/Makefile b/gateware/rtl/Makefile index 48491e9..702d664 100644 --- a/gateware/rtl/Makefile +++ b/gateware/rtl/Makefile @@ -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 diff --git a/gateware/rtl/picorv32/picorv32.v b/gateware/rtl/picorv32/picorv32.v index 85b6b8d..a9198a4 100644 --- a/gateware/rtl/picorv32/picorv32.v +++ b/gateware/rtl/picorv32/picorv32.v @@ -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 diff --git a/gateware/soc.py b/gateware/soc.py index 7f7c301..0ff7efa 100644 --- a/gateware/soc.py +++ b/gateware/soc.py @@ -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()