Guest ivanjh

Connecting new peripherals to AVR Code

18 posts in this topic

What's the approach for adding new peripherals?

The standard 64 I/O registers are pretty limiting once you consider the power of internal peripherals you can create.

I've hacked together something where the peripherals can be configured by the "Hijacks unused external SRAM space" method. Since we're just tapping into existing signals, that's just fine for output devices.

How about input? In theory maybe I can hack up "external_mux" and override "ram_data_out" for my "memory mapped device"... I not sure if "cpuwait" could cause an issue... all seems a bit messy.

At the same time there seems to be something in "RAMAdrDcd" commented out which could be related to memory mapped IO.

The other option might be to find two spare I/O registers, and use them as a window thru to a second "bus" [Reg1=Addr,Reg2=Data] turning 2 registers into a virtual 256.

Any thoughts?

Share this post


Link to post
Share on other sites

Hi Ivan,

This "hijacking unused SRAM space" technique is already used in later versions of the AV controller (for example, see ATMega128), but it does not hijack external SRAM space but the internal SRAM space. This adds IO space of some 160 SFR addresses.

Since it is the internal SRAM space, no external timing issues are involved. You may check the ATMega128 docs and try using the same techniques - this will also make the core compatible with a lot of open source AVR software available out there.

- Girish

Share this post


Link to post
Share on other sites

Hey Ivan,

I'm fighting with this right now as well, I'm putting an example core into the external SRAM space so people can see how to add their own cores. Like you say, its easy for output devices but input devices need to interface with the I/O mux and we want to make it as easy for end users as possible.

If you want to drop by the IRC channel at some point I'm going to be working on this later today.

Jack.

Share this post


Link to post
Share on other sites

I'm in Sydney Australia (GMT+11) so IRC might be difficult.

I was hacking with extending "external_mux" to have:

io_port_bus() <= ram <= io_mm_port_bus()

And then attached peripherals to the 16 bit memory address lines:

adr        : in std_logic_vector(15 downto 0);

and

ramadr    => adr,

I'm brand new to this, so I have no idea what else may be required.

Share this post


Link to post
Share on other sites

Hmm... I think there's some issues with the current implementation.

Take the following snippet of code:


_SFR_MEM8(0x1000)=0x45;
  0:  85 e4          ldi    r24, 0x45      ; 69
  2:  80 93 00 10    sts    0x1000, r24
PORTA = 0x23;
  6:  83 e2          ldi    r24, 0x23      ; 35
  8:  8b bb          out    0x1b, r24      ; 27

When writing to our core via the memory mapped address:

The "core_ramadr" (which maps to "adr" in our user peripheral) is set to 0x1000, "core_dbusout" set to 0x45, and the "core_iowe" line is pulsed. At this point everything is as expected, and our user core reads (0x45) from the bus.

Next we write to another IO port.

"core_adr" is set to 0x1B, "core_dbusout" set to 0x23 and the "core_iowe" line is pulsed. At this point as expected, the PORT reads the data from the bus...

... and the user core reads (0x23) from the data bus since the "core_ramadr" is still 0x1000. Whoops!

It seems that the problem with attaching to the RAM address bus and core_io(w/r)e signal, is that the RAM address may be left unchanged until the next RAM operation - causing unexpected misfiring during subsequent IO operations.

(I've tested this in simulation - the code above causes the user core "control_reg" be assigned 0x45, then 0x23.)

Share this post


Link to post
Share on other sites

Also, the memory mapped IO defined by the constant "const_ram_to_io_c".

constant const_ram_to_io_c   : std_logic_vector := "00010000000";

I think it's meant to make the region 0x1000 to 0x3FFF mapped IO, but it's used as:

...ramadr_reg_in(15 downto 5)=const_ram_to_io_c...

Which means it's only 32 addresses long: 0001000000000000 to 0001000000011111 (0x1000 to 0x101F).

0x1000 to 0x3FFF would be (0001000000000000 to 0011111111111111) and can't be expressed with a single prefix. (The MSBs are 0001,0010,0011)

I've changed mine to support 0x1000 to 0x1FFF

 
constant const_ram_to_io_c  : std_logic_vector := "0001";  -- LD/LDS/LDD/ST/STS/STD ADDRESSING GENERAL I/O PORT 0x1000 0x3FFF

...ramadr_reg_in(15 downto 12)=const_ram_to_io_c...

Share this post


Link to post
Share on other sites

I also just realised that the clash will work both ways...

A device on the high ramadr, may read/write when lower IO is addressed...

and a lower addressed device, may read/write when the high IO is addressed.

i.e. If you address 0x1011, you're also addressing 0x11 (DDRD). If you address any low IO address, it may address anything still pointed to by the ramadr.

... and to be should I've verified this in the simulator (actually I found it in there).

I wonder how hard it would be to increase the width of the ioadr bus (a full 8/16 bits)?

We'd need to expand:


  •  
  • Papilio_AVR8.core_adr
     
  • AVR_Core.adr
     
  • AVR_Core.adr_int
     
  • pm_fetch_dec.adr (out)
     
  • pm_fetch_dec.adr_int (multiplexed from the decoded instructions, or partial ramadr_int)
     
  • io_adr_dec.adr (in)
     
  • io_reg_file.adr (in)
     
  • (and the peripherals)

  Such a change shouldn't affect the instruction set or existing code, and to me it makes sense to expand the IO bus in such an IO expansion opportunity rich environment (in an FPGA).

Share this post


Link to post
Share on other sites

Good catch on this,

I read this post yesterday and have been thinking about it since. I came to the same conclusion that increasing the width of the ioadr bus seems to make the most sense. I'm hoping to dig into the code today after I take care of all the business related tasks that built up over the weekend.

Thank you again for pointing out this issue.

Jack.

Share this post


Link to post
Share on other sites

Awesome!

I took a look, github makes it so easy, and it looks great.

I went ahead and pulled in your changes, made sure everything synthesized correctly, and made a new V1.5 release.

I'm working on testing the C/RAM Wing so that will hopefully put the changes through some pretty good testing in the real world.

Thank you very much for the code contribution.

Jack.

Share this post


Link to post
Share on other sites

That's my first git push.  :)  Still trying to wrap my head around git command line syntax.

Ooh external ram... I didn't think about that. Blocking a full 4K (0x1000-0x1FFF) for IO might be a bit wasteful if you've given a device external RAM (since you can only address 64k). But it's pretty easy to change the size of the window... so probably not a big deal.

If you think it works for sharing, I'll continue to push my ideas into branches on my github fork. That way I can mention the branch url, and it's easier to know what I'm babling about.

Share this post


Link to post
Share on other sites

That was my first git pull as well, it was effortless.  ;D

To start out with C/RAM testing I'm just going to control address and data with a couple registers. Will look into memory mapping it later.

Please do continue to push any ideas into github, it seems a fabulous way to do this sort of thing. SVN was much more of a hassle.

Thanks,

Jack.

Share this post


Link to post
Share on other sites

Well, the IO bus changes are holding together for me (in initial simulation tests at least).

I created two components, an "up then down" counter, and a comparator. Plugged together, I get phase correct PWM.  ;)

Just as an example, I put together 1 counter & 16 compators (16 sync'd PWM channels).

An AVR core & 16 x 16bit PWM channels? All with only 54% of the LUTS in a 250K Papilio!

Just for kicks, I turned ports A thru F into PWM channels (48 PWM Channels), and that consumed 64% of my 250K Papilio LUTS!

Hmm... I don't remember seeing a member of the AVR family with 48 PWM channels.  ???

I'll push it, but it needs a good tidy-up first. Next: Verifying the simulation in reality.  :o

post-0-13431627471163_thumb.gif

Share this post


Link to post
Share on other sites

Sweet!

These are the sorts of things that really show the cool types of things you can do with the Papilio.  :)

We need to start working on an official "Core Playground" and github repository so we can start making a collection of cores.

Jack.

Share this post


Link to post
Share on other sites

@ivanjh:

Using core_adr instead of core_ramadr causes my core to ignore everything because 0x1001 is never set into core_adr when doing:


#define customCoreStatus  _SFR_MEM8(0x1001)
var statusByte = customCoreStatus;
...

But it works connecting the core to core_ramadr. What am I missing?

Thanks.

Share this post


Link to post
Share on other sites

Browsing ivanjh's github I see there is a file that didn't make it to V1.5 of the core: papilio_core_template.vhd

which contains the key to make the custom core work with core_adr:

io_base_address_generic : STD_LOGIC_VECTOR (15 downto 0) := x"0FE0" -- _SFR_IO8(0x0FE0) / _SFR_MEM8(0x1000)

Share this post


Link to post
Share on other sites

Ah, yes, the RAM space starts with 32 general purpose working registers that don't exist in the IO space.


  •  
  • ramadr = ioadr  + 32
     
  • ioadr = ramadr - 32

  The space allocated to memory mapped IO is RAM space 0x1000-0x1FFF (I/O space 0x0FE0-0x1FDF).

P.S. When playing with extra cores, make sure you keep unused EXT_MUX inputs zeroed (io_port_out/io_port_out_en), otherwise you can get 'undefined' propagating thru during simulations.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now