# 重做 quiz1,並完成延伸題目 contributed by < `type59ty` , `littlepee` > ###### tags: `sysprog2018` ## 測驗 `1` 考慮以下程式碼: ```C int my_xor(int x, int y) { return (x | y) OP1 (OP2 x OP3 OP4 y); } int main() { int x = 3, y = 5; return my_xor(x, y); } ``` `my_xor` 嘗試不用 XOR 運算子做出等效的 XOR 運算,其中 OP1, OP2, OP3, OP4 都是 operator,請補完實作。 OP1 = ==&== OP2 = ==~== OP3 = ==|== OP4 = ==~== ## 解題思路 題目要求做出 xor 等效運算,所以可以從邏輯閘的角度去想,用 and, or gate 組出 xor gate xor 真值表如下 | A | B |$A \oplus B$| |--|--|---| | 0 | 0 | 0| | 0 | 1 | 1| | 1 | 0 | 1| | 1 | 1 | 0| 根據真值表寫成布林代數表示式:$A \oplus B$ = $\overline{\rm A}B + A\overline{\rm B}$ ![](https://i.imgur.com/9efZK1g.png) :::info 運用 [demorgan's law](https://zh.wikipedia.org/wiki/%E5%BE%B7%E6%91%A9%E6%A0%B9%E5%AE%9A%E5%BE%8B) ,可以對原本的 xor 做等效電路 ![](https://i.imgur.com/WWElycI.png) ::: 簡單來說: **先相乘再反相 = 先反相再相加,先相加再反相 = 先反相再相乘** - 而反相再反相,不會改變原本電路 $\overline{A}B + A\overline{B}$ $=$ $\overline{{\overline{\overline{\rm A}B + A\overline{B}}}}$ $=$ $({\overline{\overline{\overline{A}B})({\overline {A\overline{B}}}}})$ - 再根據 demorgan's law ,將第一個 bar 後面的 A,B 化成 $=$ ${(\overline{A+\overline{B}) (\overline{A}+B})}$ - 將裡面的 A,B 乘開 $=$ ${(\overline{\rm \overline{A}.\overline{B}) + (AB})}$ - 最後再將第二個 bar 後面化成 $=$ $(A+B) (\overline{A}+\overline{B})$ - 所以原本的 xor: $\overline{\rm A}B + A\overline{\rm B}$ 可以等效化成 $(A+B) (\overline{\rm A}+\overline{\rm B})$ 補上測試程式碼: ```C #include <stdio.h> int my_xor(int x, int y) { return (x | y) & (~ x | ~ y); } int main() { int x = 3, y = 5; printf("xor(%d, %d) = %d\n", x, y, my_xor(x, y)); return 0; } ``` 測試結果: ```shell $ gcc -o test_xor test_xor.c $ ./test_xor xor(3, 5) = 6 ``` --- ## 測驗 `2` parity (check) bit 可翻譯為「奇偶校驗位元」或「同位檢查位元」,常見於降低資料傳輸的錯誤。在資料傳送出去前,先在資料原有位元額外加上一個 parity bit,再將 parity bit 與資料的位元所組成的位元傳送出去,待接收完畢後,就檢查看看是否有奇數個 `1` 或偶數個 `1`,以判斷有無發生錯誤。 parity bit 有兩種類型: * 偶校驗位 (even): `1` 的個數加起來須為偶數個 * 奇校驗位 (odd): `1` 的個數加起來須為奇數個 範例 1: * 輸入: 254 * 輸出: odd parity * 解釋 * 254~10~ 的二進位表示為 `11111110`,其中共有 7 個 `1`,奇數個 範例 2: * 輸入: 1742346774 * 輸出: even parity 同位元檢查已經廣泛地應用到電腦的主記憶體,其優點是只要利用 XOR 或 NOT,就能製造成硬體;缺點則是無法更正錯誤,也無法偵測到所有錯誤,一旦接收到的位元圖樣同時有偶數個 (2, 4, 6, ...個) 位元錯誤,就偵測不到錯誤,因為在這種情況下,`1` 的個數仍舊會維持奇數個或偶數個。 考慮到以下判斷 parity bit 的程式碼 ```cpp #include <stdio.h> /* Generate look-up table while pre-processing */ #define P2(n) n, n ^ 1, n ^ 1, n #define P4(n) P2(n), P2(n ^ 1), P2(n ^ 1), P2(n) #define P6(n) P4(n), P4(n ^ 1), P4(n ^ 1), P4(n) #define LOOK_UP P6(0), P6(1), P6(1), P6(0) /* LOOK_UP is the macro expansion to generate table */ unsigned int table[256] = { LOOK_UP }; int parity(int num) { /* Number is considered to be of 32 bits */ int max = 16; // Dividing the number into 8-bit // chunks while performing XOR while (max >= 8) { num = num ^ (num >> max); max /= N; } // Masking the number with 0xff (11111111) // to produce valid 8-bit result return table[num & 0xff]; } int main() { unsigned int num = 1742346774; /* Result is 1 for odd parity, 0 for even parity */ int result = parity(num); printf("%s parity\n", parity(num) ? "odd" : "even"); return 0; } ``` 補完程式碼。 N = ==2== ### 解題思路 下圖為 num 變化的過程, parity check 的原則就是將所有 bits 之間作 xor 運算 (0 ^ 1 ^ 2 ^ ... ^ 31 ),所以這邊 while 裡面做的就是讓所有的 bits 作 xor ,結果最後會集中到 LSB ```clike int max = 16; while (max >= 8) { num = num ^ (num >> max); max /= 2; } ``` ![](https://i.imgur.com/3N9oWGP.png) 以下用 8 bit 當作例子,相連的字母代表 `xor` 運算, `*` 代表 don't care ,可以看到最後結果會集中到 LSB ``` a b c d e f g h a b c d ------------------------------------------------------------- xor * * * * ae bf cg dh * * * * ae bf ------------------------------------------------------------- xor * * * * * * aceg bdfh * * * * * * aceg ------------------------------------------------------------- xor * * * * * * * * abcdefgh ``` :::warning 延伸題目: 解釋和實作循環冗餘碼 (CRC) 程式碼 ::: ### 循環冗餘碼 (CRC) - [循環冗餘校驗 - 維基百科](https://zh.wikipedia.org/wiki/%E5%BE%AA%E7%92%B0%E5%86%97%E9%A4%98%E6%A0%A1%E9%A9%97) - [[教學] CRC(循環冗餘碼)長除法](http://oilcut123.pixnet.net/blog/post/354497867-%5B%E6%95%99%E5%AD%B8%5D-crc%28%E5%BE%AA%E7%92%B0%E5%86%97%E9%A4%98%E7%A2%BC%29%E9%95%B7%E9%99%A4%E6%B3%95-%E6%95%99%E4%BD%A0%E5%A6%82%E4%BD%95%E7%AE%97crc%E9%95%B7) - [CRC Series, Part 3: CRC Implementation Code in C/C++](https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code) - [最通俗的CRC校驗原理剖析](http://blog.51cto.com/winda/1063951) ### 基本介紹 CRC 是兩個位元組資料流採用 [modulo 2 division](https://www.geeksforgeeks.org/modulo-2-binary-division/)(沒有進位,使用 XOR 來代替減法)相除所得到的餘數。 假設資料位元為: $\ x^7+x^5+x^3+x$ ( $1010\ 1010$ ) 產生的多項式為: $\ \ \ \ x^4+x^2+x^1+1$ ( $10111$ ) 由於產生多項式 $x^4+x^2+x^1+1$ 的羃次為 4 (最高 $x^4$),故先在資料位元 $1010\ 1010$ 的後面加上四個 0 得到 $1010\ 1010\ 0000$ 以長除法求取 $1010\ 1010\ 0000$ 除以生成多項式 $x^4+x^2+x^1+1$ ($10111$) 的餘數 CRC 使用 [modulo 2 division](https://www.geeksforgeeks.org/modulo-2-binary-division/),不能化為十進位做減法運算,相減時規則為: $1-1=0$ $0-0=0$ $1-0=1$ $0-1=1$ 等同XOR運算 數字相同為 0,相異為 1 | A | B |$A \oplus B$| |--|--|---| | 0 | 0 | 0| | 0 | 1 | 1| | 1 | 0 | 1| | 1 | 1 | 0| ![](https://i.imgur.com/vScK8S3.png) CRC 碼為上述所求出的餘數 $1100$,而完整訊息則是在原始的資料位元 $1010\ 1010$ 後面加上 CRC 碼 $1100$ ,得到 $1010\ 1010\ 1100$ ,只要收訊端有正確的接收到訊息,該資訊將能被產生的多項式整除 (餘數為零)。 - 常用的 CRC |名稱 | 多項式 |表示法| |--|--|---| | CRC-1 | $x+1$(用途:硬體,也稱為奇偶校驗位)| 0x1 | | CRC-8 | $x^8+x^7+x^6+x^4+x^2+1$ | 0xD5| | CRC-12 | $x^{12}+x^{11}+x^3+x^2+x^1+1$(用途:通訊系統) | 0x180F| | ... | | | ### 原理分析 CRC是基於[有限域](https://zh.wikipedia.org/zh-tw/%E6%9C%89%E9%99%90%E5%9F%9F)GF(2)的多項式環 (除以2的同餘),所有係數皆為 0 或 1 的多項式係數的集合,並且集合對於所有的代數操作都是封閉的,例如: $x^3+2x+1$ 等價於 $x^3+1$ > $2x$ 的部份因為除2同餘的關係被消除了。 同樣可以對多項式作除法並且得到商和餘數 例如,如果用 $x^3 + x^2 + x$ 除以 $x + 1$ ,會得到: $\frac{(x^3+x^2+x)}{(x+1)} = (x^2+1)-\frac{1}{(x+1)}$ 移項一下 $(x^3+x^2+x)=(x^2+1)(x+1)-1$ 等價於: $(x^2+x+1)x=(x^2+1)(x+1)-1$ 這裡除法得到了商 $x^2 + 1$ 和餘數 -1,因為是奇數所以最後一位是 1。 字串中的每一位其實就對應了這樣類型的多項式的係數。為了得到CRC,我們首先將其乘以 $x^n$,這裡 n 是一個固定多項式的階數,然後再將其除以這個固定的多項式,餘數的係數就是CRC。 在上面的等式中, $x^2+x+1$ 表示了本來的資訊位是 `111` , $x+1$ 是 CRC 的==鑰匙==,最高次為 1,所以將原來的資訊乘上 $x^1$ 得到 $(x^3+x^2+x)$,可視為原來的資訊位補1個零成為 `1110`。 一般來說,其形式為: $M(x)\cdotp x^n=Q(x)K(x)-R(x)$ 這裡的 $M(x)$ 是原始的資訊多項式,$K(x)$ 是 n 階的 CRC==鑰匙==多項式, $Q(x)$ 為商, $R(x)$ 為餘 $M(x)\cdotp x^n$ 表示了將原始資訊後面加上 n 個 0, $R(x)$ 是餘數多項式,即是 CRC 的「校驗和」,在通訊中,發送者在原始的資訊資料M後附加上 n 位的 R(替換本來附加的0)再發送。 接收者收到 M 和 R 後,檢查 $M(x)\cdotp x^n+R(x)$ 是否能被 $K(x)$ 整除。如果是,則接收者認為該資訊是正確的。 其中 $M(x)\cdotp x^n+R(x)$ 就是發送者所想要發送的資料,也稱作 **codeword**。 ### 實作 以下為 CRC-8 的程式碼實作 ```cpp #include <stdio.h> #define POLYNOMIAL 0xD5 unsigned int crc8(unsigned int message) { unsigned int remainder; remainder = message; for (unsigned int bit = 0; bit < 8; ++bit) { if (remainder & 0x80) remainder ^= POLYNOMIAL; remainder = (remainder << 1); } return (remainder >> 4); } int main() { unsigned int data = 0x56; unsigned int crc = crc8(data); printf("Input data: %x\n", data); printf("CRC code : %x\n", crc); } ``` 輸出結果: ```shell $ gcc -o crc crc.c $ ./crc Input data: 56 CRC code : c ``` 說明: * 一開始定義產生多項式 (除數) 為 0xD5 ,跟 input data (被除數) 0x56 做 [modulo 2 division](https://www.geeksforgeeks.org/modulo-2-binary-division/) * 若被除數之 MSB 為 `1`,就執行運算,並且每次將餘數左移 1 bit,至多 bit length 次,最後取 remainder 前 4 bits 為回傳值,這個值即是上述分析所提到的 CRC 的「校驗和」,這個值會被附加在 input data 之後 ( $M(x)\cdotp x^n+R(x)$ ),即上述的 **codeword**。 * 將 codeword 除以 CRC 多項式,若餘數為 0 代表傳送的資料正確,否則錯誤,此例中 0x56c $\div$ 0xD5 餘數為 0 代表結果正確