blob: 53f2f6d576f1188f60a2cebe11b657b7e9142adc [file] [log] [blame]
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _HARDWARE_INTERP_H
#define _HARDWARE_INTERP_H
#include "pico.h"
#include "hardware/structs/interp.h"
#include "hardware/regs/sio.h"
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_INTERP, Enable/disable assertions in the interpolation module, type=bool, default=0, group=hardware_interp
#ifndef PARAM_ASSERTIONS_ENABLED_INTERP
#define PARAM_ASSERTIONS_ENABLED_INTERP 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
/** \file hardware/interp.h
* \defgroup hardware_interp hardware_interp
*
* Hardware Interpolator API
*
* Each core is equipped with two interpolators (INTERP0 and INTERP1) which can be used to accelerate
* tasks by combining certain pre-configured simple operations into a single processor cycle. Intended
* for cases where the pre-configured operation is repeated a large number of times, this results in
* code which uses both fewer CPU cycles and fewer CPU registers in the time critical sections of the
* code.
*
* The interpolators are used heavily to accelerate audio operations within the Pico SDK, but their
* flexible configuration make it possible to optimise many other tasks such as quantization and
* dithering, table lookup address generation, affine texture mapping, decompression and linear feedback.
*
* Please refer to the RP2040 datasheet for more information on the HW interpolators and how they work.
*/
#define interp0 interp0_hw
#define interp1 interp1_hw
/** \brief Interpolator configuration
* \defgroup interp_config interp_config
* \ingroup hardware_interp
*
* Each interpolator needs to be configured, these functions provide handy helpers to set up configuration
* structures.
*
*/
typedef struct {
uint32_t ctrl;
} interp_config;
static inline uint interp_index(interp_hw_t *interp) {
assert(interp == interp0 || interp == interp1);
return interp == interp1 ? 1 : 0;
}
/*! \brief Claim the interpolator lane specified
* \ingroup hardware_interp
*
* Use this function to claim exclusive access to the specified interpolator lane.
*
* This function will panic if the lane is already claimed.
*
* \param interp Interpolator on which to claim a lane. interp0 or interp1
* \param lane The lane number, 0 or 1.
*/
void interp_claim_lane(interp_hw_t *interp, uint lane);
/*! \brief Claim the interpolator lanes specified in the mask
* \ingroup hardware_interp
*
* \param interp Interpolator on which to claim lanes. interp0 or interp1
* \param lane_mask Bit pattern of lanes to claim (only bits 0 and 1 are valid)
*/
void interp_claim_lane_mask(interp_hw_t *interp, uint lane_mask);
/*! \brief Release a previously claimed interpolator lane
* \ingroup hardware_interp
*
* \param interp Interpolator on which to release a lane. interp0 or interp1
* \param lane The lane number, 0 or 1
*/
void interp_unclaim_lane(interp_hw_t *interp, uint lane);
/*! \brief Set the interpolator shift value
* \ingroup interp_config
*
* Sets the number of bits the accumulator is shifted before masking, on each iteration.
*
* \param c Pointer to an interpolator config
* \param shift Number of bits
*/
static inline void interp_config_set_shift(interp_config *c, uint shift) {
valid_params_if(INTERP, shift < 32);
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_SHIFT_BITS) |
((shift << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) & SIO_INTERP0_CTRL_LANE0_SHIFT_BITS);
}
/*! \brief Set the interpolator mask range
* \ingroup interp_config
*
* Sets the range of bits (least to most) that are allowed to pass through the interpolator
*
* \param c Pointer to interpolation config
* \param mask_lsb The least significant bit allowed to pass
* \param mask_msb The most significant bit allowed to pass
*/
static inline void interp_config_set_mask(interp_config *c, uint mask_lsb, uint mask_msb) {
valid_params_if(INTERP, mask_msb < 32);
valid_params_if(INTERP, mask_lsb <= mask_msb);
c->ctrl = (c->ctrl & ~(SIO_INTERP0_CTRL_LANE0_MASK_LSB_BITS | SIO_INTERP0_CTRL_LANE0_MASK_MSB_BITS)) |
((mask_lsb << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) & SIO_INTERP0_CTRL_LANE0_MASK_LSB_BITS) |
((mask_msb << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB) & SIO_INTERP0_CTRL_LANE0_MASK_MSB_BITS);
}
/*! \brief Enable cross input
* \ingroup interp_config
*
* Allows feeding of the accumulator content from the other lane back in to this lanes shift+mask hardware.
* This will take effect even if the interp_config_set_add_raw option is set as the cross input mux is before the
* shift+mask bypass
*
* \param c Pointer to interpolation config
* \param cross_input If true, enable the cross input.
*/
static inline void interp_config_set_cross_input(interp_config *c, bool cross_input) {
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_CROSS_INPUT_BITS) |
(cross_input ? SIO_INTERP0_CTRL_LANE0_CROSS_INPUT_BITS : 0);
}
/*! \brief Enable cross results
* \ingroup interp_config
*
* Allows feeding of the other lane’s result into this lane’s accumulator on a POP operation.
*
* \param c Pointer to interpolation config
* \param cross_result If true, enables the cross result
*/
static inline void interp_config_set_cross_result(interp_config *c, bool cross_result) {
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_CROSS_RESULT_BITS) |
(cross_result ? SIO_INTERP0_CTRL_LANE0_CROSS_RESULT_BITS : 0);
}
/*! \brief Set sign extension
* \ingroup interp_config
*
* Enables signed mode, where the shifted and masked accumulator value is sign-extended to 32 bits
* before adding to BASE1, and LANE1 PEEK/POP results appear extended to 32 bits when read by processor.
*
* \param c Pointer to interpolation config
* \param _signed If true, enables sign extension
*/
static inline void interp_config_set_signed(interp_config *c, bool _signed) {
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_SIGNED_BITS) |
(_signed ? SIO_INTERP0_CTRL_LANE0_SIGNED_BITS : 0);
}
/*! \brief Set raw add option
* \ingroup interp_config
*
* When enabled, mask + shift is bypassed for LANE0 result. This does not affect the FULL result.
*
* \param c Pointer to interpolation config
* \param add_raw If true, enable raw add option.
*/
static inline void interp_config_set_add_raw(interp_config *c, bool add_raw) {
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_ADD_RAW_BITS) |
(add_raw ? SIO_INTERP0_CTRL_LANE0_ADD_RAW_BITS : 0);
}
/*! \brief Set blend mode
* \ingroup interp_config
*
* If enabled, LANE1 result is a linear interpolation between BASE0 and BASE1, controlled
* by the 8 LSBs of lane 1 shift and mask value (a fractional number between 0 and 255/256ths)
*
* LANE0 result does not have BASE0 added (yields only the 8 LSBs of lane 1 shift+mask value)
*
* FULL result does not have lane 1 shift+mask value added (BASE2 + lane 0 shift+mask)
*
* LANE1 SIGNED flag controls whether the interpolation is signed or unsig
*
* \param c Pointer to interpolation config
* \param blend Set true to enable blend mode.
*/
static inline void interp_config_set_blend(interp_config *c, bool blend) {
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_BLEND_BITS) |
(blend ? SIO_INTERP0_CTRL_LANE0_BLEND_BITS : 0);
}
/*! \brief Set interpolator clamp mode (Interpolator 1 only)
* \ingroup interp_config
*
* Only present on INTERP1 on each core. If CLAMP mode is enabled:
* - LANE0 result is a shifted and masked ACCUM0, clamped by a lower bound of BASE0 and an upper bound of BASE1.
* - Signedness of these comparisons is determined by LANE0_CTRL_SIGNED
*
* \param c Pointer to interpolation config
* \param clamp Set true to enable clamp mode
*/
static inline void interp_config_set_clamp(interp_config *c, bool clamp) {
c->ctrl = (c->ctrl & ~SIO_INTERP1_CTRL_LANE0_CLAMP_BITS) |
(clamp ? SIO_INTERP1_CTRL_LANE0_CLAMP_BITS : 0);
}
/*! \brief Set interpolator Force bits
* \ingroup interp_config
*
* ORed into bits 29:28 of the lane result presented to the processor on the bus.
*
* No effect on the internal 32-bit datapath. Handy for using a lane to generate sequence
* of pointers into flash or SRAM
*
* \param c Pointer to interpolation config
* \param bits Sets the force bits to that specified. Range 0-3 (two bits)
*/
static inline void interp_config_set_force_bits(interp_config *c, uint bits) {
invalid_params_if(INTERP, bits > 3);
// note cannot use hw_set_bits on SIO
c->ctrl = (c->ctrl & ~SIO_INTERP0_CTRL_LANE0_FORCE_MSB_BITS) |
(bits << SIO_INTERP0_CTRL_LANE0_FORCE_MSB_LSB);
}
/*! \brief Get a default configuration
* \ingroup interp_config
*
* \return A default interpolation configuration
*/
static inline interp_config interp_default_config() {
interp_config c = {0};
// Just pass through everything
interp_config_set_mask(&c, 0, 31);
return c;
}
/*! \brief Send configuration to a lane
* \ingroup interp_config
*
* If an invalid configuration is specified (ie a lane specific item is set on wrong lane),
* depending on setup this function can panic.
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane to set
* \param config Pointer to interpolation config
*/
static inline void interp_set_config(interp_hw_t *interp, uint lane, interp_config *config) {
invalid_params_if(INTERP, lane > 1);
invalid_params_if(INTERP, config->ctrl & SIO_INTERP1_CTRL_LANE0_CLAMP_BITS &&
(!interp_index(interp) || lane)); // only interp1 lane 0 has clamp bit
invalid_params_if(INTERP, config->ctrl & SIO_INTERP0_CTRL_LANE0_BLEND_BITS &&
(interp_index(interp) || lane)); // only interp0 lane 0 has blend bit
interp->ctrl[lane] = config->ctrl;
}
/*! \brief Directly set the force bits on a specified lane
* \ingroup hardware_interp
*
* These bits are ORed into bits 29:28 of the lane result presented to the processor on the bus.
* There is no effect on the internal 32-bit datapath.
*
* Useful for using a lane to generate sequence of pointers into flash or SRAM, saving a subsequent
* OR or add operation.
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane to set
* \param bits The bits to set (bits 0 and 1, value range 0-3)
*/
static inline void interp_set_force_bits(interp_hw_t *interp, uint lane, uint bits) {
// note cannot use hw_set_bits on SIO
interp->ctrl[lane] |= (bits << SIO_INTERP0_CTRL_LANE0_FORCE_MSB_LSB);
}
typedef struct {
io_rw_32 accum[2];
io_rw_32 base[3];
io_rw_32 ctrl[2];
} interp_hw_save_t;
/*! \brief Save the specified interpolator state
* \ingroup hardware_interp
*
* Can be used to save state if you need an interpolator for another purpose, state
* can then be recovered afterwards and continue from that point
*
* \param interp Interpolator instance, interp0 or interp1.
* \param saver Pointer to the save structure to fill in
*/
void interp_save(interp_hw_t *interp, interp_hw_save_t *saver);
/*! \brief Restore an interpolator state
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param saver Pointer to save structure to reapply to the specified interpolator
*/
void interp_restore(interp_hw_t *interp, interp_hw_save_t *saver);
/*! \brief Sets the interpolator base register by lane
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1 or 2
* \param val The value to apply to the register
*/
static inline void interp_set_base(interp_hw_t *interp, uint lane, uint32_t val) {
interp->base[lane] = val;
}
/*! \brief Gets the content of interpolator base register by lane
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1 or 2
* \return The current content of the lane base register
*/
static inline uint32_t interp_get_base(interp_hw_t *interp, uint lane) {
return interp->base[lane];
}
/*! \brief Sets the interpolator base registers simultaneously
* \ingroup hardware_interp
*
* The lower 16 bits go to BASE0, upper bits to BASE1 simultaneously.
* Each half is sign-extended to 32 bits if that lane’s SIGNED flag is set.
*
* \param interp Interpolator instance, interp0 or interp1.
* \param val The value to apply to the register
*/
static inline void interp_set_base_both(interp_hw_t *interp, uint32_t val) {
interp->base01 = val;
}
/*! \brief Sets the interpolator accumulator register by lane
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \param val The value to apply to the register
*/
static inline void interp_set_accumulator(interp_hw_t *interp, uint lane, uint32_t val) {
interp->accum[lane] = val;
}
/*! \brief Gets the content of the interpolator accumulator register by lane
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \return The current content of the register
*/
static inline uint32_t interp_get_accumulator(interp_hw_t *interp, uint lane) {
return interp->accum[lane];
}
/*! \brief Read lane result, and write lane results to both accumulators to update the interpolator
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \return The content of the lane result register
*/
static inline uint32_t interp_pop_lane_result(interp_hw_t *interp, uint lane) {
return interp->pop[lane];
}
/*! \brief Read lane result
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \return The content of the lane result register
*/
static inline uint32_t interp_peek_lane_result(interp_hw_t *interp, uint lane) {
return interp->peek[lane];
}
/*! \brief Read lane result, and write lane results to both accumulators to update the interpolator
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \return The content of the FULL register
*/
static inline uint32_t interp_pop_full_result(interp_hw_t *interp) {
return interp->pop[2];
}
/*! \brief Read lane result
* \ingroup hardware_interp
*
* \param interp Interpolator instance, interp0 or interp1.
* \return The content of the FULL register
*/
static inline uint32_t interp_peek_full_result(interp_hw_t *interp) {
return interp->peek[2];
}
/*! \brief Add to accumulator
* \ingroup hardware_interp
*
* Atomically add the specified value to the accumulator on the specified lane
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \param val Value to add
* \return The content of the FULL register
*/
static inline void interp_add_accumulater(interp_hw_t *interp, uint lane, uint32_t val) {
interp->add_raw[lane] = val;
}
/*! \brief Get raw lane value
* \ingroup hardware_interp
*
* Returns the raw shift and mask value from the specified lane, BASE0 is NOT added
*
* \param interp Interpolator instance, interp0 or interp1.
* \param lane The lane number, 0 or 1
* \return The raw shift/mask value
*/
static inline uint32_t interp_get_raw(interp_hw_t *interp, uint lane) {
return interp->add_raw[lane];
}
#ifdef __cplusplus
}
#endif
#endif