Sunday, August 11, 2024

CPULATOR--Stop Showering, Learn ARM ASM

 Let's start this one with a snipping tool PNG of the ubiquitous Arduino Nano:

Let's say I want to blink its LED in an extremely laborious manner.....

I could craft a really long string of 1's and 0's and somehow feed this into the Nano's memory.

If I got everything just right the Nano's on-board LED would---well--blink

Of course sequencing the correct the 1's and 0's, getting them into the nano's flash memory, troubleshooting things when the LED just sat there, everything else? would take days, weeks, who knows.



Of course there are easier, highly abstracted ways to do this. 

The popular one: write a few lines of code using Arduino's handy Sketch language, hit "compile and upload" on your PC, optionally wire up a breadboard: se habla blink. 

Weeks? Nope. Minutes. 

Sketch allows us to impress our colleagues, puzzle our spouses, and, as tech nerds, in record time, get on with our lives.  ASM? Not as much.

However I stopped using sketch many years ago....too easy. I'm into tech torture. 

Assembly language is about as low as you can go without having to calculate actual sequences of 1's and 0's.  

This time I document my first steps learning assembly programming for ARM processors, the instruction set used in so many popular MCU's, smartphones, personal computers, and every other damn thing. 

The platform of choice for learning ARM ASM, for me, was the amazing CPULATOR

The different SIMS CPULATOR has to offer. Free! Amazing.


SEE-PHEW LATOR?

This is an awesome online learning tool. Code, compile, and debug NIOS, ARM, MIPS, or RISC-V assembly. The simulator has virtual peripherals like 7 segment LED's and switches, also a code editor, a disassembler, and a searchable memory map.

It's browser based--so, nothing to install or download.

Overall: if you want a challenge, and you want to really learn how computers work at a deep level, ASM could be hugely benficial.

With that out of the way.....

Let's CODE!

There are plenty of good resources for learning ARM ASM: tutorial video series that start here and here for instance.

I found the ARM ASM documentation (here) somewhat hard to follow but discovered I could google ASM instructions like ADD or MOV and usually get what I needed pretty fast: explanations of what the instructions did and code examples.

I also found a number of "cheat sheets", the one here is good for instance. To decipher however you need a cheat sheet for this cheat sheet:
  • Rd,Rt:  a destination or target register
  • Rn, Rm,Rs:   source registers
  • "instruction":  MOV, ADD, STR -- the instructions, Elmo.
  • "Operand": data in a register 
  • <Operand2>  go to page 5 of the cheatsheet for details
  • <amode2/amode3/amode4 etc>  go to page 5 for details
  • {cond} if/then based on a condition (for instance, if a CSPR flag is set): Go to page 5 for details.

I posted whatever fragments I got to work on github--here.  

For the rest of this post I will focus on a somewhat complex (for me anyway) algorithm I wrote for CPULATOR. 

It takes 6 least significant bytes in an .equ variable (which could pretty easily be put into a register) and puts them into the sim's 6 digit fake 7 seg displays:




 
You can get the code for this here.  Let's dig in.
 

First, how does the memory map for this work--The 7 seg is marked 0xff200020:




That's the memory mapped location to address the rightmost 7 segment display, but, what about the remaining leftmost 7 segment displays that appear to the left of it?

