Arithmetic operations in vhdl. How to multiply std_logic vector by real number? - vhdl

For school tutorial I need to make a component that receives integer values in the interval 0 to 1000. The output return S=V*C, where C depends on:
C=1 when V is in range [0,10]
C=0.75 when V is in range [11,100]
C=0.3125 when V is in range [101,1000]
I tried the code below, but it doesn't compile. I guess, I have a problem with types. How should I program a real number to multiply with a std_logic_vector?
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity comp1 is
port(
V: in std_logic_vector(9 downto 0);
S:out std_logic_vector(13 dowto 0));
end comp1;
architecture syn of comp1 is
begin
process is
variable c: unsigned(4 downto 0);
variable r: unsigned(13 downto 0);
begin
if unsigned(V) < 11 then
c:=1;
elsif unsigned(V) < 101 then
c:=0.75;
elsif others =>
c:=0.3125;
end if;
r:=unsigned(V)*C;
S<=std_logic_vector(r(13 downto 0));
end process;
end architecture;

Your question is not fully clear: what do you need the code for. Depending on your answer, there are actually multiple solutions.
Prior problem: representation and rounding
As you already found out, seeing you use numeric_std, is that a std_logic_vector by itself doesn't represent any value. It's just an array of std_logic elements. Therefore you should not do any operation of bare std_logic_vectors.
Now, by casting the vector to an unsigned type, you define it as representing an (unsigned) integer value. But now you get the problem that integers cannot represent fractions. So what's 0.1 in integer? 0. That's easy. But what's 0.9 in integer? Well, that depends on your rounding scheme. If you simply truncate the number, then the answer is 0 again. But using standard rounding (+0.5), the answer is 1. You haven't told us what rounding scheme you want (I don't know if you thought about it)
P.s. why is your S 14 bits wide? If V is 10 bits and the largest factor is 1.0, then S will also only need 10 bits...
Implementations
Let's first define an entity
library ieee;
use ieee.std_logic_1164.all;
entity comp1 is
port(
V : in std_logic_vector(9 downto 0);
S : out std_logic_vector(9 downto 0)
);
end entity;
Real operations
You can just convert everything to floating point (real) and perform you operation. This will solve all rounding for you and you have much freedom. The problem is that real types are not (yet) supported in synthesis. Still, for a test it works as it should
architecture behavior of comp1 is
signal V_real, S_real : real;
use ieee.numeric_std.all;
begin
V_real <= real(to_integer(unsigned(V)));
S_real <=
V_real when V_real <= 10.0 else
V_real * 0.75 when V_real <= 100.0
else V_real * 0.3125;
S <= std_logic_vector(to_unsigned(integer(S_real), S'length));
end architecture;
Fixed-point
With VHDL-2008 they tried to bridge the problem of not having point-representation for synthesis, by introducing synthesizable fixed-point packages. When using these packages, you can even select the rounding scheme you want. This is because rounding requires extra resources and is not always necessary. Warning: Use of the packages takes some getting used to.
architecture behavior of comp1 is
use ieee.fixed_pkg.all;
signal V_fix : ufixed(9 downto 0);
signal C : ufixed(0 downto -15);
signal S_fix : ufixed(10 downto -15); -- range of V*C+1
use ieee.numeric_std.all;
begin
V_fix <= to_ufixed(V, V_fix);
C <= to_ufixed(1, C) when V_fix <= 10 else
to_ufixed(0.75, C) when V_fix <= 100 else
to_ufixed(0.3125, C);
S_fix <= V_fix * C;
S <= std_logic_vector(to_unsigned(S_fix, S'length));
end architecture;
p.s. as mentioned, you need to compile in VHDL-2008 mode for this to work.
Integer arithmetic
If you look at you multiplication factors, you can see that they can be represented by fractions:
0.75 = 3/4
0.3125 = 5/16
This mean you can simply use integer arithmetic to perform the scaling.
architecture behavior of comp1 is
signal V_int, S_int : integer range 0 to 1000;
use ieee.numeric_std.all;
begin
V_int <= to_integer(unsigned(V));
S_int <=
V_int when V_int <= 10 else
V_int*3/4 when V_int <= 100
else V_int*5/16;
S <= std_logic_vector(to_unsigned(S_int, S'length));
end architecture;
NB Integer arithmetic has no rounding scheme, thus numbers are truncated!
Low-level optimizations: Shift-and-add
In the comments Brian referred to using shift and add operations. Going back to the integer arithmetic section of my answer, you see that the denominators are actually powers-of-2, which can be realized using bit-shift operations
x/4 = x/(2^2) = x>>2 (right shift by 2)
x/16 = x/(2^4) = x>>4 (rightshift by 4)
At the same time, the numerators can also be realized using bitshift and add operations
x*3 = x*"11"b => x + x<<1 (left shift by 1)
x*5 = x*"101"b => x + x<<2 (left shift by 2)
Both can be combined in one operations. Note, although you must remember that left shift will throw away the bits shifted out. This can cause a problem, as the fractions of the values are required for correct results. So you need to add bits to calculate the intermediate results.
architecture behavior of comp1 is
use ieee.numeric_std.all;
signal V_uns4, S_uns4 : unsigned(13 downto 0); -- add 4 bits for correct adding
begin
V_uns4 <= resize(unsigned(V),V_uns4'length);
S_uns4 <=
shift_left(V_uns4,4) when V_uns4 <= 10 else
shift_left(V_uns4,3) + shift_left(V_uns4,2) when V_uns4 <= 100 -- "11" >> 2
else shift_left(V_uns4,2) + V_uns4; --"101" >> 4
S <= std_logic_vector(resize(shift_right(S_uns4,4),S'length));
end architecture;
This method will likely require the least resourses in synthesis. But is does require low level optimizations, which require additional design effort.
Testbench
Here's how I tested my code
entity comp1_tb is end entity;
library ieee;
architecture tb of comp1_tb is
use ieee.std_logic_1164.all;
signal V,S : std_logic_vector(9 downto 0);
use ieee.numeric_std.all;
signal V_int, S_int : integer range 0 to 1000;
begin
DUT: entity work.comp1
port map(
V => V,
S => S);
V <= std_logic_vector(to_unsigned(V_int, V'length));
S_int <= to_integer(unsigned(S));
V_stim : process begin
V_int <= 1;
wait for 1 ns;
assert (S_int = 1) report "S should be 1*1 = 1;" severity warning;
V_int <= 10;
wait for 1 ns;
assert (S_int = 10) report "S should be 10*1 = 10;" severity warning;
V_int <= 100;
wait for 1 ns;
assert (S_int = 75) report "S should be 100*0.75 = 75;" severity warning;
V_int <= 1000;
wait for 1 ns;
assert (S_int = 312 OR S_int = 313) report "S should be 1000*0.3125 = 312 or 313;" severity warning;
wait;
end process;
end architecture;

add V signal to process sensitivity list;
use shifting and addititon instead of direct multiplication as Brian said.

Related

Getting "test2.vhd(43): VHDL Compiler exiting" error with if-statement on commented out lines but not with the "sum2" lines

As the title says, I'm getting a compiler exiting error with the if-statement when I use the lines commented out with the "sum" value but not with the "sum" 2 value and I'm not sure why.
Code:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use IEEE.std_logic_unsigned.all;
entity test2 is port
(
a, b : IN unsigned( 3 DOWNTO 0 );
cin : IN unsigned;
sum : OUT unsigned( 4 DOWNTO 0 )
);
end test2;
architecture behavioral of test2 is
signal a_5, b_5, cin_5, sum2 : unsigned(4 downto 0) := (others => '0');
signal x, y : unsigned(4 downto 0) := (others => '0');
signal z : std_logic;
begin
a_5 <= ('0' & a);
b_5 <= ('0' & b);
cin_5 <= ('0' & '0' & '0' & '0' & cin);
sum <= a_5 + b_5 + cin_5;
sum2 <= a_5 + b_5 + cin_5;
process (sum2, b_5)
--process (sum, b_5)
begin
if (sum2 > b_5) then
--if (sum > b_5) then
z <= '1';
else
z <= '0';
end if;
end process;
end behavioral;
For some context:
I'm working on an adder that adds two 4bit numbers and eventually displays the decimal value on a 7seg display.
I want to take the "sum" value and check if it is greater than decimal value 9 and if so then it sets a flag to always have the 7seg display for the 10s value display a 1. (I only need to count up to decimal value 19). I can probably do this another method but I started doing it this way and got stuck here and I think this is something fundamental I am just not understanding.
sum is a port, which has the type "out". Ports out type "out" can't be read. If you want to read an output port, you must use the type "buffer". sum2 instead is a signal, which can always be read
(By the way you should only use numeric_std and not std_logic_unsigned, which is an old solution and not preferred anymore).

Vivado VHDL width mismatch - how can I fix it?

Please consider this very simple minimal reproducible code:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
entity test is
generic ( LENGTH : integer range 1 to 16 := 5 );
Port ( x : in STD_LOGIC;
y : out STD_LOGIC_VECTOR(15 downto 0)
);
end test;
architecture Behavioral of test is
signal a : std_logic_vector (15 downto 0);
signal b : std_logic_vector (LENGTH - 1 downto 0);
signal i : integer range 0 to LENGTH-1 := 1;
begin
y <= a;
process
begin
if i = LENGTH then
i <= 1;
else
a <= a(15 downto i + 1) & b(i downto 0);
end if;
i <= i + 1;
end process;
end Behavioral;
My need is to join some elements of b into a, depending on i. By running the RTL on Vivado, it says:
[Synth 8-690] width mismatch in assignment; target has 16 bits, source has 20 bits
I don't really get why. Anyhow, the overall range will be 15 - (i + 1) + (i - 0) = 15 ... 0 and fits in the 16 bits of output -- what's the deal for 20 bits?
I should say the problem vanishes (obviously) if I use plain constants instead of i, but I still don't get what's going on.
For runtime variable I (as per the question)...
instead of a big CASE, you can use the value of I to generate masks, and evaluate (A and MASKA) or (B and MASKB). Which is equivalent to the multiplexer the synthesis tool would generate if it wasn't broken.
For generic I (it's not fair to move the goalposts in the comments!)
this approach generates unnecessary hardware, which will be optimised out by any competent synthesis tool.
(There are of course other problems with this code; I assume you deleted the clock, taking the MCVE notion a bit too far. You should leave it valid synthesisable code)

how to create a xored ring oscillator using VHDL

I want to create xored oscillators using multiple inverters. The number of oscillator and inverter should be defined in generic. I have finished 1 oscillator but I don't know how to generate the same oscillator multiple times and let them xored.
this is a part of my code:
gen_ring_oscillator:
for i in 1 to NUM_INVERTER-1 generate
osc_chain(i)<= not osc_chain(i-1);
end generate;
ring_oscillator:process(osc_chain, en_oc, osc_reset)
begin
if (osc_reset = '1') then
osc_chain(0) <= '0';
elsif (en_oc = '1') then
osc_chain(0) <= osc_chain(NUM_INVERTER-1);
ro_out <= osc_chain(NUM_INVERTER-1);
end if;
end process;
I have alreday used osc_chain as a signal between the inverters.
By default VHDL assumes zero delay elements. As a result, the oscillation frequency will be 1/0 = error (infinitely large). This will cause an "maximum number of iterations reached" error
Thus you will have to configure the component delay manually, by adding after x ns with your assignment.
osc_chain(i)<= not osc_chain(i-1) after 10 ns;
More information can be found here and here.
Complete example (with some variation in delay):
library ieee;
use ieee.std_logic_1164.all;
entity ring_osc is
port(clk_out : out std_logic);
end entity;
architecture rtl of ring_osc is
signal osc_chain : std_logic_vector(2 downto 0) := (others => '0');
begin
gen_inv: for i in 0 to 2 generate
osc_chain(i) <= not osc_chain((i+1) mod 3) after (10 + i) * 1 ns;
end generate;
clk_out <= osc_chain(0);
end architecture;
Note: for a ring oscillator to work, you need an odd number of inverters.

VHDL Data Flow description of Gray Code Incrementer

I am trying to write the VHDL code for a Gray Code incrementer using the Data Flow description style. I do not understand how to translate the for loop I used in the behavioral description into the Data Flow description. Any suggestion?
This is my working code in behavioral description
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity graycode is
Generic (N: integer := 4);
Port ( gcode : in STD_LOGIC_VECTOR (N-1 downto 0);
nextgcode : out STD_LOGIC_VECTOR (N-1 downto 0));
end graycode;
architecture Behavioral of graycode is
begin
process(gcode)
variable bcode : STD_LOGIC_VECTOR(N-1 downto 0);
variable int_bcode : integer;
begin
for i in gcode'range loop
if(i < gcode'length - 1) then
bcode(i) := gcode(i) XOR bcode(i+1);
else
bcode(i) := gcode(i);
end if;
end loop;
int_bcode := to_integer(unsigned(bcode));
int_bcode := int_bcode + 1;
bcode := std_logic_vector(to_unsigned(int_bcode, N));
for i in gcode'range loop
if(i < gcode'length - 1) then
nextgcode(i) <= bcode(i) XOR bcode(i+1);
else
nextgcode(i) <= bcode(i);
end if;
end loop;
end process;
end Behavioral;
'Dataflow' means 'like it would look in a circuit diagram'. In other words, the flow of data through a real circuit, rather than a high-level algorithmic description. So, unroll your loops and see what you've actually described. Start with N=2, and draw out your unrolled circuit. You should get a 2-bit input bus, with an xor gate in it, followed by a 2-bit (combinatorial) incrementor, followed by a 2-bit output bus, with another xor gate, in it. Done, for N=2.
Your problem now is to generalise N. One obvious way to do this is to put your basic N=2 circuit in a generate loop (yes, this is dataflow, since it just duplicates harwdare), and extend it. Ask in another question if you can't do this.
BTW, your integer incrementor is clunky - you should be incrementing an unsigned bcode directly.
Dataflow means constructed of concurrent statements using signals.
That means using generate statements instead of loops. The if statement can be an if generate statement with an else in -2008 or for earlier revisions of the VHDL standard two if generate statements with the conditions providing opposite boolean results for the same value being evaluated.
It's easier to just promote the exception assignments to their own concurrent signal assignments:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity graycode is
generic (N: natural := 4); -- CHANGED negative numbers wont be interesting
port (
gcode: in std_logic_vector (N - 1 downto 0);
nextgcode: out std_logic_vector (N - 1 downto 0)
);
end entity graycode;
architecture dataflow of graycode is
signal int_bcode: std_logic_vector (N - 1 downto 0); -- ADDED
signal bcode: std_logic_vector (N - 1 downto 0); -- ADDED
begin
int_bcode(N - 1) <= gcode (N - 1);
TO_BIN:
for i in N - 2 downto 0 generate
int_bcode(i) <= gcode(i) xor int_bcode(i + 1);
end generate;
bcode <= std_logic_vector(unsigned(int_bcode) + 1);
nextgcode(N - 1) <= bcode(N - 1);
TO_GRAY:
for i in N - 2 downto 0 generate
nextgcode(i) <= bcode(i) xor bcode(i + 1);
end generate;
end architecture dataflow;
Each iteration of a for generate scheme will elaborate a block statement with an implicit label of the string image of i concatenated on the generate statement label name string.
In each of these blocks there's a declaration for the iterated value of i and any concurrent statements are elaborated into those blocks.
The visibility rules tell us that any names not declared in the block state that are visible in the enclosing declarative region are visible within the block.
These mean concurrent statements in the block are equivalent to concurrent statement in the architecture body here with a value of i replaced by a literal equivalent.
The concurrent statements in the generate statements and architecture body give us a dataflow representation.
And with a testbench:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity graycode_tb is
end entity;
architecture foo of graycode_tb is
constant N: natural := 4;
signal gcode: std_logic_vector (N - 1 downto 0);
signal nextgcode: std_logic_vector (N - 1 downto 0);
signal bcode: std_logic_vector (N - 1 downto 0);
begin
DUT:
entity work.graycode
generic map ( N => N)
port map (
gcode => gcode,
nextgcode => nextgcode
);
STIMULi:
process
variable gv: std_logic_vector (N - 1 downto 0);
variable bv: std_logic_vector (N - 1 downto 0);
begin
wait for 10 ns;
for i in 0 to 2 ** N - 1 loop
bv := std_logic_vector(to_unsigned( i, bv'length));
gv(N - 1) := bv (N - 1);
for i in N - 2 downto 0 loop
gv(i) := bv(i) xor bv(i + 1);
end loop;
gcode <= gv;
bcode <= bv;
wait for 10 ns;
end loop;
wait;
end process;
end architecture;
We can see the effects of incrementing int_bcode:

signal statement must use <= to assign value to signal

I've got this error in the expression "individuos(x):=cout" of the following code. What I'm trying to do is assign to each array of individuos a different random "cout" input sequentially. If I change the expression to "individuos <= cout", it'll asign the same "cout" to all "individuos", the same will happen if i trie to build a sequential statement with the assert function. How do I fix this?
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_TEXTIO.ALL;
use ieee.numeric_std.all;
--package genetica_type is
--type genetica is array(0 to 49) of unsigned(7 downto 0);
--type fitness is array(0 to 49) of unsigned (2 downto 0);
--end package genetica_type;
use work.genetica_type.all;
entity conexao is
Port (
clk : in bit;
cout: in unsigned (7 downto 0);
individuos: out genetica
);
end entity;
architecture Behavioral of conexao is
--type genetica is array (0 to 49) of std_logic_vector (7 downto 0);
--signal s_individuos : genetica;
--signal i: genetica;
begin
process (clk)
begin
If (clk 'event and clk = '1') then
for x in 0 to 49 loop
individuos(x) := cout;
end loop;
end if ;
end process;
end Behavioral;
I've got this error in the expression "individuos(x):=cout" of the following code.
That is a syntax error. Use <= exactly as the compiler says.
What I'm trying to do is assign to each array of individuos a different random "cout" input sequentially. If I change the expression to "individuos <= cout", it'll asign the same "cout" to all "individuos"
That is exactly what you ask it to do :
If (clk 'event and clk = '1') then
for x in 0 to 49 loop
individuos(x) <= cout;
end loop;
end if ;
On every rising clock edge, loop 50x performing 50 assignments, each of the same data, to all 50 addresses.
What I think you want to do, is, on every clock, perform ONE assignment, and increment the address to point to the next location.
signal x : natural range 0 to individuos'high;
...
if rising_edge(clk) then
individuos(x) <= cout;
x <= x + 1 mod individuos'length;
end if;
This code has several other differences from yours:
It uses the simpler rising_edge(clk) function
It will still work when you change the size of the input array.
It still has a bug : if you change the array lower bound to something other than 0, it will fail... for example:
type genetica is array(3 to 49) of ...
Easy to catch this with an assert:
Assert individuos'low = 0 report "Array Individuos bound error" severity failure;
It also runs continuously. If you want to start and stop it, or reset the address counter, or stop when it reaches 50, that takes additional logic.

Resources