Tuesday, May 21, 2019

[Design] Thiết kế khối ALU 4 bit đơn giản và môi trường self test, random test sử dụng UVM

I. Mục tiêu

Bộ xử lý logic về toán học (ALU - Arithmetic and Logic Unit) là một mạch tổ hợp để xử lý các tác vụ về logic và toán học dựa trên hai số hạng. Các tác vụ cho ALU thực hiện được điều khiển bằng các ngõ nhập function-select.

Hình 1.1 Khối ALU 4 bit


Mục đích của bài thí nghiệm này là thiết kế một ALU đơn giản như sau:
- Độ dài các toán hạng là 4-bit.
- Các ngõ nhập function-select gồm có: M, S0 và S1.
- Các tác vụ ALU thực hiện được cho trong bảng bên dưới:

Hình 1.2 Xử lý các tác vụ về logic và toán học dựa trên input ngõ vào

II. Tóm tắt các bước tiến hành.
Trước tiên chúng ta tiến hành thiết kế cho các bộ cộng half adder ( 2 ngõ vào 1 bit , 2 ngõ ra 1 bit), full adder ( 3 ngõ vào 1 bit bao gồm thêm 1 biến nhớ c_in, 2 ngõ ra 1 bit). Hai bộ này giúp chúng ta tiến hành thực hiện phép tính cộng cho các số hạng.

Thiết kế khối “alu_single” để tính toán các kết quả 1 bit dữ liệu. Từ 4 khối “alu_single” ta có được khối 1 khối “alu” 4 bit dữ liệu.
Khối “logic_unit” thực hiện phép toán “AND”, “OR”, “XOR” và “NXOR”.
Khối “arithmetic_unit” thực hiện phép toán “+” các số hạng đầu vào.
Khối “mux_2to1” lựa chọn tín hiệu output từ khối “logic_unit” hoặc khối “arithmetic_unit” dựa vào ngõ vào “M”.
Kết quả output ngõ ra đi qua 1 FF để tín hiệu có thể output đồng bộ với cạnh lên của clock.
Trong bài này, Chúng ta còn thực hiện viết test bench self test. Môi trường tự dump kết quả pass hoặc fail dựa vào kết quả so sánh input ngõ ra và kết quả dự đoán.
Bên cạnh đó chúng ta còn xây dựng 1 môi trường uvm hoàn chỉnh để tiến hành random test. Môi trường UVM chỉ dành cho việc kiểm tra Verilog code với nhiều input và output. Tuy nhiên, Trong bài này chúng ta tiến hành build một môi trường hoàn chỉnh để từng bước làm quen với nó.
III. Lý thuyết hoạt động của mạch.
Mạch half adder và full adder được thiết kế tương tự như lab 1 (Bộ add 4 bit).

Đối với mạch half adder ta có 2 giá trị ngõ vào 1 bit và 2 giá trị ngõ ra. Half adder giúp chúng ta tính toán kết quả cộng 2 ngõ vào 1 bit và cho ra giá trị ngõ ra 1 bit và 1 bit nhớ.



Hình 3.1 Khối half adder.
Bảng 3.1 Bảng giá trị vào/ra cho khối half adder.
in_1
in_2
sum
c_out
0
0
0
0
0
1
1
0
1
0
1
0
1
1
0
1

Khối full adder dùng để tính tổng giá trị 3 ngõ vào 1 bit (thêm 1 biến c_in) ở đây ta sử dụng 2 khối half adder để xây đựng khối full adder.

Hình 3.2 Khối full adder.




Hình 3.3 Xây dựng khối full adder từ 2 khối half adder.

Bảng 3.2 bảng giá trị vào ra cho khối full adder.
c_in
in_1
in_2
sum
c_out
0
0
0
0
0
0
0
1
1
0
0
1
0
1
0
0
1
1
0
1
1
0
0
1
0
1
0
1
0
1
1
1
0
0
1
1
1
1
1
1













Khối logic_unit được mô tả như bên dưới:





Hình 3.4 Khối logic_unit.


S1
S0
in_0
in_1
H
0
0
A
B
A&B
0
1
A
B
A|B
1
0
A
B
A^B
1
1
A
B
~(A^B)