Turns out I had to left shift bits (LSL) by 8 to have them occupy LED's to the right of xff200020, then add any subsequent data to that, finally, STR it:

       LDR R2,[R0] @load value found at mem_0[0] into R2

       LSL R2,#8   @now, offset this value by 8 positions
 
       LDR R3,[R0,#4] @get mem_0[1] and put in into R3
   
       ADD R2,R2,R3  @add value of mem_0[1] to what is already in                           @R2,the LSL's mem_0[0]
      STR R2,[R1]   @finally store this puppy into 7seg 


Makes sense, but I also incorrectly guessed that STR'ing to 0xff200028 would get me to another 7 seg.  Nope.

After the 4 rightmost 7 segment LED's we are out of bit shifts--this is a 32 bit CPU so we can't shift beyond 8*4.  

The rightmost 2 start at 0xff200030. 

To get to the 7 seg furthest left, LSL and add. Same idea as above, but from a different starting point in mapped memory.

It seems the idea of getting a value, putting it into a register or memory then shifting it, repeating with a lesser shift, then adding, is pretty common in low level programming.  For example I found the video here from the great "Core Dumped" YouTube channel outling a somewhat similar idea used for casting strings to integers. 
 
OK, we can address each 7 segment display, but writing a hex integer to them looks like crap. Of course, this is a 7 segment display, with different bits lighting up different LED's, so simply writing hex values 0x0-0xF directly will come out fugly:

The 7 seg may look bad, but boards you get from this blog's sponsor, PCBWAY, never will.  

   
Therefore I had to write an algorithm inside the algorithm to convert hex to appropriate values for the 7 seg displays.

Some of the steps needed:

Create an array:
 segs: .word ZERO,ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE,XA,XB,XC,XD,XE,XF

Create a series of variables to populate the array:
 .equ ZERO, 0x3F
.equ ONE, 0x06
.equ TWO, 0x5B
.equ THREE,0x4F
.equ FOUR,0x66
.equ FIVE,0x6D
.equ SIX,0x7D
.equ SEVEN,0x07
.equ EIGHT,0x7F
.equ NINE,0x67
.equ XA,0x77
.equ XB,0x7C
.equ XC,0x39
.equ XD,0x5E
.equ XE,0x79
.equ XF,0x71

Put the array into memory--the address of which is stored in Reg 2:
LDR R2,=segs @memory array of values for 7 seg conversion

Translate the hex value to the correct 7 segment equivalent by  walking the array for a match (this is not the whole subroutine, but you hopefully get the idea):

   MUL R3,R3,R4     @we need to multiply value of counter R3                              @by 4.
   LDR R0,[R2,R3]!  @retrieve address found in array SEGS                                @offset by number in R3, put the value                                @ found into R0.
   MOV R3,#0        @we have a match, reset R3 counter to                                @zero.
   LDR R2,=segs     @we have a match, put R2 value back to                                @first element of SEGS
   MOV R3,#0x10     @R3 is COUNTER for matching numbers and                              @the getting value in SEGS                                            @array.
 

Wow, a lot of work.  And I am sure it can be refactored to much simpler code. But for now, it works.

OUTTRO

It became apparent that a lot I take for granted in C has to be done "from scratch" in ASM.  

I had read that programming in ASM can be difficult and time consuming and didn't think a lot about it. But once it in, I had hadn't anticipated how much wheel re-inventing was needed.

This one algorithm a lot of it and its sub-algo took me maybe 30 hours of coding over 3 really long days. 

Ouch! Granted, I am a beginner.....but there it is.

Overall my experience with ARM ASM was both good and bad. 

The positive:I was introduced to a programming pagadigm that improved my knowledge of computing "at a deep level" and had the satisfaction getting a slightly complex algo to work. 

When I put in several test values, hit "step over", and had the correct characters come up on the display (finally!) well, that was a great feeling. 
 
The negative: I also found myself going down my too familiar compulsive rabbit hole. 

Like a gambling addict, my code got close but not quite there, so I tried again, over and over and over. I couldn't tear myself away; I found myself not sleeping, not eating, not showering.  And as always I was more prone to just try things and see what happened then to step away and try to better understand why they happened. 

ARM AS&M became the 20,000 calorie sundae I couldn't stop eating, the butt-terrible violent streaming TV show I had to keep watching, the girlfriend's apartment I couldn't leave. 

So where will I go next?  Maybe I will incorporate fast ASM fragments into larger C projects. Maybe. ASM was a lot of terrible fun, so, maybe/probably. 

But I also have to think about things from the neck down.  Full time ASM could be unhealthy for me.






No comments:

Post a Comment

Rotary Encoder Expermenter's Board: Improving the Hardware

Quick one this time....I have posted a few projects lately that incorporated a Raspberry Pi Pico, rotary encoder, and .96" OLED:  here ...