8. Design Examples

8.1. Introduction

In previous chapters, some simple designs were introduces e.g. mod-m counter and flip-flops etc. to introduce the Verilog programming. In this chapter various examples are added, which can be used to implement or emulate a system on the FPGA board.

All the design files are provided inside the ‘VerilogCodes’ folder inside the main project directory; which can be used to implement the design using some other software as well. Each section shows the list of Verilog-files require to implement the design in that section. Lastly, all designs are tested using Modelsim and on Altera-DE2 FPGA board. Set the desired design as ‘top-level entity’ to implement or simulate it.

8.2. Random number generator

In this section, random number generator is implemented using linear feedback shift register. Verilog files required for this example are listed below,

  • rand_num_generator.v
  • rand_num_generator_visualTest.v
  • clockTick.v
  • modMCounter.v

Note that, ‘clockTick.v’ and ‘modMCounter.v’ are discussed in Chapter Section 6.

8.2.1. Linear feedback shift register (LFSR)

Long LFSR can be used as ‘pseudo-random number generator’. These random numbers are generated based on initial values to LFSR. The sequences of random number can be predicted if the initial value is known. However, if LFSR is quite long (i.e. large number of initial values are possible), then the generated numbers can be considered as random numbers for practical purposes.

LFSR polynomial are written as \(x^3 + x^2 + 1\), which indicates that the feedback is provided through output of ‘xor’ gate whose inputs are connected to positions 3, 2 and 0 of LFSR. Some of the polynomials are listed in Table 8.1.

Table 8.1 List of feedback polynomials
Number of bits} Feedback polynomial}
3 \({x^3} + {x^2} + 1\)
4 \({x^4} + {x^3} + 1\)
5 \({x^5} + {x^3} + 1\)
6 \({x^6} + {x^5} + 1\)
7 \({x^7} + {x^6} + 1\)
9 \({x^9} + {x^5} + 1\)
10 \({x^{10}} + {x^7} + 1\)
11 \({x^{11}} + {x^9} + 1\)
15 \({x^{15}} + {x^{14}} + 1\)
17 \({x^{17}} + {x^{14}} + 1\)
18 \({x^{18}} + {x^{11}} + 1\)

Random numbers are generated using LFSR in Listing 8.1. The code implements the design for 3 bit LFSR, which can be modified for LFSR with higher number of bits as shown below,

Explanation Listing 8.1

The listing is currently set according to 3 bit LFSR i.e. N = 3 in Line 12. ‘q’ is the output of LFSR, which is random in nature. Lines 26-35 sets the initial value for LFSR to 1 during reset operations. Note that, LFSR can not have ‘0’ as initial values. Feedback polynomial is implemented at Line 43. Line 55 shifts the last N bits (i.e. N to 1) to the right by 1 bit and the N^{th} bit is feed with ‘feedback_value’ and stored in ‘r_next’ signal. In next clock cycle, value of r_next is assigned to r_reg through Line 34. Lastly, the value r_reg is available to output port from Line 56.

Simulation results are shown in Fig. Fig. 8.1. Here, we can see that total 7 different numbers are generated by LFSR, which can be seen between two cursors in the figure. Further, q values are represented in ‘hexadecimal format’ which are same as r_reg values in ‘binary format’.

Note

Note that, in Fig. Fig. 8.1, the generated sequence contains ‘8, C, 6, B, 5, 2 and 1’; and if we initialize the system with any of these values, outputs will contain same set of numbers again. If we initialize the system with ‘3’ (which is not the set), then the generate sequence will be entirely different.

../_images/rand_num_generator.jpg

Fig. 8.1 Random number generation with N = 3

To modify the feedback polynomial, first insert the correct number of bits (i.e. N) in Line 12. Next, modify the feedback_value at line 43, according to new value of ‘N’.

Note

Maximum-length for a polynomial is defined as \(2^N-1\), but not all the polynomials generate maximum length; e.g. N = 5 generates total 28 sequences (not 31) before repetition as shown in Fig. Fig. 8.2.

../_images/rand_num_generatorN5.jpg

Fig. 8.2 Total sequences are 28 (not 31) for N = 5}

Listing 8.1 Random number generation with LFSR
 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
// rand_num_generator.v
// created by : Meher Krishna Patel
// date : 22-Dec-16
// Feedback polynomial : x^3 + x^2 + 1
// maximum length : 2^3 - 1 = 7
// if parameter value is changed,
// then choose the correct Feedback polynomial i.e. change 'feedback_value' pattern


module rand_num_generator
#(
    parameter N = 3
)

