element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
FPGA
  • Technologies
  • More
FPGA
Blog Lattice iCE40UP5K-EVB Evaluation Board - Part 2: Using the DSP Multiplier
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join FPGA to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: jc2048
  • Date Created: 30 Mar 2026 11:02 AM Date Created
  • Views 70 views
  • Likes 5 likes
  • Comments 3 comments
  • sine
  • fpga
  • cordic
  • vhdl
  • dsp
  • lattice
  • ice40up5k
  • s/pdif
Related
Recommended

Lattice iCE40UP5K-EVB Evaluation Board - Part 2: Using the DSP Multiplier

jc2048
jc2048
30 Mar 2026
Lattice iCE40UP5K-EVB Evaluation Board - Part 2: Using the DSP Multiplier

Introduction

This is a follow-on from this blog and will make more sense if you read that one first. The hardware is exactly the same as in that previous blog, all I'm going to do with this one is adapt (hack might be a better word) the VHDL to perform a different task.

Having used the logic elements in the device, I thought it would be interesting to try one of the hard multipliers - the device has eight DSP [digital signal processing] blocks, each of which features a multiplier and an accumulator (a MAC). Since the test code in that previous blog already generates two sine waves, a very easy thing to do would be to simply multiply them together. It's especially convenient in that the multiplier in the DSP block can be used with 16-bit signed values, which happens to be the size of the sines I'm generating with the CORDIC component. I'm going to leave one set to 440Hz and set the other to sixteen times that, plus a little bit (7040.1Hz) so that they move slowly relative to each other.

This is the diagram of the DSP block from the family datasheet.

image

It looks very complicated, but isn't really. If you take away the pipeline registers and the muxes, that are there to bypass them if they're not used, what's left is a 16x16 multiplier, made up of four 8-bit multipliers, on the left, and a 32-bit accumulator, made up of a pair of 16-bit accumulators, on the right. That allows the DSP block to work with a single 16 bit multiplier and single 32 bit accumulator, or to be split in two and operate with a pair of 8 bit multipliers, each with a 16-bit accumulator.

The VHDL

There are basically two ways I can use the multiplier. One is instantiation, the other inference.

With instantiation, I create an instance of a DSP component and connect to it as we would for any component in VHDL. That uses a black box component supplied by Lattice that the synthesis and place-and-route know to map to the hard multiplier. That gives a lot of control over the DSP block, but does mean I'll need to read the documentation, and it locks us into the Lattice ecosystem. The 'IP Catalog' includes a multiplier part which is an easier way to set up the instantiation - I haven't tried that yet.

With inference, we simply use the multiply operator within VHDL and trust that the synthesis will realise that it can use the hard multiplier within the DSP block and not try and build one out of logic. It almost certainly will do. The advantage of inference is a fair chance of portability (particularly in this case, where other, more expensive, FPGAs designed for serious DSP work generally have multipliers with a longer wordlength).

Here's the top level code. It's not actually all that much different: a multiply operator to do the work, some manipulation of the result (which will be 32 bit) to take just the top half in order to reduce it to 16 bits again, and that's it. The other two, for the components, are the same as before.

----------------------------------------------------------------------
--              ***** ice40up5k_evn_test_mult.vhd *****             --
--                                                                  --
-- Lattice ICE40UP5K evaluation board multiplier test.                  --
-- Generates fixed pair of CORDIC 16-bit sine waves, multiplies them-- 
-- and formats result for an S/PDIF serial optical link.            --
--                                                                  --
----------------------------------------------------------------------
-- (C)2026 Jon Clift                                                --
-- Free to use however you want. No warranty as to correctness.     --
-- No guarantee of fitness for any purpose. No obligation to support--
----------------------------------------------------------------------
-- Rev    Date         Comments                                     --
-- 01     25-Mar-2026  based on ice40up5k_evn_test.vhd              --
----------------------------------------------------------------------

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

entity ice40up5k_evn_test is port(
    clk_12:            in STD_LOGIC;          --- system clock in (from 12MHz oscillator)
    clk_12_288:        in STD_LOGIC;          --- clock in (from my 12.288MHz oscillator)
    tp1:               out STD_LOGIC;        --- scope trigger at start of frame
    spdif_out:         out STD_LOGIC);        --- s/pdif data stream (to optical tx)
end ice40up5k_evn_test;

architecture arch_ice40up5k_evn_test of ice40up5k_evn_test is

