Programmable Devices
CPLDs, FPGAs, SoC FPGAs, Configuration, and Transceivers
20640 Discussions

Questions regarding interfacing of & accessing custom hardware blocks

Altera_Forum
Honored Contributor II
2,306 Views

Hello, 

Firstly, before you read the rest - i'm pretty "green" with FPGAs, but i do know how to set up a Nios II soft-core processor system with IP blocks using the SOPC builder, and instantiating it in Quartus II using the top-level hdl files (vhdl). And it works fine with the C code i have written to execute on it. 

 

Now I want to implement a certain, often executed, part of the C code as a hardware block ("IP block" i guess), to work as a pheripheral with the Nios II. 

From what i have gathered i obviously need the VHDL code for the hardware block that i want to create, but i'm in doubt regarding three things (mainly): 

 

- Does this VHDL code need to take anything special into account regarding the Avalon switch fabric? i.e. new port/signal names, etc.? 

I have read http://www.altera.com/literature/hb/qts/qts_qii5v4_01.pdf but i'm not sure if this means i shouldn't think of the Avalon Switch fabric in any way in my HDL. 

 

- How do I communicate between my C code executing on the Nios II and the hardware accelerator? By using addresses? Could someone show me an example on how this should be done? 

 

Does a tutorial on this exist, covering just a really simple example, like a multiplier as a hardware accelerator, and how this is "interfaced" both regarding calls from a C code and in SOPC builder? 

Basically what i want to do is what C2H can do for me, but i'd like to learn by doing it manually. I don't care if i can't do it as well as C2H. 

 

Thanks for any help.
0 Kudos
12 Replies
Altera_Forum
Honored Contributor II
806 Views

You should probably tell us what it is your implementing in the custom peripheral so we know exactly what you're component is going to need. 

 

You'll need to refer to this: 

http://www.altera.com/literature/manual/mnl_avalon_spec.pdf 

 

That is the specification for the Avalon memory-mapped and Avalon streaming specifications. You will be using the memory-mapped specification. 

 

1 - You are at the very least going to have to put an Avalon memory-mapped slave port on your custom peripheral. Yes you must obey the rules of the Avalon memory-mapped specification. Don't worry, the avalon bus is extremely easy to interface to.  

 

2 - Depending on what you are doing, you may also need an Avalon memory-mapped master (or two). For example, is your custom component going to directly access memory that the processor has written to or is going to read? 

 

Here is the basic flow. 

1 - Create your Verilog or VHDL description of the peripheral. This is really the only difficult part. Make sure you obey all the rules for Avalon slaves and masters. Everything after this is easy. 

 

2 - Use the "Component Editor" in SoPC builder to create a new SoPC component from your HDL code. This involves telling the component editor what each signal in your port list is so that it knows how to connect things to the Avalon fabric. If you need specific help here, we'll help you. 

 

3 - After you generate your SoPC system, and get into the software world, the "system.h" file will contain information about your custom component. Primarily it will contain a macro indicating the base address in the NIOS processor's memory map. You can then use this macro to access your component. You of course are responsible for knowing what the significance of each address is in your component's address map. Typically people use the IOWR and IORD macros to access the registers in their custom component. 

 

Hope this gets you started. Don't hesitate to ask for more detail as you get further along. 

 

Jake
0 Kudos
Altera_Forum
Honored Contributor II
806 Views

Hello again, 

 

First and foremost - Thank you very much for the very detailed reply, it was much appreciated! 

 

In the meanwhile i have been studying the Avalon specifications, and i now agree, it is not the first thing to focus at right now - the VHDL code describing my component is the difficult part. It is not done yet, but at that point i might have a few questions regarding the Avalon interfacing, if it turns out not to be working with it. The hardware is supposed to return the logarithm of an input, x, using the jacobian logarithm algorithm. 

 

Thanks, you made the communication between the program executing on the soft-core processor and the hardware block sound easy, and it also seems to be. If i want to read/write 4 bytes in a row i would use IORD_32DIRECT(base_addr, addr_incr) and IOWR_32DIRECT(base_addr,addr_incr,data). 

 

You mentioned that one needs to keep track of where in the address space that the values that the hardware accelerator are located at, and by that i'm not sure of. 

 

If we say that this basic full-adder is my "hardware block" - http://www.planet-source-code.com/vb/scripts/showcode.asp?txtcodeid=10203&lngwid=3, and pretend that it is interfaced correctly with the Avalon bus, and let's pretend that the SOPC builder gives me a base address for the full-adder at 0x00101000 

 

- Since Cin is stated as the first port, would that then have zero increment, i.e. base address 0x00101000, increment 0? 

- Considerering that the Cin was in std_logic(31 downto 0), and x was also in std_logic(31 downto 0), should i, in order to "assign" a value to these inputs just write it as: 

 

IOWR_32DIRECT(0x00101000,0,0x12341234); 

 

to write in the decimal value corresponding to 0x12341234 hex? 

To write the same hex value to input x, would it then be: 

 

IOWR_32DIRECT(0x00101000,1,0x12341234); 

It would be nice if it worked that way, but i am unsure if the port declarations are kept in that order when imported into SOPC builder - or if i have to modify VHDL code in some other way.
0 Kudos
Altera_Forum
Honored Contributor II
806 Views

If the addresses of the hardware component are register addresses and not memory addresses, then you really ought to use the IOWR macro. 

 

An example. Suppose you've got some hardware component in your system named "my_custom_hw" and you want to write values to registers 0 and 1 within your component: 

 

# include <io.h> // Contains IOWR and IORD macros # include "system.h" // Includes everything you need to know about your system. ... void some_function(){ ... IOWR(MY_CUSTOM_HW_BASE, 0, 0x12345678); // Write to register offset 0 of "my_custom_hw" IOWR(MY_CUSTOM_HW_BASE, 1, 0x87654321); // Write to register offset 1 of "my_custom_hw" }  

 

The other macros (IOWR_8DIRECT, IOWR_16DIRECT, IOWR_32DIRECT) are really for writing to memory addressed components. If you want to see the macros, just look at: 

C:\altera\90\nios2eds\components\altera_nios2\HAL\inc\io.h 

 

The "MY_CUSTOM_HW_BASE" macro is generated in the "system.h" file. So you never need to know what the base addresses of your components are, you just use the macro. 

 

Jake
0 Kudos
Altera_Forum
Honored Contributor II
806 Views

Hmm, i'm not sure i understand - how would i know whether i have register addresses or hardware addresses? Does this depend on how i interface to the avalon? 

 

I really feel stupid regarding this. 

What i want to do is send multiple 32 bit integers from the C code to the hardware block, to different port inputs, and then also be able to get multiple outputs, which are also 32 bit, by reading the output pins. I know the macros to write and read, and the base address of the hardware block, but i don't know what the "address increment" or second input to the macros should be. 

 

I took a look at http://www1.cs.columbia.edu/~sedwards/classes/2007/4840/lab3.pdf figure 11 and figure 16, but i'm still not sure how writing to it works completely. 

 

What i'm not sure of right now, is also why only a clock is required for slave avalon-mm blocks as a minimum. Since I would also like to read outputs from and write inputs to it, so i would have to use write, writedata and read, readdata, and a chipselect i guess - i wouldn't expect SOPC Builder to figure it out otherwise. 

 

I'm thinking that having a separate process doing the following (not checked it for errors, and probably still missing some) would work: 

 

process (clk) begin if clk'event and clk ='1' then if chipselect ='1' then if read='1' then readdata <= take_an_input; elsif write='1' then writedata <= output_eg_result_from_another_process; end if; end if; end if; end process;  

 

In other words, very much like the tutorial linked to above.
0 Kudos
Altera_Forum
Honored Contributor II
806 Views

Hi again, 

Ok, the VHDL code has been completed, and decided to use the proposed form of interfacing with avalon as above, but some modifications, since i want to write to multiple input signals: 

 

begin process (clk) begin if clk'event and clk = '1' then if reset_n = '0' then readdata <= (others => '0'); else if chipselect = '1' then -- our BCC is selected if read = '1' then -- if the hardware wants to write to avalon (avalon should read from hardware) if flag_bc = '1' then -- if a branch_cost is done readdata <= "11111111111111111111111111111111"; readdata <= result; elsif flag_bc = '0' then -- if branch cost is not done, just send 32 zeros readdata <= "00000000000000000000000000000000"; end if; end if; elsif write = '1' then -- if write is asserted (avalon should write to the hardware) case count is -- The order in which inputs are fed from C is fixed when "000" => sys_rec <= writedata; count <= "001"; when "001" => sys_ref <= writedata; count <= "010"; when "010" => par1rec <= writedata; count <= "011"; when "011" => par1ref <= writedata; count <= "100"; when "100" => par2rec <= writedata; count <= "101"; when "101" => par2ref <= writedata; count <= "110"; when "110" => x <= writedata; count <= "000"; end case; end if; end if; end if; end process;  

With the following signals in the port declaration in the entity: 

 

signal clk, reset_n, read, write, chipselect : in std_logic; signal readdata : out std_logic_vector(31 downto 0); signal writedata : in std_logic_vector(31 downto 0) ); 

It seems to import in SOPC builder, with a warning: 

avalon_slave_0: signal readdata appears 8 times (only once is allowed). 

 

Also, the case conditional statement has been made so that i write from the C program, in a specific order, i.e. when it has received the first 32 bits, it assigns them to sys_rec, the next 32 bits are assigned to sys_ref and so on.  

flag_bc is a flag which is set in another process as soon as result is computed. 

 

When the flag is 0 i want the hardware accelerator to return 32 bits of zeros. When the flag is 1 i want it to send 32 bits of ones (to indicate that there is a result) and then afterwards give me the (32 bit) result. 

 

Would this actually work as it is supposed? 

 

I suppose i am reading from the register writedata, and sending to the register readdata. How do i know in which order they come? For example would readdata be at IORD(0x01001000,0) or would it be IORD(0x01001000,1) or something else... 

 

If the rest of the vhdl can help anything, i can post that also, but it needs to be shined up a bit.
0 Kudos
Altera_Forum
Honored Contributor II
806 Views

Hi again, 

Ok, this really ought to be easy... i guess this is just a case of RTFM, and do it properly.. and then it would occured to me that there is no "magic" tricks regarding the Avalon - there's a address bus - just what i was looking for. Forget my previous post. 

 

Now the VHDL code looks like this process regarding the Avalon interface: 

 

process (clk) begin if clk'event and clk = '1' then if reset_n = '0' then readdata <= (others => '0'); else if chipselect = '1' then -- our HW is selected if read = '1' then -- if the HW wants to write to Nios (avalon should read from HW) if address ="011100" then -- 0x1C (28) readdata <= branch_cost; -- output this new branch cost. else readdata <= "11111111111111111111111111111111"; -- return when nothing has happened. end if; end if; elsif write = '1' then -- if write is asserted (avalon should write to the HW) case address is when "000000" => -- 0x00 sys_rec <= writedata; when "000100" => -- 0x04 (4) sys_ref <= writedata; when "001000" => -- 0x08 (8) par1rec <= writedata; when "001100" => -- 0x0C (12) par1ref <= writedata; when "010000" => -- 0x10 (16) par2rec <= writedata; when "010100" => -- 0x14 (20) par2ref <= writedata; when "011000" => --0x18 (24) x <= writedata; when others => null; end case; end if; end if; end if; end process;  

 

Regardless of what i'm doing, and even adding a case statement for readdata to read some of the values that are written to the variable/signals... 

But this does not work - no matter what i do (regardless of using IORD/IOWR and IORD_32DIRECT/IOWR_32DIRECT  

 

The code i use is: 

# include "system.h"# include <io.h> . . .# define WR_test(offset,data) IOWR_32DIRECT(BCC_TEST_BASE,offset,data)# define RD_test(offset) IORD_32DIRECT(BCC_TEST_BASE,offset)# define sysref 4 int main(void) { WR_test(sysref,1); WR_test(par1ref,3); printf("sysrech=%x", RD_test(sysref)); printf("sysrecd=%d", RD_test(sysref)); return 0; }  

 

I have tried defining sysref as 4 and 0x04, but by the same results 

 

No matter what i have done, it always prints that: 

sysrech=0 and sysrecd=0 

 

My entity of the VHDL is just: 

 

entity bcc_test port ( signal clk, reset_n, read, write, chipselect : in std_logic; signal readdata : out std_logic_vector(31 downto 0); signal writedata : in std_logic_vector(31 downto 0); signal address : in std_logic_vector(5 downto 0) ); end bcc_test;  

 

Any hints? From importing the component in SOPC builder, there were absolutely no errors, and the write and read waveforms also looked as expected. 

 

Thanks for any hints regarding why it always throws a zero back... :)
0 Kudos
Altera_Forum
Honored Contributor II
806 Views

