0
0
Lập trình
Flame Kris
Flame Krisbacodekiller

Tối Ưu HLS cho FPGA: Cẩm Nang Thực Tế và Chi Tiết

Đăng vào 4 tháng trước

• 6 phút đọc

Chủ đề:

KungFuTech

Giới thiệu

Tối ưu hóa High-Level Synthesis (HLS) cho FPGA là một quá trình chuyển đổi mã C/C++ thành RTL, đáp ứng các mục tiêu về thông lượng, độ trễ, diện tích và tiêu thụ điện năng mà không làm mất đi tính chính xác. Bài viết này sẽ cung cấp cho bạn một checklist ngắn gọn và đã được thử nghiệm từ thực tế mà bạn có thể áp dụng trong Vitis HLS (Xilinx), Intel HLS, Catapult, v.v. Phần ví dụ sẽ sử dụng pragma theo phong cách Vitis HLS, kèm theo ghi chú về khả năng tương thích.

1) Hiểu Rõ Cấu Trúc Tối Ưu

Cấp độ Thuật Toán

  • Lựa chọn các biểu diễn toán học/dữ liệu để giảm thiểu khối lượng công việc.

Cấp độ Vòng Lặp và Nhiệm Vụ

  • Khai thác song song (pipeline, unroll, dataflow).

Bộ Nhớ và I/O

  • Đáp ứng nhu cầu (partition, reshape, burst, stream).

Kiến Trúc Vi Mạch

  • Liên kết các toán tử/bộ nhớ, cân bằng độ trễ, chia sẻ tài nguyên.

Kiểm Tra

  • Xác minh (C/COSIM), phân tích (util/timing/II/latency), lặp lại.

2) Số Học và Cấu Trúc Mã

Sử Dụng Kiểu Dữ Liệu Chính Xác

  • Ưu tiên sử dụng ap_(u)int / ap_fixed (hoặc các loại tương đương của nhà cung cấp) hơn float/double khi ngân sách lỗi cho phép.
  • Cắt giảm chiều rộng một cách mạnh mẽ để giảm LUTs, FFs và DSP.
cpp Copy
#include "ap_int.h"
#include "ap_fixed.h"
using pix_t = ap_uint<10>; // ví dụ: pixel 10-bit
using coeff_t = ap_fixed<16,2>; // 2 bit nguyên, 14 bit phân số

Làm Rõ Các Phụ Thuộc

  • Giữ cho các vòng lặp nóng đơn giản; đẩy các điều kiện ra ngoài vòng lặp khi có thể.
  • Thay thế các cây if/else phức tạp trên đường dẫn quan trọng bằng bảng hoặc hằng số đã được tính toán trước khi hợp lý.
  • Sử dụng const, restrict (khi an toàn), và pass-by-reference để giúp trình biên dịch suy ra không có aliasing và kích hoạt bursting.

3) Tối Ưu Cấp Độ Vòng Lặp

Pipeline Trước

  • Mục tiêu: II=1 trên vòng lặp quan trọng bất cứ khi nào có thể.
cpp Copy
#pragma HLS PIPELINE II=1
for (int i = 0; i < N; i++) {  
// thân với không có phụ thuộc vòng lặp thực sự  
}

Mẹo

  • Nếu HLS không đạt được II=1, hãy kiểm tra lý do "stall" trong nhật ký tổng hợp:
    • Xung đột cổng bộ nhớ → chia nhỏ/hình dạng lại các mảng hoặc mở rộng băng thông dữ liệu.
    • Phụ thuộc vòng lặp (RAW/WAR/WAW) → cấu trúc lại bộ đệm hoặc chứng minh tính độc lập:
cpp Copy
#pragma HLS DEPENDENCE variable=buf inter false

Unroll để Thương Mại Diện Tích cho Thông Lượng

  • Unroll từng phần để phù hợp với các ngân hàng/ cổng bộ nhớ có sẵn; unroll hoàn toàn chỉ khi bạn có thể cấp phát cho nó.
cpp Copy
#pragma HLS UNROLL factor=4
for (int k=0; k<K; k++) { ... }