constant sig_resol: POSITIVE := 16;                        --- signal resolution (bits)
constant pha_resol: POSITIVE := 32;                        --- phase resolution (bits)
signal theta: SIGNED(pha_resol-1 downto 0);                --- phase accumulator
signal theta_left: SIGNED(pha_resol-1 downto 0);           --- left phase accumulator
signal theta_right: SIGNED(pha_resol-1 downto 0);          --- right phase accumulator
signal phase_increment_left: SIGNED(pha_resol-1 downto 0); --- left phase increment
signal phase_increment_right: SIGNED(pha_resol-1 downto 0); --- right phase increment
signal sine: SIGNED(sig_resol-1 downto 0);                 --- CORDIC generated sine
signal cosine: SIGNED(sig_resol-1 downto 0);               --- CORDIC generaated cosine
signal sample_left: SIGNED(sig_resol-1 downto 0);               --- left sample
signal sample_right: SIGNED(sig_resol-1 downto 0);               --- right sample
signal mult_result: SIGNED(31 downto 0);                 --- output sample (left * right)
signal delay_i: STD_LOGIC;                                 ---
signal delay_o: STD_LOGIC;                                 --- 
signal delay_o_1: STD_LOGIC;                                 --- 
signal spdif_clk_en: STD_LOGIC;                            --- 
signal spdif_sample_en: STD_LOGIC;                         --- 
signal sample_en_del1: STD_LOGIC;                         --- 
signal sample_en_del2: STD_LOGIC;                         --- 
signal prescale_count: UNSIGNED(1 downto 0);                 --- 

--- declare the s/pdif output component

component spdif_out_component is
    generic(
        in_res: POSITIVE);           --- audio sample resolution
    port (
        clk_in: in STD_LOGIC;                           --- clock
        clk_en: in STD_LOGIC;                           --- clock enable
        l_data: in SIGNED(in_res-1 downto 0);           --- left audio data in
        r_data: in SIGNED(in_res-1 downto 0);           --- right audio data in
        next_sample_en: out STD_LOGIC;                 --- trigger sample update
        spdif_data_out: out STD_LOGIC);                 --- output bitstream
end component;

--- declare the CORDIC component

component cordic is
    generic(
        input_resol: POSITIVE;                     --- input resolution
        output_resol: POSITIVE);                   --- output resolution
    port(
        clk_in: in STD_LOGIC;                      --- clock in
        delay_in: in STD_LOGIC;                    --- delay in
        delay_out: out STD_LOGIC;                  --- delay out
        theta: in SIGNED(pha_resol-1 downto 0);    --- phase in
        sine: out SIGNED(sig_resol-1 downto 0);    --- sine out
        cosine: out SIGNED(sig_resol-1 downto 0)); --- cosine out
end component;

