REVIEW Structs And Units

In

1. Introduction to Structs

The basic organization of an e program is a tree of structs. A struct is a compound type that contains data fields, procedural methods, and other members.

It is the e equivalent of a class in other object-oriented languages. 

A base struct type can be extended by adding members. Subtypes can be created from a base struct type which inherit the base type s members, and contain additional members.

2. Content

Structs And Units Part-I

Structs And Units Part-II

Structs And Units Part-III

Structs And Units Part-IV

Structs are used to define data elements and behavior of components of a test environment. A struct can hold all types of data and methods.
For reusability of e code, you can add struct members or change the behavior of a previously defined struct with .extend. This is very important feature of e language.

There are two ways to implement object-oriented inheritance in e: like inheritance or when inheritance :

Struct
Structs are the basis building blocks for any e language based testbenches. This are similar to class in C++ and thus are used for constructing compound data structures. Like in C++ and C, we can use this compound data structures in all the places like -

 

Parameter

Description

base-struct-type

The type of the struct from which the new struct inherits its members.

struct-member...

The contents of the struct. The following are types of struct members

 -data fields for storing data

-methods for procedures

-events for defining temporal triggers

-coverage groups for defining coverage points

-when, for specifying inheritance subtypes

-declarative constraints for describing relations between data fields

-on, for specifying actions to perform upon event occurrences

-expect, for specifying temporal behavior rules

Example

1 <'

 2 struct structs_units1 {

 3   addr : byte;

 4   data : byte;

 5   rd_wr: bool;

 6 };

 7 '>

In the above example, the struct_units1 is the name of struct, and it contains fields addr,data,rd_wr.
Struct Subtypes

When a struct field has a Boolean type or an enumerated type, you can define a struct subtype for one or more of the possible values for that field. What I mean by defining subtype is, defining new variables for that particular type. What this means is, variables defined for one subtype will not exist for other subtype after generation. Lets see the example below.
Example

  1 <'

  2 struct structs_units2 {

  3   addr : byte;

  4   // True it is write

  5   rd_wr: bool;

  6   // Write data is present only during

  7   // write operation

  8   when TRUE structs_units1 {

  9     data : byte;

 10   };

 11 };

 12 '>
 In the above example, data exists only when the rd_wr is TRUE i.e when it is write access.

Referring subtype

To refer to an enumerated struct subtype in a struct where no values are shared between the enumerated types, you can use this syntax:
value_name struct_type

In structs where more than one enumerated field can have the same value, you must use the following syntax to refer to the struct subtype:
value'field_name struct_type

