IAR compiler, acquire absolute RAM addresses

Go To Last Post
38 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi!

I have a project in IAR EW 5.5 compiler, with ATmega644. I need to create some variables and place them in specific addresses inside RAM.

I'd love to know where each RAM section starts and ends, but the lnkm644pas.xcl file (c:\Program Files\IAR Systems\Embedded Workbench 5.4\avr\config\) isn't much of a help:

Quote:
/* Internal data memory */
-D_..X_SRAM_BASE=100 /* Start of ram memory */
-D_..X_SRAM_TEND=100 /* End of tiny ram memory */

-D_..X_SRAM_END=10FF /* End of ram memory */


Quote:
/* Internal data memory */
-Z(DATA)TINY_I,TINY_Z,TINY_N=_..X_SRAM_BASE-_..X_SRAM_TEND
-Z(DATA)NEAR_I,NEAR_Z,NEAR_N=_..X_SRAM_BASE-_..X_SRAM_END

I understand where RAM starts and where it ends. But what about the separate parts? Near_Z for example. Where is it starting and ending? I need absolute addresses for each RAM section.

Thanks.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

IAR has excellent support. ( which they should at that price ).

Mike Adams
ADI Development, Inc.
http://www.adidev.com

... When it has to actually work.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can tell the IAR linker to do almost anything.

OTOH, I can never see the point of fixed addressing.

Put forward a good argument for it, and you might get some advice.

Seriously. If you have a full licence, ask IAR.
If you have no intention of buying a full licence, use a different compiler. The evaluation is severely size restricted.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

OTOH, I can never see the point of fixed addressing.

Put forward a good argument for it, and you might get some advice.


+1

The one reason I've seen given here is sharing some data between two programs running in the AVR. On the whole that can only mean a bootloader and an application in the same chip. There are far better ways of passing data between the two than trying to arrange for common RAM usage!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am also against fixed addressing, but for some reason I need to do it. Does anybody know how to obtain the address of NEAR_Z?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You have create some real temptation for people to answer. "For some reason" is not good enough when people already are sceptical. Be elaborate on this reason and you might get 1) good avice, 2) an alternative solution or 3) flamed. Your call...

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
You can tell the IAR linker to do almost anything.

Put forward a good argument for it, and you will get some useful advice.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
You have create some real temptation for people to answer. "For some reason" is not good enough when people already are sceptical

Even though I disagree with such a point of view, here is the scenario.
I want to have 10 const variables inside my RAM space. In every loop turn I will read these variables. If their values are not the expected values, I will reset the MCU. In this way I believe that I can give a fair solution if RAM data are corrupted.

Alexandros

Last Edited: Fri. Jan 13, 2012 - 11:54 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Put forward a good argument for it, and you will get some useful advice.

Sorry, I don't understand what you mean.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

I want to have 10 const variables inside my RAM space. In every loop turn I will read these variables. If their values are not the expected values, I will reset the MCU. In this way I believe that I can give a fair solution if RAM data are corrupted.

Then I ask again why they need a fixed address? In C you can do this:

int foo = 12345;
if (foo != 12345) {
   reset():
}

