循环码生成原理与FPGA实现

        近日,因为项目的需要,重新拾起编码理论,这个项目当中,发送端的信息序列添加了CRC码字,好在接收端进行CRC校验,检测接收到的信息序列是否在传输过程中出现了错误。CRC编码作为循环码字的一种,通常用在数据传输的检错中,其生成原理与循环码字的生成完全一致,其原理下面会进行说明。

1. 编码原理

        循环码的码字多项式都是多项式g(D)的倍式,该g(D)的阶数为r=n-k,对于一个信息序列M(D),其码字多项式可以表示为

A(D) = D^(n-k)*M(D) + r(D),

        r(D)为监督码多项式,它等于D^(n-k)*M(D)除以g(D)得到的余式,说的通俗一点,循环码的构造就是凑出一个余式r(D)使得码字多项式A(D)能够被生成多项式g(D)整除,在传输过程中码字A(D)一旦发生了错误,则接收端的码字不能够被生成多项式整除,就可以知道在传输过程中发生了错误,在循环码字的纠错范围内时,可以根据错误的图案(接收到的码字除以生成多项式的余式),找到对应错误的信息比特位。余式r(D)可以表示为

r(D) = D^(n-k)*M(D) mod g(D),

        下面举个例子看看循环码字是怎么生成的。对于一个(7, 4)的系统循环码,7表示码字长度n=7,4表示编码的信息序列长度k=4,那么监督序列的长度r=n-k=3,系统码表示生成的n比特码字中,前面k个比特的码字与信息码字完全一致,只是在信息码字后添加了r个比特的校验位。言归正传,(7, 4)系统循环码的生成多项式为g(D) = D^3 + D^2 + 1,若信息码为1001,那么怎么求编码后的码字呢?我们知道,只要求出后面r个比特的监督码字就行了,前面的k个比特的码字照抄信息码字就行了。信息码字1001写成多项式的形式为

M(D) = D^3 + 1,

        监督多项式通过求余得到:

r(D) = D^3*(D^3 + 1) mod (D^3 + D^2 + 1) = D + 1,

        码字多项式:

A(D) = D^3*(D^3 + 1) + D + 1 = D^6 + D^3 + D + 1,

        码字多项式写成序列的形式为1001011。值得一提的是,在编码过程中,所采用是GF(2)域上的加减法运算,相当于说减法也可看成加法,所有的加法都是模2加运算,即

1 + 1 = 0, 1 + 0 = 1, 0 + 1 = 1, 0 + 0 = 0,

        对于k比较长的情况,写成这种多项式的形式不便于计算,这里提供另外一种表达形式,实际上都是求余式操作,不过在形式上做了简化而已。

                                                                                         1  1  1  1

                                                                                 _____________________

                                                               1101       |         1   0  0  1  0  0  0

                                                                                   +    1   1  0  1

                                                                                   ----------------------------

                                                                                              1  0  0  0

                                                                                        +    1  1  0  1

                                                                                     -----------------------------

                                                                                                   1  0  1  0

                                                                                              +   1  1  0  1

                                                                                             -----------------------------

                                                                                                       1  1  1  0

                                                                                                 +    1  1  0  1

                                                                                               -------------------------------

                                                                                                            0  1  1   

        该方法只是将多项式操作变成了数字操作,信息序列1001后添加3个0代表乘上D^3,生成多项式D^3 + D^2 + 1写成序列形式就是1101,每次商上面的一位操作,进行模2加,余式继续同样的操作,直到补零的信息序列截止到最后一位,剩下的余式011即为监督序列,两种方法原理相同,当然结果也是相同的。

2. 硬件电路

循环码生成原理与FPGA实现_第1张图片

        上面的硬件电路图就是生成多项式g(D) = D^3 + D^2 + 1,对应的循环码字生成电路,至于为什么是这样,笔者表示上课时,老师也没讲,自己也并未深究,为了应付考试,强行记住了,然而到了做项目的时候,才发现自己对该电路完全没搞懂,于是乎,琢磨了一番,总算是搞明白了,下面且听我细细道来。

        首先,讲一下硬件电路对应的时序控制,由于该循环码字是系统码,生成的码字在前k=4个比特是将信息序列直接输出,也即与门1在1拍~4拍是开启的,信息序列进行编码,但输出相关的与门2是关闭的,与门2的输出在前4拍一直是0,在或门处,信息序列直接输出;到了5拍~7拍,需要输出监督序列了,这是信息序列已经全部处理完毕,监督序列完全寄存在了D1~D3这3个寄存器中,只要将这3比特的监督序列直接输出即可,这时与门1在5拍~7拍关闭,与门2在5拍~7拍开启,或门输出的是寄存器直接出来的监督序列。

        接下来,我们看看电路的左半部分是如何由信息序列生成监督序列的。我们看生成多项式g(D) = D^3 + D^2 + 1,对应到图上寄存器D3和D2后面的异或门,通过反馈操作实现除法求余操作。它的生成原理与第一小节编码原理给出的基本相同,但不同的地方是,由于信息比特是按照时钟逐比特输入的,没办法做到像编码原理给出的将这个信息序列对生成多项式求余,而是每进来一个信息比特,进行判断,然后操作。具体是啥意思呢,我讲个例子就清楚了。,

        信息序列还是1001,初始状态时,D1~D3三个寄存器的值都为0,输入的第一个信息比特是1,与D3的异或是1,这里为什么输入序列与D3做异或呢?其实这里隐含了一个选择操作,D1~D3代表当前的余式,D3表示余式的最高位,如果当前的输入与当前的余式最高位D3相同的话,则当前输入与余式的模2加为0,即当前输入更新使得余式最高位为0,则此时商上面上0,不用对生成多项式做模2加运算,余式只要右移一位完成余式的更新操作(一个比特与0异或等于自身,所以这种情况下,电路中异或不起作用);另一种情况是,当前的余式最高位D3与当前输入序列异或为1,表示当前输入更新后余式包含最高位,需要对生成多项式求余,求余操作通过电路中的异或进行,完成D1~D3的更新。具体步骤如下:

        (1) 初始状态:                       输入ui             D1    D2    D3                   输出uo

                                                                                  0       0        0

        (2)                                              1                     1       0        1                         1

        (3)                                              0                     1       1        1                         0

        (4)                                              0                     1       1        0                         0

        (5)                                              1                     1       1        0                         1

        D1~D3剩下的011即为监督比特,剩下3拍,逐比特输出:

        (6)                                              0                     0       1        1                         0

        (7)                                              0                     0       0        1                         1

        (8)                                              0                     0       0        0                         1

