diff --git a/doc/controller_manual.rst b/doc/controller_manual.rst index d8921a9..b89a26d 100644 --- a/doc/controller_manual.rst +++ b/doc/controller_manual.rst @@ -67,23 +67,25 @@ these are registers that reside in the memory region. The keys of the registers are 1. ``origin``: offset of the register from the beginning of the memory. -2. ``size``: Size of the register in bytes. Right now this is always ``4``, +2. ``bitwidth``: Size of the register in bits. Right now cannot be more than ``32``. even if the writable space is smaller. Always access with words. 3. ``rw``: True if writable and False if not. Sometimes this is not there because the writable might be dynamic or must be inferred from other properties. -4. ``direction``: Specifcally for registers that are Main-write-Pico-read - or Main-read-Pico-write. ``PR`` denotes Pico-read, ``PW`` denotes Pico-write. +4. ``direction``: For registers inside a ``PeekPokeInterface``, ``1`` for + writable by the Main CPU, ``2`` for writable by SWiC, and blank for read-only. ``pico0.json`` (and other PicoRV32 JSON files) are JSON objects whose keys are memory regions. Their values are objects with keys: 1. ``origin``: Absolute position of the memory region. -2. ``width``: Width of the memory region in bytes. +2. ``bitwidth``: Width of the memory region in bits. 3. ``registers``: Either ``null`` (uniform region, like above), or an object whose keys are the names of registers in the region. The values of these keys have the same interpretation as ``soc_subregions.json`` above. +A read only register is not necessarily constant! + ==================== System Within a Chip ==================== @@ -177,71 +179,33 @@ the instructions). To copy the machine code to ``test.bin``, execute:: riscv64-unknown-elf-objcopy -O binary -j .text test.elf test.bin -This code can now be loaded into the PicoRV32's ram. The next code sections -will be written in Micropython. +The standard library has ``load()`` as a method for each PicoRV32 instance. -First include the Micropython MMIO locations:: +First import the SoC memory locations:: from mmio import * -Next import ``machine``. You will almost always need this library.:: +Then load the file (the file needs to be uploaded to the SoC):: - import machine + pico0.load(filename) -Next import the PicoRV32 standard library. (Currently it is hard-coded for pico0, -this might change if more CPUs are required.):: +Fill in any registers:: - import picorv32 + pico0.regs.cl_I = 115200 -Next load the code into Micropython:: +Then run it:: - with open('test.bin', 'rb') as f: - prog = f.read() + pico0.enable() -This assumes that ``test.bin`` has been uploaded to the FPGA. We now turn off -the CPU and switch control of the RAM to the main CPU:: +To inspect how the core is running, use dump:: - machine.mem32[pico0_enable] = 0 - machine.mem32[pico0ram_iface_master_select] = 0 + from pprint import pprint + pprint(pico0.dump()) -Both of these values default to ``0`` on startup. - -Now the program can be loaded into the PicoRV32's ram:: - - assert len(prog) < 0xFFF - for offset, b in enumerate(prog, start=pico0_ram): - machine.mem8[offset] = b - -The first line of the code makes sure that the code will overflow from the -RAM region and write other parts of memory. (TODO: this is hardcoded, and -the size of the region should also be written to memory.) - -The loop goes though each byte of the program and writes it to the RAM, -starting at the beginning of the RAM. ``enumerate`` is just a fancy Python way -of a for loop with an increasing counter. - -As a sanity test, check that the program was written correctly:: - - for i in range(len(prog)): - assert machine.mem8[pico_ram + i] == prog[i] - -This can detect overwrite errors (a write to a read-only area will silently -fail) and cache mismatch errors. - -After the program is loaded, the CPU can finally be started:: - - machine.mem32[pico0ram_iface_master_select] = 1 - assert machine.mem8[pico0_ram] == 0 - machine.mem32[pico0_enable] = 1 - -The first line switches control of the RAM to the PicoRV32. The second line -checks if the switch worked. If this line fails, most likely the preemptive -interface is not properly connected to the PicoRV32 (or my code is buggy). -The final line starts the CPU. - -The state of the CPU can be inspected using ``picorv32.dump()``. This will -tell you if the CPU is in a trap state and what registers the CPU is currently -reading. +This will tell you about all the memory mapped registers, all the PicoRV32 +registers, the program counter, etc. It also includes the ``trap`` condition, +which is an integer whose values are defined in ``picorv32.v``. ``0`` indicate +normal execution (or stopped). ================ Computer Control diff --git a/doc/gateware.rst b/doc/gateware.rst index 9c0cb60..6b405e3 100644 --- a/doc/gateware.rst +++ b/doc/gateware.rst @@ -147,6 +147,39 @@ The Wishbone cache is very confusing and causes custom Wishbone bus code to not work properly. Since a lot of this memory is volatile you should never enable the cache (possible exception: SRAM). +--------------------------------------------------------- +Working Around LiteX using pre_finalize and mmio_closures +--------------------------------------------------------- + +LiteX runs code prior to calling ``finalize()``, such as CSR allocation, +that makes it very difficult to write procedural code without preallocating +lengths. + +Upsilon solves this with an ugly hack called ``pre_finalize``, which runs at +the end of the SoC main module instantiation. All pre_finalize functions are +put into a list which is run with no arguments and with their return result +ignored. + +``pre_finalize`` calls are usually due to ``PreemptiveInterface``, which uses +CSR registers. + +There is another ugly hack, ``mmio_closures``, which is used to generate the +``mmio.py`` library. The ``mmio.py`` library groups together relevant memory +regions and registers into instances of MicroPython classes. The only good +way to do this is to generate the code for ``mmio.py`` at instantiation time, +but the origin of each memory region is not known at instantiation time. The +functions have to be delayed until after memory locations are allocated, but +there is no hook in LiteX to do that, and the only interface I can think of +that one can use to look at the origins is ``csr.json``. + +The solution is a list of closures that return strings that will be put into +``mmio.py``. They take one argument, ``csrs``, the ``csr.json`` file as a +Python dictionary. The closures use the memory location origin in ``csrs`` +to generate code with the correct offsets. + +Note that the ``csr.json`` file casefolds the memory locations into lowercase +but keeps CSR registers as-is. + ==================== System Within a Chip ==================== @@ -313,3 +346,9 @@ I overrode finalize and now things are broken Each Migen module has a ``finalize()`` function inherited from the class. This does code generation and calls ``do_finalize()``, which is a user-defined function. + +========= +TODO List +========= + +Pseudo CSR bus for the main CPU? diff --git a/gateware/extio.py b/gateware/extio.py index 58cfc1b..15c1e8c 100644 --- a/gateware/extio.py +++ b/gateware/extio.py @@ -15,45 +15,51 @@ class Waveform(LiteXModule): by reading from RAM. """ public_registers = { - "run" : { - "origin": 0, - "size": 4, - "rw": True, - }, - "cntr": { - "origin": 0x4, - "size": 4, - "rw": False, - }, - "do_loop": { - "origin": 0x8, - "size" : 4, - "rw" : True, - }, - "finished_or_ready": { - "origin": 0xC, - "size" : 4, - "rw" : False, - }, - "wform_size": { - "origin": 0x10, - "size": 4, - "rw" : True, - }, - "timer": { - "origin": 0x14, - "size" : 4, - "rw" : False, - }, - "timer_spacing": { - "origin" : 0x18, - "size" : 4, - "rw" : True, - } + "run" : Register( + origin=0, + bitwidth=1, + rw=True, + ), + "cntr": Register( + origin=0x4, + bitwidth=16, + rw=False, + ), + "do_loop": Register( + origin=0x8, + bitwidth= 1, + rw= True, + ), + "finished_or_ready": Register( + origin=0xC, + bitwidth= 2, + rw= False, + ), + "wform_width": Register( + origin=0x10, + bitwidth=16, + rw= True, + ), + "timer": Register( + origin=0x14, + bitwidth= 16, + rw= False, + ), + "timer_spacing": Register( + origin= 0x18, + bitwidth= 16, + rw= True, + ) } width = 0x20 + def mmio(self, origin): + r = "" + for name, reg in self.public_registers.items(): + r += f'{name} = Register(loc={origin + reg.origin}, bitwidth={reg.bitwidth}, rw={reg.rw}),' + return r + def __init__(self, ram_start_addr = 0, spi_start_addr = 0x10000000, @@ -80,7 +86,7 @@ class Waveform(LiteXModule): timer = Signal(timer_wid) timer_spacing = Signal(timer_wid) - self.comb += If(b.cyc & b.stb & ~b.ack, + self.sync += If(b.cyc & b.stb & ~b.ack, Case(b.adr, { 0x0: If(b.we, run.eq(b.dat_w[0]), @@ -188,43 +194,49 @@ class SPIMaster(Module): # armed and finished with a transmission. # The second bit is the "ready" bit, when the master is # not armed and ready to be armed. - "ready_or_finished": { - "origin" : 0, - "width" : 4, - "rw": False, - }, + "ready_or_finished": Register( + origin= 0, + bitwidth= 2, + rw=False, + ), # One bit to initiate a transmission cycle. Transmission # cycles CANNOT be interrupted. - "arm" : { - "origin": 4, - "width": 4, - "rw": True, - }, + "arm" : Register( + origin=4, + bitwidth=1, + rw=True, + ), # Data sent from the SPI slave. - "from_slave": { - "origin": 8, - "width": 4, - "rw": False, - }, + "from_slave": Register( + origin=8, + bitwidth=32, + rw=False, + ), # Data sent to the SPI slave. - "to_slave": { - "origin": 0xC, - "width": 4, - "rw": True - }, + "to_slave": Register( + origin=0xC, + bitwidth=32, + rw=True + ), # Same as ready_or_finished, but halts until ready or finished # goes high. Dangerous, might cause cores to hang! - "wait_ready_or_finished": { - "origin": 0x10, - "width": 4, - "rw" : False, - }, + "wait_ready_or_finished": Register( + origin=0x10, + bitwidth=2, + rw= False, + ), } + def mmio(self, origin): + r = "" + for name, reg in self.public_registers.items(): + r += f'{name} = Register(loc={origin + reg.origin},bitwidth={reg.bitwidth},rw={reg.rw}),' + return r + """ Wrapper for the SPI master verilog code. """ def __init__(self, rst, miso, mosi, sck, ss_L, polarity = 0, diff --git a/gateware/region.py b/gateware/region.py index 5eb0edc..b73e617 100644 --- a/gateware/region.py +++ b/gateware/region.py @@ -19,6 +19,24 @@ module implements a basic Wishbone bus generator. All locations have to be added manually and there is no sanity checking. """ +class Register: + """ Register describes a register in a memory region. It must have an + origin and a bit width. + + Register stores all fields as attributes. + """ + def __init__(self, origin, bitwidth, **kwargs): + self.origin = origin + self.bitwidth = bitwidth + + # Assign all values in kwargs as attributes. + self.__dict__.update(kwargs) + + def _to_dict(self): + """ This function has an underscore in front of it in order + for it to not get picked up in this comprehension. """ + return {k: getattr(self,k) for k in dir(self) if not k.startswith("_")} + class BasicRegion: """ Simple class for storing a RAM region. """ def __init__(self, origin, size, bus=None, registers=None): @@ -29,9 +47,7 @@ class BasicRegion: (2**N - 1). :param bus: Instance of a wishbone bus interface. :param registers: Dictionary where keys are names of addressable - areas in the region, values have "offset" and "width", and - optionally other parameters that help with describing the - subregion. + areas in the region, values are instances of Register. """ self.origin = origin @@ -61,7 +77,12 @@ class BasicRegion: return lambda addr: addr[rightbits:32] == (self.origin >> rightbits) def to_dict(self): - return {"origin" : self.origin, "width": self.size, "registers": self.registers} + return { + "origin" : self.origin, + "width": self.size, + "registers": {k:v._to_dict() for k,v in self.registers.items()} + if self.registers is not None else None + } def __str__(self): return str(self.to_dict()) @@ -115,5 +136,124 @@ class MemoryMap(LiteXModule): def do_finalize(self): slaves = [(self.regions[n].decoder(), self.adapt(self.regions[n].bus)) for n in self.regions] + # TODO: timeout using InterconnectShared? self.submodules.decoder = Decoder(self.masterbus, slaves, register=True) +class PeekPokeInterface(LiteXModule): + """ Module that exposes registers to two Wishbone masters. + Registers can be written to by at most one CPU. Some of them are + read-only for both. + + NOTE: The interface only accepts up to 32 bit registers and does not + respect wstrb. All writes will be interpreted as word writes. + """ + + def __init__(self): + self.firstbus = Interface(data_width = 32, address_width = 32, addressing="byte") + self.secondbus = Interface(data_width = 32, address_width = 32, addressing="byte") + + # If an address is added, this is the next memory location + self.next_register_loc = 0 + + # Register description + self.public_registers = {} + + # Migen signals + self.signals = {} + + self.has_pre_finalize = False + + def mmio(self, origin): + r = "" + for name, reg in self.public_registers.items(): + can_write = True if reg.can_write == "1" else False + r += f'{name} = Register(loc={origin + reg.origin}, bitwidth={reg.bitwidth}, rw={can_write}),' + return r + + def add_register(self, name, can_write, bitwidth, sig=None): + """ Add a register to the memory area. + + :param name: Name of the register in the description. + :param bitwidth: Width of the register in bits. + :param can_write: Which CPU can write to it. One of "1", "2" or + empty (none). + """ + + if self.has_pre_finalize: + raise Exception("Cannot add register after pre finalization") + + if sig is None: + sig = Signal(bitwidth) + + if name in self.public_registers: + raise NameError(f"Register {name} already allocated") + + self.public_registers[name] = Register( + origin=self.next_register_loc, + bitwidth=bitwidth, + can_write=can_write, + ) + + self.signals[name] = sig + + # Each location is padded in memory space to 32 bits. + # Push every 32 bits to a new memory location. + while bitwidth > 0: + self.next_register_loc += 0x4 + bitwidth -= 32 + + def pre_finalize(self): + second_case = {"default": self.secondbus.dat_r.eq(0xFACADE)} + first_case = {"default": self.firstbus.dat_r.eq(0xEDACAF)} + + if self.has_pre_finalize: + raise Exception("Cannot pre_finalize twice") + self.has_pre_finalize = True + + for name in self.public_registers: + sig = self.signals[name] + reg = self.public_registers[name] + + if reg.bitwidth > 32: + raise Exception("Registers larger than 32 bits are not supported") + + def write_case(bus): + return If(bus.we, + sig.eq(bus.dat_w), + ).Else( + bus.dat_r.eq(sig) + ) + def read_case(bus): + return bus.dat_r.eq(sig) + + if reg.can_write == "2": + second_case[reg.origin] = write_case(self.secondbus) + first_case[reg.origin] = read_case(self.firstbus) + elif reg.can_write == "1": + second_case[reg.origin] = read_case(self.secondbus) + first_case[reg.origin] = write_case(self.firstbus) + elif reg.can_write == "": + second_case[reg.origin] = read_case(self.secondbus) + first_case[reg.origin] = read_case(self.firstbus) + else: + raise Exception("Invalid can_write: ", reg.can_write) + + self.width = round_up_to_pow_2(self.next_register_loc) + + # The width is a power of 2 (0b1000...). This bitlen is the + # number of bits to read, starting from 0. + bitlen = (self.width - 1).bit_length() + + def bus_logic(bus, cases): + self.sync += If(bus.cyc & bus.stb & ~bus.ack, + Case(bus.adr[0:bitlen], cases), + bus.ack.eq(1) + ).Elif(~bus.cyc, + bus.ack.eq(0)) + + bus_logic(self.firstbus, first_case) + bus_logic(self.secondbus, second_case) + + def do_finalize(self): + if not self.has_pre_finalize: + raise Exception("pre_finalize required") diff --git a/gateware/soc.py b/gateware/soc.py index 2b50a9f..3bb628c 100644 --- a/gateware/soc.py +++ b/gateware/soc.py @@ -176,9 +176,16 @@ class UpsilonSoC(SoCCore): """ pi = PreemptiveInterface(slave_bus, addressing=addressing, name=name) self.add_module(name, pi) - self.add_slave_with_registers(name, pi.add_master(), + self.add_slave_with_registers(name, pi.add_master("main"), SoCRegion(origin=None, size=slave_width, cached=False), slave_registers) + + def f(csrs): + # CSRs are not case-folded, but Wishbone memory regions are!! + return f'{name} = Register({csrs["csr_registers"][name + "_master_select"]["addr"]})' + + self.mmio_closures.append(f) + self.pre_finalize.append(lambda : pi.pre_finalize(name + "_main_PI.json")) return pi def add_blockram(self, name, size): @@ -189,36 +196,88 @@ class UpsilonSoC(SoCCore): pi = self.add_preemptive_interface_for_slave(name + "_PI", mod.bus, size, None, "word") + def f(csrs): + return f'{name} = FlatArea({csrs["memories"][name.lower() + "_pi"]["base"]}, {size})' + self.mmio_closures.append(f) + return mod, pi - def add_picorv32(self, name, size=0x1000, origin=0x10000): + def add_picorv32(self, name, size=0x1000, origin=0x10000, param_origin=0x100000): + """ Add a PicoRV32 core. + + :param name: Name of the PicoRV32 module in the Main CPU. + :param size: Size of the PicoRV32 RAM region. + :param origin: Start position of the PicoRV32. + :param param_origin: Origin of the PicoRV32 param region in the PicoRV32 + memory. + """ # Add PicoRV32 core - pico = PicoRV32(name, origin, origin+0x10) + pico = PicoRV32(name, origin, origin+0x10, param_origin) self.add_module(name, pico) - # Attach the register region to the main CPU. - self.add_slave_with_registers(name + "_dbg_reg", pico.debug_reg_read.bus, - SoCRegion(origin=None, size=pico.debug_reg_read.width, cached=False), - pico.debug_reg_read.public_registers) + # Attach registers to main CPU at pre-finalize time. + def pre_finalize(): + pico.params.pre_finalize() + self.add_slave_with_registers(name + "_params", pico.params.firstbus, + SoCRegion(origin=None, size=pico.params.width, cached=False), + pico.params.public_registers) + pico.mmap.add_region("params", + BasicRegion(origin=pico.param_origin, size=pico.params.width, bus=pico.params.secondbus, + registers=pico.params.public_registers)) + self.pre_finalize.append(pre_finalize) # Add a Block RAM for the PicoRV32 toexecute from. ram, ram_pi = self.add_blockram(name + "_ram", size=size) + # Add this at the end so the Blockram declaration comes before this one + def f(csrs): + param_origin = csrs["memories"][f'{name.lower()}_params']["base"] + return f'{name}_params = RegisterRegion({param_origin}, {pico.params.mmio(param_origin)})\n' \ + + f'{name} = PicoRV32({name}_ram, {name}_params, {name}_ram_PI)' + self.mmio_closures.append(f) + # Allow access from the PicoRV32 to the Block RAM. pico.mmap.add_region("main", - BasicRegion(origin=origin, size=size, bus=ram_pi.add_master())) + BasicRegion(origin=origin, size=size, bus=ram_pi.add_master(name))) - def picorv32_add_cl(self, name, param_origin=0x100000): + def picorv32_add_cl(self, name): """ Add a register area containing the control loop parameters to the PicoRV32. - - :param param_origin: The origin of the parameters in the PicoRV32's - address space. """ + """ pico = self.get_module(name) - params = pico.add_cl_params(param_origin, name + "_cl.json") - self.add_slave_with_registers(name + "_cl", params.mainbus, - SoCRegion(origin=None, size=params.width, cached=False), - params.public_registers) + params = pico.add_cl_params() + + def picorv32_add_pi(self, name, region_name, pi_name, origin, width, registers): + """ Add a PreemptiveInterface master to a PicoRV32 MemoryMap region. + + :param name: Name of the PicoRV32 module. + :param region_name: Name of the region in the PicoRV32 MMAP. + :param pi_name: Name of the PreemptiveInterface module in the main CPU. + :param origin: Origin of the memory region in the PicoRV32. + :param width: Width of the region in the PicoRV32. + :param registers: Registers of the region. + """ + pico = self.get_module(name) + pi = self.get_module(pi_name) + + pico.mmap.add_region(region_name, + BasicRegion(origin=origin, size=width, + bus=pi.add_master(name), registers=registers)) + + def add_spi_master(self, name, **kwargs): + spi = SPIMaster(**kwargs) + self.add_module(name, spi) + + pi = self.add_preemptive_interface_for_slave(name + "_PI", spi.bus, + spi.width, spi.public_registers, "byte") + + def f(csrs): + wid = kwargs["spi_wid"] + origin = csrs["memories"][name.lower() + "_pi"]['base'] + return f'{name} = SPI({wid}, {origin}, {spi.mmio(origin)})' + self.mmio_closures.append(f) + + return spi, pi def add_AD5791(self, name, **kwargs): """ Adds an AD5791 SPI master to the SoC. @@ -227,13 +286,7 @@ class UpsilonSoC(SoCCore): """ args = SPIMaster.AD5791_PARAMS args.update(kwargs) - spi = SPIMaster(**args) - self.add_module(name, spi) - - pi = self.add_preemptive_interface_for_slave(name + "_PI", spi.bus, - spi.width, spi.public_registers, "byte") - - return spi, pi + return self.add_spi_master(name, **args) def add_LT_adc(self, name, **kwargs): """ Adds a Linear Technologies ADC SPI master to the SoC. @@ -249,19 +302,23 @@ class UpsilonSoC(SoCCore): conv_high = Signal() self.comb += conv_high.eq(~kwargs["ss_L"]) - spi = SPIMaster(**args) - self.add_module(name, spi) + return self.add_spi_master(name, **args) - pi = self.add_preemptive_interface_for_slave(name + "_PI", spi.bus, - spi.width, spi.public_registers, "byte") - return spi, pi - - def add_waveform(self, name): - wf = Waveform() + def add_waveform(self, name, ram_len, **kwargs): + kwargs['counter_max_wid'] = minbits(ram_len) + wf = Waveform(**kwargs) self.add_module(name, wf) pi = self.add_preemptive_interface_for_slave(name + "_PI", wf.slavebus, wf.width, wf.public_registers, "byte") + + bram, bram_pi = self.add_blockram(name + "_ram", ram_len) + wf.add_ram(bram_pi.add_master(name), ram_len) + + def f(csrs): + origin = csrs["memories"][name.lower() + "_pi"]["base"] + return f'{name} = RegisterRegion({origin}, {wf.mmio(origin)})' + self.mmio_closures.append(f) return wf, pi def __init__(self, @@ -287,6 +344,15 @@ class UpsilonSoC(SoCCore): # of through MemoryMap. self.soc_subregions = {} + # The SoC generates a Python module containing information about + # how to access registers from Micropython. This is a list of + # closures that print the code that will be placed in the module. + self.mmio_closures = [] + + # This is a list of closures that are run "pre-finalize", which + # is before the do_finalize() function is called. + self.pre_finalize = [] + """ These source files need to be sorted so that modules that rely on another module come later. For instance, @@ -351,23 +417,15 @@ class UpsilonSoC(SoCCore): # Add pins platform.add_extension(io) + module_reset = platform.request("module_reset") # Add control loop DACs and ADCs. self.add_picorv32("pico0") self.picorv32_add_cl("pico0") # Add waveform generator. - self.add_waveform("wf0") - self.pico0.mmap.add_region("wf0", - BasicRegion(origin=0x400000, size=self.wf0.width, - bus=self.wf0_PI.add_master(), - registers=self.wf0.public_registers)) - - # Waveform generator RAM storage - self.add_blockram("wf0_ram", 4096) - self.wf0.add_ram(self.wf0_ram_PI.add_master(), 4096) - - module_reset = platform.request("module_reset") + self.add_waveform("wf0", 4096) + self.picorv32_add_pi("pico0", "wf0", "wf0_PI", 0x400000, self.wf0.width, self.wf0.public_registers) self.add_AD5791("dac0", rst=module_reset, @@ -376,11 +434,8 @@ class UpsilonSoC(SoCCore): sck=platform.request("dac_sck_0"), ss_L=platform.request("dac_ss_L_0"), ) - self.pico0.mmap.add_region("dac0", - BasicRegion(origin=0x200000, size=self.dac0.width, - bus=self.dac0_PI.add_master(), - registers=self.dac0.public_registers)) - self.wf0.add_spi(self.dac0_PI.add_master()) + self.picorv32_add_pi("pico0", "dac0", "dac0_PI", 0x200000, self.dac0.width, self.dac0.public_registers) + self.wf0.add_spi(self.dac0_PI.add_master("wf0")) self.add_LT_adc("adc0", rst=module_reset, @@ -389,51 +444,37 @@ class UpsilonSoC(SoCCore): ss_L=platform.request("adc_conv_0"), spi_wid=18, ) - self.pico0.mmap.add_region("adc0", - BasicRegion(origin=0x300000, size=self.adc0.width, - bus=self.adc0_PI.add_master(), - registers=self.adc0.public_registers)) + self.picorv32_add_pi("pico0", "adc0", "adc0_PI", 0x300000, self.adc0.width, self.adc0.public_registers) - # Pre-finalizations. Very bad hacks. - self.pico0_ram_PI.pre_finalize() - self.dac0_PI.pre_finalize() - self.adc0_PI.pre_finalize() - self.wf0_ram_PI.pre_finalize() - self.wf0_PI.pre_finalize() + # Run pre-finalize + for f in self.pre_finalize: + f() def do_finalize(self): with open('soc_subregions.json', 'wt') as f: - json.dump(self.soc_subregions, f) + regions = self.soc_subregions.copy() + for k in regions: + if regions[k] is not None: + regions[k] = {name : reg._to_dict() for name, reg in regions[k].items()} + json.dump(regions, f) -def generate_main_cpu_include(csr_file): +def generate_main_cpu_include(closures, csr_file): """ Generate Micropython include from a JSON file. """ with open('mmio.py', 'wt') as out: - print("from micropython import const", file=out) + print("from registers import *", file=out) + print("from waveform import *", file=out) + print("from picorv32 import *", file=out) + print("from spi import *", file=out) with open(csr_file, 'rt') as f: csrs = json.load(f) - for key in csrs["csr_registers"]: - if key.startswith("pico0"): - print(f'{key} = const({csrs["csr_registers"][key]["addr"]})', file=out) + for f in closures: + print(f(csrs), file=out) - with open('soc_subregions.json', 'rt') as f: - subregions = json.load(f) +from config import config +soc =UpsilonSoC(**config) +builder = Builder(soc, csr_json="csr.json", compile_software=True, compile_gateware=True) +builder.build() - for key in subregions: - if subregions[key] is None: - print(f'{key} = const({csrs["memories"][key.lower()]["base"]})', file=out) - else: - print(f'{key}_base = const({csrs["memories"][key.lower()]["base"]})', file=out) - print(f'{key} = {subregions[key].__repr__()}', file=out) - -def main(): - from config import config - soc =UpsilonSoC(**config) - builder = Builder(soc, csr_json="csr.json", compile_software=True) - builder.build() - - generate_main_cpu_include("csr.json") - -if __name__ == "__main__": - main() +generate_main_cpu_include(soc.mmio_closures, "csr.json") diff --git a/gateware/swic.py b/gateware/swic.py index b4a2f66..11b57a1 100644 --- a/gateware/swic.py +++ b/gateware/swic.py @@ -40,21 +40,26 @@ class PreemptiveInterface(LiteXModule): self.buses = [] self.name = name + self.master_names = [] + self.pre_finalize_done = False - def add_master(self): + def add_master(self, name): """ Adds a new master bus to the PI. :return: The interface to the bus. + :param name: Name associated with this master. """ if self.pre_finalize_done: raise Exception(self.name + ": Attempted to modify PreemptiveInterface after pre-finalize") + self.master_names.append(name) + iface = Interface(data_width=32, address_width=32, addressing=self.addressing) self.buses.append(iface) return iface - def pre_finalize(self): + def pre_finalize(self, dump_name): # NOTE: DUMB HACK! CSR bus logic is NOT generated when inserted at do_finalize time! if self.pre_finalize_done: @@ -64,6 +69,12 @@ class PreemptiveInterface(LiteXModule): 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') + + # 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) def do_finalize(self): if not self.pre_finalize_done: @@ -137,126 +148,6 @@ class PreemptiveInterface(LiteXModule): else: self.comb += Case(self.master_select.storage, cases) -class SpecialRegister: - """ Special registers used for small bits of communiciation. """ - def __init__(self, name, direction, width): - """ - :param name: Name of the register, seen in mmio.py. - :param direction: One of "PR" (pico-read main-write) or "PW" (pico-write main-read). - :param width: Width in bits, from 0 exclusive to 32 inclusive. - """ - assert direction in ["PR", "PW"] - assert 0 < width and width <= 32 - self.name = name - self.direction = direction - self.width = width - - def from_tuples(*tuples): - return [SpecialRegister(*args) for args in tuples] - -class RegisterInterface(LiteXModule): - """ Defines "registers" that are either exclusively CPU-write Pico-read - or CPU-read pico-write. These registers are stored as flip-flops. """ - - # TODO: Add no-write registers that are ready only for both ends. - # Also make more flexible signal sizes. - def __init__(self, registers): - """ - :param special_registers: List of instances of SpecialRegister. - """ - - self.picobus = Interface(data_width = 32, address_width = 32, addressing="byte") - self.mainbus = Interface(data_width = 32, address_width = 32, addressing="byte") - - pico_case = {"default": self.picobus.dat_r.eq(0xFACADE)} - main_case = {"default": self.picobus.dat_r.eq(0xEDACAF)} - - # Tuple list of (SpecialRegister, offset) - self.registers = [(reg, num*0x4) for num, reg in enumerate(registers)] - - for reg, off in self.registers: - # Round up the width of the stored signal to a multiple of 8. - wid = round_up_to_word(reg.width) - sig = Signal(wid) - def make_write_case(target_bus): - """ Function to handle write selection for ``target_bus``. """ - writes = [] - if wid >= 8: - writes.append(If(target_bus.sel[0], - sig[0:8].eq(target_bus.dat_w[0:8]))) - if wid >= 16: - writes.append(If(target_bus.sel[1], - sig[8:16].eq(target_bus.dat_w[8:16]))) - if wid >= 32: - writes.append(If(target_bus.sel[2], - sig[16:24].eq(target_bus.dat_w[16:24]))) - writes.append(If(target_bus.sel[3], - sig[24:32].eq(target_bus.dat_w[24:32]))) - return writes - - if reg.direction == "PR": - pico_case[off] = self.picobus.dat_r.eq(sig) - main_case[off] = If(self.mainbus.we, - *make_write_case(self.mainbus)).Else( - self.mainbus.dat_r.eq(sig)) - else: - main_case[off] = self.mainbus.dat_r.eq(sig) - pico_case[off] = If(self.picobus.we, - *make_write_case(self.picobus)).Else( - self.picobus.dat_r.eq(sig)) - - self.width = round_up_to_pow_2(sum([off for _, off in self.registers])) - # Since array indices are exclusive in python (unlike in Verilog), - # use the bit length of the power of 2, not the bit length of the - # maximum addressable value. - bitlen = self.width.bit_length() - def bus_logic(bus, cases): - self.sync += If(bus.cyc & bus.stb & ~bus.ack, - Case(bus.adr[0:bitlen], cases), - bus.ack.eq(1) - ).Elif(~bus.cyc, - bus.ack.eq(0)) - - bus_logic(self.mainbus, main_case) - bus_logic(self.picobus, pico_case) - - # Generate addresses - self.public_registers = {} - for reg, off in self.registers: - self.public_registers[reg.name] = { - "width" : reg.width, - "direction" : reg.direction, - "origin": off, - } - -class RegisterRead(LiteXModule): - pico_registers = { - "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "t1", "t2", - "s0/fp", "s1", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", - "s3", "s4", "s5", "s6", "s7", "t3", "t4", "t5", "t6", - } - public_registers = {name: {"origin" : num * 4, "width" : 4, "rw": False} for num, name in enumerate(pico_registers)} - """ Inspect PicoRV32 registers via Wishbone bus. """ - 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) - - # 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), - ).Elif(self.bus.cyc != 1, - self.bus.ack.eq(0) - ) - ] - def gen_pico_header(pico_name): """ Generate PicoRV32 C header for this CPU from JSON file. """ import json @@ -284,44 +175,41 @@ def gen_pico_header(pico_name): # Copyright (c) 2018 William D. Jones # SPDX-License-Identifier: BSD-2-Clause class PicoRV32(LiteXModule): - def add_cl_params(self, origin, dumpname): + def add_cl_params(self): """ Add parameter region for control loop variables. Dumps the region information to a JSON file `dumpname`. - - :param origin: Origin of the region for the PicoRV32. - :param dumpname: File to dump offsets within the region (common to - both Pico and Main CPU). - :return: Parameter module (used for accessing metadata). """ - params = RegisterInterface( - SpecialRegister.from_tuples( - ("cl_I", "PR", 32), - ("cl_P", "PR", 32), - ("deltaT", "PR", 32), - ("setpt", "PR", 32), - ("zset", "PW", 32), - ("zpos", "PW", 32), - )) - self.add_module("cl_params", params) - self.mmap.add_region("cl_params", BasicRegion(origin, params.width, params.picobus, params.public_registers)) - return params - def __init__(self, name, start_addr=0x10000, irq_addr=0x10010, stackaddr=0x100FF): + 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): 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.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) - self.debug_reg_read = RegisterRead() reg_args = {} - for i in range(1,32): - reg_args[f"o_dbg_reg_x{i}"] = self.debug_reg_read.regs[i-1] + 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] mem_valid = Signal() mem_instr = Signal() @@ -342,11 +230,8 @@ class PicoRV32(LiteXModule): 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), + self.params.signals["debug_adr"].eq(mem_addr), + self.params.signals["dat_w"].eq(mem_wdata), ] self.specials += Instance("picorv32", @@ -356,7 +241,7 @@ class PicoRV32(LiteXModule): p_PROGADDR_RESET=start_addr, p_PROGADDR_IRQ =irq_addr, p_STACKADDR = stackaddr, - o_trap = self.trap.status, + o_trap = self.params.signals["trap"], o_mem_valid = mem_valid, o_mem_instr = mem_instr, @@ -368,7 +253,7 @@ class PicoRV32(LiteXModule): i_mem_rdata = mem_rdata, i_clk = ClockSignal(), - i_resetn = self.resetpin.storage, + i_resetn = self.params.signals["enable"], o_mem_la_read = Signal(), o_mem_la_write = Signal(), @@ -391,13 +276,13 @@ class PicoRV32(LiteXModule): 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, + o_dbg_insn_addr = self.params.signals["pc"], + o_dbg_insn_opcode = self.params.signals["opcode"], **reg_args ) def do_finalize(self): + self.mmap.finalize() self.mmap.dump_mmap(self.name + ".json") gen_pico_header(self.name) - self.mmap.finalize() diff --git a/gateware/util.py b/gateware/util.py index 8b01747..034c865 100644 --- a/gateware/util.py +++ b/gateware/util.py @@ -15,7 +15,7 @@ def round_up_to_pow_2(n): # If n is a power of 2, then n - 1 has a smaller bit length than n. # If n is not a power of 2, then n - 1 has the same bit length. l = (n - 1).bit_length() - return 1 << (l + 1) + return 1 << l def round_up_to_word(n): """ Round up to 8, 16, or 32. """ diff --git a/linux/comm.py b/linux/comm.py deleted file mode 100644 index 0ee8479..0000000 --- a/linux/comm.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2023 (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. -# -# Upsilon Micropython Standard Library. - -from mmio import * - -# Write a 20 bit twos-complement value to a DAC. -def dac_write_volt(val, num): - """ - Write a 20 bit twos-complement value to a DAC. - - :param val: Two's complement 20 bit integer. The number is bitmasked - to the appropriate length, so negative Python integers are also - accepted. This DOES NOT check if the integer actually fits in 20 - bits. - - :param num: DAC number. - :raises Exception: - """ - write_dac_send_buf(1 << 20 | (val & 0xFFFFF), num) - write_dac_arm(1, num) - write_dac_arm(0, num) - -# Read a register from a DAC. -def dac_read_reg(val, num): - write_dac_send_buf(1 << 23 | val, num) - write_dac_arm(1, num) - write_dac_arm(0, num) - return read_dac_recv_buf(num) - -# Initialize a DAC by setting it's output value to 0, and -# removing the output restriction from the settings register. -def dac_init(num): - write_dac_sel(0,num) - dac_write_volt(0, num) - write_dac_send_buf(1 << 21 | 1 << 1, num) - write_dac_arm(1, num) - write_dac_arm(0, num) - return dac_read_reg(1 << 21, num) - -# Read a value from an ADC. -def adc_read(num): - write_adc_arm(1, num) - write_adc_arm(0, num) - return read_adc_recv_buf(num) diff --git a/linux/control_loop_test.py b/linux/control_loop_test.py deleted file mode 100644 index b13961d..0000000 --- a/linux/control_loop_test.py +++ /dev/null @@ -1,31 +0,0 @@ -from mmio import * -from comm import * -from sys import argv - -# The DAC always have to be init and reset -dac_init(0) - -write_dac_sel(1 << 1, 0) -write_adc_sel(2 << 1, 0) - -def cl_cmd_write(cmd, val): - write_cl_word_in(val) - write_cl_cmd(1 << 7 | cmd) - write_cl_start_cmd(1) - while not read_cl_finish_cmd(): - print('aa') - write_cl_start_cmd(0) - -def cl_cmd_read(cmd): - write_cl_cmd(cmd) - write_cl_start_cmd(1) - while not read_cl_finish_cmd(): - print('aa') - write_cl_start_cmd(0) - return read_cl_word_out() - -cl_cmd_write(2, int(argv[1])) # Setpoint -cl_cmd_write(3, int(argv[2])) # P -cl_cmd_write(4, int(argv[3])) # I -cl_cmd_write(8, int(argv[4])) # Delay -cl_cmd_write(1, 1) diff --git a/linux/noise_test.py b/linux/noise_test.py deleted file mode 100644 index c969dfc..0000000 --- a/linux/noise_test.py +++ /dev/null @@ -1,10 +0,0 @@ -from comm import * -from time import sleep_ms - -dac_init(0) -write_adc_sel(0,0) -for i in range(-300,300): - dac_write_volt(i, 0) - for j in range(0,20): - print(i, adc_read(0)) - diff --git a/linux/picorv32.py b/linux/picorv32.py new file mode 100644 index 0000000..8d8066f --- /dev/null +++ b/linux/picorv32.py @@ -0,0 +1,35 @@ +from registers import * + +class PicoRV32(Immutable): + def __init__(self, ram, params, ram_pi): + super().__init__() + + self.ram = ram + self.ram_pi = ram_pi + self.params = params + + self.make_immutable() + + def load(self, filename, force=False): + if not force and self.params.enable == 1: + raise Exception("PicoRV32 RAM cannot be modified while running") + + self.params.enable.v = 0 + self.ram_pi.v = 0 + with open(filename, 'rb') as f: + self.ram.load(f.read()) + + def enable(self): + self.ram_pi.v = 1 + self.params.enable.v = 1 + + def dump(self): + return self.params.dump() + +def test_pico(pico, filename, cl_I): + pico.params.cl_I.v = cl_I + + pico.load(filename, force=True) + pico.enable() + + return pico.dump() diff --git a/linux/registers.py b/linux/registers.py new file mode 100644 index 0000000..25362d1 --- /dev/null +++ b/linux/registers.py @@ -0,0 +1,80 @@ +import machine + +class Immutable: + def __init__(self): + super().__setattr__("_has_init", False) + + def make_immutable(self): + self._has_init = True + + def __setattr__(self, name, val): + if hasattr(self, "_has_init") and self._has_init: + raise NameError(f'{name}: {self.__class__.__name__} is immutable') + super().__setattr__(name, val) + +class FlatArea(Immutable): + def __init__(self, origin, num_words): + super().__init__() + + self.origin = origin + self.num_words = num_words + + self.make_immutable() + + def __getitem__(self, i): + if i < 0 or i >= self.num_words*4: + raise IndexError(f"Index {i} out of bounds of {self.num_words}") + return machine.mem8[self.origin + i] + + def __setitem__(self, i, v): + if i < 0 or i >= self.num_words*4: + raise IndexError(f"Index {i} out of bounds of {self.num_words}") + machine.mem8[self.origin + i] = v + + def load(self, arr): + l = len(arr) + if l >= self.num_words: + raise IndexError(f"{l} is too large for ram region ({self.num_words})") + + for num, b in enumerate(arr): + self[num] = b + for num, b in enumerate(arr): + if self[num] != b: + raise MemoryError(f"{num}: {self[num]} != {b}") + + def dump(self): + o = self.origin + return [machine.mem32[o + i*4] for i in range(0,self.num_words)] + +class Register(Immutable): + def __init__(self, loc, **kwargs): + super().__init__() + + self.loc = loc + for k in kwargs: + setattr(self, k, kwargs[k]) + + self.make_immutable() + + @property + def v(self): + return machine.mem32[self.loc] + + @v.setter + def v(self, newval): + machine.mem32[self.loc] = newval + +class RegisterRegion(Immutable): + def __init__(self, origin, **regs): + super().__init__() + + self._origin = origin + self._names = [r for r in regs] + + for r, reg in regs.items(): + setattr(self, r, reg) + + self.make_immutable() + + def dump(self): + return {n:getattr(self,n).v for n in self._names} diff --git a/linux/spi.py b/linux/spi.py new file mode 100644 index 0000000..0c5884f --- /dev/null +++ b/linux/spi.py @@ -0,0 +1,6 @@ +from registers import * + +class SPI(RegisterRegion): + def __init__(self, spiwid, origin, **regs): + self.spiwid = spiwid + super().__init__(origin, **regs) diff --git a/linux/test.mpy b/linux/test.mpy deleted file mode 100644 index 04390fc..0000000 --- a/linux/test.mpy +++ /dev/null @@ -1,41 +0,0 @@ -from micropython import const -import machine -from time import sleep_us - -dac_sel = const(4026531844) -dac_arm = const(4026531852) -dac_fin = const(4026531848) -dac_from = const(4026531856) -dac_to = const(4026531860) - -adc_fin = const(4026531864) -adc_arm = const(4026531868) -adc_dat = const(4026531872) -adc_sel = const(4026531840) - -machine.mem8[dac_sel] = 1 - -def dac_comm(val): - machine.mem32[dac_to] = val - machine.mem8[dac_arm] = 1 - while machine.mem8[dac_fin] == 0: - pass - machine.mem8[dac_arm] = 0 - -def dac_read(val): - dac_comm(1 << 23 | val) - dac_comm(0) - v = bin(machine.mem32[dac_from]) - print(v, len(v) - 2) - -# dac_comm(0b11010010001) -dac_comm(1 << 22 | 1 << 2) -dac_comm(1 << 21 | (1 << 1)) -dac_read(1 << 21) - -machine.mem8[adc_sel] = 1 -def adc_read(): - machine.mem8[adc_arm] = 1 - sleep_us(5) - machine.mem8[adc_arm] = 0 - return machine.mem32[adc_dat] diff --git a/linux/waveform.py b/linux/waveform.py new file mode 100644 index 0000000..a022cfc --- /dev/null +++ b/linux/waveform.py @@ -0,0 +1,28 @@ +from registers import * + +class Waveform(Immutable): + def __init__(self, ram, ram_pi, regs): + super().__init__() + self.ram = ram + self.ram_pi = ram_pi + self.regs = regs + + self.make_immutable() + + def run_waveform(self, wf, timer, timer_spacing, do_loop): + self.regs.run = 0 + self.regs.do_loop = 0 + + while self.regs.finished_or_ready == 0: + pass + + self.ram_pi.v = 0 + self.ram.load(wf) + + self.regs.wform_width.v = len(wf) + self.regs.timer.v = timer + self.regs.timer_spacing.v = timer_spacing + + self.regs.do_loop.v = do_loop + self.ram_pi.v = 1 + self.regs.run.v = 1 diff --git a/swic/load_exec.py b/swic/load_exec.py deleted file mode 100644 index e25d76e..0000000 --- a/swic/load_exec.py +++ /dev/null @@ -1,23 +0,0 @@ -import machine -from mmio import * - -def read_file(filename): - with open(filename, 'rb') as f: - return f.read() - -def run_program(prog, cl_I): - # Reset PicoRV32 - machine.mem32[pico0_enable] = 0 - machine.mem32[pico0ram_iface_master_select] = 0 - - offset = pico0_ram - for b in prog: - machine.mem8[offset] = b - offset += 1 - - for i in range(len(prog)): - assert machine.mem8[pico0_ram + i] == prog[i] - - machine.mem32[pico0ram_iface_master_select] = 1 - assert machine.mem8[pico0_ram] == 0 - machine.mem32[pico0_enable] = 1 diff --git a/swic/picorv32.py b/swic/picorv32.py deleted file mode 100644 index d05cde6..0000000 --- a/swic/picorv32.py +++ /dev/null @@ -1,37 +0,0 @@ -import machine -from mmio import * - -trapcode = [ - "normal", - "illegal rs1", - "illegal rs2", - "misalligned word", - "misalligned halfword", - "misalligned instruction", - "ebreak", -] - -reg_names = [ - "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "t1", "t2", - "s0/fp", "s1", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", - "s3", "s4", "s5", "s6", "s7", "t3", "t4", "t5", "t6", -] - -def u(n): - # Converts possibly signed number to unsigned 32 bit. - return hex(n & 0xFFFFFFFF) - - -# XXX: Currently hardcoded for Pico0. Future versions should accept a -# class that has fields. -def dump(): - print("Running:", "yes" if machine.mem32[pico0_enable] else "no") - print("Trap status:", trapcode[machine.mem32[pico0_trap]]) - print("Bus address:", u(machine.mem32[pico0_d_adr])) - print("Bus write value:", u(machine.mem32[pico0_d_dat_w])) - print("Instruction address:", u(machine.mem32[pico0_dbg_insn_addr])) - print("Opcode:", u(machine.mem32[pico0_dbg_insn_opcode])) - - # Skip the zero register, since it's always zero. - for num, name in enumerate(reg_names[1:],start=1): - print(name + ":", u(machine.mem32[pico0_dbg_reg + 0x4*num])) diff --git a/swic/test.c b/swic/test.c index c7235a1..df7d35a 100644 --- a/swic/test.c +++ b/swic/test.c @@ -1,10 +1,9 @@ #include +#include "../boot/pico0_mmio.h" void _start(void) { - volatile uint32_t *write = (volatile uint32_t *)(0x100000 + 0x10); - volatile uint32_t *read = (volatile uint32_t *)( 0x100000 + 0x0); - *write = *read; + *PARAMS_ZPOS = *PARAMS_CL_I; for (;;) ; }