Virtual Reality Buzzer

2022-02-17

Part of my journey through Shenzhen I/O

Problem: a radio receiver provides XBus values in a non-blocking fashion (making it different than normal XBus behavior). If there is no value, it will return -999. If the radio sends a packet containing 1, then the buzzer should turn on. If the packet instead contains a 0, then the buzzer should turn off. The buzzer is controlled via simple I/O. To make the buzzer buzz, its input needs to alternate between 0 and 100 each cycle.

The challenge here is that the radio receiver outputs packets with -999, 0, or 1 on XBus, and those packets need to generate behavior that is retained cycle-to-cycle. At the same time, we have three distinct values we might see coming off the XBus, but testing consumes the value, so if we want to test for all values, we need to store the value in acc so we can reference it multiple times. This creates a need for two pieces of state to be stored. This can be accomplished using two chips, but this introduces a cycle of latency if they interleave incorrectly, which causes the validation to fail. So here's the challenge:

  • To handle -999 (which simply proceeds with the current behavior), we must store whether the buzzer is enabled or not,
  • To test for -999, 1, and 0, we have to store the value in acc, and
  • To pass validation, we must respond to new input within one cycle.

First Attempt

I worked over several hours to develop a solution that synchronizes two MX4000s by ensuring the buzzer status is written to p1 by the first chip before the second chip reads the value.

The first chip reads from x0, storing whether the buzzer is enabled on p1. Since simple I/O is persistent, we use p1 as a register of sorts:

  mov x0 acc
  mul 100
  tgt acc -1
+ mov acc p1
end:
  slp 1

The second chip then reads the value and cycles power to the buzzer if it is enabled. The most interesting part is that it executes nops until the first chip has written a value:

  nop
  nop
  nop
  nop
  teq p0 100
+ mov 100 p1
+ slp 1
+ mov 0 p1
  slp 1

This solution took me a couple hours of thinking, and is much worse than the average solutions! It uses just a touch shy of double the power of most solutions (432 vs. less than 250), uses 2-4 extra lines of code, and costs ¥1 more! So I'm missing something fundamental here that's worth learning.

Second Attempt

I spent about 15 minutes thinking about what I could do better. To bring cost down, I swapped two MC4000s for a single MC6000. The strategy here is to add the incoming value to the existing value, which the code must ensure is either 0 or 100. I then tease apart whether the buzzer is enabled or not with teq acc 100, and if so, insert an extra oscillation cycle to avoid storing extra state:

  add x0
  teq acc -999
+ mov 0 acc
  teq acc -899
+ mov 1 acc
  tgt acc 99
+ sub 100
  mul 100
  mov acc p1
  teq acc 100
+ slp 1
+ mov 0 p1
  slp 1

This is effective! Cost is down to ¥5 and power is reduced to 326 (from 432), but it's still a long way from ~250. It only reduces code by 1 line, from 14 to 13, which is still higher than average (12, it appears). What insight am I missing?

Third Attempt

It felt like I was using too much power executing tests for various possible values. On this attempt, I tried to collapse as many checks as possible:

  add x0
  tlt acc 0
+ dgt 2
+ add 9
- dgt 0
  mul 100
  mov acc p1
  teq acc 100
+ slp 1
+ mov 0 p1
  slp 1

This leaves cost at ¥5, but reduces code to 11 lines and power to 288. Still less efficient than the average solution!!