Bảng 3.3 Hoạt động của bộ “logic_unit”.




Với khối này giá trị H không phụ thuộc vào giá trị M ngõ vào. Giá trị M ngõ vào có chức năng lựa chọn giá trị ngõ ra từ khối “logic_unit” hay từ khối “arithmetic_unit”.


S1
S0
in_0
in_1
Carry_in
G
0
0
A
B
C0
A&C0
0
1
A
B
C0
A+B+C0
1
0
A
B
C0
A+B'+C0
1
1
A
B
C0
A'+B+C0



Bảng 3.4 Hoạt động của khối “arithmetic_unit”.


Kết quả của bộ ALU 4 bit sẽ đi qua 1 FF để tín hiệu ngõ ra chạy cùng với tín hiệu clock.


Hình 3.5 Sơ đồ tổng quát của bộ alu 4 bit.




IV. Verilog code và test bench, môi trường uvm.
Thật ra code cho khối này rất ngắn và đơn giản nhưng mình luôn lựa chọn cách viết phức tạp nhất để bản thân làm quen với những cấu trúc phức tạp và tăng khả năng readable của bản thân (Sau này gặp các code dễ hơn sẽ đọc nhanh hơn).


Verilog code:
//Author: TrongTran
//Date  : 05/02/2019
//Module: ALU 4 bit data
module alu (clk, rst_n, in_1, in_2, carry_in, S0, S1, M, carry_out, out);

   parameter WIDTH = 4;
   input S0;
   input S1;
   input M;
   input clk;
   input rst_n;
   input [WIDTH-1:0] in_1;
   input [WIDTH-1:0] in_2;
   input carry_in;
   output carry_out;
   output [WIDTH-1:0] out;
   wire [WIDTH-2:0] carry_tmp;
   reg [WIDTH-1:0] out;
   reg  carry_out;
   wire [WIDTH-1:0] out_tmp;
   wire  carry_out_tmp;
// Gọi module 4 bộ alu_single 1 bit để có được 1 bộ alu 4 bit.
   alu_single alu_single_0 (.in_1(in_1[0]), .in_2(in_2[0]), .carry_in(carry_in),
                            .S0(S0), .S1(S1), .M(M), .carry_out(carry_tmp[0]), .out(out_tmp[0]));
   generate
   genvar index;
   for (index = 1; index < WIDTH - 1 ; index = index + 1) begin: re_peat
       alu_single alu_single_1 (.in_1(in_1[index]), .in_2(in_2[index]), .carry_in(carry_tmp[index-1]),
                            .S0(S0), .S1(S1), .M(M), .carry_out(carry_tmp[index]), .out(out_tmp[index]));
   end
   endgenerate
   alu_single alu_single_2 (.in_1(in_1[WIDTH - 1]), .in_2(in_2[WIDTH - 1]), .carry_in(carry_tmp[WIDTH - 2]),
                            .S0(S0), .S1(S1), .M(M), .carry_out(carry_out_tmp), .out(out_tmp[WIDTH - 1]));
// Output sẽ đi qua 1 FF để ngỏ ra được đồng bộ với clock.
always @(posedge clk or negedge rst_n) begin
   if (!rst_n) begin
      out <= 4'b0000;
      carry_out <=  1'b0;
   end else begin
      out <= out_tmp;
       carry_out <= carry_out_tmp;
   end
end
endmodule

