본문 바로가기

Verilog HDL 설계

간단한 Deinterleaver

Interleaving은 연속적인 데이터를 섞어주는 것을 의미한다. Interleaver를 쓰는 이유는 무선 데이터 전송 시에 연속적인 에러가 발생하는 경우 데이터를 섞음으로써 시간 상이나 주파수 상에서 에러를 분리하기 위해서다. 아래의 예시처럼 burst error가 생기는 경우 에러를 분산시키는 효과가 있다. 에러가 분산되면 에러를 정정(error correcting)하기 쉬워진다. 5g나 lte에서도 interleaver를 사용한다.

 

 

Deinterleaving은 interleaved data를 다시 순서대로 정렬해주는 것이다. 오늘은 Verilog로 Deinterleaver를 만드는 법을 다뤄보고자 한다.

 

2048개의 데이터를 4개의 묶음으로 interleaving하는 것은 아래와 같이 표현할 수 있다. 

 

A0: Out(4w+0) = A((w+0)    mod 512)    w = 0, 1, 2, ... , 1023

B1: Out(4w+0) = B((w+256) mod 512)

C2: Out(4w+0) = C((w+384) mod 512)

D3: Out(4w+0) = D((w+128) mod 512)

 

여기서 mod 연산을 그대로 사용할 경우 회로 복잡도가 매우 높아진다. 따라서 deinterleaver를 설계할 때는 회로 복잡도를 줄이기 위해 규칙성을 찾아야 한다.

 

A의 경우 16진수로 데이터가 저장되는 순서를 나타내면

 

0x000, 0x01, 0x02, ..., 0x1ff 까지이다.

 

B는 0x100, 0x101, 0x102, ... , 0x1ff, 0x000, 0x001, ... , 0x0ff 로 나타낼 수 있다. C와 D는 생략한다.

 

Deinterleaver는 섞여있는 데이터를 재정렬해줘야 하므로 섞여있는 데이터를 저장해둘 저장공간이 필요하다. 이럴때 쓰는게 SRAM인데, 입력된 값을 각 주소마다 저장해준다. 

 

A, B, C, D를 어떻게 저장해야할까? A, B, C, D 각각을 4개의 SRAM으로 저장할 수도 있고, 전부를 한 SRAM에 저장할 수도 있을 것이다.

 

여기서 우리는 하나의 SRAM을 사용해보겠다. 왜냐하면 SRAM은 저장을 하기 위한 cell과 더불어 각 cell에 접근하기 위한 Row, Column decoder 및 신호를 증폭하기 위한 Sense amplifier가 필요해서 여러개의 작은 SRAM보다는 한개의 큰 SRAM 쪽이 면적 면에서 이득이기 때문이다. 

 

하나의 SRAM을 사용한다면 A, B, C, D에 대해 address는 각각 0, 512, 1024, 1536을 더해줘야 할 것이다.

 

 

A0, B1, C2, D3는 위의 사진과 같은 값끼리 섞이게 된다. a0, b256, c128, d384, a1, b257, c129, d385, ... 의 순서대로 섞이게 된다. 각 index가 511을 넘게 되면 0으로 순환한다. 즉 b는 256~511, 0~255 순으로 섞이게 된다.

 

따라서 A, B, C, D는 아래와 같은 범위에 저장될 것이다.

 

A: b'000_0000_0000 ~ b'001_1111_1111

B: b'010_0000_0000 ~ b'011_1111_1111

C: b'100_0000_0000 ~ b'101_1111_1111

D: b'110_0000_0000 ~ b'111_1111_1111

 

또한 A, B, C, D의 시작 address는 각각 11'h000, 11'h300, 11'h480, 11'h780 일 것이다. 이를 2진수로 나타내면

 

A: b'00_00_000_0000

B: b'01_10_000_0000

C: b'10_01_000_0000

D: b'11_11_000_0000

 

여기서 찾을 수 있는 규칙은 세 가지다. MSB, LSB는 각각 Most, Least Significant Bit다.

 

1. MSB 2비트는 00 01 10 11 순으로 순환한다.

2. MSB 4비트에서 하위 2비트는 00 10 01 11 순으로 순환한다.

3. LSB 7비트는 000_0000 ~ 111_1111까지 순환하고, A~D 모두 같다.

 

2번 규칙은 비트 위치를 바꾸면 00 01 10 11이 된다.

 

이를 통해 SRAM을 제어할 모듈은 아래와 같이 설계될 수 있다.

 

