I am having some trouble using aggregates in my VHDL test bench (short hand shown below).
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all
entity TB is
end entity;
architecture RTL of TB is
-- constant(s)
constant CLK_PERIOD : time := 10 ns; -- 100 MHz
-- signal(s)
signal CLK : std_logic := '0';
signal nRST : std_logic := '1';
signal CONFIG_REG : std_logic_vector(7 downto 0) := (others => '0');
begin
-- clock driver
CLK <= NOT CLK after (CLK_PERIOD / 2.0);
-- main process
process
begin
-- reset driver...
nRST <=
'1',
'0' after (CLK_PERIOD * 1);
-- set initial configuration...
CONFIG_REG <= (
6 => '1',
3 downto 2 => "01",
7 | 0 => '1',
others => '0'
);
-- do test-bench stuff...
-- update configuration...
CONFIG_REG <= (
6 => '0',
3 downto 2 => "10",
7 | 0 => '1',
others => '0'
);
-- do more test-bench stuff...
end process;
end architecture;
I really want to 'name' the parts of the configuration register so that it actually reads well.
So I want to say:
Bit[6] = ENABLE
Bit[3:2] = MODE
Bit[7|0] = READY_DONE
I know that I can use a constant for the 6:
constant ENABLE : integer := 6;
and then my code looks like this:
CONFIG_REG <= (
ENABLE => '1',
3 downto 2 => "01",
7 | 0 => '1',
others => '0'
);
But I've been stumped to try and get the range 3 downto 2 and the 7|0 named so that the code looks like:
CONFIG_REG <= (
ENABLE => '1',
MODE => "01",
READY_DONE => '1',
others => '0'
);
I thought I might be able to accomplish this using aliasing and I've been looking at the VHDL Golden Reference Guide (p.15) which has been pretty helpful as far as understanding aliasing and ranges go, but I still cannot figure out how to name a range itself or an 'or'(|) of values.
Currently I have the below 'hack', which I'm not really fond of...
constant ENABLE : integer := 6;
alias MODE is CONFIG_REG(3 downto 2);
-- what to do about the OR though???
CONFIG_REG <= (
ENABLE => '1',
MODE'RANGE => "01",
7 | 0 => '1',
others => '0'
);
I really want to make my test-bench readable so that when I look at it 6 mo. from now, I'll know what it's doing without having to go and figure out "Now what was bit[6] again???" or in the event I have to hand off my code to another developer, they can easily get an idea of what I was trying to accomplish.
Any help / advice would be appreciated on how to do this.
Thanks for reading.
EDIT: Fixed the 7 | 0 to be valid:
Invalid:
7 | 0 => "10",
Valid:
7 | 0 => '1',
Beware, your code is not valid: the aggregate notation 7 | 0 stands for groups of indices, not vectors. It should be associated std_logic values, not std_logic_vector. Moreover, in VHDL versions prior 2008, the aggregate notation 3 downto 2 should also be associated std_logic values:
-- set initial configuration...
CONFIG_REG <= (
6 => '1',
3 downto 2 => '1',
7 | 0 => '0',
others => '0'
);
In VHDL 2008 the association between choices that are discrete ranges and expressions of the type of the aggregate are now supported. So, the 3 downto 2 => "01" is OK in VHDL 2008. But as VHDL 2008 is still not fully supported by many synthesizers, you should probably be careful, unless this code is not supposed to be synthesized, of course.
Anyway, using records instead of vectors could be an option for your problem. And in case you also need a vector version of the data, you could very easily write conversion functions between vector and record types. Example:
package foo is
type config_type is record
ready: std_ulogic;
enable: std_ulogic;
foobar: std_ulogic_vector(1 downto 0);
mode: std_ulogic_vector(1 downto 0);
reserved: std_ulogic;
done: std_ulogic;
end record;
function rec2vec(v: config_type) return std_ulogic_vector;
function vec2rec(v: std_ulogic_vector) return config_type;
end package foo;
package body foo is
function rec2vec(v: config_type) return std_ulogic_vector is
begin
return v.ready & v.enable & v.foobar & v.mode & v.reserved & v.done;
end function rec2vec;
function vec2rec(v: std_ulogic_vector) return config_type is
constant vv: std_ulogic_vector(7 downto 0) := v;
begin
return (ready => vv(7), enable => vv(6), foobar => vv(5 downto 4),
mode => vv(3 downto 2), reserved => vv(1), done => vv(0));
end function vec2rec;
end package body foo;
You could then use the aggregate notation to assign records:
signal config_reg: config_type;
...
config_reg <= (
ready => '1',
enable => '1',
foobar => "--",
mode => "01",
others => '0'
);
And convert to-from vectors:
signal config_reg_v: std_ulogic_vector(7 downto 0);
...
config_reg_v <= rec2vec(config_reg);
...
config_reg <= vec2rec(config_reg_v);
...
config_reg <= vec2rec(X"ff");
Note: I used std_ulogic and std_ulogic_vector instead of the resolved std_logic and std_logic_vector. There are good reasons for that but it is another question.
I prefer to define full width constants in my package:
subtype ConfigRegType is std_logic_vector(7 downto 0) ;
constant CFG_ENABLE : ConfigRegType := B"0_1_000000" ;
constant CFG_MODE0 : ConfigRegType := B"0000_00_00" ;
constant CFG_MODE1 : ConfigRegType := B"0000_00_00" ;
constant CFG_MODE2 : ConfigRegType := B"0000_10_00" ;
constant CFG_MODE3 : ConfigRegType := B"0000_11_00" ;
constant CFG_READY : ConfigRegType := B"1_0000000" ;
constant CFG_DONE : ConfigRegType := B"0000000_1" ;
. . .
Now when you are ready to write to it, simply "or" the value set you want:
CONFIG_REG <= CFG_ENABLE or CFG_MODE1 or CFG_READY or CFG_DONE ;
I have played with other ways, however, like you are noting they seem to require knowledge of details of the implementation.
Related
I am studying VHDL for my degree and I was asked to fix the error in this code, but after many tries I cannot manage to make it run. The compiler returns "Mem_Addr is used but not declared", highlighting the line that I mention below. I cannot manage to declare Mem_Addr properly.
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
LIBRARY altera_mf;
USE altera_mf.altera_mf_components.ALL;
ENTITY Ifetch IS
PORT( SIGNAL Instruction : OUT STD_LOGIC_VECTOR( 31 DOWNTO 0 );
SIGNAL PC_plus_4_out : OUT STD_LOGIC_VECTOR( 7 DOWNTO 0 );
SIGNAL Add_result : IN STD_LOGIC_VECTOR( 7 DOWNTO 0 );
SIGNAL Branch : IN STD_LOGIC;
SIGNAL Zero : IN STD_LOGIC;
SIGNAL PC_out : OUT STD_LOGIC_VECTOR( 9 DOWNTO 0 );
SIGNAL clock, reset : IN STD_LOGIC);
END Ifetch;
ARCHITECTURE behavior OF Ifetch IS
SIGNAL PC, PC_plus_4 : STD_LOGIC_VECTOR( 9 DOWNTO 0 );
SIGNAL next_PC : STD_LOGIC_VECTOR( 7 DOWNTO 0 );
BEGIN
--ROM for Instruction Memory
data_memory: altsyncram
GENERIC MAP (
operation_mode => "ROM",
width_a => 32,
widthad_a => 8,
lpm_type => "altsyncram",
outdata_reg_a => "UNREGISTERED",
-- Reads in mif file for initial data memory values
init_file => "program.mif",
intended_device_family => "Cyclone")
-- Fetch next instruction from memory using PC
PORT MAP (
clock0 => clock,
-- ERROR HERE
address_a => Mem_Addr,
--
q_a => Instruction
);
-- Instructions always start on a word address - not byte
PC(1 DOWNTO 0) <= "00";
-- copy output signals - allows read inside module
PC_out <= PC;
PC_plus_4_out <= PC_plus_4;
-- send word address to inst. memory address register
Mem_Addr <= Next_PC;
-- Adder to increment PC by 4
PC_plus_4( 9 DOWNTO 2 ) <= PC( 9 DOWNTO 2 ) + 1;
PC_plus_4( 1 DOWNTO 0 ) <= "00";
-- Mux to select Branch Address or PC + 4
Next_PC <= X"00" WHEN Reset = '1' ELSE
Add_result WHEN ( ( Branch = '1' ) AND ( Zero = '1' ) )
ELSE PC_plus_4( 9 DOWNTO 2 );
-- Store PC in register and load next PC on clock edge
PROCESS
BEGIN
WAIT UNTIL ( clock'EVENT ) AND ( clock = '1' );
IF reset = '1' THEN
PC <= "0000000000" ;
ELSE
PC( 9 DOWNTO 2 ) <= Next_PC;
END IF;
END PROCESS;
END behavior;
You're connecting the address port of the ram to a signal called mem_addr - only that signal does not exist because you didnt declare it.
You need something like this next to the other signals:
signal Mem_Addr : std_logic_vector(7 downto 0);
First of all, in the Entity declaration the attributes 'SIGNAL' are unnecessary. Regarding your problem, you did not declare Mem_Addr signal. You have to declare it in the architecture :
architecture BEHAVIOR of ...
begin
signal Mem_Addr : std_logic_vector(X downto 0);
Make sure to match X to the length-1 of the signal you connect it to (address_a)
Also, you have to drive the Mem_Addr signal with some signal carrying address values. Otherwise this signal will have undefined value, or be equal to the default value and will not impact the data memory.
So to fix this issue you have to find out if any signal in the Entity carries the address that you want to pass to the altsyncram memory or find out how to determine it based on the input signals of your component.
I am trying to convert some Verilog code to VHDL. I have difficulties to translate initial block in Verilog to VHDL properly.
As far as I know, the initial block corresponds to the process statement without a sensitivity list but we have to add a "wait" statement before the "end process".I tried it but it did not work. I tried some other methods too (using exit clause, conditional clause ( wait until), "for- generate" without process, etc) but none was successful.
Here is the Verilog code I want to convert, and it works properly
module MyRAM #(parameter DATA_WIDTH=24, parameter ADDR_WIDTH=10)
(
input [(DATA_WIDTH-1):0] data,
input [(ADDR_WIDTH-1):0] read_addr, write_addr,
input we, clk,
output reg [(DATA_WIDTH-1):0] q
);
// Declare the RAM variable
reg [DATA_WIDTH-1:0] ram[2**ADDR_WIDTH-1:0];
initial
begin : INIT
integer i;
for(i = 1; i < ((2**ADDR_WIDTH)-1); i = i+1) begin
if (i == 132) ram[i] = 24'h550000;
else if (i == 133) ram[i] = 24'h005500;
else if (i == 134) ram[i] = 24'h000055;
else ram[i] = 24'h000000;
end
//*/
end
always # (negedge clk)
begin
// Write
if (we)
ram[write_addr] <= data;
q <= ram[read_addr];
end
endmodule
and this is the VHDL code I have written so far:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity MyRAM is
generic
(DATA_WIDTH: integer;
ADDR_WIDTH: integer);
port
(
data :in std_logic_vector ((DATA_WIDTH-1) downto 0);
read_addr :in std_logic_vector((ADDR_WIDTH-1) downto 0);
write_addr :in std_logic_vector(( DATA_WIDTH-1) downto 0);
we :in std_logic;
clk :in std_logic;
q :out std_logic_vector( 23 downto 0)
);
end MyRAM;
architecture behavioral of MyRAM is
constant case1:std_logic_vector(23 downto 0):=
(16=>'1',18=>'1',20=>'1',22=>'1',others=>'0');
constant case2:std_logic_vector(23 downto 0):=
(8=>'1',10=>'1',12=>'1',14=>'1',others=>'0');
constant case3:std_logic_vector(23 downto 0):=
(0=>'1',2=>'1',4=>'1',6=>'1',others=>'0');
type ram is array ( 0 to (2**ADDR_WIDTH-1)) of
std_logic_vector((DATA_WIDTH-1) downto 0);
shared variable origram:ram;
signal s_q: std_logic_vector(23 downto 0);
begin
process
begin
for ii in 1 to (2**ADDR_WIDTH-1) loop
if (ii = 132) then
origram(ii) := case1;
elsif (ii = 133) then
origram(ii) := case2;
elsif (ii = 134) then
origram(ii) := case3;
else
origram(ii) :=(others=>'0');
end if;
end loop;
wait;
end process;
process (clk)
begin
if falling_edge(clk) then
if (we ='1') then
origram(to_integer(unsigned(write_addr))) := data;
s_q <= origram(to_integer(unsigned(read_addr)));
end if;
end if;
end process;
q<=s_q;
end behavioral;
And this is the error message:
Error (10533): VHDL Wait Statement error at MyRAM.vhd(88): Wait Statement must contain condition clause with UNTIL keyword
I do not have much experience in these languages, so I would appreciate any kind of help
The answer is both yes and no. While yes, you can do pretty much what you can do in an initial block in a process, in your situation the answer is you are actually initialising a signal. For this you need to use a function, and set the initial value:
type ram is array ( 0 to (2**ADDR_WIDTH-1)) of std_logic_vector((DATA_WIDTH-1) downto 0);
function init_ram return ram is
variable r : ram;
begin
-- set the contents of the ram
end function init_ram;
shared variable origram:ram := init_ram;
Processes with wait at the end are only for simulation (which would mimic an initial block in verilog used for testbench stimulus)
Note: from VHDL 2002, using a shared variable like this is illegal as it should be a protected type (which is not synthesisable currently). The only reason you might want a shared variable (rather than a signal) to infer a ram is to get write-before-read behaviour in a RAM. It is very annoying most of the Xilinx Inference examples use a shared variable. Switching your code to VHDL2008 will throw the error mentioned above.
A process with a ram variable instead of a shared variable can provide an initial value as well:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity MyRAM is
generic (
DATA_WIDTH: integer;
ADDR_WIDTH: integer
);
port (
data: in std_logic_vector (DATA_WIDTH - 1 downto 0);
read_addr: in std_logic_vector (ADDR_WIDTH - 1 downto 0);
write_addr: in std_logic_vector (DATA_WIDTH - 1 downto 0);
we: in std_logic;
clk: in std_logic;
q: out std_logic_vector (DATA_WIDTH - 1 downto 0)
);
end entity MyRAM;
architecture behavioral of MyRAM is
constant case1: std_logic_vector(23 downto 0) :=
(16 => '1', 18 => '1', 20 => '1', 22 => '1', others => '0');
constant case2: std_logic_vector(23 downto 0) :=
( 8 => '1', 10 => '1', 12 => '1', 14 => '1', others => '0');
constant case3: std_logic_vector(23 downto 0) :=
( 0 => '1', 2 => '1', 4 => '1', 6 => '1', others => '0');
type ram is array ( 0 to 2 ** ADDR_WIDTH - 1) of
std_logic_vector(DATA_WIDTH - 1 downto 0);
begin
MY_RAM:
process (clk)
function init_origram return ram is
variable ramval: ram;
begin
for ii in ram'left to ram'right loop
if ii = 132 then -- note the presumption ram has at least 135 elements
ramval(ii) := case1;
elsif ii = 133 then
ramval(ii) := case2;
elsif ii = 134 then
ramval(ii) := case3;
else
ramval(ii) := (others => '0');
end if;
end loop;
return ramval;
end function;
variable origram: ram := init_origram;
begin
if falling_edge(clk) then
if we = '1' then -- write before read
origram(to_integer(unsigned(write_addr))) := data;
end if;
q <= origram(to_integer(unsigned(read_addr)));
end if;
end process;
end architecture behavioral;
This would be useful in IEEE Std 1076-2000, -2002 and -2008 compliant tool chains where shared variables are required to be protected types as well as earlier standard revisions.
IEEE Std 1076-2008
9.3.3 Aggregates
9.3.3.1 General:
element_association ::=
[ choices => ] expression
choices ::= choice { | choice }
You can also use the separator '|` to provide multiple values for choices:
constant case1: std_logic_vector(23 downto 0) :=
-- (16 => '1', 18 => '1', 20 => '1', 22 => '1', others => '0');
(16 | 18 | 20 | 22 => '1', others => '0');
or even provide a base specifier X bit string for a hexidecimal value here (15.8 Bit string literals).
I am working on a single cycle processor using vhdl. I was trying to solve bugs in the code but eventually we were trapped in two situations in the instruction memory and data memory(in imem & dmem):
there is a part in the code ( “wait on a”) that can’t be synthesized on quarus and also a problem with loops that make the code loop infinitely on quartus
we tried to replace loops and (wait on a) with (process(a)) but also we have a problem that there can’t be a process inside another process.
How can I solve these bugs?
library IEEE;
use IEEE.STD_LOGIC_1164.all; use STD.TEXTIO.all;
use IEEE.STD_LOGIC_arith.all;
entity imem is -- instruction memory
port(a: in STD_LOGIC_VECTOR(31 downto 0);
rd: out STD_LOGIC_VECTOR(31 downto 0));
end;
architecture behave of imem is -- instruction memory
type ramtype is array (63 downto 0) of STD_LOGIC_VECTOR(31 downto 0);
begin
process is
file mem_file: TEXT;
variable L: line;
variable ch: character;
variable i, index, result: integer;
--type ramtype is array (63 downto 0) of STD_LOGIC_VECTOR(31 downto 0);
variable mem: ramtype;
begin
-- initialize memory from file
for i in 0 to 63 loop -- set all contents low
mem(i) := (others => '0');
end loop;
index := 0;
FILE_OPEN(mem_file, "memfile.dat", READ_MODE);
while not endfile(mem_file) loop
readline(mem_file, L);
result := 0;
for i in 1 to 8 loop
read(L, ch);
if '0' <= ch and ch <= '9' then
result := character'pos(ch) - character'pos('0');
elsif 'a' <= ch and ch <= 'f' then
result := character'pos(ch) - character'pos('a') + 10;
elsif 'A' <= ch and ch <= 'F' then
result := character'pos(ch) - character'pos('A') + 10;
else report "Formaterror on line " & integer'image(index)
severity error;
end if;
mem(index)(35-i*4 downto 32-i*4) :=conv_std_logic_vector(result,4);
end loop;
index := index + 1;
end loop;
-- read memory
loop
rd <= mem(conv_integer(unsigned(a(7 downto 2))));
wait on a;
end loop;
end process;
end;
As reported by #Tricky, I overlooked that you're using quartus. As far was we both know, Quartus does not support reading files in initializer functions. The whole package textio for file I/O is not supported / ignored. You need to use the Altera Mega Function primitives from VHDL library altera_mf called altsyncram to represent a RAM or ROM. The PoC-Library has an implementation here.
altsyncram example for a single-port RAM:
library altera_mf;
use altera_mf.all;
mem : altsyncram
generic map (
address_aclr_a => "NONE",
indata_aclr_a => "NONE",
init_file => INIT_FILE,
intended_device_family => getAlteraDeviceName(DEVICE),
lpm_hint => "ENABLE_RUNTIME_MOD = NO",
lpm_type => "altsyncram",
numwords_a => DEPTH,
operation_mode => "SINGLE_PORT",
outdata_aclr_a => "NONE",
outdata_reg_a => "UNREGISTERED",
power_up_uninitialized => "FALSE",
widthad_a => A_BITS,
width_a => D_BITS,
width_byteena_a => 1,
wrcontrol_aclr_a => "NONE"
)
port map (
clocken0 => ce,
wren_a => we,
clock0 => clk,
address_a => a_sl,
data_a => d,
q_a => q
);
Source: https://github.com/VLSI-EDA/PoC/blob/master/src/mem/ocram/altera/ocram_sp_altera.vhdl?ts=2
Original answer:
You need to put your RAM initializing code into a function, which is returning the initial values for your RAM. In the function, you read the external file and convert each line to a memory value.
Here are some snippets to get you onto the right way:
architecture a of e is
type ram_type is array(natural range <>) of std_logic_vector(31 downto 0);
function initialize(
constant file_name : string;
constant size : positive
) return ram_type is
file mem_file : text;
variable result : ram_type(0 to size - 1);
begin
file_open(mem_file, file_name, READ_MODE);
while not endfile(mem_file) loop
-- ... read and convert content
end loop;
file_close(mem_file);
return result;
end function;
signal mem : ram_type := initialize("memfile.dat", 64);
begin
process(a)
begin
rd <= mem(to_integer(unsigned(a(7 downto 2))));
end process;
More hints:
Normally reading an instruction memory (BlockRAM) is a clocked process. Currently you are reading asynchronously on every address change.
A wait on statement is equivalent to a process with a sensitivity list.
You should close opened files.
Don't use package STD_LOGIC_arith, use numeric_std instead.
I'm trying to implement an encryption algorithm in VHDL, and I have created a Feedback Shift Register component with generic parameters to improve reusability. It's my first time using generics and arrays, so please bear with me.
This component takes the feedback bits as an input, and it connects some of its bits (taps) to an output port, but this connections can be changed using a generic parameter. Code for the FSR component:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.fsr_taps_type.all;
entity FSR is
generic (
r_WIDTH : integer; -- Register width
r_STEP : integer; -- Update step
r_FWIDTH : integer; -- Feedback output width
r_HWIDTH : integer; -- h-function output width
r_TAPS : TAPS; -- Change the size according to the number of taps
r_STATE : TAPS
);
port (
clk : in std_logic;
rst : in std_logic;
fb_in : in std_logic_vector ((r_STEP-1) downto 0);
init : in std_logic;
ini_data : in std_logic_vector ((r_WIDTH-1) downto 0);
out_data : out std_logic_vector ((r_STEP-1) downto 0);
fb_out : out std_logic_vector ((r_FWIDTH-1) downto 0);
h_out : out std_logic_vector ((r_HWIDTH-1) downto 0)
);
end entity;
architecture behavioural of FSR is
signal shifted,shifted_next : std_logic_vector((r_WIDTH-1) downto 0);
begin
process(clk,rst)
begin
if rst = '1' then
shifted <= (others => '0');
elsif clk'event and clk = '1' then
shifted <= shifted_next;
end if;
end process;
process (fb_in,init,ini_data,shifted)
begin
if init = '1' then
shifted_next <= ini_data;
else
shifted_next <= shifted((r_WIDTH-r_STEP-1) downto 0) & fb_in;
end if;
end process;
out_data <= shifted ((r_WIDTH-1) downto (r_WIDTH-r_STEP));
-- The bits defined in the r_TAPS and r_STATE arrays are connected to the outputs in the same order as they are written (left to right)
-- Example: r_TAPS := (10,6) will create fb_out (1 downto 0) = bit10 & bit 6, in that order
-- Connect taps in the order of r_TAPS
gen_feedback: for I in (r_FWIDTH-1) downto 0 generate
fb_out(I) <= shifted(r_STATE(r_FWIDTH-I-1));
end generate gen_feedback;
-- Connect output bits for h function
gen_h: for I in (r_HWIDTH-1) downto 0 generate
h_out(I) <= shifted(r_STATE(r_HWIDTH-I-1));
end generate gen_h;
end architecture;
My problem comes when instantiating this component twice in the same file, using different generic values. This is the generic map of both instances:
LFSR : FSR
generic map (
r_WIDTH => 128,
r_STEP => STEP,
r_FWIDTH => 6,
r_HWIDTH => 7,
r_TAPS (0 to 5) => (128,121,90,58,47,32),
r_STATE (0 to 6) => (34,49,68,86,108,115,120)
)
NFSR : FSR
generic map (
r_WIDTH => 128,
r_STEP => STEP,
r_FWIDTH => 29,
r_HWIDTH => 2,
r_TAPS (0 to 28) => (40,36,35,33,106,104,103,58,50,46,117,115,111,110,88,80,101,69,67,63,125,61,60,44,128,102,72,37,32),
r_STATE => (33,116)
)
When I only create the first instance, the elaboration works as expected and Vivado gives no error whatsoever. However, when I add the second instance, I get an out of range error:
ERROR: [Synth 8-97] array index 0 out of range (FSR.vhdl:54)
Line 54 is the line inside the first for generate loop:
fb_out(I) <= shifted(r_STATE(r_FWIDTH-I-1));
Only the second instance gives an error. I have tried copying the parameters from the first instance into the second, and I still get the same error.
What am I doing wrong?
EDIT: I added the whole code for the FSR component.
EDIT 2: I changed the type declaration for TAPS, so that the array is constrained:
type TAPS is array (0 to 31) of integer;
This seems to be working, I just have to add an others statement to fill the unused array numbers, so this:
r_TAPS (0 to 5) => (128,121,90,58,47,32)
Becomes this:
r_TAPS (0 to 5) => (128,121,90,58,47,32,others =>0)
As I said before, I'm new to arrays in VHDL, so I would like to know if there is a way to do this for arbitrarily long arrays using an unconstrained array type.
Assuming a fifo with record type in and out, is there a simple way to handle the mapping between the record type in the input and output of the fifo wrapper and std_logic_vector in the actual fifo?
As presented, you can hide the conversation in a package. So you have 2 functions to and from FIFO interface to a record.
Rob Gaddi (vice-chair at the IEEE WG for VHDL) presented once a set of helper procedures for faster packing and unpacking std_logic_vectors to and from record types.
See his proposed pack and unpack subprograms.
The working group itself had long discussions on how to solve such an issue. There are 3 possibilities:
VHDL supports generic types on entity declarations. So a VHDL-2008 compliant synthesis tool could create a generic FIFO accepting any type *) as a FIFO element.
VHDL-2017 adds a reflection API that can at least solve the conversation from record to std_logic_vector. I'm working on a idea how to implement the reverse path.
A direct solution to iterate record elements was not found yet and is delayed to the next revision (VHDL-2020)
*) Any synthesizable scalar, array or record type.
The simplest way that I've been able to come up with so far is as follows:
First a function package to tidy up constant range declaration:
library ieee;
use ieee.std_logic_1164.all;
package Fun_pck is
function fLenAtTopOf(a : natural; b: std_logic_vector) return natural;
function fTopOf(b : std_logic_vector) return natural;
end package Fun_pck;
package body Fun_pck is
function fLenAtTopOf(a : natural; b: std_logic_vector) return natural is
begin
return a+b'left;
end function fLenAtTopOf;
function fTopOf(b: std_logic_vector) return natural is
begin
return b'left+1;
end function fTopOf;
end package body Fun_pck;
Then two packages that contains helper definitions for the fifo:
library ieee;
use ieee.std_logic_1164.all;
use work.Fun_pck.all;
package ExampleFifo_pck_private is
-- This package is 'private', i.e. only meant to be seen by the package and
-- entity in this file. This is so that the elements will not have a name
-- clash. As they are top-level constants that would be a likely problem.
-- Fifo element constants.
type width_array is array (integer range <>) of integer;
constant cElementWidths : width_array(0 to 2) := (4, 8, 1);
constant cFifoElement0 : std_logic_vector(cElementWidths(0)-1 downto 0) := (others => '0');
constant cFifoElement1 : std_logic_vector(
fLenAtTopOf(cElementWidths(1), cFifoElement0) downto
fTopOf(cFifoElement0)) := (others => '0');
constant cFifoElement2 : std_logic_vector(
fLenAtTopOf(cElementWidths(2), cFifoElement1) downto
fTopOf(cFifoElement1)) := (others => '0');
-- General Form:
--constant cFifoElementN : std_logic_vector(
-- fLenAtTopOf(cElementWidths(N), cFifoElement[N-1]) downto
-- fTopOf(cFifoElement[N-1])) := (others => '0');
end package ExampleFifo_pck_private;
library ieee;
use ieee.std_logic_1164.all;
use work.Fun_pck.all;
use work.ExampleFifo_pck_private.all;
-- Fifo item type
type tExampleFifoData is record
A : std_logic_vector(cFifoElement0'length-1 downto 0);
B : std_logic_vector(cFifoElement1'length-1 downto 0);
C : std_logic_vector(cFifoElement2'length-1 downto 0);
end record tExampleFifoData;
-- Reset constant
constant cResetExampleFifoData : tExampleFifoData := (
A => cFifoElement0,
B => cFifoElement1,
C => cFifoElement2
);
-- Length Constant
constant cExampleFifoWidth : integer := fTopOf(cFifoElement2);
-- Data array type
type tExampleFifoData_Array is array (natural range<>) of tExampleFifoData;
end package ExampleFifo_pck;
and finally entity/architecture pair that wraps the fifo module, in this case Xilinx xpm:
library ieee;
use ieee.std_logic_1164.all;
use work.ExampleFifo_pck.all;
use work.ExampleFifo_pck_private.all;
library xpm;
use xpm.vcomponents.all;
entity ExampleFifo is
port (
iWrClk : in std_logic;
iRst : in std_logic;
iWrEn : in std_logic;
iWrData : in tExampleFifoData;
oWrFull : out std_logic;
oWrProgFull : out std_logic;
iRdClk : in std_logic;
iRdEn : in std_logic;
oRdData : out tExampleFifoData;
oRdEmpty : out std_logic
);
end entity ExampleFifo;
architecture RTL of ExampleFifo is
-- Internal vector signals
signal sWrData : std_logic_vector(cExampleFifoWidth-1 downto 0) := (others => '0');
signal sRdData : std_logic_vector(cExampleFifoWidth-1 downto 0) := (others => '0');
begin
sWrData(cFifoElement0'range) <= iWrData.A;
sWrData(cFifoElement1'range) <= iWrData.B;
sWrData(cFifoElement2'range) <= iWrData.C;
oRdData.A <= sRdData(cFifoElement0'range);
oRdData.B <= sRdData(cFifoElement1'range);
oRdData.C <= sRdData(cFifoElement2'range);
x_fifo : xpm_fifo_async
generic map (
-- check:
FIFO_MEMORY_TYPE => "distributed", --string; "auto", "block", or "distributed";
ECC_MODE => "no_ecc", --string; "no_ecc" or "en_ecc";
-- check:
RELATED_CLOCKS => 0, --positive integer; 0 or 1
-- check:
FIFO_WRITE_DEPTH => 32, --positive integer
-- modify:
WRITE_DATA_WIDTH => cExampleFifoWidth, --positive integer
WR_DATA_COUNT_WIDTH => 1, --positive integer
-- check:
PROG_FULL_THRESH => 27, --positive integer
FULL_RESET_VALUE => 0, --positive integer; 0 or 1;
read_mode => "fwft", --string; "std" or "fwft";
FIFO_READ_LATENCY => 0, --positive integer;
-- modify:
READ_DATA_WIDTH => cExampleFifoWidth, --positive integer
RD_DATA_COUNT_WIDTH => 1, --positive integer
PROG_EMPTY_THRESH => 10, --positive integer
DOUT_RESET_VALUE => "0", --string
CDC_SYNC_STAGES => 2, --positive integer
WAKEUP_TIME => 0 --positive integer; 0 or 2;
)
port map (
sleep => '0',
rst => iRst,
wr_clk => iWrClk,
wr_en => iWrEn,
din => sWrData,
full => oWrFull,
overflow => open,
wr_rst_busy => open,
rd_clk => iRdClk,
rd_en => iRdEn,
dout => sRdData,
empty => oRdEmpty,
underflow => open,
rd_rst_busy => open,
prog_full => oWrProgFull,
wr_data_count => open,
prog_empty => open,
rd_data_count => open,
injectsbiterr => '0',
injectdbiterr => '0',
sbiterr => open,
dbiterr => open
);
end architecture RTL;
This is a bit of trouble, but quite manageable to declare fifo. Each different fifo still needs own version the package and entity. Naturally they can be in a single file.
After this trouble, the actual use of the fifo is very simple:
library ieee;
use ieee.std_logic_1164.all;
use work.ExampleFifo_pck.all;
entity tb is
end entity tb;
architecture sim of tb is
signal iWrClk : std_logic := '1';
signal iRst : std_logic := '0';
signal sWrEn : std_logic := '0';
signal sWrData : tExampleFifoData;
signal sWrFull : std_logic;
signal sWrProgFull : std_logic;
signal iRdClk : std_logic := '1';
signal sRdEn : std_logic := '0';
signal sRdData : tExampleFifoData;
signal sRdEmpty : std_logic;
signal sTestIn : tExampleFifoData := cResetExampleFifoData;
signal sTestOut : tExampleFifoData;
constant tests : tExampleFifoData_Array(0 to 1) :=
(0 => (x"E", x"A7", "1"), 1 => (x"7", x"AC", "0"));
begin
iWrClk <= not iWrClk after 5 ns;
iRdClk <= not iRdClk after 7.2 ns;
ExFifo : entity work.ExampleFifo
port map (
iWrClk => iWrClk,
iRst => iRst,
iWrEn => sWrEn,
iWrData => sTestIn,
oWrFull => sWrFull,
oWrProgFull => sWrProgFull,
iRdClk => iRdClk,
iRdEn => sRdEn,
oRdData => sTestOut,
oRdEmpty => sRdEmpty
);
wr : process is
begin
iRst <= '1';
for i in 0 to 5 loop
wait until rising_edge(iWrClk);
end loop;
iRst <= '0';
wait for 150 ns;
for i in 0 to 1 loop
wait for 15 ns;
wait until rising_edge(iWrClk);
sTestIn <= tests(i);
sWrEn <= '1';
wait until rising_edge(iWrClk);
sWrEn <= '0';
wait until rising_edge(iWrClk);
end loop;
wait;
end process wr;
rd : process is
begin
wait until rising_edge(iRdClk);
sRdEn <= '0';
if(sRdEmpty = '0' ans sRdEn <= '0') then
sRdEn <= '1';
end if;
end process rd;
end architecture sim;
Made to be brief...
-- items within fifo
constant c_items : integer := 8;
constant c_width : integer :=
(-- ADD NEW ITEMS HERE
c_CWidth +
c_BWidth +
c_AWidth
);
type t_intVector is array (natural range <>) of integer;
constant c_start : t_intVector(c_items downto 0) :=
(-- INSERT/MODIFY FOR NEW ITEMS
0+c_AWidth+c_BWidth+c_CWidth, -- end
0+c_AWidth+c_BWidth, -- C
0+c_AWidth, -- B
0 -- A
);
-- MODIFY FOR ADDITIONAL ITEMS
procedure f_writeToFifo(
signal o_f : out std_logic_vector;
i_A : unsigned;
i_B : unsigned;
i_C : unsigned) is
begin
o_f <= i_C &
i_B &
i_A;
end procedure f_writeToFifo;
type t_data is record
A : unsigned(c_AWidth-1 downto 0);
B : unsigned(c_BWidth-1 downto 0);
C : unsigned(c_CWidth-1 downto 0);
end record;
function f_readA(i_f : std_logic_vector) return unsigned is begin return unsigned(i_f(c_start(3)-1 downto c_start(2))); end function f_readA;
function f_readB(i_f : std_logic_vector) return unsigned is begin return unsigned(i_f(c_start(2)-1 downto c_start(1))); end function f_readB;
function f_readC(i_f : std_logic_vector) return unsigned is begin return unsigned(i_f(c_start(1)-1 downto c_start(0))); end function f_readC;
function f_read (i_f : std_logic_vector) return t_data is begin
return ( C => f_readC(i_f),
B => f_readB(i_f),
A => f_readA(i_f) );
end function f_read;
Ignore the list/position/item creation. The procedure/record/functions are what I intended to highlight.