(
    input wire clk, reset, 
    output wire [N:0] q
);

reg [N:0] r_reg;
wire [N:0] r_next;
wire feedback_value;
                        
always @(posedge clk, posedge reset)
begin 
    if (reset)
        begin
        // set initial value to 1
        r_reg <= 1;  // use this or uncomment below two line
        
//      r_reg[0] <= 1'b1; // 0th bit = 1
//      r_reg[N:1] <= 0;  // other bits are zero
        
        
        end     
    else if (clk == 1'b1)
        r_reg <= r_next;
end

//// N = 3
//// Feedback polynomial : x^3 + x^2 + 1
////total sequences (maximum) : 2^3 - 1 = 7
assign feedback_value = r_reg[3] ^ r_reg[2] ^ r_reg[0];

//// N = 4
//assign feedback_value = r_reg[4] ^ r_reg[3] ^ r_reg[0];

// N = 5, maximum length = 28 (not 31)
//assign feedback_value = r_reg[5] ^ r_reg[3] ^ r_reg[0];

//// N = 9
//assign feedback_value = r_reg[9] ^ r_reg[5] ^ r_reg[0];


assign r_next = {feedback_value, r_reg[N:1]};
assign q = r_reg;
endmodule                       

8.2.2. Visual test

Listing 8.2 can be used to test the Listing 8.1 on the FPGA board. Here, 1 second clock pulse is used to visualize the output patterns. Please read Chapter Section 6 for better understanding of the listing. Note that, N = 3 is set in Line 13 according to Listing 8.1.

For displaying outputs on FPGA board, set reset to 1 and then to 0. Then LEDs will blink to display the generated bit patterns by LFSR; which are shown in Fig. Fig. 8.1.

Listing 8.2 Visual test : Random number generation with LFSR
 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
// rand_num_generator_visualTest.v
// created by : Meher Krishna Patel
// date : 22-Dec-16
// if parameter value is changed e.g. N = 5
// then go to rand_num_generator for further modification

module rand_num_generator_visualTest
#(
    parameter N = 3
)
(
    input wire CLOCK_50, reset,
    output wire [N:0] LEDR
);

wire clk_Pulse1s;

// clock 1 s
clockTick #(.M(50000000), .N(26))
        clock_1s (.clk(CLOCK_50), .reset(reset), .clkPulse(clk_Pulse1s));


// rand_num_generator testing with 1 sec clock pulse
rand_num_generator rand_num_generator_1s 
    (   .clk(clk_Pulse1s), 
        .reset(reset),
        .q(LEDR)        
    );

endmodule 

8.3. Shift register

Shift register are the registers which are used to shift the stored bit in one or both directions. In this section, shift register is implemented which can be used for shifting data in both direction. Further it can be used as parallel to serial converter or serial to parallel converter. Verilog files required for this example are listed below,

  • shift_register.v
  • shift_register_visualTest.v
  • clockTick.v
  • modMCounter.v
  • parallel_to_serial.v
  • serial_to_parallel.v
  • parallel_and_serial_top.v
  • parallel_and_serial_top_visual.v

Note that, ‘clockTick.v’ and ‘modMCounter.v’ are discussed in Chapter Section 6.

8.3.1. Bidirectional shift register

Listing 8.3 implements the bidirectional shift register which is explained below,

Explanation Listing 8.3

In the listing, the ‘ctrl’ port is used for ‘shifting’, ‘loading’ and ‘reading’ data operation. Lines 28 clear the shift register during reset operation, otherwise go to the next state.

Lines 35-40 perform various operations based on ‘ctrl’ values. Note that, to perform right shift (Line 37), data is continuously provided from last port i.e. (data(N-1)); whereas for left shift (Line 38) data is provided from first port i.e. (data(0)).

Next, ctrl=‘00’ is provided for reading the data. It can be used for serial to parallel conversion i.e. when all the bits are shifted and register is full; then set ctrl to ‘00’ and read the data, after that set ctrl to ‘01’ or ‘10’ for getting next set of bits.

Similarly, for parallel to serial converter, first load the data using ctrl=‘11’; and then perform the shift operation until all the bits are read and then again load the data. Note that, in this case, last bit propagates (i.e. data(N-1) for right shift or data(0) for left shift) during shifting; which is actually designed for serial to parallel converter. But this will affect the working of parallel to serial converter, as we will set ctrl to ‘11’, when all the data is shifted, therefore all the register which were filled by values from last port, will be overwritten by the new parallel data.

Lastly, data is available on the output port ‘q_reg’ from Line 43. For, parallel to serial converter, use only one pin of ‘q_reg’ i.e. q_reg(0) for right shift or q(N-1) for left shift; whereas for serial to parallel conversion, complete ‘q_reg’ should be read.

Fig. Fig. 8.3 shows the shifting operation performed by the listing. Here first data ( i.e. 00110000) is loaded with ctrl=‘11’. Then shifted to right after first cursor and later to the left i.e. after second cursor.

../_images/shift_register.jpg

Fig. 8.3 Right and left shifting operations

Listing 8.3 Bidirectional shift register
 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
// shift_register.v
// created by : Meher Krishna Patel
// date : 22-Dec-16
// Functionality:
// load data and shift it data to left and right
// parallel to serial conversion (i.e. first load, then shift)
// serial to parallel conversion (i.e. first shift, then read)
// inputs:
// ctrl : to load-data and shift operations (right and left shift)
// data : it is the data to be shifted
// q_reg : store the outputs

module shift_register 
#(
    parameter N = 8
)
(
    input wire clk, reset,
    input wire [1:0] ctrl,
    input wire [N-1:0] data,
    output wire [N-1:0] q_reg
);

reg [N-1:0] s_reg, s_next;
always @(posedge clk, posedge reset)
begin
    if(reset)
        s_reg <= 0; // clear the content
    else if (clk == 1'b1)
        s_reg <= s_next; // otherwise save the next state
end

always @(ctrl, s_reg)
begin
    case (ctrl)
        0 : s_next = s_reg; // no operation (to read data for serial to parallel)
        1 : s_next = {data[N-1], s_reg[N-1:1]}; // right shift
        2 : s_next = {s_reg[N-2:0], data[0]}; // left shift
        3 : s_next = data; // load data (for parallel to serial)
    endcase
end

assign q_reg = s_reg;
endmodule 

Visual test

Listing 8.4 can be used to test the Listing 8.3 on the FPGA board. Here, 1 second clock pulse is used to visualize the output patterns. Here, outputs (i.e. q_reg) are displayed on LEDR; whereas ‘shifting-control (i.e. ctrl)’ and data-load (i.e. data) operations are performed using SW[16:15] and SW[7:0] respectively. Here, we can see the shifting of LEDR pattern twoards right or left based on SW[16:15] combination. Please read Chapter Section 6 for better understanding of the listing.

Listing 8.4 Visual test : bidirectional shift register
 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
// shift_register_visualTest.v
// created by : Meher Krishna Patel
// date : 22-Dec-16
// SW[16:15] : used for control

module shift_register_visualTest
#(
    parameter N =8 
)
(
    input wire CLOCK_50, reset,
    input wire [16:0] SW,
    output wire [N-1:0] LEDR
);

wire clk_Pulse1s;

// clock 1 s
clockTick #(.M(50000000), .N(26))
        clock_1s (.clk(CLOCK_50), .reset(reset), .clkPulse(clk_Pulse1s));
        
// shift_register testing with 1 sec clock pulse
shift_register #(.N(N))
shift_register_1s (
        .clk(clk_Pulse1s), .reset(reset),
        .data(SW[N-1:0]), .ctrl(SW[16:15]),
        .q_reg(LEDR)
);

endmodule 

8.3.2. Parallel to serial converter

If data is loaded first (i.e. ctrl = ‘11’), and later shift operation is performed (i.e.. ctrl = ‘01’ or ‘10’); then Listing 8.3 will work as ‘parallel to serial converter’. Listing 8.5 performs the conversion operation, which is tested in Section Section 8.3.4. Please read comments for further details.

Listing 8.5 Parallel to serial conversion
 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
// parallel_to_serial.v
// Meher Krishna Patel 
// Date : 26-July-17

// converts parallel data into serial

module parallel_to_serial
#(  
    parameter N = 8
)
(
    input wire clk, reset,
    input wire [ N-1:0] data_in, // parallel data
    output reg empty_tick, // for external control
    output reg data_out  // serial data
);

reg [N-1:0] data_reg, data_next;
reg [N-1:0] count_reg, count_next;
reg empty_reg, empty_next;

// conversion completed and ready for next data in register
always @(posedge clk)
    empty_tick = empty_reg;

// save initial and next value in register
always @(posedge clk, posedge reset) begin
    if(reset) begin
        count_reg <= 0;
        empty_reg <= 1;
        data_reg <= 0;
    end
    else begin
        count_reg <= count_next;
        empty_reg <= empty_next;
        data_reg <= data_next;
    end
end