module control(
    output [8-1:0] RA,
    output [3-1:0] CA,
    output reg NCE, NWRT,
    input  clk, rst
);

reg [6:0] cnt_7b;
reg [1:0] cnt_2b_MSB, offset;
reg [10:0] cnt_11b;
reg phase, enb_cnt;

wire [10:0] odd, even;
wire [1:0] reverse_cnt = {cnt_2b_MSB[0], cnt_2b_MSB[1]} + offset;

assign even = {cnt_2b_MSB, offset,      cnt_7b};
assign odd  = {cnt_2b_MSB, reverse_cnt, cnt_7b};

assign {RA, CA} = NWRT ? even : odd;

always @ (posedge clk) begin
    if (rst) begin
        NCE <= 1'b0;
        NWRT <= 1'b0;
        cnt_7b <= 7'b0;
        cnt_2b_MSB <= 2'b0;
        offset <= 2'b0;
        phase <= 1'b0;
        enb_cnt <= 1'b0;
        cnt_11b <= 11'b0;
    end
    else if (enb_cnt) begin
        NCE <= 1'b0;
        NWRT <= ~NWRT;
        
        if (cnt_2b_MSB == 2'b11 && ~NWRT) begin
            cnt_2b_MSB <= 2'b0;
            if (cnt_7b == 7'h7f) begin
                cnt_7b <= 7'b0;
                offset <= offset + 2'b1;
            end
            else if (~NWRT) begin
                cnt_7b <= cnt_7b + 7'b1;
            end
        end
        else if (~NWRT) begin
            cnt_2b_MSB <= cnt_2b_MSB + 2'b1;
        end

        if (cnt_11b == 11'h7ff & ~NWRT) begin
            phase <= ~phase;
            cnt_11b <= 11'b0;
        end
        else if (~NWRT) begin
            cnt_11b <= cnt_11b + 11'b1;
        end
    end
    else begin
        enb_cnt <= 1'b1;
        NCE <= 1'b1;
        NWRT <= ~NWRT;
    end
end

endmodule

 

우선 변수들의 용도부터 알아보자.

 

RA, CA는 각각 Row, Column Address다.

NCE는 Non Chip Enable로 0일때 SRAM을 작동, 1일때 SRAM을 정지시킨다.

NWRT는 Not WRiTe로 0일때 write, 1일때 read 동작을 하도록 SRAM을 제어한다.

 

cnt_7b는 LSB 7비트의 값을 제어할 카운터이다.

cnt_2b_MSB는 MSB 2비트 및 3~4비트를 제어하는 카운터다.

offset은 b, c, d의 주소값이 범위를 넘어갈 경우 각 시작위치로 순환시키는 카운터다.

cnt_11b는 한 deinterleaver 연산이 끝나는 타이밍을 이용하기 위한 카운터다.

phase는 홀수번째 연산동안 0, 짝수번째 연산동안 1을 나타낸다.

enb_cnt는 NWRT와 NCE 및 카운터의 시작 타이밍을 제어하기 위한 1비트 레지스터 변수다.

 

odd, even은 각각 홀수번째, 짝수번째 연산에서의 SRAM 주소값을 가진다. 이 값들을 RA, CA에 wiring하여 출력을 내보낸다.

reverse_cnt는 cnt_2b_MSB를 반전시키며, offset을 더함으로써 값이 순환하게 된다. 베릴로그에서 2비트 덧셈 결과를 2비트로 할 경우 3bit carry가 무시되면서 overflow가 발생하기 때문이다.

 

이 회로는 2 clock transition 동안 SRAM read, write를 반복하기 때문에 NWRT가 매 클락마다 반전되며, 카운터 조건문에 NWRT를 넣어 두 클락마다 카운터가 증가하도록 하였다.

 

A, B, C, D, A, ... 의 순서대로 데이터를 SRAM에 입력하기 때문에 cnt_2b_MSB가 순환할때마다 cnt_7b가 1씩 증가할 것이다. 또한 offset은 cnt_7b가 0으로 순환할 때마다 증가하도록 했다. 즉 b의 MSB가 11이고 offset이 01일 경우 reverse_cnt는 00이 된다.

phase 변수는 cnt_11b의 순환마다 반전되도록 하여 홀수, 짝수번째 연산을 구분할 수 있게 만들었다. 구분하는 이유는 한 deinterleave 동작 후 바로 다음 deinterleaving을 하기 위함이다. 첫 연산 이후를 생각해보면 이해가 될 것이다.

 

최종적으로 {RA, CA}를 NWRT, even, odd에 대해 정의함으로써 전체적인 Address Control logic이 완성된다.

 

SRAM과 top level module은 아래와 같이 작성하였다. SRAM은 단순히 동작만 비슷하게 하도록 한 behavior level 코드로, 실제 SRAM하고는 차이가 있다.

 

module SRAM
#(	parameter ADDRESSSIZE = 11,
	parameter ADDRESSBITSIZE = 18432,		    
        parameter WORDSIZE = 9)
(
    input NWRT,
    input [WORDSIZE-1:0] DIN,
    input [8-1:0] RA,
    input [3-1:0] CA,
    input NCE,
    input CK,
    output [WORDSIZE-1:0] DO 
);

syncRAM MEM(DIN, DO, {RA, CA}, ~NCE, ~NWRT, NWRT, CK);

endmodule

module syncRAM( dataIn,
                dataOut,
                Addr, 
                CS, 
                WE, 
                RD, 
                Clk
);

// parameters for the width 

parameter ADR   = 11;
parameter DAT   = 9;
parameter DPTH  = 2048;

//ports
input   [DAT-1:0]  dataIn;
output reg [DAT-1:0]  dataOut;
input   [ADR-1:0]  Addr;
input CS, 
      WE, 
      RD, 
      Clk;

      

//internal variables

reg [DAT-1:0] SRAM [0:DPTH-1];


always @ (posedge Clk)
begin

 if (CS == 1'b1) begin

  if (WE == 1'b1 && RD == 1'b0) begin

   SRAM [Addr] <= dataIn;

  end

  else if (RD == 1'b1 && WE == 1'b0) begin

   dataOut <= SRAM [Addr]; 

  end

  else;

 end

 else;

end

endmodule
module top(
    output [8:0] deleav_data,
    input  clk, rst,
    input  [8:0] data_in
);

wire [8:0] data_sram;
wire [10:0] addr;
wire [8-1:0] RA;
wire [3-1:0] CA;
wire NCE, NWRT;

SRAM MEM(
    .NWRT(NWRT),
    .DIN(data_in),
    .RA(RA),
    .CA(CA),
    .NCE(NCE),
    .CK(clk),
    .DO(deleav_data) 
);

control CTRL(
    .RA(RA),
    .CA(CA),
    .NWRT(NWRT),
    .NCE(NCE),
    .clk(clk),
    .rst(rst)
);

endmodule

 

테스트벤치는 아래와 같이 작성하였으며, vector.txt는 python을 통해 만든 테스트 벡터 세트다.

 

`timescale 1ns/1ps

module tb_deinter();

reg clk, rst, enb;
wire [7:0] RA;
wire [2:0] CA;
wire NCE, NWRT;
wire [8:0] out_data;
reg  [8:0] data [0:2048-1];
reg  [8:0] in_data;

initial begin
    clk <= 1;
    rst <= 0;
    enb <= 0;
    #10
    rst <= 1;
    enb <= 1;
    #10
    rst <= 0;
    #40960
    #40960
    #40960
    $finish;
end

integer i, j, k;

initial begin
    $readmemh("./vector.txt", data);
    #(30);
    for (j=0;j<3;j=j+1) begin
        for (i=0;i<2048;i=i+1) 
        begin
            in_data <= data[i];
            #(20);
        end
    end
end

always #5 clk <= ~clk;

control TEST(
    .RA(RA),
    .CA(CA),
    .NWRT(NWRT),
    .NCE(NCE),
    .clk(clk),
    .rst(rst)
);

top TEST1(
    .deleav_data(out_data),
    .clk(clk), 
    .rst(rst),
    .data_in(in_data)
);

endmodule

vector.txt
0.01MB

 

이 테스트벡터는 단순히 A, B, C, D를 0~511 사이의 값으로 정의한 것으로, interleaved 된 데이터 순서대로 값이 저장되어 있다. A, B, C, D 의 순서대로 저장되어 있기 때문에 알아보기 쉬울 것이다.

 

시뮬레이션 결과 중 파형 일부는 아래와 같다. deinterleaved된 상태라서 같은 값이 연속으로 4개씩 출력되고 있는것을 확인할 수 있다.

 

'Verilog HDL 설계' 카테고리의 다른 글

Countdown control & display  (0) 2021.04.23
BCD to 7 segment  (0) 2021.04.19
D latch, Master-Slave D flip-flop 구현 (gate level)  (0) 2021.04.17
8bit Binary to BCD  (0) 2021.04.14
코드 작성을 위한 몇가지 팁(1)  (3) 2021.04.14