Lát / Khối để Tăng Tính Địa Phương

  • Chia nhỏ các vòng lặp lớn thành các lát phù hợp với BRAM/URAM; kết hợp với các bộ đệm trên chip để giảm lưu lượng DDR.
cpp Copy
for (int ii=0; ii<N; ii+=Ti)  
for (int jj=0; jj<M; jj+=Tj)  
compute_tile(ii, jj);

Giúp Bộ Dự Đoán

  • Số lượng vòng lặp cải thiện các báo cáo độ trễ và lập lịch:
cpp Copy
#pragma HLS LOOP_TRIPCOUNT min=64 max=128

4) Đối Tượng Tính Toán Đa Nhiệm (DATAFLOW)

  • Sử dụng dataflow để chạy các giai đoạn sản xuất/tiêu thụ song song. Kết nối các giai đoạn với hls::stream (hoặc các kênh Intel).
cpp Copy
#include "hls_stream.h"
void stageA(hls::stream& out);  
void stageB(hls::stream& in, hls::stream& out);  
void stageC(hls::stream& in);
void top(hls::stream& in, hls::stream& out) {
#pragma HLS DATAFLOW
static hls::stream s1("s1"), s2("s2");
#pragma HLS STREAM variable=s1 depth=64
#pragma HLS STREAM variable=s2 depth=64
stageA(s1);  
stageB(s1, s2);  
stageC(s2);
}

Mẹo

  • Chọn độ sâu FIFO để hấp thụ tính đột biến và đáp ứng các khoảng khởi động giữa các giai đoạn.
  • Tránh đọc/ghi cùng một mảng từ nhiều nhiệm vụ trừ khi bạn phân ngân hàng/chia nhỏ một cách chính xác.

5) Tuning Bộ Nhớ & Giao Diện

Chia Nhỏ / Hình Dạng Lại Các Mảng để Thêm Cổng

  • PARTITION tạo ra các ngân hàng song song thực sự (tốt cho truy cập ngẫu nhiên).
  • RESHAPE đóng gói nhiều phần tử mỗi từ (tuyệt vời cho truy cập tuần tự và bề rộng bùng nổ).
cpp Copy
#pragma HLS ARRAY_PARTITION variable=buf cyclic factor=4 dim=1
#pragma HLS ARRAY_RESHAPE variable=line factor=16 dim=1

Bùng Nổ DDR và Căn Chỉnh Bề Rộng

  • Sử dụng m_axi (Vitis) và các kiểu rộng (ap_uint<256/512>) để phù hợp với bề rộng DDR hoặc NoC; đảm bảo các mẫu truy cập liên tục.
  • Thêm offset=slave & tên bundle= hợp lý cho nhiều cổng.
cpp Copy
void kernel(ap_uint<512>* in, ap_uint<512>* out, int N) {
#pragma HLS INTERFACE m_axi port=in offset=slave bundle=gmem0 depth=1024  
#pragma HLS INTERFACE m_axi port=out offset=slave bundle=gmem1 depth=1024  
#pragma HLS INTERFACE s_axilite port=N bundle=control  
#pragma HLS INTERFACE s_axilite port=return bundle=control  
// ...  
}

Stream cho Thông Lượng Cao và Độ Trễ Thấp

  • Sử dụng AXI4-Stream ở đầu và hls::stream bên trong cho các pipeline theo dòng tỷ lệ (video, radio, ML).
cpp Copy
#pragma HLS INTERFACE axis port=in_axis
#pragma HLS INTERFACE axis port=out_axis

6) Liên Kết Tài Nguyên & Kiến Trúc Vi

Liên Kết Các Toán Tử và Bộ Nhớ

  • Liên kết các phép nhân với DSPs (thông lượng) hoặc LUTs (tiết kiệm DSPs).
  • Chọn BRAM so với URAM cho các bộ đệm lớn; một cổng/ hai cổng phù hợp.
cpp Copy
#pragma HLS RESOURCE variable=mul_op core=DSP48
#pragma HLS BIND_STORAGE variable=tile type=ram_2p impl=bram

Kiểm Soát Chia Sẻ So với Nhân Bản

  • Sử dụng UNROLL để nhân bản tính toán, hoặc ALLOCATION/RESOURCE pragmas để giới hạn số lượng các toán tử cho diện tích.
cpp Copy
#pragma HLS ALLOCATION operation instances=mul limit=2

Cân Bằng Độ Trễ

  • Đối với các chuỗi cộng dài hoặc chuỗi MAC, HLS thường sẽ chèn các thanh ghi; bạn có thể ràng buộc:
cpp Copy
#pragma HLS LATENCY min=1 max=6

7) Thông Lượng So Với Độ Trễ So Với Fmax

  • II (Initiation Interval) kiểm soát thông lượng (mẫu/cycle).
  • Độ trễ là tổng số chu kỳ từ đầu vào đến đầu ra.
  • Fmax đến từ thời gian sau tổng hợp; rút ngắn các đường dẫn quan trọng (giảm fan-out, cân bằng cây, sử dụng DSPs).
  • Lưu ý về đồng hồ: Đặt thời gian mục tiêu trong các ràng buộc công cụ (ví dụ: Vitis HLS create_clock -period 5) thay vì trong mã; điều chỉnh cho đến khi thời gian sạch với biên độ.

8) Xác Minh & Báo Cáo

  • C-sim: Chứng minh tính chính xác của thuật toán nhanh chóng.
  • C/RTL Co-sim: Xác thực rằng RTL khớp với C dưới I/O thực tế.
  • Báo cáo: Kiểm tra
    • II và độ trễ đạt được,
    • Lý do đình chỉ (phụ thuộc/cổng),
    • Bản đồ tài nguyên (LUT/FF/DSP/BRAM/URAM),
    • Hiệu quả bùng nổ giao diện.
  • Kiểm tra bit-chính xác cho số cố định: đo SNR/PSNR hoặc ngân sách lỗi so với số thực vàng.

9) Ví Dụ: FIR Streaming với Một Mẫu mỗi Chu Kỳ

  • Phiên bản này duy trì II=1 bằng cách unroll phép nhân MAC và hoàn toàn phân vùng các hệ số và thanh ghi dịch. Nó sử dụng số cố định, I/O AXI-Stream và hoạt động tốt bên trong một pipeline DATAFLOW.
cpp Copy
#include "ap_fixed.h"
#include "hls_stream.h"
using data_t = ap_fixed<16,8>;  
using acc_t = ap_fixed<32,12>; // bộ tích lũy rộng hơn  
const int N = 64;
struct axis_t {  
data_t data;  
bool last;  
};

void fir64(hls::stream& in, hls::stream& out, const data_t coeff[N]) {
#pragma HLS INTERFACE axis port=in
#pragma HLS INTERFACE axis port=out
#pragma HLS INTERFACE ap_ctrl_none port=return
#pragma HLS ARRAY_PARTITION variable=coeff complete dim=1
static data_t shift_reg[N];
#pragma HLS ARRAY_PARTITION variable=shift_reg complete dim=1
while (true) {
#pragma HLS PIPELINE II=1
axis_t x = in.read();
// dịch
for (int i = N-1; i > 0; --i) {
#pragma HLS UNROLL
shift_reg[i] = shift_reg[i-1];
}
shift_reg[0] = x.data;
// MAC
acc_t acc = 0;
for (int i = 0; i < N; ++i) {
#pragma HLS UNROLL
acc += (acc_t)shift_reg[i] * (acc_t)coeff[i];
}
axis_t y;
y.data = (data_t)acc;
y.last = x.last;
out.write(y);
if (x.last) break;  // đơn giản kết thúc khung
}
}

Kết Luận

Tối ưu hóa HLS cho FPGA là một kỹ năng quan trọng giúp nâng cao hiệu suất của hệ thống bạn đang phát triển. Bằng cách áp dụng các kỹ thuật tối ưu hóa được trình bày trong bài viết này, bạn có thể cải thiện đáng kể thông lượng, độ trễ và hiệu quả sử dụng tài nguyên. Hãy thử nghiệm với các phương pháp này trong dự án của bạn và theo dõi những cải tiến đạt được. Nếu bạn có bất kỳ câu hỏi nào, hãy để lại ý kiến của bạn dưới bài viết này!

Nội dung bài viết

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào