• Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

  • Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

  • Integrated Circuit Design - Chia sẻ kiến thức về vi mạch

    Vi mạch và Ứng dụng

Thứ Bảy, 20 tháng 4, 2019

[SystemC][High Level Design]Bài 4 - Mô tả model code cho TOP-module bằng SystemC

Bài viết này hướng dẫn cách mô hình hóa TOP module của model code. TOP module là thành phần gọi các sub-module để tạo ra các instance. Trước khi đọc bài viết này, các bạn nên đọc các bài viết trước đó để hiểu một số thuật ngữ.
(bài 1  bài 2 bài 3)

1) Phương pháp viết model code cho TOP module
Việc mô tả TOP module gồm các thành phần cơ bản sau:
  • Gọi tất cả các file header của các sub-module
  • Khai báo TOP module
  • Khai báo port
  • Khai báo tín hiệu kết nối giữa các instance
  • Khai báo các instance
  • Constructor
    • Liệt kê tất cả các instance
    • Kết nối các instance
Cú pháp khai báo TOP module – cách 1
//Include header file of sub-modules
SC_MODULE (top_module_name) {
  //Port_declaration
  //Signal_declaration
  //Instance_eclaration
  SC_CTOR (top_module_name) : instance_list {
   //Instance_connection
  }
};
Trong đó: 
  • Instance_declaration được khai báo theo cú pháp sau:
module_name instance_name;

  • Instance_list được khai báo như sau:
instance_name_0(“id_string_0”),

instance_name_1(“id_string_1”), …
với id_string là một chuỗi ký tự định danh bất kỳ không chứa khoảng trắng.
  • Instance_connection được khai báo như sau:
instance_name.port_name(signal_name);

với port_name là tên port của module và signal_name là tên tín hiệu nối đến port đó. signal_name có thể trùng hoặc khác port_name.

Cú pháp khai báo TOP module – cách 2
//Include header file of sub-modulesSC_MODULE (top_module_name) {  //Port_declaration  //Signal_declaration  //Poiter_creation
  SC_CTOR (top_module_name) {    //Instance_creation
    //Instance_connection
  }
};
Trong đó:
  • Pointer_creation được khai báo như sau: 
module_name *instance_name; 
  • Instance_creation được khai báo như sau: 
instance_name = new module_name(“id_string”); 
  • Instance_connection là kết nối của các instance được khai báo như sau: 
instance_name->port_name(signal_name);

Để tương thích với phần mềm Vivado của Xilinx sử dụng trong tài liệu này, chúng ta phải sử dụng cách 1 cho khai báo TOP module.
2) File TOP module
Model code của file TOP module scpu_top

#include "scpu_fetch.h"
#include "scpu_decoder.h"
#include "scpu_execute.h"
//--------------------------------------
//Project: Simple CPU
//Module:  TOP module
//Function:Connect all sub-modules (FETCH, DECODER, EXECUTE)
//Authod:  Nguyen Hung Quan
//Page:    VLSI Technology
//--------------------------------------

SC_MODULE (scpu_top) {
sc_in<bool> clk;
sc_in<bool> rst_n;
//
//Internal connection
//
sc_signal<bool> dc_load_pc;
sc_signal<bool> dc_imm;
sc_signal<sc_uint<2> > dc_addr_sel;
sc_signal<sc_uint<8> > dc_rs;
sc_signal<sc_uint<8> > dc_rd;
sc_signal<bool> dc_mem_wr;
sc_signal<bool> dc_load_dr;
sc_signal<bool> dc_load_ir;
//
sc_signal<sc_uint<8> > fetch_ir;
sc_signal<sc_uint<8> > fetch_dr;
sc_signal<sc_uint<8> > fetch_mem_dout;
sc_signal<sc_uint<8> > ex_dout;
//
sc_signal<sc_uint<2> > dc_op;
//
//Instance declaration
scpu_fetch   scpu_fetch_inst;
scpu_decoder scpu_decoder_inst;
scpu_execute scpu_execute_inst;
//
SC_CTOR (scpu_top) : scpu_fetch_inst("scpu_fetch_inst"), scpu_decoder_inst("scpu_decoder_inst"), scpu_execute_inst("scpu_execute_inst"){
  //instance connection of scpu_fetch_inst
      scpu_fetch_inst.clk(clk);
      scpu_fetch_inst.rst_n(rst_n);
      scpu_fetch_inst.dc_load_pc(dc_load_pc);
      scpu_fetch_inst.dc_imm(dc_imm);
      scpu_fetch_inst.dc_addr_sel(dc_addr_sel);
      scpu_fetch_inst.dc_rs(dc_rs);
      scpu_fetch_inst.dc_rd(dc_rd);
      scpu_fetch_inst.dc_mem_wr(dc_mem_wr);
      scpu_fetch_inst.dc_load_dr(dc_load_dr);
      scpu_fetch_inst.dc_load_ir(dc_load_ir);
      scpu_fetch_inst.fetch_ir(fetch_ir);
      scpu_fetch_inst.fetch_dr(fetch_dr);
      scpu_fetch_inst.fetch_mem_dout(fetch_mem_dout);
  //instance connection of scpu_decoder_inst
  scpu_decoder_inst.clk(clk);
  scpu_decoder_inst.rst_n(rst_n);
  scpu_decoder_inst.fetch_ir(fetch_ir);
  scpu_decoder_inst.fetch_dr(fetch_dr);
  scpu_decoder_inst.fetch_mem_dout(fetch_mem_dout);
  scpu_decoder_inst.ex_dout(ex_dout);
  scpu_decoder_inst.dc_load_pc(dc_load_pc);
  scpu_decoder_inst.dc_imm(dc_imm);
  scpu_decoder_inst.dc_addr_sel(dc_addr_sel);
  scpu_decoder_inst.dc_rs(dc_rs);
  scpu_decoder_inst.dc_rd(dc_rd);
  scpu_decoder_inst.dc_mem_wr(dc_mem_wr);
  scpu_decoder_inst.dc_load_dr(dc_load_dr);
  scpu_decoder_inst.dc_load_ir(dc_load_ir);
  scpu_decoder_inst.dc_op(dc_op);
//instance connection of scpu_execute_inst
     scpu_execute_inst.dc_rs(dc_rs);
     scpu_execute_inst.dc_rd(dc_rd);
     scpu_execute_inst.dc_op(dc_op);
     scpu_execute_inst.ex_dout(ex_dout);
}
};

Hình 1: TOP module của SCPU
Kết thúc bài này, chúng ta đã có thể tạo được tất cả các file code của ví dụ SCPU. Nhóm tác giả sẽ đưa link tải source code hoàn chỉnh trong những bài sau. Hiện tại, các bạn có thể tự viết model code của mình và hãy phản hồi cho chúng tôi nếu gặp bất cứ vấn đề gì.

Lịch sử cập nhật:
1. 2019.Apr.21 - Tạo lần đầu

Danh sách tác giả:
1. Lê Hoàng Vân
2. Trương Công Hoàng Việt
3. Nguyễn Hùng Quân

Thứ Năm, 18 tháng 4, 2019

[SystemC][High Level Design]Bài 3 - Mô tả model code cho sub-module bằng SystemC