I never need to know what address the linker has placed 'foo' at for the very reason that I just refer to it by name. (though if I'm curious &foo will tell me where the linker put it).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you Clawson. Those variables will have fixed addresses to cover all RAM space. For example if I have 1K RAM, then they will be placed at addresses 0, 100, 200, ....., 900, 1000.
For no other reason I would use fixed addresses in C. I never have.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you have 1K of RAM and place your canary variables at evey 100th byte at least one of them stand a substantial risk to be overwritten by the stack. Or, if you like, those variables will fragment the Ram so that you don't get enough space for the stack.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think you'll have to write the whole thing in assembler to achieve this. And as Johan has pointed out, the stack will be a problem. I'm not really sure why you would pick those specific addresses, anyway. Do you have some reason to suppose that those addresses will fail, or are you looking to detect address line problems? If the latter, then I would have thought you want to use some binary based sequence of addresses, such as 0, 1, 2, 4 8 etc.

Four legs good, two legs bad, three legs stable.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
If you have 1K of RAM and place your canary variables at evey 100th byte at least one of them stand a substantial risk to be overwritten by the stack. Or, if you like, those variables will fragment the Ram so that you don't get enough space for the stack.

I mean only the NEAR_Z and NEAR_I section. The example was just to understand what I am trying to do.

Quote:
If the latter, then I would have thought you want to use some binary based sequence of addresses, such as 0, 1, 2, 4 8 etc.

Why not? As soon as I will find a solution this could also be an option.
So does someone has the solution?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

So does someone has the solution?

As already noted, I bet IAR do. Why else would you have paid $3000 for each copy of the compiler?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
As already noted, I bet IAR do. Why else would you have paid $3000 for each copy of the compiler?

OK then, none has the solution. That's all right by me, but if you don't know (not you personally clawson) just stay silent and let someone who knows to give the answer. I mean look at this thread. 15 posts for nothing. First the problem was that I shouldn't use fixed addressing. Then the problem was that I didn't give much information. It turns out that the problem is none knows but everybody wants to answer nonsense.
As noted, I paid for IAR so I will ask there, no problem, just wanted to search for a quicker solution since google didn't give the expected results.
BUT:
Thank you for letting me know how this forum works. When I get the answer from IAR, then if tomorrow someone else has the same problem I won't help because he paid and he should ask by himself.

Since this forum seems to have obsolete views, I will suggest the moderators and administrators of this site to look into fresher ideas and point of views.

http://www.edaboard.com/

Just a reference to realize how a forum should be.
Cheers!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think that we have tried to get you to think about the problem. As I said in the third post, it is perfectly possible to do.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The thing is (partly) this: There is a substantial amount of "oddball" questions here where the OP already has decided on a specific technical solution (S) to a problem (P). He then asks how to acheive S without stating P. In a lot of cases it turns out that when P eventually gets known there is a more straight-forward solution.

Notice above that people talk about only identfying one reason to have absolute placements of variables - for letting an application proper and a bootloader ommunicate. Also notice that there are hints that there exists better solutions to this. Both these indicate that we've seen the "absolute placement of variables" question here many times before.

Since you presented only the technical solution you strived for, and actually played somewhat hard to get re the "original problem" you made it easy for us to fall into "Oh, here's another oddball question" mode. IMO both parties are to blame here. We should be more cautious not falling so easy. You could have been more outspoken starting out, and should not have dodged with "for certain reasons".

I can understand your frustration with how this evolved. I hope you can see, at least partly, why it wen't that way.

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I hope this is not off-topic but I still don't see the need to actually place variables at those specific locations. Say this is about testing for cosmic rays knocking the charge off a gate in an SRAM location because the CPU is going into space or something. I still don't see, if the intention is to test locations 100, 200, 300, .. why one couldn't just do:

C_task void main(void) {
  uint8_t * p = (uint8_t *)0x100; // start of SRAM
  uint8_t i;
  for (i=0; i < 10; i++) {
    *(p + (i *100)) = 0x5A;
  }
...
  do_some_work();
...
  p = (uint8_t *)0x100; // start of SRAM
  uint8_t i;
  for (i=0; i < 10; i++) {
    if (*(p + (i *100)) != 0x5A) {
       reset();
    }
  }
}

No variables placed at absolute addresses in that!

Of course you cannot know what the linker has chosen to place at 0x100, 0x100+100, 0x100+200 and so on but, then again, when you use absolute addressing you couldn't know whether the stack was near/at one of those locations either!

BTW this kind of navel inspecting testing is completely pointless. Suppose the cosmic ray knocked a '1' off a bit in the PC or SP register rather than some randomly picked SRAM location - what then?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

you can create your own named segment, locate it statically where you want, or place it relative to the stacks, as that is what I assume you are trying to detect overflow of. Then simply place the constants into the named segment.

How to do this is described fairly well in the IAR documentation. Beyond that, contact IAR, you bought a license, so you are entitled to support. If you are using an eval copy, then that is what it is, unsupported for evaluation only, not for production code. If you are using a pirated copy, you're on your own... I can only suggest buying a license, or moving to a compiler that is priced within your budget.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

place it relative to the stacks, as that is what I assume you are trying to detect overflow of.

Is that really what this is all about? Never crossed my mind. If so then just randomly peppering RAM at 100 byte increments is surely not the "best" way to do this anyway? Usually flooding the supposedly unused RAM with a recognizable pattern then searching for this from time to time to find the low water mark is a better strategy. Either that or a timer interrupt actually sampling SP to determine the low water mark.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Actually going back and re-reading the post, I think he want's to check the boundaries of each memory section. The sections are not fixed in size, but are only as big as they need to be for the application, the next section is then packed immediately after it. To do what he wants he would have to create several named sections, each containing one or two of his test constants, and then add them to the stacking sequence of the memory sections in the appropriate places.

In any case, I believe his efforts are wasted energy. There are far better ways to debug code, or prevent overrun conditions. Fix the problem, don't just detect & reset whenever it breaks!!

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Surely it's easier to ask the compiler to do the job of picking layout than trying to take on the job manually?

I'd really love to hear from the OP where this requirement comes from and what the true intention here is. I'm sure we all believe he/they may be operating under some misconception about compiler/linker operation and the whole thing could be "solved" if that was corrected.

BTW while this is aimed at ARM not AVR is IAR not common across all architectures?

http://supp.iar.com/Support/?not...

So is it really as simple as using "@" ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK, let me give more details.
There is an issue with a board during burst mode EMC test. Let's forget about IAR syntax for now. I am looking into board improvements, but I want to make some software improvements also. The issue was that the code flow got stack somewhere in the program, watchdog was reseting (I mean no program reset was taking place) but the program looked frozen. It stayed frozen even after the test. Couldn't possibly know where the hell stack pointer was at that time. Eventually I found out with the help of a LED. A blink was placed inside timer interrupt and the LED was blinking fine, but it was the only operating board's element during and after burst mode. Thanks to the LED I am detecting the problem even further as we speak.
Anyways, situation got very much improved, solved until now when a ResetWatchdog() function was created. There was then no wdr command in different places in the program, just a function call. But I need more fine-tuning.
I want to write specific bytes loosely in RAM into zero and non zero intitialized section. In assembler it is easy. In C it is more complicated, you have to fall to low level in any case, "fixed addressing" as already pointed out. I believe that if inside timer interrupt a validation check of those adresses values fails, then I should reset the program.
Finally those addresses must be placed with some geometry. Like if we have 10 bytes, byte0 would be at address 0, byte 5 in the middle of RAM and number 10 in the end. Maybe some sections of RAM are corrupted only, not all of it, so I will investigate samples from all RAM spectrum. Not 100% success, but improvement for sure. We are talking about zero and non zero initialized section only, not stack. It is an AVR dependent technique, but that's fine by me.
I need the program not to freeze, but to reset all the time. I would appreciate any enhancemet on this task. I hope it is much clear for you now. Secondly, is there any catch from AVR architecture point of view if I reset from inside timer interrupt? I will just use a while(1) line for this, watchdog frequency 1sec, timer interrupt every 5ms.

glitch wrote:
In any case, I believe his efforts are wasted energy. There are far better ways to debug code, or prevent overrun conditions. Fix the problem, don't just detect & reset whenever it breaks!!

Really encouraging words, but I'd only took them seriously supported by an alternative. Like shielding the emulator during EMC test...

Alexandros

Last Edited: Fri. Jan 13, 2012 - 09:03 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you wanted a better answer, more info would have been helpful. Until your last post, your information has been very vague as to what/why you were trying to do what it is you are asking.

Shielding might help, but I suggest you try adding more bypass and bulk capacitance on your board. Particularly close to the AVR.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

but I'd only took them seriously supported by an alternative. Like shielding the emulator during EMC test...

Now >>I'm<< way lost--what does shielding or other environmental solutions have to do with putting variables at a particular address?

If you want to do RAM checks, then simply use named variables and a pointer to them. Or a "void" pointer and scan/compare/test all of the RAM.

Note: "Who will watch the watchers?" In severe enough noise environments anything can happen in an AVR. I/O bits reset; interrupts lost; rogue address pointers. What you may detect as RAM corruption may not be "corruption"--it could well be code going rogue and writing there. I.e. it wasn't the SRAM that "failed", it was other internal AVR subsystems.

Summary of experience with AVRs in a decade of industrial designs:

-- AVRs aren't that bad at noise immunity
-- There may be better ones out there, but the AVRs ain't bad
-- As with most other chips, today's reduced geometries haven't helped with noise immunity
-- AVRs don't fail on their own
-- The previous is probably true of most other micros nowadays as well
-- Severe noise spikes are almost always violating Absolute Maximum Ratings
-- Severe enough noise (e.g. unsnubbed motor contactors) can cause really crazy symptoms

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
but I'd only took them seriously supported by an alternative. Like shielding the emulator during EMC test...
It was an ironic joke on glitch, but he made it a solution...
Quote:
If you want to do RAM checks, then simply use named variables and a pointer to them. Or a "void" pointer and scan/compare/test all of the RAM.
Yes but then I wouldn't have those variables in all RAM spectrum. As for the watchers you are right, but it's a step of improvement. If I can check the RAM, then why not do that?
Quote:
-- Severe noise spikes are almost always violating Absolute Maximum Ratings
-- Severe enough noise (e.g. unsnubbed motor contactors) can cause really crazy symptoms
Especially if we are talking about some KV!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The OP's goal is illogical.
If you have DATA in RAM (not on the stack), and worry it will be corrupted, just CRC it and update the CRC when the data is altered.

Geesh.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
The OP's goal is illogical.
If you have DATA in RAM (not on the stack), and worry it will be corrupted, just CRC it and update the CRC when the data is altered.

Geesh.


Thanks Geesh for your attention. I have seen with my own eyes that the method I described had very good results, CRC has much more messy implementation. The problem is that this method is implemented in assembler in the first place.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
OTOH, I can never see the point of fixed addressing.

I used that method , 4 fixed RAM locations that supply variable patterns for a 4 digit
multiplexed LED display.

When ever the required display data needed
to change, just write the pattern to that RAM location.

The scan routine just kept using the same four locations.

Never say never. :roll:

I'll believe corporations
are people when Texas executes one.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

tubecut wrote:
I used that method , 4 fixed RAM locations

Could you please give a few more details? In which compiler did you do this? What syntax did you use?

This is exactly the technique as I know it too my friend. 4 bytes, but only in used RAM space. In C this is (i think) impossible, this is why I thought of 10 bytes in all RAM. Behaviour improvement during EMC was obvious. My collegue has done that in AS4 assembler.

As many times as I explain it, people just don't understand that I cannot use pointers to "blindly" write bytes in RAM. I wouldn't know if those bytes are occupied by other variables or not. This is why those RAM positions MUST belong to some fixed address variable, to be sure that no conflict or overlap with other variables will take place. I cannot check IOs etc, but it is an improvement with RAM only. If a register gets to change, then there is a great possibility that RAM has changed also. There is also the possibility that some bytes only are changed in RAM not all, it is observed, it is not my mind's artifact. That's why I also need dispersion of those bytes in RAM. I am not tunnel visioned. If someone offers a better alternative suited to the "traditional way" and "right methods" of writing C (that is avoid fixed addressing), I will gladly follow. But I expect from all people who say that it's a waste of time, to have their boards EMC verified and faced such problems, otherwise it is just theory. Needs are driving you to do things, sometimes against methods you've learned to school.

Last Edited: Sun. Jan 15, 2012 - 10:36 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think you're attacking the problem from the wrong end. If your product is getting upset during susceptance tests, fix that problem. Bosch with some of their ecus grouped variables into small blocks then did a checksum on them. The idea was the variables would get updated in one lot so it was easy to check before the new calc and easy to update after. In C, you could have structures that group a particular calculation and do the checksum stuff before and after. Looks a little like a C++ object.........
No need for jiggery- pokery, just a slightly different way of doing things. I've been looking at brand 'T''s hercules processors recently for a safety project. The top end has two cores in lock step and if they disaggree an exception is raised. There's also ecc on the ram. The interesting thing will be seeing how often you get ram or cpu exceptions. A good indicator for the susceptance tests methinks.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
Bosch with some of their ecus grouped variables into small blocks then did a checksum on them. The idea was the variables would get updated in one lot so it was easy to check before the new calc and easy to update after. In C, you could have structures that group a particular calculation and do the checksum stuff before and after. Looks a little like a C++ object.........

Yes Kartman, this is a decent alternative I have already thought of. What I couldn't figure out, was this. It seemed that during EMC test nothing was working properly except timer interrupt. So I thought that maybe the best place to do this check is inside timer interrupt. If I use 2K RAM for example, it would not be rational to do this time consuming check inside timer interrupt, but from outside. Now the PC value was a mystery during test, so I don't trust that the program flow will pass from this RAM test part of code.

But thank you for the solution, since it is a well known technique, I'm starting a web investigation on this.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

alexis4 wrote:
It seemed that during EMC test nothing was working properly except timer interrupt. So I thought that maybe the best place to do this check is inside timer interrupt
It really does sound like you need to spend effort sorting out the cause rather than how to identify the symptom.

What are you going to do in the app when you have found RAM corruption anyway?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MartinM57 wrote:
What are you going to do in the app when you have found RAM corruption anyway?
Reset.

Imagine the following situation. Program is completely stack during EMC test. When test stops, program is still stack. The only way to get the program going again is to strike it with a surge pulse again.
But if it is reseting, then when EMC test stops, a reset occurs and program starts to operate normally.
I don't expect from the program to operate with no problems during EMC test, but to continue normal operation when the test stops. If during test it is resetting all the time, this is an expectable behaviour. But not stay stacked during and after test, I think everybody realize that.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I expect the program to work during the EMC test. If it fails, then the test has failed. I assume we're talking about the fast transients test here. One of my designs paases this test and even exceeds the requirement for industrial. We just kept cranking up the volts and the device didn't miss a beat. There was just RC filters and sensible pcb design.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

As many times as I explain it, people just don't understand that I cannot use pointers to "blindly" write bytes in RAM. I wouldn't know if those bytes are occupied by other variables or not. This is why those RAM positions MUST belong to some fixed address variable, to be sure that no conflict or overlap with other variables will take place.

Then you have misunderstood what placement of memory sections using absolute addressing in the linker script is going to deliver. You face EXACTLY the same problem. As soon as you do any kind of absolute addressing of any kind you are now in competition with the linker that believes it "owns" the whole memory.

You can do this in GCC (for example) with:

int var = 12345 __attribute__((section(".mysect")));

then on the command line to the linker:

--section-start=.mysect=0x800123

(don't ask why it uses a 0x800000 offset but this places that int at location 0x0123 in SRAM).

From the link I gave in this thread previously it *looks* like the IAR syntax is simply:

int var = 12345 @ 0x123;

But I *still* don't see what this gains over "standard C":

int * p = (int *) 0x123;
*p = 12345;

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
There was just RC filters and sensible pcb design.
Kartman you are right. The board is already under upgrade. Just looking for soft fine-tunings as well.

clawson wrote:
From the link I gave in this thread previously it *looks* like the IAR syntax is simply:

int var = 12345 @ 0x123;

But I *still* don't see what this gains over "standard C":

int * p = (int *) 0x123; 
*p = 12345;


Thank you clawson for the attention.
IAR syntax is this, I used it before I even post the thread, the problem is that although it compiles OK, an error is thrown during code download. It looks like besides editing project options, I have to edit a couple of files inside IAR program file as well.

As for the two code snipsets. The first one declares a variable in a fixed location. So linker will not use this memory location because it is already occupied by this variable (var). With the second code snipset I write to an address with the use of a pointer. But I don't know what may be stored inside 0x123 address. There could be another variable in there. By writing to this address via pointer, I may spoil its value. Am I missing something?

Thanks.