
This is a Triple Modular Redundancy (TMR) voter chip for safety-critical embedded systems. It interfaces with 3 redundant low-power processors via SPI and votes their 8 output bits.
ui_in[7:0]), sent to all 3 processors.uo_out[7:0]).uio):
cs_n (uio[0], out)sclk (uio[1], out)miso0 (uio[2], in), mosi0 (uio[3], out)miso1 (uio[4], in), mosi1 (uio[5], out)miso2 (uio[6], in), mosi2 (uio[7], out)The chip acts as SPI master using SPI Mode 0 (CPOL=0, CPHA=0).
This means:
sclk idles lowThe design polls all 3 CPUs in parallel once per millisecond.
next_prn + switches + majority_byte.echoed_prn + desired_out + desired_valid.1.024 MHz from the 8.192 MHz project clock.Each SPI slice has its own 8-bit LFSR with polynomial x^8 + x^6 + x^5 + x^4 + 1.
The 3 reset seeds are:
0x2A0x540xA8The PRG protocol is staged across frames:
N, the master sends a new next_prn byteN+1, the CPU echoes that stored byte as echoed_prnechoed_prn against the locally stored PRG state for that sliceThe CPU does not need to compute the PRG sequence itself. It only needs to stage and echo the last received PRG byte.
If the echoed PRG byte is wrong for one slice, that slice is invalid for that frame.
Each processor also returns:
desired_out: the 8 bits it wants to drivedesired_valid: one validity bit per output bitFor each slice and each bit:
desired_valid bit is 1, the slice contributes the processor's desired_out bitThat per-slice fallback state is intentionally stored redundantly inside each slice.
The common voter then performs a pure bitwise 2-of-3 majority over the 3 slice-resolved bits.
The majority_byte sent back to each processor is also per-CPU:
1 means that CPU sent a valid bit and that bit matched the final voted output0 means the CPU bit was invalid or disagreed with the final voted outputThis majority_byte is reported one frame later, because it is computed from the frame that just completed and transmitted in the next frame.
Timing summary:
0.5 ms1 msThe CPU side only needs to stage the received PRG byte and echo it back on the next frame. It does not need to compute the PRG sequence.
#include <stdint.h>
typedef struct {
uint8_t staged_prn;
uint8_t desired_out;
uint8_t desired_valid;
} cpu_state_t;
// rx_buffer receives: next_prn, switches, majority_byte
// tx_buffer sends: echoed_prn, desired_out, desired_valid
void spi_slave_handler(cpu_state_t *state, uint8_t rx_buffer[3], uint8_t tx_buffer[3]) {
uint8_t next_prn = rx_buffer[0];
uint8_t switches = rx_buffer[1];
uint8_t majority = rx_buffer[2];
tx_buffer[0] = state->staged_prn;
tx_buffer[1] = compute_desired_outputs(switches, majority);
tx_buffer[2] = compute_desired_valid(switches, majority);
state->staged_prn = next_prn;
}
This mirrors the current Verilog protocol: staged PRG echo, per-bit output validity, and per-CPU majority feedback.
ui_in[7:0]): Switches from the demo board, forwarded to all 3 processors.uo_out[7:0]): Voted output byte.uio): SPI signals as listed above.From the project root:
./build -t
./build -s
./build -t -s
The current cocotb suite covers:
majority_byte timing and contentsWaveforms are written to test/tb.fst.
cs_n and sclkmiso and one dedicated mosi line per CPU8.192 MHzOn the Tiny Tapeout demo board, the RP2350 controller can be used for basic bring-up.
The RP2350 SPI1 peripheral in slave mode is connected to cs_n, sclk, miso0, and mosi0.
For simple majority experiments, miso0 and miso2 can be driven from the same RP2350 transmit signal so that two processor channels return the same data.
Each external CPU should:
next_prndesired_outdesired_validechoed_prn, desired_out, and desired_valid on the next poll| # | Input | Output | Bidirectional |
|---|---|---|---|
| 0 | in0 | out0 | cs_n |
| 1 | in1 | out1 | sclk |
| 2 | in2 | out2 | miso0 |
| 3 | in3 | out3 | mosi0 |
| 4 | in4 | out4 | miso1 |
| 5 | in5 | out5 | mosi1 |
| 6 | in6 | out6 | miso2 |
| 7 | in7 | out7 | mosi2 |