Bài viết này hướng dẫn mô hình hóa các mạch tổ hợp và mạch tuần tự bằng SystemC. Cách xây dựng sub-module với SystemC. Bên cạnh đó, một số lưu ý khi sử dụng SystemC để mô hình hóa mạch logic tổng hợp được. Trong loạt bài viết này, phần mềm được sử dụng là phần mềm Xilinx Design Tool - Vivado HL Design, một phần mềm sử dụng cho FPGA.
Các bạn cần đọc bài 1 bài 2 trước khi đọc bài này. Đặc biệt, model code mình họa trong bài này được viết dựa trên model specification ở bài 2.
1) SystemC là gì?
SystemC là một thư viện C++. Nó hỗ trợ rất nhiều các class khác nhau để mô hình hóa các hoạt động phần cứng. Tuy nhiên, tài liệu này chỉ tập trung vào việc mô tả model code khả tổng hợp. Đồng thời, nhóm tác giả không có ý định phân tích tất cả các khả năng có thể sử dụng của SystemC mà chỉ giới thiệu một cách mô hình hóa đơn giản giúp bạn đọc nhanh chóng nắm bắt và thực hành.
Bên cạnh đó, chúng tôi sẽ so sánh sự tương đương giữa SystemC code và RTL code trong mô tả mạch logic.
2) Mô hình hóa sub-module
Một sub-module được mô hình hóa bằng các từ khóa SystemC sau đây:
  • SC_MODULE: khai báo module
  • sc_in: khai báo port input
  • sc_out: khai báo port output
  • sc_inout: khai báo port hai chiều vừa là input, vừa là output
  • sc_signal: khai báo tín hiệu giao tiếp nội bộ giữa các process.
  • SC_METHOD: khai báo các process để mô hình hóa mạch tổ hợp và mạch tuần tự
Hình 1: Mô hình model code của sub-module
Một sub-module được mô tả trong hai file:
  • file header (.h) chứa các khai báo
  • file body (.cpp) chứa code của các process để mô hình hóa mạch logic.
3) Mô tả file header (.h)
3.1) Cấu trúc file header như sau
Cú pháp:
#include "systemc.h"
SC_MODULE (module_name) {
  //port_declaration
  //signal_declaration
  //variable_declaration
  //process_declaration
  //module_constructor
};
Cấu trúc file header như sau:
  • [Bắt buộc] Gọi thư viện SytemC #include "systemc.h"
  • [Bắt buộc] Khai báo module với từ khóa SC_MODULE
    • module_name: tên module do người thiết kế đặt
  • [Bắt buộc] port_declaration: Khai báo port input/output/inout
  • [Tùy chọn] signal_declaration: Khai báo tín hiệu nội bộ nếu có
  • [Tùy chọn] variable_declaration: Khai báo biến sử dụng nội bộ nếu có
  • [Bắt buộc] process_declaration: Khai báo các process
  • [Bắt buộc] module_constructor: Khai báo constructor
    • [Bắt buộc] Đăng ký các process
    • [Bắt buộc] Khai báo danh sách độ nhạy cho các process
3.2) Khai báo port
Cú pháp:
port_type<data_type> port_name;
Trong đó:
  • port_type là loại port:
    • sc_in là port input
    • sc_out là port output
    • sc_inout là port hai chiều
  • data_type là loại dữ liệu
    • bool: kiểu dữ liệu chỉ có hai giá trị 1 hoặc 0
    • sc_uint<n>: kiểu dữ liệu không dấu có số bit là n ≤ 64 bit
    • sc_biguint<n>: kiểu dữ liệu không dấu có số bit là n > 64 bit
  • port_name là tên port do người thiết kế đặt
Một số ví dụ về khai báo port:
  • sc_in<bool>: khai báo port input độ rộng 1 bit
  • sc_out<bool>: khai báo port output độ rộng 1 bit
  • sc_inout<bool>: khai báo port hai chiều độ rộng 1 bit
  • sc_in<sc_uint<n> >: khai báo port input độ rộng n bit với n ≤ 64 bit
  • sc_out< sc_uint<n> >: khai báo port output độ rộng n bit với n ≤ 64 bit
  • sc_inout< sc_uint<n> >: khai báo port hai chiều độ rộng n bit với n ≤ 64 bit
  • sc_in<sc_biguint<n> >: khai báo port input độ rộng n bit với n > 64 bit
  • sc_out<sc_biguint<n> >: khai báo port output độ rộng n bit với n > 64 bit
  • sc_inout<sc_biguint<n> >: khai báo port hai chiều độ rộng n bit với n > 64 bit
Chú ý, khi khai báo một port nhiều bit, một khoảng trắng bắt buộc phải có nằm giữa dấu “>” cuối cùng và dấu “>” ngay trước nó.
3.3) Khai báo tín hiệu
Cú pháp:
sc_signal<data_type> signal_name;
Trong đó: 
  • data_type: giống như mô tả trong phần khai báo port
  • signal_name: là tên tín hiệu do người thiết kế đặt
3.4) Khai báo biến nội
Cú pháp:
<data_type> variable_name;
Trong đó: 
  • data_type: giống như mô tả trong phần khai báo port
  • variable_name: là tên biến do người thiết kế đặt
3.5) Khai báo process
Cú pháp:
void process_name();
Trong đó:
  • process_name là tên process do người thiết kế đặt
3.6) Constructor
Cú pháp constructor:
SC_CTOR (module_name) {
//process_register
//sensitivity_list

}
Trong đó:
  • module_name là tên module do người thiết kế đặt. module_name của constrcutor phải trùng với tên module đã khai báo tại SC_MODULE
  • process_register là khai báo đăng ký process sử dụng SC_METHOD. Ngoài SC_METHOD, chúng ta còn có thể dùng SC_THREAD SC_CTHREAD để mô tả process nhưng để đảm bảo tổng hợp tốt trên nhiều phần mềm, hai loại này sẽ không được sử dụng. 
  • sensitivity_list là danh sách độ nhạy của process đã đăng ký. 
Cú pháp đăng ký process:
SC_METHOD (process_name);
Trong đó:
  • process_name là tên process do người thiết kế đặt và trùng với khai báo process. 
Cú pháp khai báo danh sách độ nhạy của mô hình mạch tổ hợp:
sensitive << signal_name_0 << signal_name_1 << …; 
Cú pháp khai báo danh sách độ nhạy của mô hình mạch tuần tự dùng reset đồng bộ hoặc không có reset:
sensitive << clock_name.pos(); //positive edge
hoặc:
sensitive << clock_name.neg(); //negative edge
Cú pháp khai báo danh sách độ nhạy của mô hình mạch tuần tự dùng reset bất đồng bộ:
sensitive << clock_name.pos(); //positive edge
sensitive << reset_name.pos(); //positive edge 
hoặc:
sensitive << clock_name.pos(); //positive edge
sensitive << reset_name.neg(); //negative edge 
hoặc:
sensitive << clock_name.neg(); //negative edge
sensitive << reset_name.pos(); //positive edge 
hoặc:
sensitive << clock_name.neg(); //negative edge
sensitive << reset_name.neg(); //negative edge
3.7) Ví dụ về file header của khối FETCH