3. FPGA实现

        根据第二部分的电路原理分析,我们可以将循环码的编码分成两个阶段,第一个阶段为编码阶段,码字输出选择信息序列的输入直接作为输出;第二阶段为监督码字输出阶段,在第一阶段的最后,监督码字实际上已经完全生成,存储在寄存器中,第二阶段的任务就是将监督码字逐个输出,由此可以通过状态机实现该操作。状态机可以设置三种状态:IDLE, ENCODING, OUT。IDLE状态表示状态机处于空闲状态,不进行任何操作;ENCODING状态时,状态机进行输入信息序列的循环编码,同时输出信息码字;OUT状态时,输出监督码字。下面将采用verilog语言实现循环码的编码过程:

module cyc_encoding(
  input                       clk,
  input                       rst_n,
  input                       e_start_i,                //编码启动信号,比第一个信息序列早一个时钟周期
  input                       symbol_i,                //输入信息序列
  output reg              e_start_o,               //输出启动信号
  output reg              symbol_o                //输出编码序列
);

// 常量定义
parameter INFO_BITS_LEN = 4;          //信息序列长度
parameter CODED_BITS_LEN = 7;     //编码码字长度

wire enable;
wire encoding_start;
wire encoding_end;

reg [2:0] symbol_cnt;

// 状态机定义
parameter IDLE = 3'b001;
parameter ENCODING = 3'b010;
parameter OUT = 3'b100;
reg [2:0] state;

/**********************************************************/
// 编码状态机
/**********************************************************/
always @(posedge clk)
  begin
    if(!rst_n)
        state <= IDLE;
    else begin
        case(state)
            IDLE:  state <= e_start_i ? ENCODING : IDLE;
            ENCODING:  state <= encoding_start ? OUT : ENCODING;
            OUT:  state <= encoding_end ? IDLE : OUT;
            default:  state <= IDLE;
        endcase
    end
  end

assign enable = (state != IDLE);

/**********************************************************/
// 符号计数器
/**********************************************************/
always @(posedge clk)
    begin
        if(!rst_n)
            symbol_cnt <= 3'd0;
        else
            symbol_cnt <= enable ? (symbol_cnt + 1'b1) : 3'd0;
    end

assign encoding_start = (symbol_cnt == INFO_BITS_LEN -1);
assign encoding_end = (symbol_cnt == CODED_BITS_LEN -1);

reg [2:0] encoding_reg;
/**********************************************************/
// 寄存器更新操作
/**********************************************************/
always @(posedge clk)
    begin
        if(!rst_n)
            encoding_reg <= 3'd0;
        else begin
            case(state)
                IDLE:  encoding_reg <= 3'd0;
                ENCODING:
                    begin
                        encoding_reg[0] <= encoding_reg[2] ^ symbol_i;
                        encoding_reg[1] <= encoding_reg[0];
                        encoding_reg[2] <= encoding_reg[1] ^ encoding_reg[2] ^ symbol_i;
                    end
                 OUT:  encoding_reg <= {encoding_reg[1:0], 1'b0};
                 default:  encoding_reg <= 3'd0;
            endcase
        end
    end

/**********************************************************/
// 输出编码后的符号
/**********************************************************/
always @(posedge clk)
    begin
        if(!rst_n)
            symbol_o <= 1'b0;
        else begin
            case(state)
                IDLE:  symbol_o <= 1'b0;
                ENCODING:  symbol_o <= symbol_i;  //信息序列
                OUT:  symbol_o <= encoding_reg[2];  //监督序列
                default:  symbol_o <= 1'b0;
            endcase
        end
    end

/**********************************************************/
// 启动信号输出
/**********************************************************/
always @(posedge clk)
    begin
        if(!rst_n)
           e_start_o <= 1'b0;
        else
           e_start_o <= e_start_i;
    end

你可能感兴趣的:(HW-FPGA)