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.

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.
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.
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.
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
[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
Top Comments
-
Jan Cumps
-
Cancel
-
Vote Up
0
Vote Down
-
-
Sign in to reply
-
More
-
Cancel
-
jc2048
in reply to Jan Cumps
-
Cancel
-
Vote Up
+1
Vote Down
-
-
Sign in to reply
-
More
-
Cancel
Comment-
jc2048
in reply to Jan Cumps
-
Cancel
-
Vote Up
+1
Vote Down
-
-
Sign in to reply
-
More
-
Cancel
Children