#include "systemc.h"
//--------------------------------------
//Project: Simple CPU
//Module: FETCH - header file
//Function:Read instruction and data from memory
//Author: Nguyen Hung Quan
//Page: VLSI Technology
//--------------------------------------

SC_MODULE (scpu_fetch) {
//
//Port declaration
//
//Input
sc_in<bool> clk;
sc_in<bool> rst_n;
sc_in<bool> dc_load_pc;
sc_in<bool> dc_imm;
sc_in<sc_uint<2> > dc_addr_sel;
sc_in<sc_uint<8> > dc_rs;
sc_in<sc_uint<8> > dc_rd;
sc_in<bool> dc_mem_wr;
sc_in<bool> dc_load_dr;
sc_in<bool> dc_load_ir;
//Ouptut
sc_out<sc_uint<8> > fetch_ir;
sc_out<sc_uint<8> > fetch_dr;
sc_out<sc_uint<8> > fetch_mem_dout;
//
//Signal declaration
//
sc_signal<sc_uint<8> > mem_dout;
sc_signal<sc_uint<8> > pc;
sc_signal<sc_uint<8> > mem_addr;
sc_signal<sc_uint<8> > fetch_dr_in;
//
//Variable declaration
//
sc_uint<8> mem_array [256];
//
//Process declaration
//
//memory
void IR_DR_INPUT();
void MEM_OUTPUT();
void MEM_WRITE();
//fetch
void INSTRUCTION_REG();
void DATA_REG_IN();
void DATA_OUTPUT();
void PROGRAM_COUNTER();
void MEMORY_ADDR();
//
//Module Constructor
//

SC_CTOR (scpu_fetch) {
  SC_METHOD (IR_DR_INPUT);
    sensitive << fetch_dr;
    sensitive << pc;
    sensitive << dc_imm;
  //
  SC_METHOD (MEM_OUTPUT);
    sensitive << mem_addr;
  //
  SC_METHOD (MEM_WRITE);
    sensitive << clk.pos();
  //
  SC_METHOD (INSTRUCTION_REG);
    sensitive << clk.pos();
  //
  SC_METHOD (DATA_REG_IN);
    sensitive << clk.pos();
  //
  SC_METHOD (DATA_OUTPUT);
    sensitive << fetch_dr_in;
   //
  SC_METHOD (PROGRAM_COUNTER);
    sensitive << clk.pos();
  //
  SC_METHOD (MEMORY_ADDR);
    sensitive << dc_addr_sel;
    sensitive << pc;
    sensitive << fetch_dr;
    sensitive << dc_rs;
  }
};
4) Mô tả file body (.cpp)
4.1) Cách mô tả file body
Cú pháp: 
#include "header_file"
//process_body
Trong đó:
  • header_file là file “.h” của sub-module
  • process_body là mô hình mạch logic, mạch tổ hợp hoặc mạch tuần tự, của tất cả các process trong sub-module
Cú pháp mô tả phần code của process:
void module_name::process_name () {
//variable_declaration
//model_code
}
Trong đó: 
  • module_name là tên module do người thiết kế đặt. Tên sử dụng ở đây phải trùng với tên khai báo bởi SC_MODULE
  • process_name là tên process do người thiết kế đặt. Tên sử dụng ở đây phải trùng với tên khai báo process bởi void. 
  • variable_declaration là khai báo biến trung gian sử dụng trong process nếu cần model_code là code SystemC mô hình mạch logic.
4.2) Cách thức mô hình hóa mạch tổ hợp
Trong mô hình hóa mạch tổ hợp, tất cả các tín hiệu đầu vào ảnh hưởng đến hoạt động của mạch phải được liệt kê đầy đủ trong danh sách độ nhạy. Việc khai báo thiếu tín hiệu trong danh sách độ nhạy sẽ làm việc mô phỏng model code không chính xác.
Method read() được sử dụng để đọc giá trị các tín hiệu đầu vào của một process.
Hình 2: Một ví dụ về mô tả mạch tổ hợp
4.3) Cách thức mô hình hóa mạch tuần tự
Khi mô hình hóa mạch tuần tự, danh sách độ nhạy chỉ chứa tín hiệu clock. Nếu mạch tuần tự sử dụng reset bất đồng bộ thì danh sách độ nhạy có thêm tín hiệu reset. 
Để khai báo cạnh tích cực cả tín hiệu clock và reset, hai method sau đây được sử dụng: 
  • pos() mô tả cạnh lên 
  • neg() mô tả cạnh xuống 
Chú ý, tuy còn có cách khác để mô tả cạnh tích cực của clock và reset như: 
  • sensitive_pos mô tả cạnh lên 
  • sensitive_neg mô tả cạnh xuống Nhưng để dảm bảo tương thích với phần mềm tổng hợp Vivado sử dụng trong loạt bài này, chúng ta nên chỉ dùng method pos() và neg().
Hình 3: Một ví dụ về mô tả mạch tuần tự dùng reset đồng bộ

4.4) Ví dụ về file body của khối FETCH
#include "scpu_fetch.h"
//
//MEMORY
//
void scpu_fetch::IR_DR_INPUT () {
//
sc_uint<8> mem_sel;
sc_uint<8> dr_tmp;
//
dr_tmp = fetch_dr_in.read();
//
mem_sel = (dc_imm.read() == 1)? dr_tmp: pc.read();
//
mem_dout = mem_array[mem_sel];
}
//
void scpu_fetch::MEM_OUTPUT () {
fetch_mem_dout.write(mem_array[mem_addr.read()]);
}
//
void scpu_fetch::MEM_WRITE () {
sc_uint<1> mem_wr;
sc_uint<8> mem_din;
//
mem_wr  = dc_mem_wr.read();
mem_din = dc_rd.read();
//
if (mem_wr == 1) {
mem_array[mem_addr.read()] = mem_din;
}
}
//
//FETCH
//
void scpu_fetch::INSTRUCTION_REG () {
if (rst_n.read() == 0) {
fetch_ir.write(0x00);
}
else if (dc_load_ir.read() == 1) {
fetch_ir.write(mem_dout.read());
}
}
//
void scpu_fetch::DATA_REG_IN () {
if (dc_load_dr.read() == 1) {
fetch_dr_in = mem_dout.read();
}
}
void scpu_fetch::DATA_OUTPUT () {
fetch_dr.write(fetch_dr_in.read());
}
//
void scpu_fetch::PROGRAM_COUNTER () {
sc_uint<8> pc_dr_tmp;
//
pc_dr_tmp = fetch_dr_in.read();
//
if (rst_n.read() == 0) {
pc = 0x00;
}
else if (dc_load_pc.read() == 1) {
if (dc_imm.read() == 1) {
pc = pc_dr_tmp;
}
else {
pc = pc.read() + 0x1;
}
}
}
//
void scpu_fetch::MEMORY_ADDR () {
sc_uint<8> mem_addr_dr_tmp;
sc_uint<2> mem_addr_sel;
//
mem_addr_dr_tmp = fetch_dr_in.read();
mem_addr_sel    = dc_addr_sel.read();
//
switch (mem_addr_sel) {
case 0x1:
mem_addr = dc_rs.read();
    break;
case 0x2:
mem_addr = mem_addr_dr_tmp;
    break;
default:
mem_addr = pc.read();
    break;
}
}
Chú ý:
  • Để lấy giá trị từ một port input hoặc một tín hiệu thì sử dụng cú pháp sau:
port_input_name/signal_name.read()
  • Để gán giá trị cho port output thì sử dụng cú pháp sau:
port_output_name.write(assigned_value)
  • Để gán giá trị cho một tín hiệu hoặc biến nội thì sử dụng cú pháp sau:
signal_name/variable_name = assigned_value;
assigned_value có thể là một hằng số hoặc một biểu thức.

5) So sánh giữa model code và RTL code
5.1) Mô tả module
Hình 4 thể hiện sự tương đồng giữa mô tả module trong model code với SystemC và RTL code với Verilog HDL. Một số điểm khác biệt cần lưu ý là: 
  • Khi mô tả module trong SystemC cần gọi thư viện SystemC và phải khai báo constructor vì các từ khóa của SystemC là tên các class được xây dựng từ C++. 
  • Trong SystemC, tên module xuất hiện tại hai vị trí là khi khai báo module với từ khóa SC_MODULE và khi khai báo constructor với từ khóa SC_CTOR. Trong Verilog HDL, tên module chỉ xuất hiện khi khai báo module với từ khóa module
  • Khi mô tả module trong SystemC không cần khai báo danh sách port như trong Verilog HDL. Việc khai báo tín hiệu nội và biến nội của SystemC có sự khác biệt. Tín hiệu nội cần khai báo với từ khóa sc_signal còn biến nội chỉ cần khai báo với loại dữ liệu. Trong khi đó, Verilog HDL không phân biệt điều này, tất cả các thành phần sử dụng nội bộ chỉ cần khai báo với kiểu dữ liệu như wire, reg, …
Hình 4: So sánh việc mô tả module của SystemC và Verilog HDL
5.2) Mô tả mạch logic
Hình 5 minh họa sự tương đồng giữa model code và RTL code khi mô tả một mạch logic. Trong minh họa này, mạch logic là một mạch tuần tự có reset đồng bộ. Một số điểm khác biệt cần lưu ý như sau: 
  • Mỗi mô hình mạch logic trong SystemC cần một định danh gọi là tên process, ví dụ như INSTRUCTION_REG. Trong Verilog HDL, điều này là không cần thiết. 
  • Trong SystemC, để mô hình một mạch logic, chúng ta cần “khai báo process” dùng từ khóa void và "đăng ký process” với từ khóa SC_METHOD. Trong Verilog HDL, chỉ cần dùng một từ khóa để mô tả như always, assign, …
Hình 5: So sánh việc mô tả mạch logic của SystemC và Verilog HDL

Lịch sử cập nhật:
1. 2019.Apr.18 - Tạo lần đầu

Danh sách tác giả:
1. Lê Hoàng Vân
2. Trương Công Hoàng Việt
3. Nguyễn Hùng Quân

Thứ Tư, 17 tháng 4, 2019

[SystemC][High Level Design]Bài 2 - Phân tích model specification

Bài viết này hướng dẫn việc thực hiện model specification trên ví dụ SCPU. Để thực hiện được bước này các bạn cần phải hoàn chỉnh bước phân tích logic để tạo ra logic specification. Logic specification được hướng dẫn cụ thể trong các tài liệu đã liệt kê trong bài 1.

1) Model specification là gì?
Ở bước này, chúng ta cần xác định rõ các điểm sau: 
  1. Số lượng module: số lượng này bằng với số khối chức năng (sub-module) cộng với top module đã được phân tích trong logic specification. 
  2. Số lượng process và tên cụ thể của từng process trong sub-module: người thiết kế cần phân định rõ mỗi process sẽ bao gồm các mạch nguyên lý (mạch logic) nào. Tên process sẽ đặt bằng chữ in hoa thể hiện chức năng của mạch nguyên lý. Việc dùng chữ in hoa giúp dễ phân biệt giữa tên process với tên biến hoặc tên tín hiệu. 
  3. Số lượng port và tên cụ thể của các port ở các module: Thông tin này đã có đầy đủ ở logic specification. 
  4. Số lượng các tín hiệu (signal) và tên cụ thể của từng tín hiệu: số lượng các tín hiệu chính là tổng số ngõ ra của tất cả các process nếu là sub-module hoặc tổng số ngõ ra của các sub-module nếu là top module, không tính các ngõ ra nối đến port. Ngõ ra của một process là tín hiệu sẽ được kết nối đến một hoặc nhiều process khác trong cùng một module hoặc với module khác. Khi tín hiệu này nối đến module khác, nó được gọi là port.
Hình 1: Cấu trúc một sub-module trong phương pháp HLD
Hình 2: Cấu trúc một top module trong phương pháp HLD
2) Process
Process được sử dụng để mô hình hóa các mạch logic, mạch tổ hợp và mạch tuần tự, của phần cứng. Process cho phép mô tả các hành vi song song như hoạt động của phần cứng. Các process sẽ thực thi song song còn code bên trong một process được thực thi tuần tự.
3) Phân tích tổng quan SCPU
SPCU gồm bốn module, một top module và ba sub-module.
  1.  scpu_top là top module SPCU kết nối các sub-module
  2.  scpu_fetch là module chứa tất cả các process của khối FETCH
  3. scpu_decoder là module chứa tất cả các process của khối DECODER
  4. scpu_execute là module chứa tất cả các process của khối EXECUTE.

Hình 3: Cấu trúc tổng quan của SCPU
Hình 3 thể hiện rõ số lượng module với tên gọi cụ thể. Hình 4 xác định các port và tín hiệu của TOP module.
Hình 4: Các port và tín hiệu của TOP mdule
Việc phân tích tổng quan cho model SCPU đã hoàn tất. Bạn đọc có thể thấy việc phân tích model kế thừa toàn bộ logic specification và thêm một số thông tin khác để hỗ trợ việc viết model code về sau.
4) Phân tích chi tiết SCPU
4.1) Những lưu ý khi phân tích sub-module
Điểm khác của sub-module so với TOP module là sub-module chứa các mô hình mạch tổ hợp và mạch tuần tự của một thiết kế phần cứng, trong khi TOP module chỉ chứa các kết nối giữa các sub-module.
Các mạch tổ hợp và mạch tuần tự được mô hình hóa thành các process trong SystemC. Để đảm bảo model code tổng hợp được trên nhiều phần mềm khác nhau và dễ đọc, các bạn cần lưu ý một số điểm sau đây:
  1. Nếu một tín hiệu vừa là port ngõ ra của module vừa là tín hiệu nội sử dụng giữa các process trong module thì tạo một tín hiệu trung gian, khác với tên port ngõ ra, để sử dụng
  2. Không đặt chung mạch tổ hợp và mạch tuần tự trong cùng một process
  3. Tên process là chữ in hoa và tên các port, tên tín hiệu và tên module là chữ thường.
  4. Đặt tên process phải gợi nhớ được chức năng của mạch tổ hợp hoặc mạch tuần tự mà nó mô hình.