always @* begin
    count_next = count_reg;
    empty_next = empty_reg;
    data_next = data_reg;
    // parallel_to_serial data
    data_out = data_reg[count_reg];
    
    // coversion completed , load the next data
    if (count_reg == N-1) begin
        count_next = 0;  // restart count
        empty_next = 1; 
        data_next = data_in;  // load next data
        
    end
    else begin // else continue counting
        count_next = count_reg + 1;
        empty_next = 0;
    end
end

endmodule 

8.3.3. Serial to parallel converter

If shifting is performed first (i.e.. ctrl = ‘01’ or ‘10’), and later data is read (i.e. ctrl = ‘00’); then Listing 8.6 will work as ‘serial to parallel converter’. Listing 8.5 performs the conversion operation, which is tested in Section Section 8.3.4. Please read comments for further details.

Listing 8.6 Serial to parallel conversion
 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
// serial_to_parallel.v

// converts serial data to parallel

module serial_to_parallel
#(  
    parameter N = 8
)
(
    input wire clk, reset,
    input wire data_in, // serial data
    output wire full_tick,  // for external control
    output reg [N-1:0] data_out  // parallel data
);

reg [N-1:0] data_reg, data_next;
reg [N-1:0] count_reg, count_next;
reg full_reg, full_next;

// register is full i.e. parallel data is ready to read
assign full_tick = full_reg;  

// save initial and next value in register
always @(posedge clk, posedge reset) begin
    if(reset) begin
        count_reg <= 0;
        full_reg <= 0;
        data_reg <= 0;
    end
    else begin
        count_reg <= count_next;
        full_reg <= full_next;
        data_reg <= data_next;
    end
end

always @* begin
    count_next = count_reg;
    full_next = full_reg;
    data_next = data_reg;
    data_next[count_reg] = data_in;

    // coversion completed , send data to output
    if (count_reg == N-1) begin
        count_next = 0;
        full_next = 1;
        // conversion completed, send data to output
        data_out = data_reg;
    end
    else begin  // else continue count
        count_next = count_reg + 1;
        full_next = 0;
    end
end
endmodule 

8.3.4. Test for Parallel/Serial converters

Here, 4-bit count (i.e. parallel data) is generated using Mod-12 counter. This data is converted into serial data by Listing 8.5; and sent to Listing 8.6, where data is again converted into parallel and the result (i.e. count) is displayed at output as shown in Listing 8.7. The simulation results are shown in Fig. Fig. 8.5. Lastly, visual verification circuit is shown in Listing 8.8. Note that, empty_tick signal is used as clock for modMCounter (see red line in Fig. :numref:`fig_parallel_and_serial_design`), so that next count will be available when previous conversion is completed. Please read comments for further details.

../_images/parallel_and_serial_design.jpg

Fig. 8.4 RTL view of Listing 8.7

Listing 8.7 Test for Parallel/Serial converters (results are in Fig. Fig. 8.5)
 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
// parallel_and_serial_top.v

// test parallel_to_serial.v and serial_to_parallel.v
// parallel data (i.e. count from modMCounter) is converted into 
// serial data using parallel_to_serial.v then tranmitted. 
// Next,  transmitted data is received at serial_to_parallel.v and 
// converted back to parallel. If everything  is working properly then 
//  count should be displayed on 'out_count'. 

module parallel_and_serial_top(
    reset,
    clk,
    out_count
);

input wire  reset;
input wire  clk;
output wire [3:0] out_count;

wire    eclk;
wire    [3:0] parallel_count;
wire    serial_count;

// count from modMCounter
modMCounter unit_counter(
    .clk(eclk),
    .reset(reset),
    
    .count(parallel_count));
    defparam    unit_counter.M = 12;
    defparam    unit_counter.N = 4;


// count converted into serial data
parallel_to_serial  unit_p_s(
    .clk(clk),
    .reset(reset),
    .data_in(parallel_count),
    .empty_tick(eclk),
    .data_out(serial_count));
    defparam    unit_p_s.N = 4;


// serial data converted back to parallel data
serial_to_parallel  unit_s_p(
    .clk(clk),
    .reset(reset),
    .data_in(serial_count),
    
    .data_out(out_count));
    defparam    unit_s_p.N = 4;


endmodule
../_images/parallel_and_serial_top.jpg

Fig. 8.5 Simulation results of Listing 8.7

Listing 8.8 Visual test for Parallel/Serial converters
 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
// parallel_and_serial_top_visual.v

// visual test for parallel_and_serial_top.v

module parallel_and_serial_top_visual(
  reset,
  CLOCK_50,
  LEDG
);

input wire  reset;
input wire  CLOCK_50;
output wire[3:0] LEDG;

wire  clk1ms;

// parallel_and_serial_top
parallel_and_serial_top  unit_p_s_top(
  .reset(reset),
  .clk(clk1ms),
  .out_count(LEDG));

// 1 ms clock
clockTick  unit_clk1ms(
  .clk(CLOCK_50),
  .reset(reset),
  .clkPulse(clk1ms));
  defparam  unit_clk1ms.M = 5000000;
  defparam  unit_clk1ms.N = 23;

endmodule

8.4. Random access memory (RAM)

RAM is memory cells which are used to store or retrieve the data. Further, FPGA chips have separate RAM modules which can be used to design memories of different sizes and types, as shown in this section. Verilog files required for this example are listed below,

  • single_port_RAM.v
  • single_port_RAM_visualTest.v
  • dual_port_RAM.v
  • dual_port_RAM_visualTest.v

8.4.1. Single port RAM

Single port RAM has one input port (i.e. address line) which is used for both storing and retrieving the data, as shown in Fig. Fig. 8.6. Here ‘addr[1:0]’ port is used for both ‘read’ and ‘write’ operations. Listing 8.9 is used to generate this design.

../_images/single_port_RAM.jpg

Fig. 8.6 RTL view : Single port RAM (Listing 8.9)

Explanation Listing 8.9

In the listing port ‘addr’ (Line 23) is 2 bit wide as ‘addr_width’ is set to 2 (Line 17). Therefore, total ‘4 elements (i.e. 2^2)’ can be stored in the RAM. Further, ‘din’ port (Line 24) is 3 bit wide as ‘data_width’ is set to 3 (Line 18); which means the data should be 3 bit wide. In summary, current RAM-designs can store ‘4 elements’ in it and each elements should be 3-bit wide (see declaration at Line 28 as well).

Write enable (we) port should be high and low for storing and retrieving the data respectively. ‘din’ port is used to write the data in the memory; whereas ‘dout’ port is used for reading the data from the memory. In Lines 32-33, the write operation is performed on rising edge of the clock; whereas read operation is performed at Line 37.

Lastly, Fig. Fig. 8.7 shows the simulation results for the design. Here, ‘we’ is set to 1 after first cursor and the data is written at three different addresses (not 4). Next, ‘we’ is set to 0 after second cursor and read operations are performed for all addresses. Since, no values is stored for address ‘10’, therefore dout is displayed as ‘UUU’ for this address as shown after third cursor.

../_images/single_port_RAM_Wave.jpg

Fig. 8.7 Simulation results : Single port RAM (Listing 8.9)

Listing 8.9 Single port RAM
 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
// single_port_RAM.v
// created by : Meher Krishna Patel
// date : 26-Dec-16
// Functionality:
// store and retrieve data from single port RAM
// ports:
// we : write enable
// addr : input port for getting address
// din : input data to be stored in RAM
// data : output data read from RAM
// addr_width : total number of elements to store (put exact number)
// addr_bits : bits requires to store elements specified by addr_width
// data_width : number of bits in each elements

module single_port_RAM
#(
    parameter addr_width = 2,
                 data_width = 3
)
(
    input wire clk,
    input wire we,
    input wire [addr_width-1:0] addr,
    input wire [data_width-1:0] din,
    output wire [data_width-1:0] dout
);

reg [data_width-1:0] ram_single_port[2**addr_width-1:0];

always @(posedge clk)
begin
    if (we == 1) // write data to address 'addr'
        ram_single_port[addr] <= din;
end

// read data from address 'addr'
assign dout = ram_single_port[addr];

endmodule 

8.4.2. Visual test : single port RAM

Listing 8.10 can be use to test the Listing 8.9 on FPGA board. Different combination of switches can be used to store and retrieve the data from RAM. These data will be displayed on LEDs during read operations.

Listing 8.10 Visual test : single port RAM
 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
// single_port_RAM_visualTest.v
// created by : Meher Krishna Patel
// date : 26-Dec-16

// Functionality:
    // store and retrieve data from single port RAM
// ports:
    // Write Enable (we) : SW[16]
    // Address (addr) : SW[15-14]
    // din : SW[2:0]
    // dout : LEDR

module single_port_RAM_visualTest
#(
    parameter ADDR_WIDTH = 2,
    parameter DATA_WIDTH = 3
)
(
    input wire CLOCK_50,
    input wire [16:0] SW,
    output wire [DATA_WIDTH-1:0] LEDR
);

single_port_RAM single_port_RAM_test( 
        .clk(CLOCK_50),
        .we(SW[16]),
        .addr(SW[15:14]),
        .din(SW[2:0]),  
        .dout(LEDR)
);

