Skip to content

Allow a variable number of instructions per pseudoclock #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 143 additions & 23 deletions prawnblaster/prawnblaster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ extern "C"{
}

#ifndef PRAWNBLASTER_OVERCLOCK
const char VERSION[16] = "1.2.0";
const char VERSION[16] = "1.3.0";
#else
const char VERSION[16] = "1.2.0-overclock";
const char VERSION[16] = "1.3.0-overclock";
#endif //PRAWNBLASTER_OVERCLOCK

int DEBUG;
Expand All @@ -53,6 +53,8 @@ constexpr unsigned int wait_array_size = max_waits + 4;
unsigned int instructions[instruction_array_size];
// max_waits + 4
unsigned int waits[wait_array_size];
// Storage for the number of instructions for each pseudoclock
unsigned int num_pseudoclock_instructions[4];

#define SERIAL_BUFFER_SIZE 256
char readstring[SERIAL_BUFFER_SIZE] = "";
Expand Down Expand Up @@ -104,6 +106,70 @@ struct pseudoclock_config
bool configured;
};


// Instruction storage utilities

unsigned int convert_prawnblaster_instruction_count_to_instruction_count(unsigned int prawnblaster_instruction_count) {
return 2 * prawnblaster_instruction_count + 2;
}

unsigned int convert_instruction_count_to_prawnblaster_instruction_count(unsigned int instruction_count) {
return (unsigned int)((instruction_count - 2) / 2);
}

void set_num_prawnblaster_instructions(int pseudoclock, unsigned int num_instructions) {
// Double the instruction count to convert between PrawnBlaster instruction and our
// instruction storaged (a PrawnBlaster instructions in 2x 32-bit integers). Assign
// an extra two instructions to account for the required stop instruction at the end
// that is not writable.
num_pseudoclock_instructions[pseudoclock] = convert_prawnblaster_instruction_count_to_instruction_count(num_instructions);
}

unsigned int get_num_prawnblaster_instructions(int pseudoclock) {
return convert_instruction_count_to_prawnblaster_instruction_count(num_pseudoclock_instructions[pseudoclock]);
}

void reset_instructions() {
for (int i = 0; i < instruction_array_size; i++) {
instructions[i] = 0;
}
}

void reset_default_num_pseudoclock_instructions() {
unsigned int instructions_per_pseudoclock = (unsigned int) max_instructions / num_pseudoclocks_in_use;
for (int i = 0; i < 4; i++) {
if (i < num_pseudoclocks_in_use) {
set_num_prawnblaster_instructions(i, instructions_per_pseudoclock);
} else {
// This pseudoclock is not in use, so clear the number of instruction storage.
set_num_prawnblaster_instructions(i, 0);
}
}
}

unsigned int pseudoclock_instruction_start_addr(int pseudoclock) {
unsigned int addr = 0;
for (int i = 0; i < pseudoclock; i++) {
addr += num_pseudoclock_instructions[i];
}
return addr;
}

// Get the number of free instructions available to a pseudoclock
unsigned int num_free_prawnblaster_instructions(int pseudoclock) {
unsigned int reserved_instructions = 0;
for (int i = 0; i < num_pseudoclocks_in_use; i++) {
if (i != pseudoclock) {
reserved_instructions += num_pseudoclock_instructions[i];
}
}
// The number of PrawnBlaster instructions available to this pseudoclock is thus
// half of: the number of instructions available in total in the array, minus the
// number currently assigned to other pseudoclocks, minus two to indicate a stop
// instruction at the end (that can't be written to)
return convert_instruction_count_to_prawnblaster_instruction_count(instruction_array_size - reserved_instructions);
}

// Thread safe functions for getting/setting status
int get_status()
{
Expand All @@ -120,7 +186,7 @@ void set_status(int new_status)
mutex_exit(&status_mutex);
}

bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset, uint32_t hwstart, int max_instructions_per_pseudoclock, int max_waits_per_pseudoclock)
bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset, uint32_t hwstart, int max_waits_per_pseudoclock)
{
// Zero out waits array
int max_waits = (max_waits_per_pseudoclock + 1);
Expand All @@ -133,12 +199,14 @@ bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset,
int words_to_send = 0;
int wait_count = 1; // We always send a stop message
bool previous_instruction_was_wait = false;
int max_words = (max_instructions_per_pseudoclock * 2 + 2);
for (int i = config->sm * max_words; i < ((config->sm + 1) * max_words); i += 2)
int max_words = num_pseudoclock_instructions[config->sm];
unsigned int instruction_start_address = pseudoclock_instruction_start_addr(config->sm);
unsigned int maximum_pseudoclock_instructions = num_pseudoclock_instructions[config->sm];
for (int i = instruction_start_address; i < (instruction_start_address + maximum_pseudoclock_instructions); i += 2)
{
if (instructions[i] == 0 && instructions[i + 1] == 0)
{
words_to_send = i + 2 - config->sm * max_words;
words_to_send = i + 2 - instruction_start_address;
break;
}
else if (instructions[i] == 0)
Expand Down Expand Up @@ -296,7 +364,7 @@ bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset,
config->instructions_dma_channel, // The DMA channel
&instruction_c, // DMA channel config
&config->pio->txf[config->sm], // Write address to the PIO TX FIFO
&instructions[config->sm * max_words], // Read address to the instruction array
&instructions[instruction_start_address], // Read address to the instruction array
words_to_send, // How many values to transfer
true // Start immediately
);
Expand Down Expand Up @@ -588,7 +656,7 @@ void core1_entry()
pseudoclock_configs[i].sm = i;
pseudoclock_configs[i].OUT_PIN = OUT_PINS[i];
pseudoclock_configs[i].IN_PIN = IN_PINS[i];
success = configure_pseudoclock_pio_sm(&pseudoclock_configs[i], offset, hwstart, max_instructions / num_pseudoclocks_in_use, max_waits / num_pseudoclocks_in_use);
success = configure_pseudoclock_pio_sm(&pseudoclock_configs[i], offset, hwstart, max_waits / num_pseudoclocks_in_use);
if (!success)
{
if (DEBUG)
Expand Down Expand Up @@ -871,7 +939,7 @@ void loop()
else if (strncmp(readstring, "setnumpseudoclocks", 17) == 0)
{
unsigned int num_pseudoclocks;
int parsed = sscanf(readstring, "%*s %u %u", &num_pseudoclocks);
int parsed = sscanf(readstring, "%*s %u", &num_pseudoclocks);
if (parsed < 1)
{
fast_serial_printf("invalid request\r\n");
Expand All @@ -890,15 +958,47 @@ void loop()
{
waits[i] = 0;
}
// reset instructions
for (int i = 0; i < max_instructions * 2 + 8; i++)
{
instructions[i] = 0;
}
// Update the number of pseudoclocks
num_pseudoclocks_in_use = num_pseudoclocks;
// reset instructions
reset_instructions();
// Reset the number of instructions per pseudoclock to the default for this number of pseudoclocks
reset_default_num_pseudoclock_instructions();
fast_serial_printf("ok\r\n");
}
}
// Set number of instructions for a pseudoclock
else if (strncmp(readstring, "setnuminstructions", 18) == 0)
{
unsigned int num_instructions;
unsigned int pseudoclock;
int parsed = sscanf(readstring, "%*s %u %u", &pseudoclock, &num_instructions);
if (parsed < 2)
{
fast_serial_printf("invalid request\r\n");
}
else if (pseudoclock < 0 || pseudoclock > 3)
{
fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n");
}
else if (pseudoclock >= num_pseudoclocks_in_use)
{
fast_serial_printf("The specified pseudoclock must be enabled using setnumpseudoclocks\r\n");
}
else if (num_instructions <= 0)
{
fast_serial_printf("The number of instructions must be greater than 0\r\n");
}
else if (num_instructions > num_free_prawnblaster_instructions(pseudoclock))
{
fast_serial_printf("The number of instructions cannot be greated than the number of remaining instructions (%u)\r\n", num_free_prawnblaster_instructions(pseudoclock));
}
else
{
set_num_prawnblaster_instructions(pseudoclock, num_instructions);
reset_instructions();
}
}
else if (strncmp(readstring, "setinpin", 8) == 0)
{
unsigned int pin_no;
Expand Down Expand Up @@ -1143,7 +1243,8 @@ void loop()
unsigned int reps;
unsigned int pseudoclock;
int parsed = sscanf(readstring, "%*s %u %u %u %u", &pseudoclock, &addr, &half_period, &reps);
int address_offset = pseudoclock * (max_instructions * 2 / num_pseudoclocks_in_use + 2);
unsigned int address_offset = pseudoclock_instruction_start_addr(pseudoclock);
unsigned int max_pseudoclock_instructions = get_num_prawnblaster_instructions(pseudoclock);
if (parsed < 4)
{
fast_serial_printf("invalid request\n");
Expand All @@ -1152,9 +1253,13 @@ void loop()
{
fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n");
}
else if (addr >= max_instructions)
else if (pseudoclock >= num_pseudoclocks_in_use)
{
fast_serial_printf("invalid address\r\n");
fast_serial_printf("The specified pseudoclock must be enabled using setnumpseudoclocks\r\n");
}
else if (addr >= max_pseudoclock_instructions)
{
fast_serial_printf("invalid address for pseudoclock %u. Must be less than %u\r\n", pseudoclock, max_pseudoclock_instructions);
}
else if (reps == 0)
{
Expand Down Expand Up @@ -1202,7 +1307,8 @@ void loop()
unsigned int addr;
unsigned int pseudoclock;
int parsed = sscanf(readstring, "%*s %u %u", &pseudoclock, &addr);
int address_offset = pseudoclock * (max_instructions * 2 / num_pseudoclocks_in_use + 2);
unsigned int address_offset = pseudoclock_instruction_start_addr(pseudoclock);
unsigned int max_pseudoclock_instructions = get_num_prawnblaster_instructions(pseudoclock);
if (parsed < 2)
{
fast_serial_printf("invalid request\r\n");
Expand All @@ -1211,9 +1317,13 @@ void loop()
{
fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n");
}
else if (addr >= max_instructions)
else if (pseudoclock >= num_pseudoclocks_in_use)
{
fast_serial_printf("invalid address\r\n");
fast_serial_printf("The specified pseudoclock must be enabled using setnumpseudoclocks\r\n");
}
else if (addr >= max_pseudoclock_instructions)
{
fast_serial_printf("invalid address for pseudoclock %u. Must be less than %u\r\n", pseudoclock, max_pseudoclock_instructions);
}
else
{
Expand Down Expand Up @@ -1244,7 +1354,8 @@ void loop()
unsigned int inst_count;
unsigned int pseudoclock;
int parsed = sscanf(readstring, "%*s %u %u %u", &pseudoclock, &start_addr, &inst_count);
int address_offset = pseudoclock * (max_instructions * 2 / num_pseudoclocks_in_use + 2);
unsigned int address_offset = pseudoclock_instruction_start_addr(pseudoclock);
unsigned int max_pseudoclock_instructions = get_num_prawnblaster_instructions(pseudoclock);
if (parsed < 3)
{
fast_serial_printf("invalid request\n");
Expand All @@ -1253,7 +1364,11 @@ void loop()
{
fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n");
}
else if (start_addr + inst_count >= max_instructions)
else if (pseudoclock >= num_pseudoclocks_in_use)
{
fast_serial_printf("The specified pseudoclock must be enabled using setnumpseudoclocks\r\n");
}
else if (start_addr + inst_count >= max_pseudoclock_instructions)
{
fast_serial_printf("Invalid address and/or too many instructions (%d + %d).\r\n", start_addr, inst_count);
}
Expand Down Expand Up @@ -1299,7 +1414,7 @@ void loop()
instructions[address_offset + addr * 2 + 1] = (half_period - 4) / 2;
addr++;
}
else
else
{
reps_error_count++;
last_reps_error_idx = (address_offset + addr * 2 + 1) / 2;
Expand Down Expand Up @@ -1449,6 +1564,11 @@ int main()
}
// start with only one in use
num_pseudoclocks_in_use = 1;
// reset instruction array to 0
reset_instructions();
// Update the number of instructions available to the pseudoclock
reset_default_num_pseudoclock_instructions();
// Set PIO core
pio_to_use = pio0;

// initialise the status mutex
Expand Down
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ Note: the commands are only read if terminated with `\r\n`.
* `getfreqs`: Responds with a multi-line string containing the current operating frequencies of various clocks (you will be most interested in `pll_sys` and `clk_sys`). Multiline string ends with `ok\n`.
* `abort`: Prematurely ends buffered-execution.
* `setclock <mode:int> <freq:int>`: Reconfigures the clock source. See below for more details.
* `setnumpseudoclocks <number:int>`: Set the number of independent pseudoclocks. Must be between 1 and 4 (inclusive). Default at boot is 1. Configuring a number higher than one reduces the number of available instructions per pseudoclock by that factor. E.g. 2 pseudoclocks have 15,000 instructions each. 3 pseudoclocks have 10,000 instructions each. 4 pseudoclocks have 7,500 instructions each.
* `setnumpseudoclocks <number:int>`: Set the number of independent pseudoclocks. Must be between 1 and 4 (inclusive). Default at boot is 1. Unless otherwise configured (see `setnuminstructions`), the number of instructions per pseudoclock will be equally divided between the number of pseudoclocks specified. This command will also clear the instruction storage and reset the number of instructions per pseudoclock to the default.
* `setnuminstructions <pseudoclock:int> <num_instructions:int>`: Set the number of instructions available to a pseudoclock. If this command is not used, the number of instructions defaults to `total_available_instructions/num_pseudoclocks`. If the number of instructions for a pseudoclock is reduced using this command, then those spare instructions can be reallocated to other pseudoclocks in subsequent invocations of this command. This command will also clear the instruction storage.
* `getwait <pseudoclock:int> <wait:int>`: Returns an integer related to the length of wait number `wait` for the pseudoclock `pseudoclock` (pseudoclock is zero indexed). `wait` starts at `0`. The length of the wait (in seconds) can be calculated by subtracting the returned value from the relevant wait timeout and dividing the result by the clock frequency (by default 100 MHz). A returned value of `4294967295` (`2^32-1`) means the wait timed out. There may be more waits available than were in your latest program. If you had `N` waits, query the first `N` values (starting from 0). Note that wait lengths and only accurate to +/- 1 clock cycle as the detection loop length is 2 clock cycles. Indefinite waits should report as `4294967295` (assuming that the trigger pulse length is sufficient, see the FAQ below). Can be queried during buffered execution and will return `wait not yet available` if the wait has not yet completed.
* `start`: Immediately triggers the execution of the instruction set.
* `hwstart`: Triggers the execution of the instruction set(s), but only after first detecting logical high on the trigger input(s).
Expand Down