Điểm 1 cần tuân thủ vì một số phần mềm không hỗ trợ tổng hợp việc “đọc” giá trị từ một port ngõ ra. Vì vậy, chúng ta sẽ thao tác trên một tín hiệu trung gian và lấy tín hiệu này gán đến ngõ ra mong muốn. Bạn đọc sẽ hình dung rõ hơn trong các phân tích tiếp theo và phần hướng dẫn viết model code.
Điểm 2 cần phải tuân thủ khi mạch tổ hợp và mạch tuần tự là hai mạch độc lập. Trong model code, mạch tổ hợp chỉ phụ thuộc vào trạng thái các ngõ vào trong khi mạch tuần tự hoạt động theo các sự kiện của xung clock và reset. Ràng buộc này tương tự với việc bạn mô tả RTL code.
Điểm 3 và điểm 4 giúp model dễ đọc hơn.
4.2) Module FETCH
4.2.1) Các process mô hình hóa memory
Các mạch liên quan đến memory gồm 3 process:
  1. IR_DR_INPUT: Lựa chọn giá trị từ memory để đưa đến thanh ghi IR và DR. Ngõ ra của process này là mem_dout[7:0] và đây là một signal.
  2. MEM_OUTPUT: Lựa chọn giá trị từ memory để đưa đến khối DECODER. Ngõ ra của process này là fetch_mem_dout[7:0] và đây là một port của khối FETCH.
  3. MEM_WRITE: Giải mã địa chỉ để ghi vào memory. Ngõ ra của process là 256 thanh ghi của mảng bộ nhớ mem_array và đây là các signal nối đến process IR_DR_INPUT và MEM_OUTPUT.
Hình 5: Process mô hình hóa IR_DR_INPUT

Hình 6: Process mô hình hóa MEM_OUTPUT
Hình 7: Process mô hình hóa MEM_ARRAY


4.2.2) Process mô hình hóa thanh ghi IR
Hình 8: Process mô hình hóa thanh ghi IR

4.2.3) Process mô hình hóa thanh ghi DR
Thanh ghi DR được mô hình hóa bởi 2 process là DATA_REG_IN và DATA_OUTPUT.
  1. DATA_REG_IN là process mô hình hóa thanh ghi DR với ngõ ra là fetch_dr_in[7:0]. Ở đây fetch_dr_in[7:0] là một tín hiệu nội sẽ được kết nối đến các process khác trong khối FETCH. Đồng thời giá trị của ngõ cũng được đưa đến ngõ ra fetch_dr[7:0] để nối đến khối DECODER
  2. DATA_OUTPUT chỉ là process gán giá trị thanh ghi DR đến output fetch_dr[7:0].
Như đã trình bày ở phần trên, giá trị thanh ghi DR vừa được sử dụng ở các process khác bên trong khối FETCH, vừa là ngõ ra đến module khác nên tín hiệu trung gian fetch_dr_in[7:0] được tạo ra. Điều này khác với logic specification ban đầu.

Hình 9: Process mô hình hóa thanh ghi DR
4.2.4) Process mô hình hóa thanh ghi PC
Process mô hình hóa thanh ghi PC sử dụng tín hiệu fetch_dr_in làm ngõ vào chứ khoogn dùng trực tiếp fetch_dr[7:0] như logic specification.
Hình 10: Process mô hình hóa thanh ghi PC
4.2.5) Process mô hình hóa mạch chọn địa chỉ truy xuất memory
Hình 11: Process mô hình hóa mạch chọn địa chỉ truy xuất memory
4.3) Module DECODER
4.3.1) Process mô hình hóa logic lựa chọn giá trị thanh ghi Rd và Rs
Ở đây process sử dụng tín hiệu nội dc_rs_tmp[7:0] giữ giá trị của tín hiệu dc_rs[1:0], chứ không đưa trực tiếp giá trị vào tín hiệu dc_rs[7:0] như logic specification.
Hình 12: Process mô hình hóa thanh ghi Rs
Đối với logic lựa chọn thanh ghi Rd, cách mô tả cũng tương tự như logic lựa chọn thanh ghi Rs.
Hình 13: Process mô hình hóa thanh ghi Rd
4.3.2) Mạch giải mã lệnh và điều khiển các thanh ghi R0/R1/R2/R3
Đối với mạch này ta chia ra làm 3 process:
  • SEL_REG_LOAD tạo tín hiệu điều khiển việc ghi giá trị vào R0/R1/R2/R3
  • REG_IN_SEL lựa chọn dữ liệu lưu vào thanh ghi R0/R1/R2/R3
  • LOAD_DATA_TO_REG mô hình các thanh ghi R0/R1/R2/R3
Hình 14: Process mô hình hóa logic tạo tín hiệu cho phép ghi
Hình 15: Process mô hình hóa logic chọn giá trị ghi
Hình 16: Process mô hình hóa các thanh ghi R0/R1/R2/R3
4.3.3) Control Counter
Hình 17: Hai process mô hình hóa control counter
4.3.4) Process mô hình hóa logic giải mã lệnh nhảy
Hình 18: Process mô hình hóa logic giải mã lệnh nhảy
4.3.5) Process mô hình hóa các ngõ ra DECODER






Hình 19: Các process mô hình hóa các ngõ ra khối DECODER
4.4) Module EXECUTE
Khối EXECUTE chỉ có một proccess là ALU mô hình hóa các tính toán số học.
Hình 20: Process mô hình hóa ALU của khối EXECUTE
Kết thúc bài này chúng ta đã phân tích xong model specification của SCPU. Một số điểm cần lưu ý:
  • Model specification được tạo ra từ logic specification nên các bạn cần đọc cách tạo logic specification của SCPU trước khi đọc bài này. Trong model specification, chúng ta chỉ phân chia các logic thành các process để phục vụ việc viết model code sau này.
  • Model specification giúp xác định rõ các port của module và tín hiệu giao tiếp giữa các process
Lịch sử cập nhật:
1. 2019.Apr.17 - Tạo lần đầu
2. 2019.Apr.20 - Sửa input của từ *[8] thành *[7] hình 18 - Process mô hình hóa logic giải mã lệnh nhảy  

1. Trương Công Hoàng Việt
2. Lê Hoàng Vân
3. Nguyễn Hùng Quân

Thứ Hai, 15 tháng 4, 2019

[SystemC][High Level Design]Bài 1 - HLD là gì?

Bài viết này là bài mở đầu cho chuỗi bài viết hướng dẫn thiết kế và mô phỏng cơ bản mức cao sử dụng thư viện SystemC. Bài viết dành cho các bạn mới bắt đầu tìm hiểu về High Level Design (HLD) hoặc SystemC nhưng chưa biết bắt đầu từ đâu. Chuỗi bài viết sẽ tập trung vào việc giúp bạn nhanh chóng sử dụng ngay SystemC cho thiết kế của mình. 

