Add a counter threshold at which the M0 will change to a new mode.
This lays the groundwork for implementing timed operations (#86). The M0 can be configured to automatically change modes when its byte count reaches a specific value. Checking the counter against the threshold and dispatching to the next mode is handled by a new `jump_next_mode` macro, which replaces the unconditional branches back to the start of the TX and RX loops. Making this change work requires some rearrangement of the code, such that the destinations of all conditional branch instructions are within reach. These branch instructions (`b[cond] label`) have a range of -256 to +254 bytes from the current program counter. For this reason, the TX shortfall handling is moved earlier in the file, and branches in the idle loop are restructured to use an unconditional branch to rx_start, which is furthest away. The additional code for switching modes adds 9 cycles to the normal RX path, and 10 to the TX path (the difference is because the dispatch in `jump_next_mode` is optimised for the longer RX path).
This commit is contained in:
@ -32,6 +32,8 @@ struct m0_state {
|
|||||||
uint32_t num_shortfalls;
|
uint32_t num_shortfalls;
|
||||||
uint32_t longest_shortfall;
|
uint32_t longest_shortfall;
|
||||||
uint32_t shortfall_limit;
|
uint32_t shortfall_limit;
|
||||||
|
uint32_t threshold;
|
||||||
|
uint32_t next_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum m0_mode {
|
enum m0_mode {
|
||||||
|
@ -66,9 +66,9 @@ shadow registers.
|
|||||||
|
|
||||||
There are four key code paths, with the following worst-case timings:
|
There are four key code paths, with the following worst-case timings:
|
||||||
|
|
||||||
RX, normal: 143 cycles
|
RX, normal: 152 cycles
|
||||||
RX, overrun: 73 cycles
|
RX, overrun: 73 cycles
|
||||||
TX, normal: 132 cycles
|
TX, normal: 142 cycles
|
||||||
TX, underrun: 140 cycles
|
TX, underrun: 140 cycles
|
||||||
|
|
||||||
Design
|
Design
|
||||||
@ -114,6 +114,8 @@ registers and fixed memory addresses.
|
|||||||
.equ NUM_SHORTFALLS, 0x0C
|
.equ NUM_SHORTFALLS, 0x0C
|
||||||
.equ LONGEST_SHORTFALL, 0x10
|
.equ LONGEST_SHORTFALL, 0x10
|
||||||
.equ SHORTFALL_LIMIT, 0x14
|
.equ SHORTFALL_LIMIT, 0x14
|
||||||
|
.equ THRESHOLD, 0x18
|
||||||
|
.equ NEXT_MODE, 0x1C
|
||||||
|
|
||||||
// Private variables stored after state.
|
// Private variables stored after state.
|
||||||
.equ PREV_LONGEST_SHORTFALL, 0x20
|
.equ PREV_LONGEST_SHORTFALL, 0x20
|
||||||
@ -217,6 +219,29 @@ buf_ptr .req r4
|
|||||||
mov shortfall_length, hi_zero // shortfall_length = hi_zero // 1
|
mov shortfall_length, hi_zero // shortfall_length = hi_zero // 1
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
|
.macro jump_next_mode name
|
||||||
|
// Jump to next mode if the byte count threshold has been reached.
|
||||||
|
//
|
||||||
|
// Clobbers:
|
||||||
|
threshold .req r0
|
||||||
|
new_mode .req r1
|
||||||
|
|
||||||
|
// Check count against threshold. If not a match, return to start of current loop.
|
||||||
|
ldr threshold, [state, #THRESHOLD] // threshold = state.threshold // 2
|
||||||
|
cmp count, threshold // if count != threshold: // 1
|
||||||
|
bne \name\()_loop // goto loop // 1 thru, 3 taken
|
||||||
|
|
||||||
|
// Otherwise, load and set new mode.
|
||||||
|
ldr new_mode, [state, #NEXT_MODE] // new_mode = state.next_mode // 2
|
||||||
|
str new_mode, [state, #MODE] // state.mode = new_mode // 2
|
||||||
|
|
||||||
|
// Branch according to new mode.
|
||||||
|
cmp new_mode, #MODE_RX // if new_mode == RX: // 1
|
||||||
|
beq rx_loop // goto rx_loop // 1 thru, 3 taken
|
||||||
|
bgt tx_loop // elif new_mode > RX: goto tx_loop // 1 thru, 3 taken
|
||||||
|
b idle // goto idle // 3
|
||||||
|
.endm
|
||||||
|
|
||||||
.macro handle_shortfall name
|
.macro handle_shortfall name
|
||||||
// Handle a shortfall.
|
// Handle a shortfall.
|
||||||
//
|
//
|
||||||
@ -299,15 +324,37 @@ main:
|
|||||||
str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2
|
str zero, [state, #NUM_SHORTFALLS] // state.num_shortfalls = zero // 2
|
||||||
str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2
|
str zero, [state, #LONGEST_SHORTFALL] // state.longest_shortfall = zero // 2
|
||||||
str zero, [state, #SHORTFALL_LIMIT] // state.shortfall_limit = zero // 2
|
str zero, [state, #SHORTFALL_LIMIT] // state.shortfall_limit = zero // 2
|
||||||
|
str zero, [state, #THRESHOLD] // state.threshold = zero // 2
|
||||||
|
str zero, [state, #NEXT_MODE] // state.next_mode = zero // 2
|
||||||
|
|
||||||
idle:
|
idle:
|
||||||
// Wait for RX or TX mode to be set.
|
// Wait for RX or TX mode to be set.
|
||||||
mode .req r3
|
mode .req r3
|
||||||
ldr mode, [state, #MODE] // mode = state.mode // 2
|
ldr mode, [state, #MODE] // mode = state.mode // 2
|
||||||
cmp mode, #MODE_RX // if mode == RX: // 1
|
cmp mode, #MODE_RX // if mode == RX: // 1
|
||||||
beq rx_start // goto rx_start // 1 thru, 3 taken
|
bgt tx_start // if mode > RX: goto tx_start // 1 thru, 3 taken
|
||||||
bgt tx_start // elif mode > RX: goto tx_start // 1 thru, 3 taken
|
blt idle // elif mode < RX: goto idle // 1 thru, 3 taken
|
||||||
b idle // goto idle // 3
|
b rx_start // goto rx_start // 3
|
||||||
|
|
||||||
|
tx_zeros:
|
||||||
|
|
||||||
|
// Write zeros to SGPIO.
|
||||||
|
mov zero, #0 // zero = 0 // 1
|
||||||
|
str zero, [sgpio_data, #SLICE0] // SGPIO_REG_SS[SLICE0] = zero // 8
|
||||||
|
str zero, [sgpio_data, #SLICE1] // SGPIO_REG_SS[SLICE1] = zero // 8
|
||||||
|
str zero, [sgpio_data, #SLICE2] // SGPIO_REG_SS[SLICE2] = zero // 8
|
||||||
|
str zero, [sgpio_data, #SLICE3] // SGPIO_REG_SS[SLICE3] = zero // 8
|
||||||
|
str zero, [sgpio_data, #SLICE4] // SGPIO_REG_SS[SLICE4] = zero // 8
|
||||||
|
str zero, [sgpio_data, #SLICE5] // SGPIO_REG_SS[SLICE5] = zero // 8
|
||||||
|
str zero, [sgpio_data, #SLICE6] // SGPIO_REG_SS[SLICE6] = zero // 8
|
||||||
|
str zero, [sgpio_data, #SLICE7] // SGPIO_REG_SS[SLICE7] = zero // 8
|
||||||
|
|
||||||
|
// If in TX start mode, don't count this as a shortfall.
|
||||||
|
cmp mode, #MODE_TX_START // if mode == TX_START: // 1
|
||||||
|
beq tx_loop // goto tx_loop // 1 thru, 3 taken
|
||||||
|
|
||||||
|
// Run common shortfall handling and jump back to TX loop start.
|
||||||
|
handle_shortfall tx // handle_shortfall() // 24
|
||||||
|
|
||||||
checked_rollback:
|
checked_rollback:
|
||||||
// Checked rollback handler. This code is run when the M0 is in a TX or RX mode, and is
|
// Checked rollback handler. This code is run when the M0 is in a TX or RX mode, and is
|
||||||
@ -389,28 +436,8 @@ tx_write:
|
|||||||
// Update counts.
|
// Update counts.
|
||||||
update_counts // update_counts() // 4
|
update_counts // update_counts() // 4
|
||||||
|
|
||||||
// Jump back to TX loop start.
|
// Jump to next mode if threshold reached, or back to TX loop start.
|
||||||
b tx_loop // goto tx_loop // 3
|
jump_next_mode tx // jump_next_mode() // 13
|
||||||
|
|
||||||
tx_zeros:
|
|
||||||
|
|
||||||
// Write zeros to SGPIO.
|
|
||||||
mov zero, #0 // zero = 0 // 1
|
|
||||||
str zero, [sgpio_data, #SLICE0] // SGPIO_REG_SS[SLICE0] = zero // 8
|
|
||||||
str zero, [sgpio_data, #SLICE1] // SGPIO_REG_SS[SLICE1] = zero // 8
|
|
||||||
str zero, [sgpio_data, #SLICE2] // SGPIO_REG_SS[SLICE2] = zero // 8
|
|
||||||
str zero, [sgpio_data, #SLICE3] // SGPIO_REG_SS[SLICE3] = zero // 8
|
|
||||||
str zero, [sgpio_data, #SLICE4] // SGPIO_REG_SS[SLICE4] = zero // 8
|
|
||||||
str zero, [sgpio_data, #SLICE5] // SGPIO_REG_SS[SLICE5] = zero // 8
|
|
||||||
str zero, [sgpio_data, #SLICE6] // SGPIO_REG_SS[SLICE6] = zero // 8
|
|
||||||
str zero, [sgpio_data, #SLICE7] // SGPIO_REG_SS[SLICE7] = zero // 8
|
|
||||||
|
|
||||||
// If in TX start mode, don't count this as a shortfall.
|
|
||||||
cmp mode, #MODE_TX_START // if mode == TX_START: // 1
|
|
||||||
beq tx_loop // goto tx_loop // 1 thru, 3 taken
|
|
||||||
|
|
||||||
// Run common shortfall handling and jump back to TX loop start.
|
|
||||||
handle_shortfall tx // handle_shortfall() // 24
|
|
||||||
|
|
||||||
rx_start:
|
rx_start:
|
||||||
|
|
||||||
@ -466,8 +493,8 @@ rx_loop:
|
|||||||
// Update counts.
|
// Update counts.
|
||||||
update_counts // update_counts() // 4
|
update_counts // update_counts() // 4
|
||||||
|
|
||||||
// Jump back to RX loop start.
|
// Jump to next mode if threshold reached, or back to RX loop start.
|
||||||
b rx_loop // goto rx_loop // 3
|
jump_next_mode rx // jump_next_mode() // 12
|
||||||
|
|
||||||
rx_shortfall:
|
rx_shortfall:
|
||||||
|
|
||||||
|
@ -260,6 +260,15 @@ void request_transceiver_mode(transceiver_mode_t mode)
|
|||||||
transceiver_request.seq++;
|
transceiver_request.seq++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void set_m0_mode(enum m0_mode mode) {
|
||||||
|
// All values of the threshold setting are valid. So setting a mode,
|
||||||
|
// it's necessary to set up a transition back to the same mode, for
|
||||||
|
// when the M0 counter wraps.
|
||||||
|
m0_state.mode = mode;
|
||||||
|
m0_state.next_mode = mode;
|
||||||
|
m0_state.threshold = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void transceiver_shutdown(void)
|
void transceiver_shutdown(void)
|
||||||
{
|
{
|
||||||
baseband_streaming_disable(&sgpio_config);
|
baseband_streaming_disable(&sgpio_config);
|
||||||
@ -271,7 +280,7 @@ void transceiver_shutdown(void)
|
|||||||
led_off(LED2);
|
led_off(LED2);
|
||||||
led_off(LED3);
|
led_off(LED3);
|
||||||
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF);
|
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF);
|
||||||
m0_state.mode = M0_MODE_IDLE;
|
set_m0_mode(M0_MODE_IDLE);
|
||||||
// The M0 may already be waiting for the next SGPIO
|
// The M0 may already be waiting for the next SGPIO
|
||||||
// exchange interrupt. In order to ensure that the M0
|
// exchange interrupt. In order to ensure that the M0
|
||||||
// switches over to its idle loop, we need to set the
|
// switches over to its idle loop, we need to set the
|
||||||
@ -289,14 +298,14 @@ void transceiver_startup(const transceiver_mode_t mode) {
|
|||||||
led_off(LED3);
|
led_off(LED3);
|
||||||
led_on(LED2);
|
led_on(LED2);
|
||||||
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_RX);
|
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_RX);
|
||||||
m0_state.mode = M0_MODE_RX;
|
set_m0_mode(M0_MODE_RX);
|
||||||
m0_state.shortfall_limit = _rx_overrun_limit;
|
m0_state.shortfall_limit = _rx_overrun_limit;
|
||||||
break;
|
break;
|
||||||
case TRANSCEIVER_MODE_TX:
|
case TRANSCEIVER_MODE_TX:
|
||||||
led_off(LED2);
|
led_off(LED2);
|
||||||
led_on(LED3);
|
led_on(LED3);
|
||||||
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_TX);
|
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_TX);
|
||||||
m0_state.mode = M0_MODE_TX_START;
|
set_m0_mode(M0_MODE_TX_START);
|
||||||
m0_state.shortfall_limit = _tx_underrun_limit;
|
m0_state.shortfall_limit = _tx_underrun_limit;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -377,20 +377,25 @@ int write_register(hackrf_device* device, uint8_t part,
|
|||||||
return HACKRF_ERROR_INVALID_PARAM;
|
return HACKRF_ERROR_INVALID_PARAM;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void print_state(hackrf_m0_state *state) {
|
static const char * mode_name(uint32_t mode) {
|
||||||
const char *mode_names[] = {"IDLE", "RX", "TX_START", "TX_RUN"};
|
const char *mode_names[] = {"IDLE", "RX", "TX_START", "TX_RUN"};
|
||||||
const uint32_t num_modes = sizeof(mode_names) / sizeof(mode_names[0]);
|
const uint32_t num_modes = sizeof(mode_names) / sizeof(mode_names[0]);
|
||||||
|
if (mode < num_modes)
|
||||||
|
return mode_names[mode];
|
||||||
|
else
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_state(hackrf_m0_state *state) {
|
||||||
printf("M0 state:\n");
|
printf("M0 state:\n");
|
||||||
printf("Mode: %u (%s)\n",
|
printf("Mode: %u (%s)\n", state->mode, mode_name(state->mode));
|
||||||
state->mode,
|
|
||||||
state->mode < num_modes ?
|
|
||||||
mode_names[state->mode] :
|
|
||||||
"UNKNOWN");
|
|
||||||
printf("M0 count: %u bytes\n", state->m0_count);
|
printf("M0 count: %u bytes\n", state->m0_count);
|
||||||
printf("M4 count: %u bytes\n", state->m4_count);
|
printf("M4 count: %u bytes\n", state->m4_count);
|
||||||
printf("Number of shortfalls: %u\n", state->num_shortfalls);
|
printf("Number of shortfalls: %u\n", state->num_shortfalls);
|
||||||
printf("Longest shortfall: %u bytes\n", state->longest_shortfall);
|
printf("Longest shortfall: %u bytes\n", state->longest_shortfall);
|
||||||
printf("Shortfall limit: %u bytes\n", state->shortfall_limit);
|
printf("Shortfall limit: %u bytes\n", state->shortfall_limit);
|
||||||
|
printf("Mode change threshold: %u bytes\n", state->threshold);
|
||||||
|
printf("Next mode: %u (%s)\n", state->mode, mode_name(state->mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void usage() {
|
static void usage() {
|
||||||
|
@ -169,6 +169,10 @@ typedef struct {
|
|||||||
uint32_t longest_shortfall;
|
uint32_t longest_shortfall;
|
||||||
/** Shortfall limit in bytes. */
|
/** Shortfall limit in bytes. */
|
||||||
uint32_t shortfall_limit;
|
uint32_t shortfall_limit;
|
||||||
|
/** Threshold m0_count value for next mode change. */
|
||||||
|
uint32_t threshold;
|
||||||
|
/** Mode which will be switched to when threshold is reached. */
|
||||||
|
uint32_t next_mode;
|
||||||
} hackrf_m0_state;
|
} hackrf_m0_state;
|
||||||
|
|
||||||
struct hackrf_device_list {
|
struct hackrf_device_list {
|
||||||
|
Reference in New Issue
Block a user