Viterbi Encoder

Class Diagram

The UML diagram of Fig. 11 shows the overview of the class.

@startuml
  class sc_core::sc_module

  class encoder<int output, int input, int memory> {
    .. Inputs ..
    sc_in_clk clk
    sc_in<sc_lv<input> > in
    sc_in<sc_lv<memory * input> > polynomials[output]

    .. Outputs ..
    sc_out<sc_logic > out

    .. Signals ..
    sc_signal<sc_lv<memory * input> > mem_bus_conv
    sc_signal<bool> clk_div
    sc_signal<sc_logic> data_in_drv[memory]
    sc_signal<sc_logic> conv_outs[output]
    sc_signal<sc_lv<output> > conv_par_bus
    sc_signal<sc_lv<memory> > mem_bus[input]


    .. Sub-modules ..
    convolution<memory * input> * conv_block[output]
    shift_register<memory> * register_bank[input]
    clock_divider<output> * clk_divider
    serializer<output> * serial

    __ Processes __
    prc_split_input()
    prc_update_conv_par_bus()
    prc_arrange_memory_bus()

  }
  encoder -up-|> sc_core::sc_module
@enduml

Fig. 11 Viterbi Encoder Class Diagram

Class Description

template<int output, int input, int memory>
class encoder

Viterbi Encoder

sc_core::sc_in_clk clk

Input clock

sc_in<sc_lv<input>> in

Parallel input to be encoded

sc_in<sc_lv<memory * input>> polynomials[output]

Polynomials to convolve with

sc_out<sc_logic> out

Serialized encoded output

sc_signal<sc_lv<memory * input>> mem_bus_conv

Arranged for convolution memory bus

sc_signal<bool> clk_div

Divided clk signal

sc_signal<sc_logic> data_in_drv[memory]

Data in driver (distribute to shift registers)

sc_signal<sc_logic> conv_outs[output]

Convolution outputs array

sc_signal<sc_lv<output>> conv_par_bus

Parallel convolution outputs bus

sc_signal<sc_lv<memory>> mem_bus[input]

Shift registers’ memory buses

convolution<memory * input> *conv_block[output]

Convolution Modules

shift_register<memory> *register_bank[input]

Shift Registers

clock_divider<output> *clk_divider

Clock divider

serializer<output> *serial

Serializer for the output

void prc_split_input(void)

Split the in every input to connect with each shift register of the bank. There are input shift registers.

list sensitivity

in

void prc_update_conv_par_bus(void)

Merge all convolution blocks outputs into a single parallel bus.

list sensitivity

conv_outs

void prc_arrange_memory_bus(void)

Create a single parallel bus merging all parallel buses of all shift registers.

list sensitivity

mem_bus

Structure

Fig. 12 shows the structure of the our Viterbi encoder implementation without using lookup tables.

../_images/encoder_circuit.png

Fig. 12 Viterbi Encoder Circuit

Simulation Results

The code of the test case of the viterbi_encoder_lkup is shown below;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
...

static const int n = 2;
static const int k = 1;
static const int m = 4;

...

static const int output_size = n * (2* m - k);

SC_TEST(encoder) {
  ...

  // Create signals
  sc_signal<sc_lv<k> > in; //logic vector for shift register
  sc_signal<sc_logic> out_0; //logic output of output of convolution
  sc_signal<sc_logic> out_1; //logic output of output of convolution

  sc_signal<sc_lv<m> > mem_bus[k]; //logic vector for shift register
  sc_signal<sc_lv<m * k> > mem_bus_conv; //logic vector for shift register
  sc_signal<sc_logic> serial_in_drv[n];
  sc_signal<sc_lv<m> > polynomials[n];
  sc_lv<output_size> expected_out = "11110111010111";
  sc_lv<m> input_out = "1011";

  // Create module
  encoder<n, k, m> vencoder("ViterbiEncoder");
  encoder_lkup<n, k, m> vencoder_lkup("ViterbiEncoderLKUP");

  // Assign polynomials
  polynomials[0] = "1111";
  polynomials[1] = "1101";

  ...

  vencoder.clk(sys_clock);
  vencoder.in(in);
  vencoder.out(out_0);

  vencoder_lkup.clk(sys_clock);
  vencoder_lkup.in(in);
  vencoder_lkup.out(out_1);

  // Output verification (11110111010111)
  current_check_time = 220;
  SC_EXPECT_AT(sc_logic('0'), out_0, current_check_time, SC_NS);
  SC_EXPECT_AT(sc_logic('0'), out_1, current_check_time, SC_NS);
  current_check_time += clock_period / 2;

  for (int i = 0; i < output_size; i++) {
    SC_EXPECT_AT(sc_logic(expected_out.get_bit(output_size - i -1)), out_0, current_check_time, SC_NS);
    SC_EXPECT_AT(sc_logic(expected_out.get_bit(output_size - i -1)), out_1, current_check_time, SC_NS);
    current_check_time += clock_period;
  }

  ...

  // Set the serial input to encode
  for (int i = 0; i < m; i++) {
    in = sc_lv<k>(sc_logic(input_out.get_bit(m - i -1)));
    sc_start(2*clock_period, SC_NS);
  }

  in = "0";
  sc_start(200, SC_NS);

}

Note

  • Both implementation of Viterbi encoder are being tested the same way.
  • Both encoders have the same input.
  • The input is \(b1011\) and the expected encoded value \(b11110111010111\)
  • The output is being verified with the SC_EXPECT_AT

Fig. 16 shows the result of the simulation.

../_images/encoder_simulation1.png

Fig. 13 Encoder Simulation Wave Result

Note

  • At \(200ns\) the input starts to be in encoded. Both encoders have the same input.
  • Just \(output\) cycles after the encoding starts.
  • The encoded value’s MSb is transmitted first.
  • Every in state has to be stable for \(output\) cycles.
  • out_0 and out_1 have the same baudrate as the sys_clock
  • out_0 and out_1 present the same behavior as expected
  • out_0 and out_1 are set back to sc_logic(‘0’) after encoding is done (\(430ns\)).