Example

  1 <'

  2 struct structs_units3 {

  3   addr : byte;

  4   // True it is write

  5   rd_wr: bool;

  6   // Write data is present only during write operation

  7   when TRUE'rd_wr structs_units3 {

  8     data : byte;

  9   };

 10 };

 11

 12 extend sys {

 13   obj : structs_units3;

 14   // This method shows a sub type can accessed

 15   print_obj () is {

 16     if (obj.rd_wr == TRUE) {

 17       out ("Access Type    is : Write");

 18       outf("Access Address is : %x\n",obj.addr);

 19       // To access subtype object is bit complicated, as it does not exit

 20       // in nomal struct

 21       outf("Access Data    is : %x\n",obj.as_a(TRUE'rd_wr structs_units3).data);

 22     } else {

 23       out ("Access Type    is : Read");

 24       outf("Access Address is : %x\n",obj.addr);

 25       // Below code if you uncomment it will compile, but will give run time

 26       // error, as data does not exist when rd_wr is false

 27       //outf("Access Data    is : %x\n",obj.as_a(TRUE'rd_wr structs_units3).data);

 28     };

 29   };

 30   // Just generate the obj and print it

 31   run() is also {

 32     for {var i : int = 0; i < 4; i = i + 1} do {

 33       gen obj;

 34       print_obj();

 35     };

 36   };

 37 };

 38 '>

Access Type    is : Read

Access Address is : 74

Access Type    is : Write

Access Address is : e6

Access Data    is : 2b

Access Type    is : Write

Access Address is : 96

Access Data    is : 35

Access Type    is : Write

Access Address is : 1d

Access Data    is : d1

Extending struct And subtypes
Expending the existing structs are done to add struct members to a previously defined struct or struct subtype.
Members added to the base struct type in extensions apply to all other extensions of the same struct.

Thus, for example, if you extend a method in a base struct with is only, it overrides that method in every one of the like children.

Parameter

Description

struct-subtype

Adds struct members to the specified subtype of the base struct type only. The added struct members are known only in that subtype, not in other subtypes.

base-struct-type

The base struct type to extend.

member...

The contents of the struct. The following are types of struct members

.

-data fields for storing data

.

-methods for procedures

-events for defining temporal triggers

.

-coverage groups for defining coverage points

.

-when, for specifying inheritance subtypes

.

-declarative constraints for describing relations between data fields

-on, for specifying actions to perform upon event occurrences

.

-expect, for specifying temporal behavior rules

The extension of a struct can be empty, containing no members.
Example

  1 <'

  2 struct structs_units4 {

  3   addr : byte;

  4   data : byte;

  5   rd_wr: bool;

  6 };

  7

  8 // Empty Extension

  9 extend structs_units4 {

 10

 11 };

 12

 13 // New elements added

 14 extend structs_units4 {

 15   drive_delay : uint;

 16 };

 17

 18 // Add new method

 19 extend structs_units4 {

 20   say_hi() is {

 21     out ("Hello Dude");

 22   };

 23 };

 24 '>

Extending SubTypes

A struct subtype is an instance of the struct in which one of its fields has a particular value. A struct subtype can optionally be specified with extend, so that the extension only applies to that subtype.

  1 <'

  2 struct structs_units5 {

  3   addr : byte;

  4   rd_wr: bool;

  5

  6   when TRUE'rd_wr structs_units5 {

  7     data : byte;

  8   };

  9 };

 10

 11 // Short Way to extend subtype

 12 extend TRUE'rd_wr structs_units5 {

 13   write_delay : uint;

 14 };

 15

 16 // Second natual way to extend subtype

 17 extend structs_units5 {

 18   when TRUE'rd_wr structs_units5 {

 19     no_writes : byte;

 20     keep no_writes   < 10;

 21     keep write_delay < 10;

 22   };

 23 };

 24

 25 extend sys {

 26   obj : structs_units5;

 27    // This method shows a sub type can accessed

 28   print_obj () is {

 29     if (obj.rd_wr == TRUE) {

 30       out ("Access Type           is : Write");

 31       outf("Access Address        is : %x\n",

 32          obj.addr);

 33       outf("Access Data           is : %x\n",

 34          obj.as_a(TRUE'rd_wr structs_units5).data);

 35       outf("Access write_delay    is : %x\n",obj.

 36          as_a(TRUE'rd_wr structs_units5).write_delay);

 37       outf("Access no_writes      is : %x\n",obj.

 38          as_a(TRUE'rd_wr structs_units5).no_writes);

 39     } else {

 40       out ("Access Type           is : Read");

 41       outf("Access Address        is : %x\n",obj.addr);

 42     };

 43   };

 44   // Just generate the obj and print it

 45   run() is also {

 46     for {var i : int = 0; i < 2; i = i + 1} do {

 47       gen obj;

 48       print_obj();

 49     };

 50   };

 51 };

 52

 53 '>
 Access Type           is : Write

Access Address        is : 74

Access Data           is : 2b

Access write_delay    is : 7

Access no_writes      is : 9

Access Type           is : Read

Access Address        is : e6

Defining Fields
Defines a field to hold data of a specific type. You can specify whether it is a physical field or a virtual field, and whether the field is to be automatically generated. For scalar data types, you can also specify the size of the field in bits or bytes.

Note : You can reference a field before it is declared as long as the declaration of the field is in the same file. In the case of cyclic import, the field may be declared in one of the current set of imported files.

Syntax :[!][%] field-name[: type] [[min-val .. max-val]][((bits | bytes):num)]

Parameter

Description

!

Denotes an ungenerated field. The ! and % options can be used together, in either order.

%

Denotes a physical field. The ! and % options can be used together, in either order.

field-name

The name of the field being defined.

type

The type for the field. This can be any scalar type, string, struct, or list. If the field name is the same as an existing type, you can omit the type part of the field definition. Otherwise, the type specification is required.

min-val..max-val

An optional range of values for the field, in the form. If no range is specified, the range is the default range for the field s type.

(bits | bytes, num)

The width of the field in bits or bytes. This syntax allows you to specify a width for the field other than the default width. This syntax can be used for any scalar field, even if the field has a type with a known width.


Physical Fields

A field defined as a physical field (with the % option) is packed when the struct is packed. Fields that represent data that is to be sent to the HDL device in the simulator or that are to be used for memories in the simulator or in Specman Elite, need to be physical fields. Nonphysical fields are called virtual fields and are not packed automatically when the struct is packed, although they can be packed individually.
If no range is specified, the width of the field is determined by the field's type. For a physical field, if the field's type does not have a known width, you must use the (bits | bytes : num) syntax to specify the width.

Ungenerated Fields
A field defined as ungenerated (with the ! option) is not generated automatically. This is useful for fields that are to be explicitly assigned during the test, or whose values involve computations that cannot be expressed in constraints.
Ungenerated fields get default initial values (0 for scalars, NULL for structs, empty list for lists). An ungenerated field whose value is a range (such as [0..100]) gets the first value in the range. If the field is a struct, it will not be allocated and none of the fields in it will be generated.

Assigning Values to Fields
Unless you define a field as ungenerated, Specman Elite will generate a value for it when the struct is generated, subject to any constraints that exist for the field. However, even for generated fields, you can always assign values in user-defined methods or predefined methods such as init(), pre_generate(), or post_generate(). The ability to assign a value to a field is not affected by either the ! option or generation constraints.


Example Of Fields

  1 <'

  2 struct structs_units6 {

  3   %addr : byte;

  4   %data : byte;

  5   %rd_wr: bool;

  6   rd_wt : uint [0..100];

  7   wr_wt : uint [0..100];

  8    ! drive_delay : uint [0..10];

  9 };

 10

 11 extend sys {

 12   obj : structs_units6;

 13   run() is also {

 14     for {var i : int = 0; i < 2 ; i = i + 1} do {

 15       gen obj;

 16       print obj;

 17     };

 18   };

 19 };

 20 '>
obj = structs_units6-@0: structs_units6

----------------------------------------------         @structs_units6

0              %addr:                          116

1              %data:                          2

2              %rd_wr:                         FALSE

3              rd_wt:                          77

4              wr_wt:                          78

5              !drive_delay:                   0

obj = structs_units6-@1: structs_units6

----------------------------------------------         @structs_units6

0              %addr:                          230

1              %data:                          240

2              %rd_wr:                         TRUE

3              rd_wt:                          58

4              wr_wt:                          26

5          !drive_delay:                   0
drive_delay is always 0, as it not generated.

Introduction to Units

e Units are the basic structural blocks for creating verification modules (verification cores) that can easily be integrated together to test larger and larger portions of an HDL design as it develops. 

Units, like structs, are compound data types that contain data fields, procedural methods, and other members. Unlike structs, however, a unit instance is bound to a particular component in the DUT (an HDL path). Furthermore, each unit instance has a unique and constant place (an e path) in the run-time data structure of an e program. Both the e path and the complete HDL path associated with a unit instance are determined before the run begins.

To understand better about the applications of units, look at the below example. Which is a switch fabric with 6 ports. We can have one unit, which implements the driver part of the functionality of the testbench, and this driver can be instantiated 6 times with each of them have unique HDL paths.

The basic run-time data structure of an e program is a tree of unit instances whose root is sys, the only predefined unit in e. Additionally there are structs that are dynamically bound to unit instances. The run-time data structure of a typical e program using units is similar to that of the switch_fabric program shown in figure below.

Each unit instance in the unit instance tree of the switch_fabric matches a module instance in the Verilog DUT, as shown in figure above. The one-to-one correspondence in this particular design between e unit instances and DUT module instances is not required for all designs. In more complex designs, there may be several levels of DUT hierarchy corresponding to a single level of hierarchy in the tree of e unit instances.
Binding an e unit instance to a particular component in the DUT hierarchy allows you to reference signals within that DUT component using relative HDL path names. This ability to use relative path names to reference HDL objects allows you to freely change the combination of verification cores as the HDL design and the verification environment evolve. Regardless of where the DUT component is instantiated in the final integration, the HDL path names in the verification environment remain valid.

Defining Units And Fields

Units are the basic structural blocks for creating verification modules (verification cores) that can easily be integrated together to test larger designs. Units are a special kind of struct, with two important properties:

Because the base unit type (any_unit) is derived from the base struct type (any_struct), user-defined units have the same predefined methods. In addition, units can have verilog members and have several specialized predefined methods.
A unit type can be extended or used as the basis for creating unit subtypes. Extended unit types or unit subtypes inherit the base type's members and contain additional members.
Syntax :
 unit unit-type [like base-unit-type] { [unit-member; ...]}

Parameter

Description

unit-type

The type of the new unit.

base-unit-type

The type of the unit from which the new unit inherits its members.

unit-member

Like structs, units can have the following types of members

 .

-data fields for storing data

.

-methods for procedures

.

-events for defining temporal triggers

.

-coverage groups for defining coverage points

.

-when, for specifying inheritance subtypes

.

-declarative constraints for describing relations between data fields

-on, for specifying actions to perform upon event occurrences

.

-expect, for specifying temporal behavior rules

The definition of a unit can be empty, containing no members.

Example

  1 module switch_fabric(

  2   clk, reset, data_in0, data_in1, data_in2,

  3   data_in3, data_in4, data_in5, data_in_valid0,

  4   data_in_valid1, data_in_valid2, data_in_valid3,

  5   data_in_valid4, data_in_valid5, data_out0,

  6   data_out1, data_out2, data_out3, data_out4,

  7   data_out5, data_out_ack0, data_out_ack1,

  8   data_out_ack2, data_out_ack3, data_out_ack4,

  9   data_out_ack5

 10 );

 11

 12 input           clk, reset;

 13 input  [7:0]    data_in0, data_in1, data_in2, data_in3;

 14 input  [7:0]    data_in4, data_in5;

 15 input           data_in_valid0, data_in_valid1, data_in_valid2;

 16 input  [7:0]    data_in_valid3, data_in_valid4, data_in_valid5;

 17 output [7:0]    data_out0, data_out1, data_out2, data_out3;

 18 output [7:0]    data_out4, data_out5;

 19 output          data_out_ack0, data_out_ack1, data_out_ack2;

 20 output [7:0]    data_out_ack3, data_out_ack4, data_out_ack5;

 21

 22 switch port_0 ( .clk(clk), .reset(reset), .data_in(data_in0),

 23   .data_in_valid(data_in_valid0), .data_out(data_out0),

 24   .data_out_ack(data_out_ack0));

 25

 26 switch port_1 ( .clk(clk), .reset(reset), .data_in(data_in1),

 27   .data_in_valid(data_in_valid1), .data_out(data_out1),

 28   .data_out_ack(data_out_ack1));

 29

 30 switch port_2 ( .clk(clk), .reset(reset), .data_in(data_in2),

 31   .data_in_valid(data_in_valid2), .data_out(data_out2), .

 32   data_out_ack(data_out_ack2));

 33

 34 switch port_3 ( .clk(clk), .reset(reset), .data_in(data_in3),

 35   .data_in_valid(data_in_valid3), .data_out(data_out3),

 36   .data_out_ack(data_out_ack3));

 37

 38 switch port_4 ( .clk(clk), .reset(reset), .data_in(data_in4),

 39   .data_in_valid(data_in_valid4), .data_out(data_out4),

 40   .data_out_ack(data_out_ack4));

 41

 42 switch port_5 ( .clk(clk), .reset(reset), .data_in(data_in5),

 43   .data_in_valid(data_in_valid5), .data_out(data_out5),

 44   .data_out_ack(data_out_ack5));

 45

 46 endmodule

 47

 48 module switch (

 49   clk,

 50   reset,

 51   data_in,

 52   data_in_valid,

 53   data_out,

 54   data_out_ack

 55 );

 56

 57 input   clk;

 58 input   reset;

 59 input [7:0]  data_in;

 60 input   data_in_valid;

 61 output [7:0]  data_out;

 62 output  data_out_ack;

 63

 64 reg [7:0]  data_out;

 65 reg   data_out_ack;

 66

 67 always @ (posedge clk)

 68 if (reset) begin

 69    data_out <= 0;

 70    data_out_ack <= 0;

 71 end else if (data_in_valid) begin

 72    data_out <= data_in;

 73    data_out_ack <= 1;

 74 end else begin

 75    data_out <= 0;

 76    data_out_ack <= 0;

 77 end

 78

 79 endmodule

  1 `include "switch_fabric.v"

  2

  3 module unit_example();

  4

  5 reg  clk, reset;

  6 reg  [7:0] data_in0, data_in1, data_in2, data_in3;

  7 reg  [7:0] data_in4, data_in5;

  8 reg  data_in_valid0, data_in_valid1, data_in_valid2;

  9 reg  data_in_valid3, data_in_valid4, data_in_valid5;

 10 wire [7:0] data_out0, data_out1, data_out2;

 11 wire [7:0] data_out3, data_out4, data_out5;

 12 wire data_out_ack0, data_out_ack1, data_out_ack2;

 13 wire data_out_ack3, data_out_ack4, data_out_ack5;

 14

 15 initial begin

 16   clk = 0;

 17   reset = 0;

 18    #10  reset = 1;

 19    #10  reset = 0;

 20   data_in0  = 0;      

 21   data_in1  = 0;

 22   data_in2  = 0;

 23   data_in3  = 0;

 24   data_in4  = 0;

 25   data_in5  = 0;

 26   data_in_valid0  = 0;

 27   data_in_valid1  = 0;

 28   data_in_valid2  = 0;

 29   data_in_valid3  = 0;

 30   data_in_valid4  = 0;

 31   data_in_valid5  = 0;

 32 end

 33

 34 always #2.5 clk = ~clk;

 35

 36 switch_fabric U_switch_fabric(

 37  .clk(clk), .reset(reset),

 38  .data_in0(data_in0), .data_in1(data_in1),

 39  .data_in2(data_in2), .data_in3(data_in3),

 40  .data_in4(data_in4), .data_in5(data_in5),

 41  .data_in_valid0(data_in_valid0), .data_in_valid1(data_in_valid1),

 42  .data_in_valid2(data_in_valid2), .data_in_valid3(data_in_valid3),

 43  .data_in_valid4(data_in_valid4), .data_in_valid5(data_in_valid5),

 44  .data_out0(data_out0), .data_out1(data_out1),

 45  .data_out2(data_out2), .data_out3(data_out3),

 46  .data_out4(data_out4), .data_out5(data_out5),

 47  .data_out_ack0(data_out_ack0), .data_out_ack1(data_out_ack1),

 48  .data_out_ack2(data_out_ack2), .data_out_ack3(data_out_ack3),

 49  .data_out_ack4(data_out_ack4), .data_out_ack5(data_out_ack5)

 50 );

 51

 52 endmodule

  1 <'

  2 unit switch_driver {

  3    inst_no : uint;

  4    event clk is rise('clk')@sim;

  5    // This is real dump driver!!!

  6    drive_data(data : byte)@clk is {

  7      wait cycle;

  8       outf ("Driving port : %d\n",inst_no);

  9       outf ("    in  data : %d\n",data);

 10      'data_in(inst_no)'       = data;

 11      'data_in_valid(inst_no)' = 1;

 12      wait cycle;

 13      'data_in(inst_no)'       = 0;

 14      'data_in_valid(inst_no)' = 0;

 15      wait cycle;

 16       outf ("    out data : %d\n",data);

 17      // Check if the data is coming out

 18      if ('data_out_ack(inst_no)' == 1) {

 19        check that data == 'data_out(inst_no)';

 20      } else {

 21        out ("There seems to be a error in DUT");

 22      };

 23    };

 24 };

 25

 26 unit switch_fabric {

 27   ports: list of switch_driver is instance;

 28   keep ports.size() == 6;

 29   // Assign path and unique number to each drive instance

 30   keep for each in ports { 

 31     .hdl_path() == "";

 32     .inst_no    == index;

 33   };

 34   // Have clock reference

 35   event clk is rise('clk')@sim;

 36   // Drive traffic into switch

 37   txgen()@clk is {

 38     wait [10]*cycle;

 39     ports[0].drive_data(10);

 40     ports[1].drive_data(11);

 41     ports[2].drive_data(12);

 42     ports[3].drive_data(13);

 43     ports[4].drive_data(14);

 44     ports[5].drive_data(15);

 45     wait [10]*cycle;

 46     stop_run();

 47   };

 48 };

 49

 50 extend sys {

 51   switch_fabric :  switch_fabric is instance;

 52   keep switch_fabric.hdl_path() == "unit_example";

 53   run() is also {

 54     start switch_fabric.txgen();

 55   };

 56 };

 57 '>

Driving port : 0

     in  data : 10

     out data : 10

 Driving port : 1

     in  data : 11

     out data : 11

 Driving port : 2

     in  data : 12

     out data : 12

 Driving port : 3

     in  data : 13

     out data : 13

 Driving port : 4

     in  data : 14

     out data : 14

 Driving port : 5

     in  data : 15

     out data : 15

Passing Parameters

As in any programming language, we can pass parameters to function. In this case methods. Passing of parameters can be donw with one of the following ways

Scalar Parameter Passing

Scalar parameters include numeric, Boolean, and enumerated types. When you pass a scalar parameter to a method, by default the value of the parameter is passed, this is same as in other programming language.

  1 <'

  2 struct scoreboard {

  3      ! mem_list : list of int;

  4     // This method uses one input parameter

  5     add_item (addr : intis {

  6       mem_list.push(addr);

  7     };

  8     // This method does not have any input

  9     // parameters

 10     print_sb () is {

 11      print mem_list;

 12     };

 13 };

 14 // Extend sys to run the simulate above methods

 15 extend sys {

 16   U_sb : scoreboard;

 17   run() is also {

 18     U_sb.add_item(10);

 19     U_sb.add_item(22);

 20     U_sb.print_sb();

 21   };

 22 };

 23 '>

  mem_list =

0.             10

1.           22

Compound Parameter Passing

Sometimes we may want to pass lot of parameters to a methods. Instead of typing each and every parameter, we can create a struct and pass this struct as parameter. Once again this is same as in C or C++. We can also pass a list as parameter to a method.

  1 <'

  2 struct mem_base_object {

  3   addr : int;

  4   data : int;

  5   cmd  : bool;

  6 };

  7

  8 struct mem_txgen {

  9    ! base_object : mem_base_object;

 10   num_cmds    : int;

 11   // This methods the commands

 12   gen_cmds () is {

 13    var i : int = 0;

 14    for i from 1 to  num_cmds do {

 15      // gen commands

 16      gen base_object;

 17      // Print the commands

 18      print_cmds(base_object);

 19    };

 20   };

 21   // This method prints the command details

 22   print_cmds (cmd_struct : mem_base_object) is {

 23     out ("Following command is generated");

 24     outf("      Address  : %x\n",cmd_struct.addr);

 25     outf("      Date     : %x\n",cmd_struct.data);

 26     if (cmd_struct.cmd) {

 27       out("      CMD      : Read");

 28     } else {

 29       out("      CMD      : Write");

 30     };

 31   };

 32 };

 33 // Extend sys to run the simulate above methods

 34 extend sys {

 35   txgen : mem_txgen;

 36   keep txgen.num_cmds == 2;

 37   run() is also {

 38     txgen.gen_cmds();

 39   };

 40 };

Following command is generated

      Address  : 87abfa57

      Date     : af6d7f9

      CMD      : Read

Following command is generated

      Address  : f49771af

      Date     : 827afe11

      CMD      : Read

Passing By Reference

As in the C and C++, we can pass a parameter as reference, in this case what happens is if the variable is modified inside the method, the value gets reflected in global variable also. This is applicable to both Compound parameter passing and also to scalar parameter passing.

  1 <'

  2 struct counter_struct {

  3      ! count_var : int;

  4     // Counter

  5     counter () is {

  6       var i : int = 0;

  7       for i from 0 to 10 do {

  8         increment(count_var); // There is no * here

  9         outf("Current value of count : %x\n",count_var);

 10       };

 11     };

 12     // This method increments the passed parameter

 13     // * is for the data type and not to variable

 14     increment (count_passed : *intis {

 15       count_passed = count_passed + 1;

 16     };

 17 };

 18 // Extend sys to run the simulate above methods

 19 extend sys {

 20   U_c : counter_struct;

 21   run() is also {

 22      U_c.counter();

 23   };

 24 };

 25 '>

Current value of count : 1

Current value of count : 2

Current value of count : 3

Current value of count : 4

Current value of count : 5

Current value of count : 6

Current value of count : 7

Current value of count : 8

Current value of count : 9

Current value of count : a

Current value of count : b

  Bạn Có Đam Mê Với Vi Mạch hay Nhúng      -     Bạn Muốn Trau Dồi Thêm Kĩ Năng

Mong Muốn Có Thêm Cơ Hội Trong Công Việc

Và Trở Thành Một Người Có Giá Trị Hơn

Bạn Chưa Biết Phương Thức Nào Nhanh Chóng Để Đạt Được Chúng

Hãy Để Chúng Tôi Hỗ Trợ Cho Bạn. SEMICON  


Lần cập nhật cuối ( Thứ hai, 03 Tháng 4 2023 23:24 )