Where does your chipselect signal come from? AFAIK it doesn't exist in the Avalon interface. Just check read and write inputs, they are only asserted when your component is addressed.

0 Kudos
Altera_Forum
Honored Contributor II
806 Views

Chipselect does exist in Avalon but it is optional. If you use chipselect, you must gate read and write with it. If you don't use chipselect, read and write are only asserted when your component is selected.

0 Kudos
Altera_Forum
Honored Contributor II
806 Views

I had a look at the avalon interface specifications (http://www.altera.com/literature/manual/mnl_avalon_spec.pdf?gsa_pos=5&wt.oss_r=1&wt.oss=avalon) before posting, and if I'm not mistaken the chipselect signal only exists on tri-stated Avalon MM interfaces.

0 Kudos
Altera_Forum
Honored Contributor II
806 Views

Ah that is interesting. The spec has changed. My assumption is that they are trying to get people to move away from using chipselect on regular slave interfaces.  

 

However, I have to assume that the interface still behaves to it's original spec. If the component editor allows you to add chipselect as a slave signal, then the fabric knows what to do with it.  

 

I normally do not use the chipselect signal as it's provided with "read" and "write". The only time I use it is when I'm actually interfacing to an external IC that needs the signal (even then you could derive it from read and write). 

 

Jake
0 Kudos
Altera_Forum
Honored Contributor II
806 Views

I have the same problem no matter what i do. 

Did you succeed on reading and writing to your component?
0 Kudos
Altera_Forum
Honored Contributor II
806 Views

 

--- Quote Start ---  

Hi again, 

Ok, this really ought to be easy... i guess this is just a case of RTFM, and do it properly.. and then it would occured to me that there is no "magic" tricks regarding the Avalon - there's a address bus - just what i was looking for. Forget my previous post. 

 

Now the VHDL code looks like this process regarding the Avalon interface: 

 

process (clk) begin if clk'event and clk = '1' then if reset_n = '0' then readdata <= (others => '0'); else if chipselect = '1' then -- our HW is selected if read = '1' then -- if the HW wants to write to Nios (avalon should read from HW) if address ="011100" then -- 0x1C (28) readdata <= branch_cost; -- output this new branch cost. else readdata <= "11111111111111111111111111111111"; -- return when nothing has happened. end if; end if; elsif write = '1' then -- if write is asserted (avalon should write to the HW) case address is when "000000" => -- 0x00 sys_rec <= writedata; when "000100" => -- 0x04 (4) sys_ref <= writedata; when "001000" => -- 0x08 (8) par1rec <= writedata; when "001100" => -- 0x0C (12) par1ref <= writedata; when "010000" => -- 0x10 (16) par2rec <= writedata; when "010100" => -- 0x14 (20) par2ref <= writedata; when "011000" => --0x18 (24) x <= writedata; when others => null; end case; end if; end if; end if; end process;  

 

Regardless of what i'm doing, and even adding a case statement for readdata to read some of the values that are written to the variable/signals... 

But this does not work - no matter what i do (regardless of using IORD/IOWR and IORD_32DIRECT/IOWR_32DIRECT  

 

The code i use is: 

# include "system.h"# include <io.h> . . .# define WR_test(offset,data) IOWR_32DIRECT(BCC_TEST_BASE,offset,data)# define RD_test(offset) IORD_32DIRECT(BCC_TEST_BASE,offset)# define sysref 4 int main(void) { WR_test(sysref,1); WR_test(par1ref,3); printf("sysrech=%x", RD_test(sysref)); printf("sysrecd=%d", RD_test(sysref)); return 0; }  

 

I have tried defining sysref as 4 and 0x04, but by the same results 

 

No matter what i have done, it always prints that: 

sysrech=0 and sysrecd=0 

 

My entity of the VHDL is just: 

 

entity bcc_test port ( signal clk, reset_n, read, write, chipselect : in std_logic; signal readdata : out std_logic_vector(31 downto 0); signal writedata : in std_logic_vector(31 downto 0); signal address : in std_logic_vector(5 downto 0) ); end bcc_test;  

 

Any hints? From importing the component in SOPC builder, there were absolutely no errors, and the write and read waveforms also looked as expected. 

 

Thanks for any hints regarding why it always throws a zero back... :) 

--- Quote End ---  

 

 

 

 

did find the answer?
0 Kudos
Reply