endmodule 

8.4.3. Dual port RAM

In single port RAM, the same ‘addr’ port is used for read and write operations; whereas in dual port RAM dedicated address lines are provided for read and write operations i.e. ‘addr_rd’ and ‘addr_wr’ respectively, as shown in Fig. Fig. 8.8. Also, the listing can be further modified to allow read and write operation through both the ports.

../_images/dual_port_RAM.jpg

Fig. 8.8 Dual port RAM

Listing 8.11 is used to implement Fig. Fig. 8.8, which is same as Listing 8.9 with three changes. First, two address ports are used at Line 25 (i.e. ‘addr_rd’ and ‘addr_wr’) instead of one. Next, ‘addr_wr’ is used to write the data at Line 35; whereas ‘addr_rd’ data is used to retrieve the data at Line 39. Hence, read and write operation can be performed simultaneously using these two address lines.

Fig. Fig. 8.9 shows the simulation results for dual port RAM. Here, on the first cursor, ‘011’ is written at address ‘01’. On next cursor, this value is read along with writing operation as location ‘10’. Lastly, last two cursor shows that if read and write operation is performed simultaneously on one address e.g. ‘01’, then new data will be available at ‘dout’ port after one clock cycle.

../_images/dual_port_RAM_wave.jpg

Fig. 8.9 Simulation results : Dual port RAM (Listing 8.11)

Listing 8.11 Dual port RAM
 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
// dual_port_RAM.v
// created by : Meher Krishna Patel
// date : 26-Dec-16

// Functionality:
    // store and retrieve data from dual port RAM
// ports:
    // we : write enable
    // addr_wr : address for writing data
    // addr_rd : address for reading
    // din : input data to be stored in RAM
    // data : output data read from RAM
    // addr_width : total number of elements to store (put exact number)
    // addr_bits : bits requires to store elements specified by addr_width
    // data_width : number of bits in each elements

module dual_port_RAM
#(
    parameter addr_width = 2,
                 data_width = 3
)
(
    input wire clk,
    input wire we,
    input wire [addr_width-1:0] addr_wr, addr_rd,
    input wire [data_width-1:0] din,
    output wire [data_width-1:0] dout
);

reg [data_width-1:0] ram_dual_port[2**addr_width-1:0];

always @(posedge clk)
begin
    if (we == 1) // write data to address 'addr_wr'
        ram_dual_port[addr_wr] <= din;
end

// read data from address 'addr_rd'
assign dout = ram_dual_port[addr_rd];

endmodule 

8.4.4. Visual test : dual port RAM

Listing 8.12 can be use to test the Listing 8.11 on FPGA board. Different combination of switches can be used to store and retrieve the data from RAM. These data will be displayed on LEDs during read operations.

Listing 8.12 Visual test : dual port RAM
 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
// dual_port_RAM_visualTest.v
// created by : Meher Krishna Patel
// date : 26-Dec-16

// Functionality:
    // store and retrieve data from single port RAM
// ports:
    // Write Enable (we) : SW[16]
    // Address (addr_wr) : SW[15-14]
    // Address (addr_rd) : SW[13-12]
    // din : SW[2:0]
    // dout : LEDR

module dual_port_RAM_visualTest
#(
    parameter ADDR_WIDTH = 2,
    parameter DATA_WIDTH = 3
)
(
    input wire CLOCK_50,
    input wire [16:0] SW,
    output wire [DATA_WIDTH-1:0] LEDR
);

dual_port_RAM dual_port_RAM_test( 
        .clk(CLOCK_50),
        .we(SW[16]),
        .addr_wr(SW[15:14]),
        .addr_rd(SW[13:12]),
        .din(SW[2:0]),  
        .dout(LEDR)
);

endmodule 

8.5. Read only memory (ROM)

ROMs are the devices which are used to store information permanently. In this section, ROM is implemented on FPGA to store the display-pattern for seven-segment device, which is explained in Section Section 6.5. Verilog files required for this example are listed below,

  • ROM_sevenSegment.v
  • ROM_sevenSegment_visualTest.v

8.5.1. ROM implementation using RAM (block ROM)

Listing 8.13 implements the ROM (Lines 27-47), which stores the seven-segment display pattern in it (Lines 30-45). Since the address width is 16 (Line 16), therefore total 16 values can be stored with different addresses (Lines 30-45). Further, 16 addresses can be represented by 4-bits, therefore ‘addr_bit’ is set to 4 at Line 17. Lastly, total 7 bits are required to represent the number in 7-segment-display, therefore ‘data_width’ is set to 7 at Line 18.

Listing 8.13 Seven segment display pattern stored in ROM
 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
//ROM_sevenSegment.v
// created by : Meher Krishna Patel
// date : 25-Dec-16

// Functionality:
    // seven-segment display format for Hexadecimal values (i.e. 0-F) are stored in ROM
// ports:
    // addr : input port for getting address
    // data : ouput data at location 'addr'
    // addr_width : total number of elements to store (put exact number)
    // addr_bits : bits requires to store elements specified by addr_width
    // data_width : number of bits in each elements

module ROM_sevenSegment
#(
    parameter addr_width = 16, // store 16 elements
                 addr_bits = 4, // required bits to store 16 elements
                 data_width = 7 // each element has 7-bits
)
(
    input wire clk,
    input wire [addr_bits-1:0] addr,
    output reg [data_width-1:0] data  // reg (not wire)
);


always @*
begin
    case(addr)
        4'b0000 : data = 7'b1000000; // 0
        4'b0001 : data = 7'b1111001; // 1
        4'b0010 : data = 7'b0100100; // 2
        4'b0011 : data = 7'b0110000; // 3 
        4'b0100 : data = 7'b0011001; // 4
        4'b0101 : data = 7'b0010010; // 5
        4'b0110 : data = 7'b0000010; // 6
        4'b0111 : data = 7'b1111000; // 7
        4'b1000 : data = 7'b0000000; // 8
        4'b1001 : data = 7'b0010000; // 9
        4'b1010 : data = 7'b0001000; // a
        4'b1011 : data = 7'b0000011; // b
        4'b1100 : data = 7'b1000110; // c
        4'b1101 : data = 7'b0100001; // d
        4'b1110 : data = 7'b0000110; // e
        default : data = 7'b0001110; // f
    endcase 
end

endmodule 

8.5.2. Visual test

Listing 8.14 is provided the input ‘addr’ through switches ‘SW’ (Line 20) and then output from Listing 8.13 is received to signal ‘data’ (Lines 22-23); which is finally displayed on seven segment display devices (Line 27) and LEDs (Line 28). Here, we can see the switch value on the seven-segment display devices along with the ROM content on LEDs; e.g. if SW value is ‘011’ then ‘3’ will be displayed on seven-segment display and ‘0000110’ will be displayed on LEDs.

Listing 8.14 Display ROM data on seven segment display and LEDs
 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
// ROM_sevenSegment_visualTest.v
// created by : Meher Krishna Patel
// date : 25-Dec-16

// Functionality:
    // retrieve data from ROM and display on seven-segment device and LEDs
// ports:
    // SW : address in binary format
    // HEX0 : display data on seven segment device
    // LEDR : display data on LEDs

module ROM_sevenSegment_visualTest
(
    input wire [3:0] SW,
    output wire [6:0] HEX0,
    output wire [6:0] LEDR
);

// signal to store received data, so that it can be displayed on
// two devices i.e. seven segment display and LEDs
wire [6:0] data;

ROM_sevenSegment seven_segment_ROM(
    .addr(SW), .data(data)
);

assign HEX0 = data; // display on seven segment devices
assign LEDR = data; // display on LEDs

endmodule 

8.6. Queue with first-in first-out functionality

In this section, Queue is designed with first-in first-out functionality. Verilog files required for this example are listed below,

  • queue.v
  • queue_top.v
  • clockTick.v
  • modMCounter.v

Note that, ‘clockTick.v’ and ‘modMCounter.v’ are discussed in Chapter Section 6.

8.6.1. Queue design

Listing 8.15 is the Queue design, which is an example of circular queue. Here, two pointers i.e. Front and rear are used to track the status of the queue i.e. Full or Empty. If read-operation is performed and Front & Rear pointers are at same location, then queue is empty; whereas if write-operation is performed and Front & Rear pointers are at same location, then queue is full. Please read the comments for further details.

Listing 8.15 Queue design
 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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// queue.v

// queue with first-in first-out operation. 

module queue
#(parameter data_width = 4,  // number of bits in each data
            address_width = 4,  // total addresses = 2^4
            max_data = 2**address_width // max_data = total_addresses
)
(
    input wire clk, reset,
    input wire read_cmd, write_cmd, // read and write command 
    input wire [data_width-1:0] write_data,  // write data to FIFO
    output reg [data_width-1:0] read_data,  // read data from FIFO
    output wire full,  // no space to write in FIFO
    output wire empty // nothing to read from FIFO
);

