Khi nào nó gọn gàng hơn để sử dụng các đại diện YAMOR so với INTEGER?


11

Trong luồng nhận xét về câu trả lời cho câu hỏi này: Đầu ra sai trong thực thể VHDL, nó đã được nêu:

"Với các số nguyên, bạn không có quyền kiểm soát hoặc truy cập vào biểu diễn logic bên trong trong FPGA, trong khi SLV cho phép bạn thực hiện các thủ thuật như sử dụng chuỗi mang một cách hiệu quả"

Vì vậy, trong trường hợp nào bạn đã thấy nó gọn gàng hơn khi sử dụng vectơ biểu diễn bit hơn là sử dụng số nguyên để truy cập vào biểu diễn bên trong? Và bạn đã đo được những lợi thế gì (về diện tích chip, tần số xung nhịp, độ trễ, hay cách khác.)?


Tôi nghĩ rằng đó là một cái gì đó khó đo lường, vì rõ ràng đó chỉ là vấn đề kiểm soát việc thực hiện cấp thấp.
clabacchio

Câu trả lời:


5

Tôi đã viết mã được đề xuất bởi hai áp phích khác trong cả hai vectorinteger dạng dạng, chú ý để cả hai phiên bản hoạt động theo cách tương tự nhất có thể.

Tôi đã so sánh các kết quả trong mô phỏng và sau đó được tổng hợp bằng cách sử dụng Synplify Pro nhắm mục tiêu Xilinx Spartan 6. Các mẫu mã dưới đây được dán từ mã làm việc, vì vậy bạn sẽ có thể sử dụng chúng với trình tổng hợp yêu thích của mình và xem liệu nó có hoạt động giống như vậy không.


Số lượt truy cập

Đầu tiên, bộ đếm, theo đề xuất của David Kessner:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity downcounter is
    generic (top : integer);
    port (clk, reset, enable : in  std_logic; 
         tick   : out std_logic);
end entity downcounter;

Kiến trúc vector:

architecture vec of downcounter is
begin
    count: process (clk) is
        variable c : unsigned(32 downto 0);  -- don't inadvertently not allocate enough bits here... eg if "integer" becomes 64 bits wide
    begin  -- process count
        if rising_edge(clk) then  
            tick <= '0';
            if reset = '1' then
                c := to_unsigned(top-1, c'length);
            elsif enable = '1' then
                if c(c'high) = '1' then
                    tick <= '1';
                    c := to_unsigned(top-1, c'length);
                else
                    c := c - 1;
                end if;
            end if;
        end if;
    end process count;
end architecture vec;

Kiến trúc số nguyên

architecture int of downcounter is
begin
    count: process (clk) is
        variable c : integer;
    begin  -- process count
        if rising_edge(clk) then  
            tick <= '0';
            if reset = '1' then
                c := top-1;
            elsif enable = '1' then
                if c < 0 then
                    tick <= '1';
                    c := top-1;
                else
                    c := c - 1;
                end if;
            end if;
        end if;
    end process count;
end architecture int;

Các kết quả

Thông thái mã, số nguyên có vẻ thích hợp hơn với tôi vì nó tránh to_unsigned() cuộc gọi. Nếu không, không có nhiều để lựa chọn.

Chạy nó thông qua Synplify Pro với việc top := 16#7fff_fffe#tạo ra 66 LUT cho vectorphiên bản và 64 LUT cho integerphiên bản. Cả hai phiên bản đều sử dụng nhiều dây chuyền. Cả hai báo cáo tốc độ xung nhịp vượt quá 280 MHz . Bộ tổng hợp hoàn toàn có khả năng thiết lập việc sử dụng tốt chuỗi mang - tôi đã xác minh trực quan với trình xem RTL rằng logic tương tự được tạo ra với cả hai. Rõ ràng một bộ đếm ngược với bộ so sánh sẽ lớn hơn, nhưng điều đó sẽ giống với cả số nguyên và vectơ một lần nữa.


Chia cho 2 ** n quầy

Được đề xuất bởi ajs410:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity clkdiv is
    port (clk, reset : in     std_logic;
        clk_2, clk_4, clk_8, clk_16  : buffer std_logic);
end entity clkdiv;

Kiến trúc vector

architecture vec of clkdiv is

begin  -- architecture a1

    process (clk) is
        variable count : unsigned(4 downto 0);
    begin  -- process
        if rising_edge(clk) then  
            if reset = '1' then
                count  := (others => '0');
            else
                count := count + 1;
            end if;
        end if;
        clk_2 <= count(0);
        clk_4 <= count(1);
        clk_8 <= count(2);
        clk_16 <= count(3);
    end process;

end architecture vec;

Kiến trúc số nguyên

Bạn phải nhảy qua một số vòng để tránh chỉ sử dụng to_unsignedvà sau đó chọn các bit sẽ tạo ra hiệu ứng tương tự như trên:

architecture int of clkdiv is
begin
    process (clk) is
        variable count : integer := 0;
    begin  -- process
        if rising_edge(clk) then  
            if reset = '1' then
                count  := 0;
                clk_2  <= '0';
                clk_4  <= '0';
                clk_8  <= '0';
                clk_16 <= '0';
            else
                if count < 15 then
                    count := count + 1;
                else
                    count := 0;
                end if;
                clk_2 <= not clk_2;
                for c4 in 0 to 7 loop
                    if count = 2*c4+1 then
                        clk_4 <= not clk_4;
                    end if;
                end loop; 
                for c8 in 0 to 3 loop
                    if count = 4*c8+1 then
                        clk_8 <= not clk_8;
                    end if;
                end loop; 
                for c16 in 0 to 1 loop
                    if count = 8*c16+1 then
                        clk_16 <= not clk_16;
                    end if;
                end loop; 
            end if;
        end if;
    end process;
end architecture int;

Các kết quả

Code-khôn ngoan, trong trường hợp này, vector phiên bản rõ ràng là tốt hơn!

Về kết quả tổng hợp, đối với ví dụ nhỏ này, phiên bản số nguyên (như ajs410 dự đoán) không tạo ra 3 LUT bổ sung như một phần của bộ so sánh, tôi quá lạc quan về trình tổng hợp, mặc dù nó đang làm việc với một đoạn mã bị che khuất khủng khiếp!


Công dụng khác

Các vectơ là một chiến thắng rõ ràng khi bạn muốn số học bao quanh (các bộ đếm có thể được thực hiện dưới dạng một dòng chẵn):

vec <= vec + 1 when rising_edge(clk);

đấu với

if int < int'high then 
   int := int + 1;
else
   int := 0;
end if;

mặc dù ít nhất nó rõ ràng từ mã mà tác giả dự định bao bọc.


Một cái gì đó tôi chưa sử dụng trong mã thực, nhưng suy ngẫm:

Tính năng "gói tự nhiên" cũng có thể được sử dụng để "tính toán qua tràn". Khi bạn biết rằng đầu ra của một chuỗi phép cộng / phép trừ và phép nhân bị giới hạn, bạn không phải lưu trữ các bit cao của các phép tính trung gian như (trong phần bù 2 giây), nó sẽ xuất hiện "trong quá trình rửa" tại thời điểm bạn nhận được đầu ra. Tôi đã nói rằng bài báo này có bằng chứng về điều này, nhưng nó có vẻ hơi dày đặc đối với tôi để đánh giá nhanh! Lý thuyết về bổ sung và tràn máy tính - HL Garner

Sử dụng integers trong tình huống này sẽ gây ra lỗi mô phỏng khi chúng được bọc, mặc dù cuối cùng chúng ta biết rằng chúng sẽ mở khóa.


Và như Philippe đã chỉ ra, khi bạn cần một số lớn hơn 2 ** 31, bạn không có lựa chọn nào khác ngoài sử dụng vectơ.


Trong khối mã thứ hai bạn có variable c : unsigned(32 downto 0);... không phải clà biến 33 bit?
clabacchio

@clabacchio: có, cho phép truy cập vào 'carry-bit' để xem phần bao quanh.
Martin Thompson

5

Khi viết VHDL, tôi khuyên bạn nên sử dụng std_logic_vector (slv) thay vì số nguyên (int) cho TÍN HIỆU . (Mặt khác, sử dụng int cho generic, một số hằng và một số biến có thể rất hữu ích.) Nói một cách đơn giản, nếu bạn khai báo tín hiệu của kiểu int hoặc phải chỉ định một phạm vi cho một số nguyên thì có lẽ bạn đang làm Có gì đó không đúng.

Vấn đề với int là lập trình viên VHDL không biết biểu diễn logic bên trong của int là gì và vì vậy chúng ta không thể tận dụng lợi thế của nó. Ví dụ, nếu tôi xác định một int của phạm vi từ 1 đến 10, tôi không biết trình biên dịch mã hóa các giá trị đó như thế nào. Hy vọng rằng nó sẽ được mã hóa thành 4 bit, nhưng chúng ta không biết nhiều hơn thế. Nếu bạn có thể thăm dò các tín hiệu bên trong FPGA, nó có thể được mã hóa thành "0001" thành "1010" hoặc được mã hóa thành "0000" đến "1001". Cũng có thể nó được mã hóa theo cách hoàn toàn vô nghĩa đối với con người chúng ta.

Thay vào đó chúng ta chỉ nên sử dụng slv thay vì int, vì sau đó chúng ta có quyền kiểm soát mã hóa và cũng có quyền truy cập trực tiếp vào các bit riêng lẻ. Có quyền truy cập trực tiếp là rất quan trọng, như bạn sẽ thấy sau.

Chúng ta chỉ có thể chuyển một int thành slv bất cứ khi nào chúng ta cần truy cập vào các bit riêng lẻ, nhưng điều đó thực sự rất lộn xộn, rất nhanh. Điều đó giống như nhận được điều tồi tệ nhất của cả hai thế giới thay vì tốt nhất của cả hai thế giới. Mã của bạn sẽ khó để trình biên dịch tối ưu hóa và hầu như bạn không thể đọc được. Tôi không khuyên bạn điều này.

Vì vậy, như tôi đã nói, với slv, bạn có quyền kiểm soát mã hóa bit và truy cập trực tiếp vào các bit. Vậy bạn có thể làm gì với điều này? Tôi sẽ chỉ cho bạn một vài ví dụ. Giả sử bạn cần xuất xung một lần sau mỗi 4.294.000.000 đồng hồ. Đây là cách bạn sẽ làm điều này với int:

signal count :integer range 0 to 4293999999;  -- a 32 bit integer

process (clk)
begin
  if rising_edge(clk) then
    if count = 4293999999 then  -- The important line!
      count <= 0;
      pulse <= '1';
    else
      count <= count + 1;
      pulse <= '0';
    end if;
  end if;
end process;

Và cùng một mã sử dụng slv:

use ieee.numeric_std.all;
signal count :std_logic_vector (32 downto 0);  -- a 33 bit integer, one extra bit!

process (clk)
begin
  if rising_edge(clk) then
    if count(count'high)='1' then   -- The important line!
      count <= std_logic_vector(4293999999-1,count'length);
      pulse <= '1';
    else
      count <= count - 1;
      pulse <= '0';
    end if;
  end if;
end process;

Hầu hết các mã này là giống hệt nhau giữa int và slv, ít nhất là theo nghĩa kích thước và tốc độ của logic kết quả. Tất nhiên một người đang đếm ngược và người kia đang đếm ngược, nhưng điều đó không quan trọng đối với ví dụ này.

Sự khác biệt là trong "dòng quan trọng".

Với ví dụ int, điều này sẽ dẫn đến một bộ so sánh 32 đầu vào. Với 4 LUT đầu vào mà Xilinx Spartan-3 sử dụng, điều này sẽ đòi hỏi 11 LUT và 3 cấp độ logic. Một số trình biên dịch có thể chuyển đổi điều này thành phép trừ sẽ sử dụng chuỗi mang và trải rộng tương đương với 32 LUT nhưng có thể chạy nhanh hơn 3 cấp độ logic.

Với ví dụ slv, không có so sánh 32 bit nên "không LUT, mức logic bằng không". Hình phạt duy nhất là bộ đếm của chúng tôi là thêm một chút. Bởi vì thời gian bổ sung cho bit bổ sung này là tất cả trong chuỗi mang, nên có độ trễ thời gian bổ sung "gần như bằng không".

Tất nhiên đây là một ví dụ cực đoan, vì hầu hết mọi người sẽ không sử dụng bộ đếm 32 bit theo cách này. Nó áp dụng cho các quầy nhỏ hơn, nhưng sự khác biệt sẽ ít kịch tính hơn mặc dù vẫn còn đáng kể.

Đây chỉ là một ví dụ về cách sử dụng slv trên int để có thời gian nhanh hơn. Có nhiều cách khác để sử dụng slv-- chỉ cần một số trí tưởng tượng.

Cập nhật: Đã thêm nội dung để giải quyết ý kiến ​​của Martin Thompson về việc sử dụng int với "if (Count-1) <0"

(Lưu ý: Tôi giả sử bạn có nghĩa là "nếu tính <0", vì điều đó sẽ làm cho nó tương đương với phiên bản slv của tôi hơn và loại bỏ sự cần thiết cho phép trừ thêm đó.)

Trong một số trường hợp, điều này có thể tạo ra việc thực hiện logic dự định nhưng nó không được đảm bảo để hoạt động mọi lúc. Nó sẽ phụ thuộc vào mã của bạn và cách trình biên dịch của bạn mã hóa giá trị int.

Tùy thuộc vào trình biên dịch của bạn và cách bạn chỉ định phạm vi int của mình, hoàn toàn có thể giá trị int bằng 0 không mã hóa thành một vectơ bit "0000 ... 0000" khi nó đưa nó vào logic Logic. Để biến thể của bạn hoạt động, nó phải mã hóa thành "0000 ... 0000".

Ví dụ: giả sử bạn xác định một int có phạm vi từ -5 đến +5. Bạn đang mong đợi giá trị 0 được mã hóa thành 4 bit như "0000" và +5 là "0101" và -5 là "1011". Đây là sơ đồ mã hóa bổ sung twos điển hình.

Nhưng đừng cho rằng trình biên dịch sẽ sử dụng bổ sung twos. Mặc dù khác thường, bổ sung có thể dẫn đến logic "tốt hơn". Hoặc, trình biên dịch có thể sử dụng một loại mã hóa "thiên vị" trong đó -5 được mã hóa thành "0000", 0 là "0101" và +5 là "1010".

Nếu mã hóa của int là "chính xác" thì trình biên dịch có thể sẽ suy ra phải làm gì với bit carry. Nhưng nếu nó không chính xác thì logic kết quả sẽ rất kinh khủng.

Có thể việc sử dụng một int theo cách này có thể dẫn đến kích thước và tốc độ logic hợp lý, nhưng nó không phải là một sự đảm bảo. Chuyển sang một trình biên dịch khác (ví dụ XST sang Synopsis) hoặc chuyển sang một kiến ​​trúc FPGA khác có thể gây ra sự cố chính xác xảy ra.

Chưa ký / Đã ký so với slv vẫn là một cuộc tranh luận khác. Bạn có thể cảm ơn ủy ban chính phủ Hoa Kỳ đã cho chúng tôi rất nhiều lựa chọn trong VHDL. :) Tôi sử dụng slv vì đó là tiêu chuẩn để giao tiếp giữa các mô-đun và lõi. Ngoài ra, và một số trường hợp khác trong mô phỏng, tôi không nghĩ rằng có một lợi ích to lớn khi sử dụng slv trên chữ ký / không dấu. Tôi cũng không chắc chắn nếu tín hiệu đã ký / không dấu hỗ trợ ba tín hiệu đã nêu.


4
David, những đoạn mã không tương đương. Người ta đếm từ 0 đến một số tùy ý (với một toán tử so sánh đắt tiền); số khác đếm xuống 0 từ một số tùy ý. Bạn có thể viết cả hai thuật toán bằng số nguyên hoặc vectơ và bạn sẽ nhận được kết quả xấu khi tính vào một số tùy ý và kết quả tốt khi đếm về 0. Lưu ý rằng các kỹ sư phần mềm cũng sẽ đếm ngược về 0 nếu họ cần vắt kiệt hiệu năng hơn một chút khỏi vòng lặp nóng.
Philippe

1
Giống như Philippe, tôi không tin rằng đây là một so sánh hợp lệ. Nếu ví dụ số nguyên được đếm ngược và sử dụng, if (count-1) < 0tôi nghĩ bộ tổng hợp sẽ suy ra bit thực hiện và tạo ra nhiều mạch giống như ví dụ slv của bạn. Ngoài ra, chúng ta không nên sử dụng unsignedloại này trong những ngày này :)
Martin Thompson

2
@DavidKessner bạn chắc chắn đã cung cấp THOROUGH và câu trả lời hợp lý, bạn đã có +1 của mình. Tôi phải hỏi mặc dù ... tại sao bạn lo lắng về tối ưu hóa trong suốt thiết kế? Sẽ không tốt hơn nếu tập trung nỗ lực của bạn vào các lĩnh vực mã yêu cầu hoặc tập trung vào SLV cho các điểm giao diện (cổng thực thể) để tương thích? Tôi biết rằng trong hầu hết các thiết kế của mình, tôi không đặc biệt quan tâm đến việc sử dụng LUT được giảm thiểu, miễn là nó đáp ứng được thời gian và phù hợp với bộ phận. Nếu tôi có những ràng buộc đặc biệt chặt chẽ, tôi chắc chắn có ý thức hơn về thiết kế tối ưu, nhưng không phải là một quy tắc chung.
akohlsmith

2
Tôi hơi ngạc nhiên về số lượng bình chọn lên cho câu trả lời này. @ bit_vector @ chắc chắn là mức độ trừu tượng chính xác để mô hình hóa và tối ưu hóa kiến ​​trúc vi mô, nhưng một khuyến nghị chung lại loại các loại "mức cao" như @ số nguyên @ cho tín hiệu và cổng là điều tôi thấy lạ. Tôi đã thấy đủ mã phức tạp và không thể đọc được do thiếu tính trừu tượng để biết giá trị mà các tính năng này cung cấp và sẽ rất buồn nếu tôi phải bỏ lại chúng.
trondd

2
@david Nhận xét tuyệt vời. Đúng là chúng ta vẫn ở thời trung cổ so với phát triển phần mềm theo nhiều cách, nhưng từ kinh nghiệm của tôi với Quartus tổng hợp tích hợp và Synplify tôi không nghĩ mọi thứ tệ đến thế. Chúng hoàn toàn có khả năng xử lý nhiều thứ như kiểm tra lại đăng ký và tối ưu hóa khác giúp cải thiện hiệu suất trong khi duy trì khả năng đọc. Tôi nghi ngờ phần lớn đang nhắm mục tiêu vào một số bộ công cụ và thiết bị, nhưng đối với trường hợp của bạn, tôi hiểu yêu cầu về mẫu số ít phổ biến nhất :-).
trondd

2

Lời khuyên của tôi là hãy thử cả hai, và sau đó xem xét các báo cáo tổng hợp, bản đồ và địa điểm. Các báo cáo này sẽ cho bạn biết chính xác có bao nhiêu LUT mỗi cách tiếp cận đang tiêu thụ, chúng cũng sẽ cho bạn biết tốc độ tối đa mà logic có thể hoạt động.

Tôi đồng ý với David Kessner rằng bạn đang tự hào về chuỗi công cụ của mình và không có câu trả lời "đúng". Tổng hợp là ma thuật đen và cách tốt nhất để biết chuyện gì đã xảy ra là đọc kỹ và đọc kỹ các báo cáo được tạo ra. Các công cụ Xilinx thậm chí cho phép bạn nhìn thấy bên trong FPGA, ngay đến cách mỗi LUT được lập trình, cách kết nối chuỗi mang, cách kết cấu kết nối tất cả các LUT, v.v.

Đối với một ví dụ ấn tượng khác về cách tiếp cận của ông Kessner, hãy tưởng tượng rằng bạn muốn có nhiều tần số xung nhịp ở 1/2, 1/4, 1/8, 1/16, v.v. Bạn có thể sử dụng một số nguyên liên tục đếm từng chu kỳ, và sau đó có nhiều bộ so sánh với giá trị nguyên đó, với mỗi đầu ra của bộ so sánh tạo thành một bộ phận đồng hồ khác nhau. Tùy thuộc vào số lượng bộ so sánh, fanout có thể trở nên lớn một cách vô lý và bắt đầu tiêu thụ thêm LUT chỉ để đệm. Cách tiếp cận SLV sẽ chỉ lấy mỗi bit riêng lẻ của vectơ làm đầu ra.


1

Một lý do rõ ràng là ký và không dấu cho phép các giá trị lớn hơn số nguyên 32 bit. Đó là một lỗ hổng trong thiết kế ngôn ngữ VHDL, điều này không cần thiết. Một phiên bản mới của VHDL có thể khắc phục điều đó, yêu cầu các giá trị nguyên để hỗ trợ kích thước tùy ý (gần giống với BigInt của Java).

Ngoài ra, tôi rất thích nghe về các điểm chuẩn thực hiện khác nhau cho số nguyên so với các vectơ.

BTW, Jan Decaluwe đã viết một bài luận hay về điều này: Những Ints này được tạo ra cho Countin '


Cảm ơn Philippe (mặc dù đó không phải là ứng dụng "tốt hơn thông qua truy cập vào đại diện nội bộ", đó là những gì tôi thực sự theo đuổi ...)
Martin Thompson

Bài luận đó là tốt, nhưng hoàn toàn bỏ qua việc thực hiện cơ bản và dẫn đến tốc độ và kích thước logic. Tôi đồng ý với hầu hết những gì Decaluwe nói, nhưng anh ta không nói gì về kết quả tổng hợp. Đôi khi kết quả tổng hợp không thành vấn đề, và đôi khi chúng làm được. Vì vậy, đó là một cuộc gọi phán xét.

1
@David, tôi đồng ý rằng Jan không đi sâu vào chi tiết về cách các công cụ tổng hợp phản ứng với các số nguyên. Nhưng, không, nó không phải là một cuộc gọi phán xét. Bạn có thể đo lường kết quả tổng hợp và xác định kết quả của công cụ tổng hợp đã cho. Tôi nghĩ rằng OP có nghĩa là câu hỏi của anh ấy như một thách thức đối với chúng tôi để tạo ra các đoạn mã và kết quả tổng hợp chứng minh sự khác biệt (nếu có) trong hiệu suất.
Philippe

@Philippe Không, ý tôi là đó là lời kêu gọi phán xét nếu bạn quan tâm đến tất cả các kết quả tổng hợp. Không phải là kết quả tổng hợp chính nó là một cuộc gọi phán xét.

@DavidKessner OK. Tôi đã hiểu nhầm.
Philippe
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.