2

The below VHDL snippet correctly gets me the 16-bit CRC for a single input byte. How would I extend that for multiple input bytes, e.g. a frame that now spans 128 bytes to be crc'd?

NB: function 'crc16' was generated using some online tool, but I derived it by myself too, so I am confident it works OK. Currently, the testbench below supplies the CRC function one byte per call.

CRC characteristics:

  • CRC polynomial: 0x8005
  • Input reflected: yes
  • Output reflected: yes
  • Seed value: 0xFFFF
  • XOR-out value: 0xFFFF (IIUC, negate the CRC)

Code:

library ieee; 
use ieee.std_logic_1164.all;

use ieee.numeric_std.all;

entity crc is 
port ( clk: in std_logic;
       data_in: in std_logic_vector(7 downto 0);             
       crc_out: out std_logic_vector(15 downto 0)
      );
end crc;

architecture crc_arch of crc is     

function reverse_vector(v: in std_logic_vector)
return std_logic_vector is
    variable result: std_logic_vector(v'RANGE);
    alias vr: std_logic_vector(v'REVERSE_RANGE) is v;
begin
    for i in vr'RANGE loop
        result(i) := vr(i);
    end loop;

    return result;
end;    


function crc16( data_i: in std_logic_vector(7 downto 0);             
                     crc_i: in std_logic_vector(15 downto 0))
return std_logic_vector is  
    variable crc_o: std_logic_vector(15 downto 0);
begin
    crc_o(15) := crc_i(7) xor crc_i(8) xor crc_i(9) xor crc_i(10) xor crc_i(11) xor crc_i(12) xor crc_i(13) xor crc_i(14) xor crc_i(15) xor 
                    data_i(0) xor data_i(1) xor data_i(2) xor data_i(3) xor data_i(4) xor data_i(5) xor data_i(6) xor data_i(7);          
    crc_o(14) := crc_i(6);      
    crc_o(13) := crc_i(5);
    crc_o(12) := crc_i(4);
    crc_o(11) := crc_i(3);  
    crc_o(10) := crc_i(2);  
    crc_o(9)  := crc_i(1) xor crc_i(15) xor data_i(7);
    crc_o(8)  := crc_i(0) xor crc_i(14) xor crc_i(15) xor data_i(6) xor data_i(7);
    crc_o(7)  := crc_i(13) xor crc_i(14) xor data_i(5) xor data_i(6);
    crc_o(6)  := crc_i(12) xor crc_i(13) xor data_i(4) xor data_i(5);               
    crc_o(5)  := crc_i(11) xor crc_i(12) xor data_i(3) xor data_i(4);
    crc_o(4)  := crc_i(10) xor crc_i(11) xor data_i(2) xor data_i(3);
    crc_o(3)  := crc_i(9) xor crc_i(10) xor data_i(1) xor data_i(2);
    crc_o(2)  := crc_i(8) xor crc_i(9) xor data_i(0) xor data_i(1);
    crc_o(1)  := crc_i(9) xor crc_i(10) xor crc_i(11) xor crc_i(12) xor crc_i(13) xor crc_i(14) xor crc_i(15) xor 
                    data_i(1) xor data_i(2) xor data_i(3) xor data_i(4) xor data_i(5) xor data_i(6) xor data_i(7);
    crc_o(0)  := crc_i(8) xor crc_i(9) xor crc_i(10) xor crc_i(11) xor crc_i(12) xor crc_i(13) xor crc_i(14) xor crc_i(15) xor 
                    data_i(0) xor data_i(1) xor data_i(2) xor data_i(3) xor data_i(4) xor data_i(5) xor data_i(6) xor data_i(7);

    return crc_o;
end;


begin 

    crc_out <= not reverse_vector(crc16(reverse_vector(data_in), x"FFFF"));

end architecture crc_arch; 

Testbench:

LIBRARY ieee;
USE ieee.std_logic_1164.ALL;

ENTITY tb_crc IS
END tb_crc;

ARCHITECTURE behavior OF tb_crc IS 

-- Component Declaration for the Unit Under Test (UUT)

COMPONENT crc
PORT(
        clk: std_logic;
     data_in : IN  std_logic_vector(7 downto 0);
     crc_out : OUT  std_logic_vector(15 downto 0)
    );
END COMPONENT;


--Inputs
signal tb_clk : std_logic := '0';
signal tb_data_in : std_logic_vector(7 downto 0) := (others => '0');

--Outputs
signal tb_crc_out : std_logic_vector(15 downto 0);

-- Clock period definitions
constant clk_period : time := 10 ns;

BEGIN

-- Instantiate the Unit Under Test (UUT)
uut: crc PORT MAP (
         clk => tb_clk,
      data_in => tb_data_in,
      crc_out => tb_crc_out
    );

-- Clock process definitions
clk_process :process
begin
    tb_clk <= '1';
    wait for clk_period/2;
    tb_clk <= '0';
    wait for clk_period/2;
end process;

-- Stimulus process
stim_proc: process
begin       
  -- hold reset state for 100 ns.
  wait for 100 ns;  


  -- insert stimulus here

    tb_data_in <= x"01";        
    wait for clk_period;

    tb_data_in <= x"02";
    wait for clk_period;

    tb_data_in <= x"03";
    wait for clk_period;

    tb_data_in <= x"04";
    wait for clk_period;

    wait;
end process;

END;

Thanks for reading, Chris

user2286339
  • 184
  • 1
  • 4
  • 18
  • You might want to have a look at this generic implementation: https://github.com/VLSI-EDA/PoC/blob/master/src/comm/comm_crc.vhdl. You are often better off using available, already tested implementations. – Thomas B Preusser Aug 23 '16 at 19:03
  • There's actually a good example (not using a function call) on line. See [OutputLogic.com » CRC Generator](http://outputlogic.com/?page_id=321) with data width 8, polynomial width 16 and protocol CRC16 for USB 2.0 (the same as your 0x8005). The second tab will allow you to produce VHDL sample code (an I think U.S. copyright law says it's the person pushing the button who is the author, meaning the copyright notice may be invalid). Any way it shows how to introduce sucessive bytes per clock and produce your crc. –  Aug 23 '16 at 20:18
  • The purported source for the VHDL crc generator used by OutputLogic.com can be downloaded from opencores [Parallel CRC Generator](http://opencores.org/project,parallelcrcgen) with a login (requiring signup). –  Aug 23 '16 at 20:25
  • CRC is a bit-wise operation. The polynomial is applied to each bit of an input vector. That means your code is an unrolled loop of 16 CRC single-bit calculations performed on bits 0 to 15 of the input word. To process more bits, you can either unroll it further (Fmax will decrease) or you pipe in the next word at every clock cycle. That's what [comm_crc](http://poc-library.readthedocs.io/en/release/PoC/comm/comm_crc.html) does. Please note the [inner loop](https://github.com/VLSI-EDA/PoC/blob/master/src/comm/comm_crc.vhdl#L102-L105) which shifts and xors the generator polynomial `BITS` times. – Paebbels Aug 23 '16 at 21:10
  • @user1155120: I just checked the 'outputlogic' site. It seems the USB-CRC16 and the one I use have the same properties. But then, the HDL from that site doesn't match up with my known-good test cases, e.g. byte pattern 0x1 0x2 0x3 0x4 should result in a CRC of 0xD45E (result reflected and negated). Re you earlier comment: the testbench and uut from above only take a single byte as input, not accumulating. – user2286339 Aug 23 '16 at 22:54

1 Answers1

3

The parallel CRC generator software commonly used on various websites is open source. I downloaded and converted the source from C++ to C (declarations for types boolean, bool and values true and false).

The license terms allow modification while retaining the copyright notice. I removed the invalid copyright claim on the output as well as fixing some comment characters in the disclaimer and altered the formatting to suit. (I always intended to also fit the output into 80 columns, lining up terms in columns would also be useful).

It generates code almost identical to yours:

crc-gen vhdl 8 16 8005

------------------------------------------------------------------------------- 
-- THIS SOURCE FILE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS
-- OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
-- WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-------------------------------------------------------------------------------
-- CRC entity/architecture for
-- data(7:0)
-- crc(15:0) = 1+x^2+x^15+x^16;
--
library ieee;
use ieee.std_logic_1164.all;

entity crc is
    port (
        data_in:  in  std_logic_vector (7 downto 0);
        crc_en:   in  std_logic;
        rst:      in  std_logic;
        clk:      in  std_logic;
        crc_out:  out std_logic_vector (15 downto 0)
    );
end entity crc;

architecture imp_crc of crc is
    signal lfsr_q: std_logic_vector (15 downto 0);
    signal lfsr_c: std_logic_vector (15 downto 0);
begin
    crc_out <= lfsr_q;

    lfsr_c(0) <= lfsr_q(8) xor lfsr_q(9) xor lfsr_q(10) xor lfsr_q(11) xor 
                 lfsr_q(12) xor lfsr_q(13) xor lfsr_q(14) xor lfsr_q(15) xor 
                 data_in(0) xor data_in(1) xor data_in(2) xor data_in(3) xor 
                 data_in(4) xor data_in(5) xor data_in(6) xor data_in(7);
    lfsr_c(1) <= lfsr_q(9) xor lfsr_q(10) xor lfsr_q(11) xor lfsr_q(12) xor 
                 lfsr_q(13) xor lfsr_q(14) xor lfsr_q(15) xor data_in(1) xor 
                 data_in(2) xor data_in(3) xor data_in(4) xor data_in(5) xor 
                 data_in(6) xor data_in(7);
    lfsr_c(2) <= lfsr_q(8) xor lfsr_q(9) xor data_in(0) xor data_in(1);
    lfsr_c(3) <= lfsr_q(9) xor lfsr_q(10) xor data_in(1) xor data_in(2);
    lfsr_c(4) <= lfsr_q(10) xor lfsr_q(11) xor data_in(2) xor data_in(3);
    lfsr_c(5) <= lfsr_q(11) xor lfsr_q(12) xor data_in(3) xor data_in(4);
    lfsr_c(6) <= lfsr_q(12) xor lfsr_q(13) xor data_in(4) xor data_in(5);
    lfsr_c(7) <= lfsr_q(13) xor lfsr_q(14) xor data_in(5) xor data_in(6);
    lfsr_c(8) <= lfsr_q(0) xor lfsr_q(14) xor lfsr_q(15) xor data_in(6) xor 
                 data_in(7);
    lfsr_c(9) <= lfsr_q(1) xor lfsr_q(15) xor data_in(7);
    lfsr_c(10) <= lfsr_q(2);
    lfsr_c(11) <= lfsr_q(3);
    lfsr_c(12) <= lfsr_q(4);
    lfsr_c(13) <= lfsr_q(5);
    lfsr_c(14) <= lfsr_q(6);
    lfsr_c(15) <= lfsr_q(7) xor lfsr_q(8) xor lfsr_q(9) xor lfsr_q(10) xor 
                  lfsr_q(11) xor lfsr_q(12) xor lfsr_q(13) xor lfsr_q(14) xor 
                  lfsr_q(15) xor data_in(0) xor data_in(1) xor data_in(2) xor 
                  data_in(3) xor data_in(4) xor data_in(5) xor data_in(6) xor 
                  data_in(7);

REGISTERS:
    process (clk, rst)
    begin
        if rst = '1' then
            lfsr_q   <= (others => '1');
        elsif rising_edge(clk) then
            if crc_en = '1' then
                lfsr_q <= lfsr_c;
            end if;
        end if;
    end process;
end architecture imp_crc;

The interesting feature is the process, which uses a clocked register to keep a running tally of the CRC as well as to provide a seed through a reset (the (others => '1'), equivalent to x"FFFF" based on the length of lfsr_q).

You can use the reset to set the state to begin accumulating successive bytes of CRC along with crc_en to control which clocks input bytes are included in the CRC.

You could use a multiplexer instead to select between either x"FFFF" or the stored CRC so there is no 'downtime' between blocks being evaluated, that serializes the delay to include the multiplexer.

I'd imagine an enable would likely be essential in either case. You could get by with adding either one or two more signals to your port interface and testbench.

So a handcrafted testbench to use the generated CRC code:

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

entity crc_tb is
end entity;

architecture  foo of crc_tb is

    function reverse_vector(v: in std_logic_vector)
    return std_logic_vector is
        variable result: std_logic_vector(v'RANGE);
        alias vr: std_logic_vector(v'REVERSE_RANGE) is v;
    begin
        for i in vr'RANGE loop
            result(i) := vr(i);
        end loop;

        return result;
    end;    

    signal datain:   std_logic_vector (7 downto 0);
    signal data_in:  std_logic_vector (7 downto 0);
    signal crc_en:   std_logic := '0';
    signal rst:      std_logic;
    signal clk:      std_logic := '0';
    signal crc_out:  std_logic_vector (15 downto 0);

    signal crcout:   std_logic_vector (15 downto 0);
begin

    crcout <= not reverse_vector (crc_out);

DUT:
    entity work.crc
        port map (
            data_in => data_in,
            crc_en => crc_en,
            rst => rst,
            clk => clk,
            crc_out => crc_out
        );

CLOCK:
    process 
    begin
        wait for 5 ns; -- half the clock period
        clk <= not clk;
        if now > 160 ns then
            wait;
        end if;
    end process;
    STIMULI:
    process
    begin
        rst <= '1';
        for i in 0 to 9 loop
            wait until rising_edge(clk);
        end loop;
        rst <= '0';
        crc_en <= '1';
        for i in 1 to 4 loop
            datain <= std_logic_vector(to_unsigned (i,8));
            data_in <= reverse_vector (std_logic_vector(to_unsigned(i,8)));
            wait until rising_edge(clk);
        end loop;
        crc_en <= '0';
        wait until rising_edge(clk);
        wait;
    end process;
end architecture;

Which gives us:

crc_tb.png

From you're comment under your question this is the correct value for four successive bytes of x"01", x"02", x"03" and x"04", the value x"D45E".

So let's apply that to your code

First the changes:

library ieee; 
use ieee.std_logic_1164.all;

use ieee.numeric_std.all;

entity crc is 
port ( clk: in std_logic;
       data_in: in std_logic_vector(7 downto 0); 
       crc_en:   in  std_logic;   -- ADDED
       rst:      in  std_logic;   -- ADDED
       crc_out: out std_logic_vector(15 downto 0)
      );
end crc;

architecture crc_arch of crc is     

function reverse_vector(v: in std_logic_vector)
return std_logic_vector is
    variable result: std_logic_vector(v'RANGE);
    alias vr: std_logic_vector(v'REVERSE_RANGE) is v;
begin
    for i in vr'RANGE loop
        result(i) := vr(i);
    end loop;

    return result;
end;    


function crc16( data_i: in std_logic_vector(7 downto 0);             
                     crc_i: in std_logic_vector(15 downto 0))
return std_logic_vector is  
    variable crc_o: std_logic_vector(15 downto 0);
begin
    crc_o(15) := crc_i(7) xor crc_i(8) xor crc_i(9) xor crc_i(10) xor crc_i(11) xor crc_i(12) xor crc_i(13) xor crc_i(14) xor crc_i(15) xor 
                    data_i(0) xor data_i(1) xor data_i(2) xor data_i(3) xor data_i(4) xor data_i(5) xor data_i(6) xor data_i(7);          
    crc_o(14) := crc_i(6);      
    crc_o(13) := crc_i(5);
    crc_o(12) := crc_i(4);
    crc_o(11) := crc_i(3);  
    crc_o(10) := crc_i(2);  
    crc_o(9)  := crc_i(1) xor crc_i(15) xor data_i(7);
    crc_o(8)  := crc_i(0) xor crc_i(14) xor crc_i(15) xor data_i(6) xor data_i(7);
    crc_o(7)  := crc_i(13) xor crc_i(14) xor data_i(5) xor data_i(6);
    crc_o(6)  := crc_i(12) xor crc_i(13) xor data_i(4) xor data_i(5);               
    crc_o(5)  := crc_i(11) xor crc_i(12) xor data_i(3) xor data_i(4);
    crc_o(4)  := crc_i(10) xor crc_i(11) xor data_i(2) xor data_i(3);
    crc_o(3)  := crc_i(9) xor crc_i(10) xor data_i(1) xor data_i(2);
    crc_o(2)  := crc_i(8) xor crc_i(9) xor data_i(0) xor data_i(1);
    crc_o(1)  := crc_i(9) xor crc_i(10) xor crc_i(11) xor crc_i(12) xor crc_i(13) xor crc_i(14) xor crc_i(15) xor 
                    data_i(1) xor data_i(2) xor data_i(3) xor data_i(4) xor data_i(5) xor data_i(6) xor data_i(7);
    crc_o(0)  := crc_i(8) xor crc_i(9) xor crc_i(10) xor crc_i(11) xor crc_i(12) xor crc_i(13) xor crc_i(14) xor crc_i(15) xor 
                    data_i(0) xor data_i(1) xor data_i(2) xor data_i(3) xor data_i(4) xor data_i(5) xor data_i(6) xor data_i(7);

    return crc_o;

end;

    signal crc_o:   std_logic_vector (15 downto 0);  -- ADDED register

begin 

    -- crc_out <= not reverse_vector(crc16(reverse_vector(data_in), x"FFFF"));

    process (clk)  -- ADDED process
    begin
        if rst = '1' then
            crc_o <= x"FFFF";
        elsif rising_edge(clk) then  
            if crc_en = '1' then
                crc_o <= crc16(reverse_vector(data_in), crc_o);
            end if;
        end if;
    end process;

    crc_out <= not reverse_vector(crc_o);  -- ADDED

end architecture crc_arch;

Added controls rst and crc_en to the entity port, added a declaration for the signal holding the register CRC value and broke up the reversing and inversion so it's not in the path of the crc_i for the crc16 function call.

The input to the register is the return value of the crc16 function call. The register is reset to the the CRC seed value.

The testbench got simpler:

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

entity crc_tb is
end entity;

architecture  foo of crc_tb is

    signal data_in:  std_logic_vector (7 downto 0);
    signal crc_en:   std_logic := '0';
    signal rst:      std_logic;
    signal clk:      std_logic := '0';
    signal crc_out:  std_logic_vector (15 downto 0);

begin

DUT:
    entity work.crc
        port map (
            data_in => data_in,
            crc_en => crc_en,
            rst => rst,
            clk => clk,
            crc_out => crc_out
        );

CLOCK:
    process 
    begin
        wait for 5 ns; -- half the clock period
        clk <= not clk;
        if now > 160 ns then
            wait;
        end if;
    end process;
STIMULI:
    process
    begin
        rst <= '1';
        for i in 0 to 9 loop
            wait until rising_edge(clk);
        end loop;
        rst <= '0';
        crc_en <= '1';
        for i in 1 to 4 loop
            data_in <= std_logic_vector(to_unsigned (i,8));
            wait until rising_edge(clk);
        end loop;
        crc_en <= '0';
        wait until rising_edge(clk);
        wait;
    end process;
end architecture;

All the changes were subtractive.

And that gives:

crc_tb_chris.png

The same answer as using the downloaded/generated VHDL code.

So the secret to using you crc16 function call is to not do any inversion or reversing from it's return value to the crc_i argument to the crc16 function call.