//  Thiết kế alu_single 1 bit từ các khối mux_2to1, logic_unit, arithmethic_unit.
module alu_single (in_1, in_2, carry_in, S0, S1, M, carry_out, out);
   input S0;
   input S1;
   input M;
   input in_1;
   input in_2;
   input carry_in;
   output carry_out;
   output out;
   wire G;
   wire H;
   wire carry_out;
   wire out;
   wire carry_out_sm;
   logic_unit logic_unit_0 (.S0(S0), .S1(S1), .in_1(in_1), .in_2(in_2), .H(H));
   arithmetic_unit arithmetic_unit_0 (.S0(S0), .S1(S1), .in_1(in_1), .in_2(in_2),
                                      .carry_in(carry_in), .carry_out(carry_out_sm), .G(G));
   mux_2to1 mux_2to1_0 (.G(G), .H(H), .M(M), .out(out));
   mux_2to1 mux_2to1_1 (.G(carry_out_sm), .H(1'b0), .M(M), .out(carry_out));
endmodule


// Thiết kế khối mux_2to1
module mux_2to1 (G, H, M, out);
   input G, H, M;
   output out;
   wire out;
   assign out = (M == 1'b0)? H: G;
endmodule

//  Thiết kế khối logic_unit
module logic_unit (S0, S1, in_1, in_2, H);
   input S0, S1, in_1, in_2;
   output H;
   wire H;
   assign H = ({S1, S0} == 2'b00) ? in_1&in_2:
              ({S1, S0} == 2'b01) ? in_1|in_2:
              ({S1, S0} == 2'b10) ? in_1^in_2:
              ({S1, S0} == 2'b11) ? ~(in_1^in_2): 1'b0;
endmodule

// Thiết kế khối arithmetic_unit
module arithmetic_unit ( S0, S1, in_1, in_2, carry_in, carry_out, G);
   input S0, S1, in_1, in_2, carry_in;
   output carry_out, G;
   wire carry_out, G;
   wire out_case01, out_case10, out_case11, carry_out_case01, carry_out_case10, carry_out_case11;
full_adder full_adder_01  (.in_1(in_1), .in_2(in_2), .c_in(carry_in), .out(out_case01), .c_out(carry_out_case01));
full_adder full_adder_10  (.in_1(in_1), .in_2(~in_2), .c_in(carry_in), .out(out_case10), .c_out(carry_out_case10));
full_adder full_adder_11  (.in_1(~in_1), .in_2(in_2), .c_in(carry_in), .out(out_case11), .c_out(carry_out_case11));
   assign {carry_out, G} = ({S1, S0} == 2'b00) ? in_1+carry_in:
                       ({S1, S0} == 2'b01) ? {carry_out_case01, out_case01}:
                       ({S1, S0} == 2'b10) ? {carry_out_case10, out_case10}:
                       ({S1, S0} == 2'b11) ? {carry_out_case11, out_case11}: 2'b00;
endmodule
// Khối full adder
module full_adder (in_1, in_2, c_in, out, c_out);
// Define input/output
input in_1;
input in_2;
input c_in;
output out;
output c_out;
wire sum_tmp, c_tmp_0, c_tmp_1;

// Add 2 inputs
half_adder half_adder_00 (.in_1(in_1), .in_2(in_2),
                         .out(sum_tmp), .c_out(c_tmp_0));
// Add sum of 2 input with carry 1
half_adder half_adder_01 (.in_1(sum_tmp), .in_2(c_in),
                         .out(out), .c_out(c_tmp_1));
// Calculate carry out
assign c_out = c_tmp_1 | c_tmp_0;
endmodule
// Khối half adder
module half_adder (in_1, in_2, out, c_out);
// defien input/output
input in_1;
input in_2;
output out;
output c_out;

// Calculate sum of 2 inputs
assign out = in_1^in_2;
// Calculate carry out
assign c_out = in_1&in_2;

endmodule

Để cho việc dễ dàng re-use ở đây mình sử dụng vòng lập :
generate
   …
endgenerate

Vòng lập này hoàn toàn có thể tổng hợp (Synthesis) được.

Nói về vòng lập generate thì việc này được sử dụng nhiều khi muốn một tác vụ được lập lại nhiều lần. Với code C sẽ sử dụng vòng lặp for trong Verilog cấu trúc generate cũng tương tự như for trong C.

Test bench

//Author: TrongTran
//Date  : 05/09/2019
//Module: ALU 4 bit data testbench
`timescale 1ns/1ns
//`include "alu.v"
module alu_tb ();
   parameter WIDTH = 4;
   reg S0;
   reg S1;
   reg M;
   reg [WIDTH-1:0] in_1;
   reg [WIDTH-1:0] in_2;
   reg carry_in;
   reg clk;
   reg rst_n;
   wire carry_out;
   wire [WIDTH-1:0] out;
   integer i;


alu alu_00 (.clk(clk), .rst_n(rst_n), .in_1(in_1), .in_2(in_2), .carry_in(carry_in), .S0(S0), .S1(S1), .M(M), .carry_out(carry_out), .out(out));

//initial begin
//   $monitor ("M_S1_S0 = %b%b%b, in_1 = %d, in_2 = %d, carry_in = %d, out = %d, carry_out = %d", M, S1, S0, in_1, in_2, carry_in, out, carry_out);
//end

initial begin
   clk = 0;
   forever #10 clk = ~clk;
end


initial begin
   #1;
   rst_n = 0;
   for ( i = 0 ; i < 3; i = i+1) begin
      S0 = $urandom;
      S1 = $urandom;
      M = $urandom;
      in_1 = $urandom;
      in_2 = $urandom;
      carry_in = $urandom;
      #20;
   end
   rst_n = 1;
   repeat (1000) begin
      S0 = $urandom;
      S1 = $urandom;
      M = $urandom;
      in_1 = $urandom;
      in_2 = $urandom;
      carry_in = $urandom;
      #20;
   end
//$display ("M_S1_S0 = %b%b%b, in_1 = %d, in_2 = %d, carry_in = %d", M, S1, S0, in_1, in_2, carry_in);
//$display ("M_S1_S0 = %b%b%b, in_1 = %d, in_2 = %d, carry_in = %d, out = %d", M, S1, S0, in_1, in_2, carry_in, out);
//$finish;
end


// If after 55 cycle, Simulation will pass.
initial begin
  repeat (25) begin
      @ (posedge clk);
   end
      $display ("[%t]--------------- SIMULATION PASS ---------------", $time);
      $finish;
end


initial begin
reg [3:0] out_tmp;
reg [3:0] out_cmp;
reg  c_tmp;
reg  c_cmp;
reg [3:0] out_1;

  repeat (20) begin
      @ (posedge clk) begin
         $display ("M_S1_S0 = %b%b%b, in_1 = %d, in_2 = %d, carry_in = %d, out = %d, out_tmp = %d, c = %d, c_tmp = %d", M, S1, S0, in_1, in_2, carry_in, out, out_tmp, carry_out, c_tmp);
         if ((out != out_cmp)||(carry_out != c_tmp)) begin
            $display ("[%t]--------------- SIMULATION FAIL ---------------", $time);
            $finish;
         end
         if (rst_n == 1'b0) begin
            out_tmp = 4'b0000;
            c_cmp = 1'b0;
         end else begin
         case ({M, S1, S0})
            3'b000: {c_cmp,out_tmp} = {1'b0,in_1&in_2};
            3'b001: {c_cmp,out_tmp} = {1'b0,in_1|in_2};
            3'b010: {c_cmp,out_tmp} = {1'b0,in_1^in_2};
            3'b011: {c_cmp,out_tmp} = {1'b0,~(in_1^in_2)};
           3'b100: {c_cmp,out_tmp} = in_1+carry_in;
            3'b101: begin
                      out_1 = in_1;
                      {c_cmp, out_tmp}  = out_1+in_2+carry_in;
                    end
            3'b110: begin
                      out_1 = ~in_2;
                      {c_cmp, out_tmp}  = out_1+in_1+carry_in;
                           $display("c_cmp = %d, out_1 = %d, in_1 = %d, in_2 = %d", c_cmp, out_1, in_1, in_2);
                    end
            3'b111: begin
                      out_1 = ~in_1;
                      {c_cmp, out_tmp}  = out_1+in_2+carry_in;
                    end
            default: {c_cmp,out_tmp} = 5'b00000;
         endcase
         end
         out_cmp = out_tmp;
         c_tmp = c_cmp;
      end
   end
end




//initial begin
//   $vcdplusfile("alu.vpd");
//   $vcdpluson();
//end
  initial begin
    $dumpfile("dump.vcd");
    $dumpvars(0, alu_tb);
end  


endmodule


Còn tiếp (+_+)

Cách tính BW và latency trong 1 hệ thống SoC sử dụng chuẩn giao tiếp AXI protocol

Tác giả:  TrongTran Ngày:  31/12/2019 Nếu bạn nào đang làm về verification cho system performance (ST) thì bài này sẽ bổ ích cho bạn. Ngày ...