1) High Level Design là gì?
HLD hay System Level Design là phương pháp thiết kế phần cứng sử dụng ngôn ngữ cấp cao như C/C++, SystemC thay vì sử dụng ngôn ngữ mô tả phần cứng Verilog hoặc VHDL. Phần cứng ở đây là vi mạch (chip).
2) Tại sao cần phải có HLD?
Một hệ thống, gồm phần cứng và phần mềm, được phát triển theo phương pháp truyền thống sẽ tốn nhiều thời gian. Bên cạnh đó, hiệu năng hệ thống chỉ có thể được đánh giá khi phần cứng phải được phát triển hoàn chỉnh. Tối thiểu, RTL code của phần cứng phải hoàn chỉnh. Sau đó, phần mềm được tích hợp, hoạt động thử nghiệm để đánh giá hiệu năng. Nếu kết quả không đạt được như mong muốn, phần cứng hoặc phần mềm phải được thiết kế lại. Nếu phần cứng phải thiết kế lại, thời gian để thực hiện có thể kéo dài đáng kể.
Hình 1(a) minh họa quy trình phát triển một hệ thống theo phương pháp truyền thống. Phần mềm phát triển trên nền tảng C/C++. Phần cứng phát triển trên nền tảng Verilog/VHDL. Hai nhánh phát triển thực hiện song song và độc lập nên hệ thống chỉ có thể được tích hợp và đánh giá chính xác ở giai đoạn cuối của quy trình khi phần cứng đã hoàn thiện. Nếu phần cứng không thể đáp ứng được yêu cầu đặt ra của hệ thống thì việc thiết kế lại rất mất thời gian.
Hình 1: (a) Phát triển hệ thống theo phương pháp truyền thống (b) phát triển hệ thống theo phương pháp HLD
Với HLD, phần cứng được mô hình hóa bằng chính ngôn ngữ dùng để phát triển phần mềm như C/C++ hay SystemC. Điều này giúp phần mềm và phần cứng có thể được tích hợp sớm để thử nghiệm và đánh giá hiệu quả và tính khả thi. Sau khi hệ thống được tối ưu như yêu cầu, phần cứng mới bắt đầu được thiết kế trùng khớp với mô hình đã phát triển. Với cách này, phần cứng được đảm bảo tương thích phần mềm và đáp ứng tốt cho hệ thống mong muốn. Bên cạnh đó, phần mềm có thể tiếp tục được thử nghiệm và hoàn chỉnh song song với việc phát triển phần cứng. 
Hình 1(b) minh họa quy trình phát triển hệ thống theo phương pháp HLD. Với mô hình này, phần cứng được mô hình hóa bằng C/C++ hoặc SystemC. Sau đó, nó được tích hợp với phần mềm để tạo thành một hệ thống hoàn chỉnh ở bước Pre-integration. Mô hình hệ thống này được sử dụng để đánh giá tính khả thi, chức năng, hiệu năng và các yêu cầu khác mà hệ thống phải đạt được. Nếu mọi yêu cầu đều đáp ứng ở bước này, phần cứng mới được thiết kế. Điều này đảm bảo khả năng thành công rất cao của hệ thống thực tế. Bạn có thể thắc mắc rằng, nếu sử dụng phương pháp HLD để thiết kế phần cứng thì thời gian thiết kế sẽ tăng đáng kể do phải làm hai bước là “thiết kế mô hình sử dụng C/C++ hoặc SystemC” và “thiết kế mức thấp sử dụng Verilog/VHDL”. Trên thực tế, thời gian thiết kế phần cứng sẽ tăng nhưng không đáng kể nếu mô hình phần cứng sử dụng ngôn ngữ cấp cao như C/C++ hoặc SystemC có thể tổng hợp được. “Tổng hợp được” nghĩa là có thể dùng phần mềm để chuyển đổi từ HLD code thành RTL code. Sau đó, chúng ta chỉ cần dùng phần mềm chuyên dụng để kiểm tra “tính tương đồng” giữa HLD code và RTL code.
3) Ngôn ngữ SystemC là gì?
SystemC là một thư viện được xây dựng trên nền tảng ngôn ngữ C++ hỗ trợ việc mô hình hóa hệ thống và phần cứng. Thư viện này chứa các class được xây dựng bằng C++. Việc mô hình hệ thống hay phần cứng là gọi và sử dụng các class C++ đã được xây dựng sẵn trong thư viện SystemC.
SystemC được chuẩn hóa trong tài liệu chuẩn IEEE Std 1666™-2011.
SystemC hỗ trợ rất nhiều các class khác nhau nhưng chỉ một phần trong số này là có thể tổng hợp được. Các phần mềm tổng hợp SystemC cũng có các giới hạn, quy định khác nhau về khả năng tổng hợp. Chính vì vậy, người viết model code theo định hướng tổng hợp cần cố gắng sử dụng các mô tả đơn giản để có thể phù hợp với càng nhiều phần mềm khác nhau. Điều này giúp cho model code tạo ra có tầm sử dụng rộng. Tuy nhiên, nó phụ thuộc vào kinh nghiệm và hiểu biết của người thiết kế nên việc viết model code tương thích với phần mềm mà bạn sẽ sử dụng là ưu tiên hàng đầu.
4) Quy trình thiết kế theo phương pháp HLD 
Quy trình HLD gồm các bước cơ bản như sau, Hình 2:
1. Input requirement: Tổng hợp và phân tích các yêu cầu của hệ thống và phần cứng. Kết quả của bước này là một tài liệu mô tả chi tiết các phân tích. 
2. Logic specification: Phân tích thiết kế từ tổng quan đến chi tiết tới mức cổng logic. Kết quả của bước này là một tài liệu phân tích chi tiết thiết kế. 
3. Model specification: Phân tích chi tiết mô hình phần cứng dựa trên logic specification đã có. Ở bước này, người thiết kế cần xác định rõ các process và các tín hiệu kết nối giữa các method. Kết quả của bước này là một tài liệu phân tích chi tiết mô hình phần cứng.
4. Model coding: Mô hình phần cứng dựa trên model specification. Trong tài liệu này, SystemC được sử dụng để mô hình phần cứng. Việc mô hình phần cứng phải tạo ra model code có thể tổng hợp được. Kết quả của bước này là các file model code.
5. Model verification: Kiểm tra các file model code. Model code được kiểm tra về cú pháp, luật thiết kế, khả năng tổng hợp và chức năng. Kết quả của bước này là các file model code đã được kiểm tra hoàn chỉnh.
6. Model synthesis: Tổng hợp model code. Model code được chuyển đổi thành RTL code (Verilog/VHDL). Kết quả của bước này là các file RTL code.
7. Consistency check: Kiểm tra tính tương đồng giữa RTL code với model code. Kết quả của bước này là các file RTL code đã được đảm bảo chức năng tương đương như model code.
Hình 2: Quy trình HLD
Các bước thực hiện sẽ được nhóm tác giả trình bày chi tiết với một ví dụ cụ thể trong những bài sau. Để có thể nhanh chóng hiểu và thử nghiệm các bạn hãy chuẩn bị những nội dung sau đây:
1. Ví dụ dùng để minh họa cho loạt bài này là thiết kế SCPU. Các bạn hãy đọc loạt bài sau để hiểu thiết kế này.
2. Tải và cài đặt phần mềm Xilinx Design Tool - Vivado HL Design để kiểm tra cú pháp và tổng hợp. Tuy đây là phần mềm biên dịch dành cho FPGA nhưng là một công cụ dễ tìm và cài đặt trên Windows.
3. Bên cạnh đó, SystemC là một thư viện của C++ nên bạn có thể tải và dùng gcc để kiểm tra cú pháp. Tuy nhiên, gcc không kiểm tra được khả năng tổng hợp của model code.

Lịch sử cập nhật:
1. 2019.Apr.15 - Tạo lần đầu

Danh sách tác giả:
1. Trương Công Hoàng Việt
2. Lê Hoàng Vân
3. Nguyễn Hùng Quân

Chủ Nhật, 24 tháng 3, 2019

[Perl][Thiết kế lõi IP bằng script] Bài 7 - Script tạo RTL code của sub-module và top module

Tiếp theo bài 6, bài viết này tập trung mô tả giải thuật tạo RTL code cho các sub-module và top module. Đồng thời giải thích chi tiết cách thực hiện code Perl để cho các script tạo RTL code. Trước khi đọc bài viết này, các bạn cần đọc các phần trước để nắm chi tiết ví dụ được trình bày trong bài viết này.

1) Nguyên tắc cơ bản để tạo RTL code
Nguyên tắc cơ bản của việc tạo RTL code từ script là xác định phần code nào là cố định, phần code nào bị thay đổi theo cấu hình của người sử dụng.
  • Phần code cố định là phần code sẽ không bị thay đổi theo cấu hình người dùng. Dù thông số cấu hình thay đổi thì phần code này luôn giữ nguyên và luôn đúng.
  • Phần code thay đổi là phần biến thiên theo giá trị cấu hình. Để script Perl có thể tạo được phần code này thì một yếu tố quan trọng là bạn phải tạo được một mẫu RTL chung có thể dễ dàng được biến đổi bởi script theo thông số cấu hình.
Nhiều cách làm có thể được áp dụng tạo ra RTL code. Bài viết này giới thiệu một cách làm mà theo tác giả là dễ thực hiện và giúp dễ kiểm soát RTL code tạo ra theo từng phần.
  1. Bước 1: Tạo một file thư viện RTL mẫu. Chú ý, đây là file có nội dung cố định, không thay đổi. File này chứa 2 phần
    1. Phần RTL code cố định
    2. Phần "từ khóa" (keyword) tượng trưng cho "phần RTL code thay đổi". "Từ khóa" được đặt tên bất kỳ nhưng phải riêng biệt, không trùng lặp với bất kỳ các từ nào khác có trong fiel thư viện RTL mẫu.
  2. Bước 2: Tạo script để thực hiện các công việc sau
    1. Đọc file thư viện RTL mẫu
    2. Phát hiện các "từ khóa" đã ghi trong file thư viện
    3. Tạo RTL code tương ứng dựa trên các thông số cấu hình và thay thế đoạn RTL code đã tạo vào vị trí từ khóa có trong file thư viện
  3. Bước 3: In tất cả các dòng code cố định trong file thư viện và các dòng code tạo ở bước 2 vào một file RTL code hoàn chỉnh.
Hình 1: Các bước tạo một file RTL code từ script
2) Script tạo RTL code khối server (sub-module)
Áp dụng cách làm đã trình bày, chúng ta thực hiện script để tạo RTL code cho khối server.
Hình 2: Sơ đồ input/output của script tạo RTL code khối server
Khối server cần 2 file thư viện:
  • sfifo_lib.v là thư viện của FIFO đồng bộ lấy từ bài viết FIFO đồng bộ. Trong file thư viện này, chúng ta sẽ khai báo tất cả cac `define và parameter cần thiết để cấu hình SFIFO như đã phân tích ở bài 4.
`ifndef EMPTY_SIGNAL
  `define EMPTY_SIGNAL
`endif
`ifndef FULL_SIGNAL
  `define FULL_SIGNAL
`endif
parameter DATA_WIDTH    = 8;
parameter POINTER_WIDTH = 3;
  • server_lib.s là thư viện RTL code của server. RTL code của thư viện này như sau:
//===================================================================================
// File name: server.v
// Project:   RTL code generator
// Module:    server block
// Function: Receive the request of source and store them to FIFO
// Author:   nguyenquan.icd@gmail.com
// Website:   http://nguyenquanicd.blogspot.com
// License:   It's free but do not remove header when you use this RTL code for your project
//===================================================================================
module server (clk, rst_n, src_req, src_data, src_addr, sv_ack, sv_sel, sv_data, abt_ack);
  //
  //Start - generated by TOOL
  //$parameter_gen  
//End - generated by TOOL
  //
  parameter SEL_WIDTH  = 2**ADDR_WIDTH; //Number of destinations
  //Ports
  //
  input clk;           //Synchronous clock
  input rst_n;         //Asynchronous reset - active low
  input src_req;       //Source request - active high
  input [7:0] src_data;//Source data
  input [ADDR_WIDTH-1:0] src_addr; //Destination address
  input [SEL_WIDTH-1:0] abt_ack; //Acknowledgment from arbiter to server
  output wire sv_ack; //Acknowledgment from server to source
  output wire [SEL_WIDTH-1:0] sv_sel; //Select arbiter of destination
  output wire [7:0] sv_data; //Data from server to arbiter
  //
  //Internal signals
  //
  wire src_we;
  wire abt_re;
  wire sfifo_empty;
  wire sfifo_full;
  wire sfifo_not_full;
  wire sfifo_not_empty;
  wire [ADDR_WIDTH-1:0] sfifo_addr;
  //
  //Logic
  //
  //Data FIFO
sfifo #(.DATA_WIDTH(8), .POINTER_WIDTH(POINTER_WIDTH)) d_sfifo (
  .clk(clk),
  .rst_n(rst_n),
  .wr(src_we),
  .rd(abt_re),
  .data_in(src_data[7:0]),
  .sfifo_empty(sfifo_empty),
  .sfifo_full(sfifo_full),
  .data_out(sv_data[7:0])
  );
  //Address FIFO
sfifo #(.DATA_WIDTH(ADDR_WIDTH), .POINTER_WIDTH(POINTER_WIDTH)) a_sfifo (
  .clk(clk),
  .rst_n(rst_n),
  .wr(src_we),
  .rd(abt_re),
  .data_in(src_addr[ADDR_WIDTH-1:0]),
  .sfifo_empty(), //Float - no-used
  .sfifo_full(),  //Float - no-used
  .data_out(sfifo_addr[ADDR_WIDTH-1:0])
  );
  //SFIFO control
  //Write to FIFO
  assign src_we = src_req & sv_ack;
  //
  assign sfifo_not_full  = ~sfifo_full;
  assign sv_ack          = sfifo_not_full;
  //
  assign sfifo_not_empty = ~sfifo_empty;
  assign abt_re          = sfifo_not_empty & |abt_ack[SEL_WIDTH-1:0];
  //Address decoder
  //Start - this RTL code is generated by script
  //$address_decoder 
//End - this RTL code is generated by script

endmodule //server
Trong đoạn code trên, các bạn sẽ thấy đây là một mô tả RTL code cho module server với gần như đầy đủ các thành phần cần thiết theo thứ tự như đã phân tích ở phần 1 của bài 4. Ngoại trừ hai vị trí tô màu vàng:
  • $parameter_gen là một từ khóa mà script Perl sẽ phát hiện và thay thế bằng các dòng khai báo giá trị parameter cho khối server lấy từ file router_spec.xls
  • $address_decoder là một từ khóa mà script Perl sẽ phát hiện và thay thế bằng mạch giải mã địa chỉ. Mạch này tạo ra tín hiệu sv_sel có số lượng bit thay đổi.
Giải thuật của script gen_server_rtl.pltạo ra RTL code khối server như sau:
Hình 3: Giải thuật script tạo RTL code khối server
Nội dung script gen_server_rtl.pl tạo ra RTL của khối server như sau:
#!/usr/bin/perl
###############################################################################
# Project:   RTL code generator
# Function:   Gen SERVER module
# Author:   nguyenquan.icd@gmail.com
# Website:   http://nguyenquanicd.blogspot.com
###############################################################################
use warnings;
use strict;
use get_config; # execute ./get_config.pm $ARGV[0]
#
#Get the value from package get_config
#
my $addr_width = $get_config::addr_width;
my $ptr_width = $get_config::ptr_width;
my $dest_num = $get_config::dest_num;
#
#Generate RTL code
#
my $lib_dir   = "./verilog_lib";
my $input_lib = "$lib_dir/server_lib.v";
my $rtl_line;
#
print "--- Reading $input_lib to create the RTL\n";
#
open (LIBFILE, "$input_lib") || die ("Cannot open $input_lib because $!");
foreach my $line (<LIBFILE>) {
  chop($line); #Remove the end character of a line - it is the new line
  $line =~ s/\r//g; #Remove symbol "^M" of a MS-DOS text file
  #Generate parameters
  if ($line =~ /\$parameter_gen/) {
    $rtl_line .= "  parameter ADDR_WIDTH = $addr_width;\n";
    $rtl_line .= "  parameter POINTER_WIDTH = $ptr_width;\n";
  }
  #Generate ADDRESS DECODER
  elsif ($line =~ /\$address_decoder/) {
    for (my $dest_id = 0; $dest_id < $dest_num; $dest_id++) {
      $rtl_line .= "  assign sv_sel[$dest_id] = sfifo_not_empty & (sfifo_addr[ADDR_WIDTH-1:0] == $dest_id);\n";
    }
  }
  else {
    $rtl_line .= "$line\n";
  }
}
close LIBFILE;
#
#Put/Insert/Copy the RTL code of FIFO to RTL code of SERVER
#
$input_lib = "$lib_dir/sfifo_lib.v";
print "--- Reading $input_lib to create the RTL\n";
open (LIBFILE, "$input_lib") || die ("Cannot open $input_lib because $!");
foreach my $line (<LIBFILE>) {
  chop($line);
  $line =~ s/\r//g; #Remove symbol "^M" of a MS-DOS text file
  $rtl_line .= "$line\n";
}
close LIBFILE;
#
#Create SERVER module
#
my $out_dir   = "./verilog_rtl";
my $output_file = "$out_dir/server.v";
print "--- Creating $output_file\n";
#
open (OUTFILE, ">$output_file") || die ("Cannot open $output_file because $!");
print OUTFILE $rtl_line;
close OUTFILE;
print "--- COMPLETED creating $output_file\n";
#
__END__
Giải thích cơ bản về đoạn code Perl trên đây:
  • Lấy thông tin cấu hình từ file excel
use get_config; # execute ./get_config.pm $ARGV[0]
#
#Get the value from package get_config
#
my $addr_width = $get_config::addr_width;
my $ptr_width = $get_config::ptr_width;
my $dest_num = $get_config::dest_num;
  • Kiểm tra từ khóa $parameter_gen và tạo các khai báo parrameter
if ($line =~ /\$parameter_gen/) {
    $rtl_line .= "  parameter ADDR_WIDTH = $addr_width;\n";
    $rtl_line .= "  parameter POINTER_WIDTH = $ptr_width;\n";
  }
  • Kiểm tra từ khóa $address_decoder và tạo RTL code mạch giải mã địa chỉ
#Generate ADDRESS DECODER
  elsif ($line =~ /\$address_decoder/) {
    for (my $dest_id = 0; $dest_id < $dest_num; $dest_id++) {
      $rtl_line .= "  assign sv_sel[$dest_id] = sfifo_not_empty & (sfifo_addr[ADDR_WIDTH-1:0] == $dest_id);\n";
    }
  }
  • Copy file thư viện sfifo_lib.v 
$input_lib = "$lib_dir/sfifo_lib.v";
print "--- Reading $input_lib to create the RTL\n";
open (LIBFILE, "$input_lib") || die ("Cannot open $input_lib because $!");
foreach my $line (<LIBFILE>) {
  chop($line);
  $line =~ s/\r//g; #Remove symbol "^M" of a MS-DOS text file
  $rtl_line .= "$line\n";
}
close LIBFILE;
  • In RTL code vào file server.v
my $out_dir   = "./verilog_rtl";
my $output_file = "$out_dir/server.v";
print "--- Creating $output_file\n";
#
open (OUTFILE, ">$output_file") || die ("Cannot open $output_file because $!");
print OUTFILE $rtl_line;
close OUTFILE;
3) Script tạo RTL code khối arbiter (sub-module)
Vì cách thực hiện tương tự khối server nê tác giả chỉ trình bày giải thuật của script tạo RTL code khối arbiter, tên gen_arbiter_rtl.pl. Chi tiết code Perl các bạn có thể download về tham khảo.
Hình 4: Giải thuật của script tạo RTL code khối arbiter
4) Script tạo RTL code khối router (top module)
Khối router là top module kết nối các server arbiter theo số lượng đã cấu hình. Script tạo RTL code khối này (gen_router_rtl.pl) chủ yếu xử lý việc tạo ra các kết nối (connection) đúng và gọi các instance.
Hình 5: Giải thuật của script tạo RTL code module router (top module)
5) Script tổng của tool router_generator
Script tổng tên gen_all_rtl.pl đơn giản chỉ gọi và chạy 3 script gen_server_rtl.plgen_arbiter_rtl.pl gen_router_rtl.pl.
system "./gen_server_rtl.pl $ARGV[0]";
system "./gen_arbiter_rtl.pl $ARGV[0]";
system "./gen_router_rtl.pl $ARGV[0]";
Lệnh chạy của tool router_generator là:
gen_all_rtl.pl <tên file cấu hình>
Ví dụ:
gen_all_rtl.pl router_spec.xls
Kết thúc bài viết này, các bạn đã có một tool tạo RTL code tự động cho ví dụ minh họa. Các bạn có thể download toàn bộ source code ở link phía cuối bài viết. Chú ý, code được tạo ra chỉ mới được tác giả kiểm tra cú pháp (syntax) chứ chưa chạy mô phỏng kiểm tra chức năng.

Link tải toàn bộ source code (Chỉ kiểm tra syntax, chưa mô phỏng RTL code):
1. Source code: router_generator
2. Pass (nếu có): nguyenquanicd