Intel® Quartus® Prime Software
Intel® Quartus® Prime Design Software, Design Entry, Synthesis, Simulation, Verification, Timing Analysis, System Design (Platform Designer, formerly Qsys)
16672 Discussions

Dual Port RAM acces in VHDL

Altera_Forum
Honored Contributor II
3,042 Views

Hi, 

 

i read a lot of similar threads on this topic here but i could not find a solution to my (maybe suuper simple;) ) questions. 

I also would like to know if this approach is a good way to implement shared memory to pass 20 .. 30 parameters . 

 

The basic idea is to create a shared memory location for Nios to dump parameters and VHDL modules to read these parameters. 

So i created a dual port RAM in Qsys, connecting to the first slave only the data master from Nios and exporting the second slave. 

i connected a VHDL module to the second slave. The whole system looks like this: 

 

https://www.alteraforum.com/forum/attachment.php?attachmentid=8978  

 

 

In Qsys the dpram was instantiated with: 

datawidth 32 bit, 

total memory size 32768 bytes, (which results in a 13 bit wide address bus) 

single clock operation. 

The Address region lasts from 0x0400_0000 to 0x0400_7FFF. 

 

I checked if i could read and write from Nios to the memory and this works fine with: 

 

IOWR(SHARED_MEM_BASE,0,0xF); int a = IORD(SHARED_MEM_BASE,0); printf("%i",a); // results in 15  

 

For the VHDL module i thought it is sufficient to connect the address where i want to read from and read the data from this address. 

But i struggle to map the addresses.  

The base_address is h"4000000" for the first memory slave but how can i now access this region through the second slave? 

I would need 25 bit to reach this address but Qsys only gives me 13.  

I have the feeling this is a really stupid question, but right now i am really stuck. 

i read the Avalon Specification but i could not find a solution there. 

 

Thanks to all for reading!! 

 

Cheers 

Tim 

 

library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; entity MemoryMaster is port ( clk : in std_logic; reset_n : in std_logic; --av_m_chipselect : in std_logic; av_m_write : out std_logic; av_m_read : out std_logic; av_m_address : out std_logic_vector(12 downto 0); av_m_writedata : out std_logic_vector(31 downto 0); av_m_readdata : in std_logic_vector(31 downto 0); q : out std_logic ); end MemoryMaster; -- base address shared memory 0x4000 000 architecture behave of MemoryMaster is begin avalon_if : process (reset_n, clk) begin if ( reset_n = '0' ) then av_m_address <= (others => '0'); av_m_write <= '0'; av_m_read <= '0'; av_m_writedata <= (others => '0'); elsif clk'event and clk ='1' then --av_m_address <= (others => '0'); --av_m_address(26) <= '1'; av_m_address <=b"0_0100_0000_0000"; if (av_m_readdata = x"F") then q <= '1'; else q <= '0'; --if ( av_m_read_n = '0' and av_m_address = b"0" ) then --av_m_readdata <= reg_temp; --elsif ( av_m_write = '0' and av_m_address = b"1" ) then end if; end if; end process avalon_if; end behave;
0 Kudos
21 Replies
Altera_Forum
Honored Contributor II
2,095 Views

Your MMMaster and the second port of the DPRam form a 'private' bus so the memory addresses start from 0. Your code with an address signal of width 13 is fine. One warning though: because of the way the memory blocks are designed the readdata will be delayed by (at least) one clock after applying the readaddress.

0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

Thanks for the answer!! 

 

So the address range on my VHDL side goes from 0x0 to 0x7FFF. 

 

When i write in C to the base address of the shared memory i should be able to get this value when reading from address b"0_0000_0000_0000" in VHDL. But i do not see anything. 

 

Also the other way round, writing to memory from VHDL does not work. 

I apply the write byteenable and a cs signal and try to write values to different location with a 32 bit (4 byte) offset. Like 

addr0 := 0_0000_0000_0000 

addr1 := 0_0000_0000_0100 

addr2 := 0_0000_0000_1000 

Since the bus is private and it is a point to point connection a CS is not necessary, but i tried just in case. 

Then i want to check the values in C with  

for (offset = sizeof(unsigned int);( offset & (mem_size -1)) != 0 ; offset << 1 ){ printf(" %d", IORD_32DIRECT(SHARED_MEM_BASE,offset)); }  

my intention was, that uploading the *sof file first, the VHDL code gets executed first and after downloading the *.elf file the C Code reads from  

the same addresses and returns the values.  

But the memory is completely empty. Am i doing it wrong with the offsets? But then is should see at least some values in memory? 

Or are my read and write routines wrong? 

 

I ran a memory test program on the shared memory and it passed OK so i think i do not have timing issues.
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

What you describe should actually work. Without a view on the complete project it is not straightforward giving the 'good' advice. 

I'm no NIOS real expert as I haven't used a NIOS II CPU yet, but have co-worked on a project where the real programmer did the NIOS code and I the rest of the HW 

If you have specified a data cache you have to set bit 31 of the address to bypass the cache.
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

Ok as an attachment i uploaded the archived project. i hope this makes it clearer. 

Thanks a lot for reading through the code !!
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

 

--- Quote Start ---  

Ok as an attachment i uploaded the archived project. i hope this makes it clearer. 

 

--- Quote End ---  

 

Yes, that helps ... 

 

Now there are a few issues with the Memorymaster.vhd code: avalon_if : process(reset_n, clk) begin if (reset_n = '0') then av_m_address <= (others => '0'); av_m_write <= '0'; av_m_read <= '0'; av_m_writedata <= (others => '0'); elsif clk'event and clk = '1' then --av_m_address <= (others => '0'); --av_m_address(26) <= '1'; av_m_address <= b"0_0000_0000_0000"; if (av_m_readdata = x"F") then q <= '1'; else q <= '0'; --if ( av_m_read_n = '0' and av_m_address = b"0" ) then --av_m_readdata <= reg_temp; --elsif ( av_m_write = '0' and av_m_address = b"1" ) then av_m_write <= '1'; av_m_chipselect <= '1'; av_m_byteenable <= "1111"; av_m_address <= b"0_0000_0000_0000"; av_m_writedata <= X"0000000C"; av_m_address <= b"0_0000_0000_0001"; av_m_writedata <= X"0000000C"; av_m_address <= b"0_0000_0000_0010"; av_m_writedata <= X"0000000C"; av_m_address <= b"0_0000_0001_0000"; av_m_writedata <= X"0000000C"; av_m_address <= b"1_1111_1111_1111"; av_m_writedata <= X"0000000C"; --av_m_address <= b"0_0000_0000_0011"; --av_m_writedata <= X"0000000C"; av_m_write <= '0'; av_m_chipselect <= '0'; av_m_byteenable <= (others => '0'); end if; end if; end process avalon_if;  

 

The line: if (av_m_readdata = x"F") then will always return false as 'av_m_rreaddata' is wider than the width of x"F" 

From the lines: av_m_write <= '1'; av_m_chipselect <= '1'; av_m_byteenable <= "1111"; av_m_address <= b"0_0000_0000_0000"; av_m_writedata <= X"0000000C"; av_m_address <= b"0_0000_0000_0001"; av_m_writedata <= X"0000000C"; av_m_address <= b"0_0000_0000_0010"; av_m_writedata <= X"0000000C"; av_m_address <= b"0_0000_0001_0000"; av_m_writedata <= X"0000000C"; av_m_address <= b"1_1111_1111_1111"; av_m_writedata <= X"0000000C"; --av_m_address <= b"0_0000_0000_0011"; --av_m_writedata <= X"0000000C"; av_m_write <= '0'; av_m_chipselect <= '0'; av_m_byteenable <= (others => '0'); only the very last assignments av_m_address <= b"1_1111_1111_1111"; av_m_writedata <= X"0000000C"; av_m_write <= '0'; av_m_chipselect <= '0'; av_m_byteenable <= (others => '0'); will be synthesized and you are inherently not writing into the DPRam, so the NIOS II will not read anything other then the data initialized at start-up which will be all zeroes after loading the sof (unless you specify a .mif file with user-data) 

You can see in the attached RTL-snip what the Synthesizer made out of your .vhd source.
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

I aligned the compare value to the databus width and now reading works perfectly !!  

 

But i still do not understand why i cannot write. 

 

At the beginning of the process i assigned the control signals, then the address where i want to write and then the data. 

After all the transfers i set the control signals to zero. But is this sequential approach correct?  

I thought the RTL viewer shows only the last state of the signals, so the values are actually ok.  

When i keep the control signals all the time on high level the RTL shows the correct assignments, but the memory is still empty. 

 

https://www.alteraforum.com/forum/attachment.php?attachmentid=8987  

 

avalon_if : process (reset_n, clk) begin elsif clk'event and clk ='1' then av_m_write <= '1'; av_m_chipselect <= '1'; av_m_byteenable <= "1111"; av_m_address <= b"0_0000_0000_0000"; av_m_writedata <= X"0000000C"; end if; end process avalon_if;
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

The new code shown will write the value 0x0000000C to address 0 continuously. 

In the code:else q <= '0'; --if ( av_m_read_n = '0' and av_m_address = b"0" ) then --av_m_readdata <= reg_temp; --elsif ( av_m_write = '0' and av_m_address = b"1" ) then av_m_write <= '1'; av_m_chipselect <= '1'; av_m_byteenable <= "1111"; av_m_address <= b"0_0000_0000_0000"; av_m_writedata <= X"0000000C"; av_m_address <= b"0_0000_0000_0001"; av_m_writedata <= X"0000000C"; av_m_address <= b"0_0000_0000_0010"; av_m_writedata <= X"0000000C"; av_m_address <= b"0_0000_0001_0000"; av_m_writedata <= X"0000000C"; av_m_address <= b"1_1111_1111_1111"; av_m_writedata <= X"0000000C"; --av_m_address <= b"0_0000_0000_0011"; --av_m_writedata <= X"0000000C"; av_m_write <= '0'; av_m_chipselect <= '0'; av_m_byteenable <= (others => '0'); end if; 

 

the synthesiser will only honour the 'last' assignment for each signal, thus of al the address assignments in the above code : 

av_m_address <= b"0_0000_0000_0000"; av_m_address <= b"0_0000_0000_0001"; av_m_address <= b"0_0000_0000_0010"; av_m_address <= b"0_0000_0001_0000"; av_m_address <= b"1_1111_1111_1111"; 

only the very last will be synthesised as it 'wins'. 

If you want to do multiple writes you have to do a write per clock cycle and you have to understand sequential coding: I suggest you read pages 91-121 of V. Pedroni's book "Circuit Design with VHDL".
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

in the book there is a pretty nice example of how to access a RAM in VHDL. I see now that my code has some flaws.  

And i think now i have a good idea of how to implement multiple r/w operations. 

But i have one question for the nios side.  

When i constantly write to the base address i should be able the see this value when i scan the addresses.  

But it is empty. I implemented an init function to write direct once, but still no results. Is a sole IORD(baseaddr,0x0) the right way to do so?
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

I would connect the memory to the Nios as 'tightly coupled data memory' and then access it directly using normal C pointers (read up on the 'volatile' keyword). 

For effficency define a single C struct for the shared memory area and use a global register variable pointing to the base for all accesses. 

If you try hard enough you can use signaltap to monitor the nios's accesses to the tightly coupled memory.
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

I first tried to deactivate the cache then after changed the memory to tightly coupled memory but it still did not work. What i did not do from the beginning was setting the clken bit to one. And that finally solved my problem now i can write to RAM through VHDL. I neglected it because i read that this signal cannot be used in dual port mode with a single clock. I assume this assumtion is wrong but i did not find an explainationo for this type in the avalon spec or in the on-chip memory implementation manual. Could you explain it? Thank you two, helped me a loot :) 

 

Cheers  

Tim
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

Should be documented somewhere. 

From memory: 

In dual-clock mode there is a clken for each clock/port 

In single-clock mode there is an 'address hold' signal for each port. 

These have much the same function - although they are inverted. 

The nios uses these (on TCM) to hold the read data when there is a pipeline stall (since the address is already updated to that for the next instruction). 

At a guess there is also a global clken that would affect both ports - so you wouldn't normall want to use it. 

 

David
0 Kudos
Altera_Forum
Honored Contributor II
2,094 Views

hi all, i have problem with dual port ram to, that is when i comple dual port ram theey siad that i havr to use share variable for this, but as i know share variable just used for simulation, if i want to use for synthesizion and download this dual port ram to the board, how to do it ???

0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

i do not really understand your question. can you be a bit more precise?

0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

library ieee; use ieee.std_logic_1164.all; entity true_dpram_sclk is port ( data_a : in std_logic_vector(7 downto 0); data_b : in std_logic_vector(7 downto 0); addr_a : in natural range 0 to 63; addr_b : in natural range 0 to 63; we_a : in std_logic := '1'; we_b : in std_logic := '1'; clk : in std_logic; q_a : out std_logic_vector(7 downto 0); q_b : out std_logic_vector(7 downto 0) ); end true_dpram_sclk; architecture rtl of true_dpram_sclk is -- Build a 2-D array type for the RAM subtype word_t is std_logic_vector(7 downto 0); type memory_t is array(63 downto 0) of word_t; -- Declare the RAM shared variable ram : memory_t; begin -- Port A process(clk) begin if(rising_edge(clk)) then if(we_a = '1') then ram(addr_a) := data_a; end if; q_a <= ram(addr_a); end if; end process; -- Port B process(clk) begin if(rising_edge(clk)) then if(we_b = '1') then ram(addr_b) := data_b; end if; q_b <= ram(addr_b); end if; end process; end rtl;  

did you see the "shared variable " as i know this shared variable just use to simulation
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

There is nothing wrong with using a shared variable here. It is often used to get write-before-read behaviour from ram. If you use a signal, you get read before write. 

 

Quartus will synthesise '93 shared variables just fine.
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

Shared variables are usually not recommended for synthesis, as it is considered bad design practise, but it can be synthesized. If you need a dual clock RAM with write access from both clock domains, a shared variable is actually the only way to do it in VHDL. Sometimes we have to make exceptions on bad design practises ;)

0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

ok me again. Now i have a question about writing and reading from VHDL to memory. I designed a state machine to handel multiple rd/wr to different address locations.  

The idea is to write the counter to the first 10 locations. After that it should switch into the read state and stay there forever. There outputs depending to the value i write from C should be toggled. 

 

When i start a memory scan i C i see something in memory but not the desired counter values. 

In the read state the ports q1 and q2 work correct only when i write in C to the base_address. But it should be working by writing to the first register so base_address +0x4. 

I think i do not really apply the next address to the memory module.  

Do you have an idea? 

 

Cheers, 

Tim 

type state_type is (addr_inc,wr,idle,rd); signal pr_state, nx_state : state_type; signal av_m_address_i : std_logic_vector(12 downto 0); signal k_p : std_logic_vector(31 downto 0); signal counter : integer := 0; begin process(clk, reset) BEGIN if reset = '1' then pr_state <= idle; elsif clk'event and clk ='1' then pr_state <= nx_state; end if; END PROCESS; process (pr_state) BEGIN case pr_state is when idle => nx_state <= wr; av_m_address_i <= b"0_0000_0000_0000"; counter <= 0; when wr => -- increment the counter value and write it to the address counter <= counter + 1; av_m_write <= '1'; av_m_clken <= '1'; av_m_chipselect <= '1'; av_m_byteenable <= X"1"; av_m_address <= av_m_address_i; av_m_writedata <= std_logic_vector(to_signed(counter,32)); counter <= counter + 1; if counter = 10 then nx_state <= rd; else nx_state <= addr_inc; end if; when addr_inc => av_m_address_i <= std_logic_vector(unsigned(av_m_address_i) + b"0_0000_0000_0100"); nx_state <= wr; when rd => av_m_address <= b"0_0000_0000_0100"; if av_m_readdata = X"00000000" then q1 <= '1'; q2 <= '0'; elsif av_m_readdata = X"00000001" then q1 <= '0'; q2 <= '1'; else q1 <= '1'; q2 <= '1'; end if; nx_state <= rd; when others => nx_state <= wr; end case; END PROCESS; end behave;
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

For better locating the error i changed the design to two states, read,idle. 

So by resetting the design it goes into idle and from there directly to read. The  

netlist viewer shows that the correct address is assigned to the output pin which is then connected to the dual port memory module of nios. 

Then i would expect the readdata value comes from this address. But it it still just responding when writing to the base address. 

I think i miss some super obvious ?!? (: 

 

 

https://www.alteraforum.com/forum/attachment.php?attachmentid=9011
0 Kudos
Altera_Forum
Honored Contributor II
2,095 Views

You never disable the write signal in your process. This means that you will continue to write values even in the read state. This code should generate a lot of latches warnings, as you aren't assigning values to every signal in every state of your state machine. I suggest that you add some default values before the case statement for every output signal used in the process to avoid this problem. You can use '0' as default value for the control signals (read, write, chip select, byte enable) and just put them to '1' when needed. 

Also remember that there is at least one latency cycle when reading from the memory, so the read state should actually be two states: one to set the address, and one to decode the read back value.
0 Kudos
Altera_Forum
Honored Contributor II
2,040 Views

Hi Daixiwen, 

 

yes you were right. I had a lot of warnings for latches. 

I redesigned the state machine to one porcess holding the complete structure.I also added default values before the case statement. Now i do not get the warnings anymore. However what i am still struggling with is multiple reading from different address.  

 

When the control signal master_setup is active, the value in the address 0x10 defines with components get initialized. (state setup_eval_var). 

From there multiple variables are read from fixed memory locations and assigned to output ports. ( halfbridge_freq_scale halfbridge_duty) 

 

But here wrong values are read and i do not know why.  

When i downscale the system to only one variable everything works fine. So for example reading only halfbridge_freq_scale the system works fine.  

The component them self work fine. Simulation with Modelsim or running with constants shows perfect behavior. 

Am i doing something wrong with the multiple address incrementation? 

 

 

 

case state is when idle => if master_setup ='1' then state <= setup_eval_var_addr; elsif master_run ='1' then state <= setup_run_addr; elsif master_wr ='1' then state <= setup_write_addr; else state <= idle; end if; when setup_eval_var_addr => av_m_clken <= '1'; av_m_cs <= '1'; av_m_byteenable <= X"F"; av_m_address <= b"0_0000_0001_0000"; state <= setup_eval_var; when setup_eval_var => av_m_clken <= '1'; av_m_cs <= '1'; av_m_byteenable <= X"F"; if av_m_rddata = X"00000001" then --read halfbridge vars state <= setup_hb_freq_addr; elsif av_m_rddata = X"00000002" then state <= setup_dp_t1_addr; elsif av_m_rddata = X"00000003" then state <= setup_npc_freq_addr; --tbd else state <= idle; end if; -- HALFBRIDGE states for reading the desired signal frequency when setup_hb_freq_addr => av_m_clken <= '1'; av_m_cs <= '1'; av_m_byteenable <= X"F"; av_m_address <= b"0_0000_0100_0000"; --64 state <= rd_hb_freq; when rd_hb_freq => av_m_clken <= '1'; av_m_cs <= '1'; av_m_byteenable <= X"F"; halfbridge_freq_scale <= av_m_rddata; --output port to halfbridge component state <= setup_hb_duty_addr; -- HALFBRIDGE states for reading the signal duty cycle value when setup_hb_duty_addr => av_m_clken <= '1'; av_m_cs <= '1'; av_m_byteenable <= X"F"; av_m_address <= b"0_0000_0001_1000"; --24 state <= rd_hb_duty; when rd_hb_duty => av_m_clken <= '1'; av_m_cs <= '1'; av_m_byteenable <= X"F"; halfbridge_duty <= av_m_rddata; if master_rd ='1' then state <= setup_hb_freq_addr; else state <= idle; end if;
0 Kudos
Reply