begin

    --- main process
    --- runs two phase accumulators, one for left and one for right
    --- CORDIC component calculates sine of each
    --- results stored and handed to spdif component for output formatting

    evb_test_stuff: process (clk_12_288) is
    begin

        if (clk_12_288'event and clk_12_288 = '1') then

            --- divide clock by 2 to run SPDIF at 6.144MHz for 48ksps

            if(spdif_clk_en = '0') then
                spdif_clk_en <= '1';
            else
                spdif_clk_en <= '0';
            end if;

            if (spdif_sample_en = '1') then
                theta_left <= theta_left + phase_increment_left;
                theta_right <= theta_right + phase_increment_right;
            end if;

            sample_en_del1 <= spdif_sample_en;
            sample_en_del2 <= sample_en_del1;

            if (spdif_sample_en = '1') then
                delay_i <= '1';
            elsif(sample_en_del2 = '1') then
                delay_i <= '0';
            end if;

            if (sample_en_del1 = '1') then
                theta <= theta_right;
            else
                theta <= theta_left;
            end if;

            delay_o_1 <= delay_o;

            if (delay_o = '1' and delay_o_1 = '0') then
                sample_left <= sine;
            end if;

            if (delay_o = '1' and delay_o_1 = '1') then
                 mult_result <= sample_left * sine;
            end if;

        end if;

        sample_right <= mult_result(31 downto 16);

        phase_increment_left(31 downto 0) <= b"0000_0010_0101_1000_1011_1111_0010_0110";    --- 440Hz 
        phase_increment_right(31 downto 0) <= b"0010_0101_1000_1100_0001_0001_0100_1100";   --- 7040.1Hz


    end process evb_test_stuff;

    --- instantiate and connect the spdif output component
		
    spdif_1: component spdif_out_component
        generic map(
            in_res => sig_resol)  --- audio sample resolution
        port map(
            clk_in => clk_12_288,
            clk_en => spdif_clk_en,
            l_data => sample_left,
            r_data => sample_right,
            next_sample_en => spdif_sample_en,
            spdif_data_out => spdif_out);

    --- instantiate and connect the CORDIC component

    cordic_1: component cordic
        generic map(
            input_resol => pha_resol,  --- input resolution
            output_resol => sig_resol) --- output resolution
        port map(
            clk_in => clk_12_288,      --- clock in
            delay_in => delay_i,       --- delay in
            delay_out => delay_o,      --- delay out
            theta => theta,            --- phase in
            sine => sine,              --- sine out
            cosine => cosine);         --- cosine out
			
    tp1 <= spdif_sample_en; 

end arch_ice40up5k_evn_test; 

Results

Here's the signal generated.

image

As before, I've passed it through a mixer to clean up the noise. Note that this isn't straight amplitude modulation: the result is double-sideband-suppressed-carrier [the sum and the difference between the two frequencies, without a carrier between them]. For amplitude modulation, the sine envelope would need to be offset so that it was all positive - I might try that later if I get a bit of time.

Here's the FFT of that waveform which shows the two sidebands and the absence of a carrier. Quite noisy, though it's difficult to quantify with a fairly basic 8-bit oscilloscope.

image

So the multiplier works fine. The synthesis has definitely used the real multiplier because I can see the use of a single DSP block reported by the place-and-route and see the connections to a DSP block on the physical view.

Here's a rather boring video showing the resulting waveform. Unfortunately, I think I have my right and left on the audio swapped - what I'm referring to as 'right' in the code comes out on the white RCA rather than the red one. Ho hum! If you copy this, you'll need to do some simple faultfinding and make a few minor corrections.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

Conclusions

Anyway, that's a very simple example of the DSP capabilities of the device in action.

The other two elements of this FPGA that I haven't looked at yet are the (single) PLL [usually just used for multiplying-up an input clock] and the block RAM. I'm going to leave the PLL for the moment and in the next blog do something with a block RAM.

Further information

[1] https://community.element14.com/technologies/fpga-group/b/blog/posts/lattice-ice40up5k-evb-evaluation-board-outputing-audio-sine-waves-over-an-optical-s-pdif-interface

[2] https://www.latticesemi.com/en/Products/FPGAandCPLD/iCE40UltraPlus

[3] https://www.latticesemi.com/products/developmentboardsandkits/ice40ultraplusbreakoutboard

[4] https://uk.farnell.com/lattice-semiconductor/ice40up5k-b-evn/breakout-board-ice40-ultraplus/dp/3770328
[�71.32 + VAT each, Feb 2026]
[5] https://en.wikipedia.org/wiki/Double-sideband_suppressed-carrier_transmission

  • Sign in to reply

Top Comments

  • jc2048
    jc2048 22 hours ago in reply to Jan Cumps +1
    I'm simply using the multiply operator in VHDL (look for the '*' on line 128), knowing that the synthesis will be sensible and do it by utilising the multiplier in the DSP block. That's inference. I infer…
  • jc2048
    jc2048 22 hours ago in reply to Jan Cumps

    I'm simply using the multiply operator in VHDL (look for the '*' on line 128), knowing that the synthesis will be sensible and do it by utilising the multiplier in the DSP block. That's inference. I infer in the code that that's what I want it to do and hope the synthesis understands what I want. For something like this it will, provided there are hard multipliers there that can be used, because it's a very obvious thing that people are going to do. I do check afterwards that it has given me what I want.

    The synthesis has quite lot of clues as to how to go about connecting it because of the nature of VHDL (heavily typed). It knows the wordsize and that the words are signed quantities from the signal declaration. I also help it by picking up the result with a 32-bit signed signal, and only afterwards reducing it to the 16 bits I need for the s/pdif. In terms of using the registers or not, it can decide whether to use them or not based on the code - my code is all step-by-step running on a clock, so there are going to be registers, but not necessarily the ones in the DSP block.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz 23 hours ago

    Nice work! And that looks like a great dev board for experimenting/learning.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps 23 hours ago

    Where do you define that it uses the DSP IP block?

    note to self: It's been too long since I last used VHDL, and I'm losing the knowledge fast.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2026 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube