diff --git a/build/Makefile b/build/Makefile index 797331f..f6c86ff 100644 --- a/build/Makefile +++ b/build/Makefile @@ -49,6 +49,7 @@ hardware-get: docker cp upsilon-hardware:/home/user/upsilon/gateware/build/digilent_arty/gateware/digilent_arty.bit ../boot/ docker cp upsilon-hardware:/home/user/upsilon/gateware/arty.dtb ../boot/ docker cp upsilon-hardware:/home/user/upsilon/gateware/csr.json ../boot/ + docker cp upsilon-hardware:/home/user/upsilon/gateware/soc_subregions.json ../boot/ docker cp upsilon-hardware:/home/user/upsilon/gateware/pico0.json ../boot/ docker cp upsilon-hardware:/home/user/upsilon/gateware/mmio.py ../boot/ hardware-clean: diff --git a/doc/controller_manual.rst b/doc/controller_manual.rst index 35d94b2..8238b94 100644 --- a/doc/controller_manual.rst +++ b/doc/controller_manual.rst @@ -34,13 +34,39 @@ The ``machine`` module contains arrays called ``mem8``, ``mem16``, and ``mem32`` They are used to directly access memory locations on the main CPU bus. Note that ``mem32`` accesses must be word aligned. -Locations of interest are included as constants in the ``mmio`` module, which -is generated from memory map JSON files (``csr.json`` and others). At some -point there will be a standard library that wraps these accesses as functions. +------------------- +Accessing Registers +------------------- ----------------- +At the lowest level, a program will write to and read from "registers." These +registers control the operations of various parts of the system. + +The main bus has two register buses: "CSR" (which is the LiteX default), and +custom Wishbone code. CSR register information is in the ``csr.json`` file. +Wishbone bus registers are allocated with regions that are specified in +``csr.json``, while the actual registers inside that region are located in +``soc_subregions.json``. These should be automatically dumped to the Micropython +file ``mmio.py`` for easy usage. + +==================== +System Within a Chip +==================== + +Systems Within a Chip (**SWiCs**) are CPUs that are controlled by the main CPU +but run seperately (they have their own registers, RAM, etc.) They can be +programmed and controlled through Micropython. + +The SWiC is a RV32IMC core. Code for the SWiC needs to be compiled for a start +address of ``0x10000`` and a IRQ handler at ``0x10010``. The default length of +the SWiC region is ``0x1000`` bytes. + +Each core is given the name ``pico0``, ``pico1``, etc. The regions of each CPU +are stored in ``pico0.json``, ``pico1.json``, etc. The system used to control +slave access to the CPU bus is a CSR (and should be in ``mmio.py``). + +================ Computer Control ----------------- +================ Micropython code can be loaded manually with SSH but this gets cumbersome. Python scripts on the controlling computer connected to the Upsilon FPGA can diff --git a/gateware/Makefile b/gateware/Makefile index e11cefb..719d82a 100644 --- a/gateware/Makefile +++ b/gateware/Makefile @@ -15,7 +15,7 @@ csr.json build/digilent_arty/digilent_arty.bit: soc.py python3 soc.py mmio.py: csr.json csr2mp.py - python3 csr2mp.py csr.json > mmio.py + python3 csr2mp.py > mmio.py clean: rm -rf build csr.json arty.dts arty.dtb mmio.py diff --git a/gateware/csr2mp.py b/gateware/csr2mp.py index 2f6b14c..48e67cc 100644 --- a/gateware/csr2mp.py +++ b/gateware/csr2mp.py @@ -13,17 +13,22 @@ import collections import argparse import json -import sys -import mmio_descr -with open(sys.argv[1], 'rt') as f: - j = json.load(f) +with open('csr.json', 'rt') as f: + csrs = json.load(f) print("from micropython import const") -for key in j["csr_registers"]: +for key in csrs["csr_registers"]: if key.startswith("pico0"): - print(f'{key} = const({j["csr_registers"][key]["addr"]})') + print(f'{key} = const({csrs["csr_registers"][key]["addr"]})') -print(f'pico0_ram = const({j["memories"]["pico0_ram"]["base"]})') -print(f'pico0_dbg_reg = const({j["memories"]["pico0_dbg_reg"]["base"]})') +with open('soc_subregions.json', 'rt') as f: + subregions = json.load(f) + +for key in subregions: + if subregions[key] is None: + print(f'{key} = const({csrs["memories"][key]["base"]})') + else: + print(f'{key}_base = const({csrs["memorys"][key]["base"]})') + print(f'{key} = {subregions[key].__repr__()}') diff --git a/gateware/extio.py b/gateware/extio.py index 11f3984..09a29df 100644 --- a/gateware/extio.py +++ b/gateware/extio.py @@ -10,8 +10,51 @@ from litex.soc.interconnect.wishbone import Interface from util import * class SPIMaster(Module): + AD5791_PARAMS = { + "polarity" :0, + "phase" :1, + "spi_cycle_half_wait" : 10, + "ss_wait" : 5, + "enable_miso" : 1, + "enable_mosi" : 1, + "spi_wid" : 24, + } + + LT_ADC_PARAMS = { + "polarity" : 1, + "phase" : 0, + "spi_cycle_half_wait" : 5, + "ss_wait" : 60, + "enable_mosi" : 0, + } + + width = 0x10 + + registers = { + "finished_or_ready": { + "origin" : 0, + "width" : 4, + "rw": False, + }, + "arm" : { + "origin": 4, + "width": 4, + "rw": True, + }, + "from_slave": { + "origin": 8, + "width": 4, + "rw": False, + }, + "to_slave": { + "origin": 0xC, + "width": 4, + "rw": True + }, + } + """ Wrapper for the SPI master verilog code. """ - def __init__(self, rst, miso, mosi, sck, ss, + def __init__(self, rst, miso, mosi, sck, ss_L, polarity = 0, phase = 0, ss_wait = 1, @@ -28,8 +71,8 @@ class SPIMaster(Module): :param ss: SS signal. :param phase: Phase of SPI master. This phase is not the standard SPI phase because it is defined in terms of the rising edge, not - the leading edge. See - :param polarity: See . + the leading edge. See https://software.mcgoron.com/peter/spi + :param polarity: See https://software.mcgoron.com/peter/spi. :param enable_miso: If ``False``, the module does not read data from MISO into a register. :param enable_mosi: If ``False``, the module does not write data @@ -39,7 +82,6 @@ class SPIMaster(Module): """ self.bus = Interface(data_width = 32, address_width=32, addressing="byte") - self.addr_space_size = 0x10 self.comb += [ self.bus.err.eq(0), @@ -62,7 +104,7 @@ class SPIMaster(Module): i_miso = miso, o_mosi = mosi, o_sck_wire = sck, - o_ss_L = ss, + o_ss_L = ss_L, i_wb_cyc = self.bus.cyc, i_wb_stb = self.bus.stb, diff --git a/gateware/mmio_descr.py b/gateware/mmio_descr.py deleted file mode 100644 index 86f9fad..0000000 --- a/gateware/mmio_descr.py +++ /dev/null @@ -1,221 +0,0 @@ -import textwrap - -class Descr: - def __init__(self, name, blen, rwperm, num, descr): - """ - :param name: Name of the pin without numerical suffix. - :param blen: Bit length of the pin. - :param doc: Restructured text documentation of the register. - :param num: The amount of registers of the same type. - :param read_only: A string that must be either "read-only" or "write-write". - """ - self.name = name - self.blen = blen - self.doc = textwrap.dedent(descr) - self.num = num - self.rwperm = rwperm - - @classmethod - def from_dict(cls, jsdict, name): - return cls(name, jsdict[name]["len"], jsdict[name]["ro"], jsdict[name]["num"], jsdict[name]["doc"]) - def store_to_dict(self, d): - d[self.name] = { - "len": self.blen, - "doc": self.doc, - "num": self.num, - "ro": ro - } - -registers = [ - Descr("adc_sel", 3, "read-write", 8, """\ - Select which on-FPGA SPI master controls the ADC. - - Valid settings: - - * ``0``: ADC is controlled by MMIO registers. - * ``0b10``: ADC is controlled by MMIO registers, but conversion is - disabled. This is used to flush output from an out-of-sync ADC. - * ``0b100``: ADC 0 only. ADC is controlled by control loop."""), - Descr("adc_finished", 1, "read-only", 8, """\ - Signals that an ADC master has finished an SPI cycle. - - Values: - - * ``0``: MMIO master is either not armed or currently in a - SPI transfer. - * ``1``: MMIO master has finished. - - This flag is on only when ``adc_arm`` is high. The flag does not - mean that data has been received successfully, only that the master - has finished it's SPI transfer."""), - Descr("adc_arm", 1, "read-write", 8, """\ - Start a DAC master SPI transfer. - - If ``adc_arm`` is raised from and the master is currently not in a SPI - transfer, the SPI master will start an SPI transfer and write data - into ``adc_recv_buf``. - - If ``adc_arm`` is raised while the master is in an SPI transfer, - nothing changes. - - If ``adc_arm`` is lowered while the master is in an SPI transfer, - nothing changes. The SPI cycle will continue to execute and it will - continue to write data to ``adc_recv_buf``. - - If the SPI transfer finishes and ``adc_arm`` is still set to - 1, then ``adc_finished`` is raised to 1. If ``adc_arm`` is lowered - in this state, then ``adc_finished`` is lowered. - - Linear Technologies ADCs must not have their SPI transfers - interrupted. The transfer can be interrupted by - - 1. Interrupt the signal physically (i.e. pulling out cable connecting - the FPGA to the ADC) - 2. Reset of the ADC master - 3. Reset of the FPGA - 4. Switching ``adc_sel`` to the control loop - - If the ADC is interrupted then it will be in an unknown transfer - state. To recover from an unknown transfer state, set ``adc_sel`` - to ``0b10`` and run a SPI transfer cycle. This will run the SPI - clock and flush the ADC buffer. The only other way is to power-cycle - the ADC. - - If ``adc_sel`` is not set to 0 then the transfer will proceed - as normal, but no data will be received from the ADC."""), - Descr("adc_recv_buf", 18, "read-only", 8, """\ - ADC Master receive buffer. - - This buffer is stable if there is no ADC transfer caused by ``adc_arm`` - is in process. - - This register only changes if an SPI transfer is triggered by the MMIO - registers. SPI transfers by other masters will not affect this register. - buffer."""), - - Descr("dac_sel", 2, "read-write", 8, """\ - Select which on-FPGA SPI master controls the DAC. - - Valid settings: - - * ``0``: DAC is controlled by MMIO registers. - * ``0b10``: DAC 0 only. DAC is controlled by control loop."""), - Descr("dac_finished", 1, "read-only", 8, """\ - Signals that the DAC master has finished transmitting data. - - Values: - - * ``0``: MMIO master is either not armed or currently in a - SPI transfer. - * ``1``: MMIO master has finished transmitting. - - This flag is on only when ``dac_arm`` is high. The flag does not - mean that data has been received or transmitted successfully, only that - the master has finished it's SPI transfer."""), - Descr("dac_arm", 1, "read-write", 8, """\ - Start a DAC master SPI transfer. - - If ``dac_arm`` is raised from and the master is currently not in a SPI - transfer, the SPI master reads from the ``dac_send_buf`` register and sends - it over the wire to the DAC, while reading data from the DAC into - ``dac_recv_buf``. - - If ``dac_arm`` is raised while the master is in an SPI transfer, - nothing changes. - - If ``dac_arm`` is lowered while the master is in an SPI transfer, - nothing changes. The SPI cycle will continue to execute and it will - continue to write data to ``dac_recv_buf``. - - If the SPI transfer finishes and ``dac_arm`` is still set to - 1, then ``dac_finished`` is raised to 1. If ``dac_arm`` is lowered - in this state, then ``dac_finished`` is lowered. - - Analog Devices DACs can have their SPI transfers interrupted without - issue. However it is currently not possible to interrupt SPI transfers - in software without resetting the entire device. - - If ``dac_sel`` is set to another master then the transfer will proceed - as normal, but no data will be sent to or received from the DAC."""), - Descr("dac_recv_buf", 24, "read-only", 8, """\ - DAC master receive buffer. - - This buffer is stable if there is no DAC transfer caused by ``dac_arm`` - is in process. - - This register only changes if an SPI transfer is triggered by the MMIO - registers. SPI transfers by other masters will not affect this register. - buffer."""), - Descr("dac_send_buf", 24, "read-write", 8, """\ - DAC master send buffer. - - Fill this buffer with a 24 bit Analog Devices DAC command. Updating - this buffer does not start an SPI transfer. To send data to the DAC, - fill this buffer and raise ``dac_arm``. - - The DAC copies this buffer into an internal register when writing data. - Modifying this buffer during a transfer does not disrupt an in-process - transfer."""), - - Descr("cl_assert_change", 1, "read-write", 1, """\ - Flush parameter changes to control loop. - - When this bit is raised from low to high, this signals the control - loop that it should read in new values from the MMIO registers. - While the bit is raised high, the control loop will read the constants - at most once. - - When this bit is raised from high to low before ``cl_change_made`` - is asserted by the control loop, nothing happens."""), - Descr("cl_change_made", 1, "read-only", 1, """\ - Signal from the control loop that the parameters have been applied. - - This signal goes high only while ``cl_assert_change`` is high. No - change will be applied afterwards while both are high."""), - - Descr("cl_in_loop", 1, "read-only", 1, """\ - This bit is high if the control loop is running."""), - Descr("cl_run_loop_in", 1, "read-write", 1, """\ - Set this bit high to start the control loop."""), - Descr("cl_setpt_in", 18, "read-write", 1, """\ - Setpoint of the control loop. - - This is a twos-complement number in ADC units. - - This is a parameter: see ``cl_assert_change``."""), - Descr("cl_P_in", 64, "read-write", 1, """\ - Proportional parameter of the control loop. - - This is a twos-complement fixed point number with 21 whole - bits and 43 fractional bits. This is applied to the error - in DAC units. - - This is a parameter: see ``cl_assert_change``."""), - Descr("cl_I_in", 64, "read-write", 1, """\ - Integral parameter of the control loop. - - This is a twos-complement fixed point number with 21 whole - bits and 43 fractional bits. This is applied to the error - in DAC units. - - This is a parameter: see ``cl_assert_change``."""), - Descr("cl_delay_in", 16, "read-write", 1, """\ - Delay parameter of the loop. - - This is an unsigned number denoting the number of cycles - the loop should wait between loop executions. - - This is a parameter: see ``cl_assert_change``."""), - - Descr("cl_cycle_count", 18, "read-only", 1, """\ - Delay parameter of the loop. - - This is an unsigned number denoting the number of cycles - the loop should wait between loop executions."""), - Descr("cl_z_pos", 20, "read-only", 1, """\ - Control loop DAC Z position. - """), - Descr("cl_z_measured", 18, "read-only", 1, """\ - Control loop ADC Z position. - """), - ] diff --git a/gateware/region.py b/gateware/region.py index 10da005..093d03a 100644 --- a/gateware/region.py +++ b/gateware/region.py @@ -21,18 +21,23 @@ added manually and there is no sanity checking. class BasicRegion: """ Simple class for storing a RAM region. """ - def __init__(self, origin, size, bus=None): + def __init__(self, origin, size, bus=None, registers=None): """ :param origin: Positive integer denoting the start location of the memory region. :param size: Size of the memory region. This must be of the form (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. """ self.origin = origin self.size = size self.bus = bus + self.registers = registers def decoder(self): """ @@ -56,7 +61,7 @@ class BasicRegion: return lambda addr: addr[rightbits:32] == (self.origin >> rightbits) def to_dict(self): - return {"origin" : self.origin, "size": self.size} + return {"origin" : self.origin, "size": self.size, "registers": self.registers} def __str__(self): return str(self.to_dict()) diff --git a/gateware/soc.py b/gateware/soc.py index 27b50e9..f671d34 100644 --- a/gateware/soc.py +++ b/gateware/soc.py @@ -63,6 +63,7 @@ from liteeth.phy.mii import LiteEthPHYMII from util import * from swic import * from extio import * +from region import BasicRegion """ Keep this diagram up to date! This is the wiring diagram from the ADC to @@ -104,6 +105,9 @@ io = [ # ("dac_mosi", 0, Pins("B11 B18 E16 D8 V12 D5 D3 D2"), IOStandard("LVCMOS33")), # ("dac_miso", 0, Pins("A11 A18 D15 C7 V10 B7 F4 H2"), IOStandard("LVCMOS33")), # ("dac_sck", 0, Pins("D12 K16 C15 E7 V11 E6 F3 G2"), IOStandard("LVCMOS33")), + ("adc_conv_0", 0, Pins("V15"), IOStandard("LVCMOS33")), + ("adc_sck_0", 0, Pins("U16"), IOStandard("LVCMOS33")), + ("adc_sdo_0", 0, Pins("P14"), IOStandard("LVCMOS33")), # ("adc_conv", 0, Pins("V15 T11 N15 U18 U11 R10 R16 U17"), IOStandard("LVCMOS33")), # ("adc_sck", 0, Pins("U16 R12 M16 R17 V16 R11 N16 T18"), IOStandard("LVCMOS33")), # ("adc_sdo", 0, Pins("P14 T14 V17 P17 M13 R13 N14 R18"), IOStandard("LVCMOS33")), @@ -149,6 +153,10 @@ class UpsilonSoC(SoCCore): for seg_num, ip_byte in enumerate(ip_str.split('.'),start=1): self.add_constant(f"{ip_name}{seg_num}", int(ip_byte)) + def add_slave_with_registers(self, name, bus, region, registers): + self.bus.add_slave(name, bus, region) + self.soc_subregions[name] = registers + def add_blockram(self, name, size, connect_now=True): mod = SRAM(size) self.add_module(name, mod) @@ -163,24 +171,49 @@ class UpsilonSoC(SoCCore): self.add_module(name, mod) return mod - def add_picorv32(self, name, size=0x1000, origin=0x10000, param_origin=0x100000): + def add_picorv32(self, name, size=0x1000, origin=0x10000): pico = PicoRV32(name, origin, origin+0x10) self.add_module(name, pico) - self.bus.add_slave(name + "_dbg_reg", pico.debug_reg_read.bus, - SoCRegion(origin=None, size=pico.debug_reg_read.width, cached=False)) + 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.registers) ram = self.add_blockram(name + "_ram", size=size, connect_now=False) ram_iface = self.add_preemptive_interface(name + "ram_iface", 2, ram) pico.mmap.add_region("main", BasicRegion(origin=origin, size=size, bus=ram_iface.buses[1])) - self.bus.add_slave(name + "_ram", ram_iface.buses[0], - SoCRegion(origin=None, size=size, cached=True)) + self.add_slave_with_registers(name + "_ram", ram_iface.buses[0], + SoCRegion(origin=None, size=size, cached=True), + None) + def picorv32_add_cl(self, name, param_origin=0x100000): + pico = self.get_module(name) param_iface = pico.add_cl_params(param_origin, name + "_cl.json") self.bus.add_slave(name + "_cl", param_iface, SoCRegion(origin=None, size=size, cached=False)) + def add_AD5791(self, name, **kwargs): + args = SPIMaster.AD5791_PARAMS + args.update(kwargs) + spi = SPIMaster(**args) + self.add_module(name, spi) + return spi + + def add_LT_adc(self, name, **kwargs): + args = SPIMaster.LT_ADC_PARAMS + args.update(kwargs) + args["mosi"] = Signal() + + # SPI Master brings ss_L low when converting and keeps it high + # when idle. The ADC is the opposite, so invert the signal here. + conv_high = Signal() + self.comb += conv_high.eq(~kwargs["ss_L"]) + + spi = SPIMaster(**args) + self.add_module(name, spi) + return spi + def __init__(self, variant="a7-100", local_ip="192.168.2.50", @@ -199,6 +232,11 @@ class UpsilonSoC(SoCCore): rst = platform.request("cpu_reset") self.submodules.crg = _CRG(platform, sys_clk_freq, True, rst) + # The SoC won't know the origins until LiteX sorts out all the + # memory regions, so they go into a dictionary directly instead + # of through MemoryMap. + self.soc_subregions = {} + """ These source files need to be sorted so that modules that rely on another module come later. For instance, @@ -263,16 +301,49 @@ class UpsilonSoC(SoCCore): # Add pins platform.add_extension(io) - self.submodules.spi0 = SPIMaster( - platform.request("module_reset"), - platform.request("dac_miso_0"), - platform.request("dac_mosi_0"), - platform.request("dac_sck_0"), - platform.request("dac_ss_L_0"), - ) - self.bus.add_slave("spi0", self.spi0.bus, SoCRegion(origin=None, size=self.spi0.addr_space_size, cached=False)) - + + # Add control loop DACs and ADCs. self.add_picorv32("pico0") + # XXX: I don't have the time to restructure my code to make it + # elegant, that comes when things work + module_reset = platform.request("module_reset") + self.add_AD5791("dac0", + rst=module_reset, + miso=platform.request("dac_miso_0"), + mosi=platform.request("dac_mosi_0"), + sck=platform.request("dac_sck_0"), + ss_L=platform.request("dac_ss_L_0"), + ) + + self.add_preemptive_interface("dac0_PI", 2, self.dac0) + self.add_slave_with_registers("dac0", self.dac0_PI.buses[0], + SoCRegion(origin=None, size=self.dac0.width, cached=False), + self.dac0.registers) + self.pico0.mmap.add_region("dac0", + BasicRegion(origin=0x200000, size=self.dac0.width, + bus=self.dac0_PI.buses[1], + registers=self.dac0.registers)) + + self.add_LT_adc("adc0", + rst=module_reset, + miso=platform.request("adc_sdo_0"), + sck=platform.request("adc_sck_0"), + ss_L=platform.request("adc_conv_0"), + spi_wid=18, + ) + self.add_preemptive_interface("adc0_PI", 2, self.adc0) + self.add_slave_with_registers("adc0", self.adc0_PI.buses[0], + SoCRegion(origin=None, size=self.adc0.width, cached=False), + self.adc0.registers) + self.pico0.mmap.add_region("adc0", + BasicRegion(origin=0x300000, size=self.adc0.width, + bus=self.adc0_PI.buses[1], + registers=self.adc0.registers)) + + def do_finalize(self): + with open('soc_subregions.json', 'wt') as f: + import json + json.dump(self.soc_subregions, f) def main(): from config import config diff --git a/gateware/swic.py b/gateware/swic.py index c35cf0f..eceb9b5 100644 --- a/gateware/swic.py +++ b/gateware/swic.py @@ -182,20 +182,22 @@ class RegisterInterface(LiteXModule): bus_logic(self.mainbus, main_case) bus_logic(self.picobus, pico_case) - def dump_json(self, filename): - """ Dump description of offsets to JSON file. """ - d = {} + # Generate addresses + self.addresses = {} for reg, off in self.registers: - d[reg.name] = { + self.addresses[reg.name] = { "width" : reg.width, "direction" : reg.direction, "offset": off } - import json - with open(filename, 'wt') as f: - json.dump(d, f) 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", + } + registers = {name: {"origin" : num * 4, "size" : 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)] @@ -233,7 +235,7 @@ class PicoRV32(LiteXModule): :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: Interface used by main cpu to control variables. + :return: Parameter module (used for accessing metadata). """ params = RegisterInterface( SpecialRegister.from_tuples( @@ -245,9 +247,9 @@ class PicoRV32(LiteXModule): ("zpos", "PW", 32), )) self.add_module("cl_params", params) - self.mmap.add_region("cl_params", BasicRegion(origin, params.width, params.picobus)) + self.mmap.add_region("cl_params", BasicRegion(origin, params.width, params.picobus, params.addresses)) params.dump_json(dumpname) - return params.mainbus + return params def __init__(self, name, start_addr=0x10000, irq_addr=0x10010, stackaddr=0x100FF): self.name = name @@ -292,7 +294,6 @@ class PicoRV32(LiteXModule): self.d_dat_w.status.eq(mem_wdata), ] - # NOTE: need to compile to these extact instructions self.specials += Instance("picorv32", p_COMPRESSED_ISA = 1, p_ENABLE_MUL = 1,