reg [data_width-1:0] queue_reg [max_data-1:0];
reg [address_width-1:0] front_reg, front_next; // pointer-front is for reading
reg [address_width-1:0] rear_reg, rear_next; // pointer-rear is for writing
reg full_reg, full_next;
reg empty_reg, empty_next;


assign full = full_reg;
assign empty = empty_reg;

wire write_enable; // enable if queue is not full

assign write_enable = write_cmd & ~full_reg; 

always @(posedge clk) begin
    // if queue_reg is full, then data will not be written
    if (write_enable)
        queue_reg[rear_reg] <= write_data; 
    if (read_cmd)
        read_data <= queue_reg[front_reg];
end

// status of the queue and location of pointers i.e. front and rear
always @(posedge clk, posedge reset) begin
    if (reset) begin
        empty_reg <= 1'b1;  // empty : nothing to read
        full_reg <= 1'b0;  // not full : data can be written 
        front_reg <= 0;
        rear_reg <= 0;
    end
    else begin
        front_reg <= front_next;
        rear_reg <= rear_next;
        full_reg <= full_next;
        empty_reg <= empty_next;
    end
end

// read and write operation
always @* begin
    front_next = front_reg;
    rear_next = rear_reg;
    full_next = full_reg;
    empty_next = empty_reg;

    // no operation for {write_cmd, read_cmd} = 00 
     
     // only read operation
    if({write_cmd, read_cmd} == 2'b01) begin // write = 0, read = 1
        if(~empty_reg) begin // not empty
            full_next = 1'b0; // not full as data is read
            front_next = front_reg + 1;
            if (front_next  == rear_reg) // empty
                empty_next = 1'b1; 
        end
    end
     // only write operation
    else if ({write_cmd, read_cmd} == 2'b10) begin // write = 1, read = 0 
        if(~full_reg) begin // not full
            empty_next = 1'b0;
            rear_next = rear_reg + 1;
            if(rear_next == front_reg)
                full_next = 1'b1;
        end
    end
     
     // both read and write operation
    else if  ({write_cmd, read_cmd} == 2'b11) begin // write = 1, read = 1 
        front_next = front_reg + 1;
        rear_next = rear_next + 1;
    end
end

endmodule

8.6.2. Visual test

Listing 8.16 is the test circuit for Listing 8.15. Please read the comments for further details.

Listing 8.16 Visual test of Queue design
 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
68
69
70
71
72
73
74
75
// queue_top.v

// write the 'count' from 'modMCounter' to the queue.
// SW[0]    : read operation
// LEDR[0]  : queue empty
// SW[1]    : write operation
// LEDR [1] : queue full

// Test : 
// 1. Keep reset high, SW[1] low and SW[0] high till LEDR[0] glow, i.e.
// queue is empty now. 
// 2. Now, keep SW[1] high, SW[0] low, then bring reset to low. 
// Now count will start and will be stored in the queue. 
// 3. Bring the SW[1] to low again and see the count at 
// that time (as count will continue to inrease). 
// 4. Now, set SW[0] high, and it will start the count from 0, to the count which was
// displayed in step 3; and after that LEDR[0] will glow again.
// 5. Next, bring SW[0] to low and SW[1] to high, and let it run. 
// after 16 count (i.e. 2^address_width), the queue will be full and 
// LED[1] will glow. 

module queue_top(
    reset,
    CLOCK_50,
    SW,
    LEDG,
    LEDR
);


input wire  reset;
input wire  CLOCK_50;
input wire  [1:0] SW;
output wire [3:0] LEDG;
output wire [17:0] LEDR;

wire    clk1s;
wire    [3:0] count;

assign  LEDR[17:14] = count[3:0];

// clock 1 sec
clockTick   unit_clkTick(
    .clk(CLOCK_50),
    .reset(reset),
    .clkPulse(clk1s));
    defparam    unit_clkTick.M = 50000000;
    defparam    unit_clkTick.N = 26;

// queue.v
queue   unit_queue(
    .clk(clk1s),
    .reset(reset),
    .read_cmd(SW[0]),
    .write_cmd(SW[1]),
    .write_data(count),
    .full(LEDR[1]),
    .empty(LEDR[0]),
    
    .read_data(LEDG));
    defparam    unit_queue.address_width = 4;
    defparam    unit_queue.data_width = 4;
    defparam    unit_queue.max_data = 16;

// mod-12 counter
modMCounter unit_counter(
    .clk(clk1s),
    .reset(reset),
    
    .count(count));
    defparam    unit_counter.M = 12;
    defparam    unit_counter.N = 4;


endmodule