PSoC 4200 Prototyping Kit (7)

前回の記事の最後の式を再掲します。

n = (a2 - a1) · m1 + a1

この式を見ると (a2 < a1) の場合に右辺第 1 項が負となり、 n の値も負になることが分かります。
+ a1 の項も効いてきそうな気がしますが、前回も述べたように、(a2 - a1) を 16 ビット左シフトしてできた「空き地」に a1 が「スポッ」とはまる形になり、上位ビットへの桁上がりはなく、「符号ビット」には影響を及ぼしません。
n の値がマイナスになっても、中国剰余定理を満足する正しい値であることに変わりはありませんが、周期測定では、ゲート区間内の入力信号の最後のエッジでのカウントと最初のエッジでのカウントとの差を求めるので、クロック・カウント値としては単調増加の必要があり、正負の値が混在するのは好ましくありません。
中国剰余定理の証明は省略しましたが、今回の応用でいえば、答え n に (m1· m2) の整数倍を加算しても正しい解となる性質があります。
したがって、n の値がマイナスの場合は (m1· m2) を足してやれば値が正の範囲に戻ります。
ただし、問題があって、目的の n の範囲は

0 〜 (m1· m2 - 1) = 4294901759

であって 32 ビット符号なし整数の表現範囲のほぼ全てを埋め尽くしています。
これに「符号」の情報を持たせると、結局 33 ビット符号付整数が必要になってしまいます。
キャリー/ボロービットを直接に扱えない C プログラムの範囲では、32 ビット符号なし整数で n の値を求めてから補正する方法は使えません。
結果の n の正負は、事前に a2 と a1 との大小関係から判断できますから、あらかじめ補正を組み込んだ計算式を求めておいて使い分けることとし、事後の補正は行わない方針にします。

  • (a2 ≥ a1) の場合

   n = (a2 - a1) · m1 + a1

  • (a2 < a1) の場合

   n = (m1· m2) + (a2 - a1) · m1 + a1
      = (m2 + a2 - a1) · m1 + a1
      = ((m1 - 1) + a2 - a1) · m1 + a1
      = (m1· m1) + (a2 - a1 - 1) · m1 + a1
      = (a2 - a1 - 1) · m1 + a1

式の変形で、最後から 2 番目の式で (m1· m1) という項が現れていますが、m1 = 216 ですから

(m1· m1) = 216· 216 = 232 = 0x1_0000_0000

となり、下位 32 ビットはゼロなので無視できて、最後の式のようになります。
求まった 2 つの計算式は a2 と a1 との大小関係により

  • (a2 - a1) を使うか
  • (a2 - a1 - 1) を使うか

の違いだけです。
今、C 言語ではなく、ボロー/キャリーが扱えるアセンブラでの処理を考えてみます。
(a2 < a1) なら (a2 - a1) の計算でボローが出て (B = 1)、そうでなければボローは出ません (B = 0)
このボローを利用すると、-1 するかどうかを条件分岐しないで計算することができます。
x86 の場合を例に取って考えます。 ax に a2 の値、dx に a1 の値が入っているとします。 a2 は (a2 - a1) の計算にしか登場しないので、ax に最初に入っていた a2 の値は書き換えてしまいます。

; ax = a2
; dx = a1
    sub  ax,dx   ; ax <- (a2 - a1)
    sbb  ax,0h   ; ax <- (a2 - a1) - 0 - Borrow

これで条件分岐によらず、2 つの計算式を使い分けることができます。
C 言語の場合には直接キャリー/ボロー・ビットを扱うことはできませんが、16 ビット符号なし整数どうしの計算を 32 ビット符号なし整数に「プロモート」して行えば、32 ビット幅の結果の上位 16 ビットには全てボロー・ビットと同じ値 (1/0) が入ります。
つまり、(a2 < a1) なら結果の上位 16 ビットは 0xFFFF となり、(a2 ≥ a1) なら結果の上位 16 ビットは 0x0000 となります。
この上位 16 ビットを 16 ビット右シフトして (a2 - a1) の結果の下位 16 ビットと足し合わせれば、上で示したアセンブラと同様の結果を得ることができます。
C プログラムの断片を示すと、

  uint32_t n;

  n  = a2;
  n -= a1;
  n += (n >> 16);

となります。
a1, a2 は uint16_t なので、代入の左辺に使うと上位 16 ビットはゼロ拡張された値、つまり常にゼロになってしまうので、上位ビットが保存されるように uint32_t の変数 n にいったん格納してから計算しています。
この結果が求まったら、あとは 16 ビット左シフトして上位に持っていき、下位 16 ビットには a1 をハメ込めば完成です。

uint32_t crt_calc(uint16_t a1, uint16_t a2)
{
  register uint32_t n;
  
  n  = a2;
  n -= a1;
  n += (n >> 16);
  n  = (n << 16) + a1; 
  return(n);
} // uint32_t crt_calc()