Skip to main content Skip to local navigation

Exploring RISC-V Options: the RP2350 (Part 6 -- Assembler Errors)

addi t2, t2, 5
Breakdown of a typical assembler instruction, "add immediate" on t2 and 5 into t1.

Writing assembler code is hard, for a few reasons. First, the lack of abstraction forces you to think about each and every step of your program. Second, the lack of abstraction requires you to know a great deal about the hardware. Then, the grammar is just weird. You might write it from left to right (operation, number, number), but the operations are read RPN-style... roughly, from right to left (number, number, operation).

Each architecture has its own assembler grammar, defined in an Instruction Set Architecture (ISA). RISC-V processors have an ISA that is distinct from, say, x86 or PIC18. So, when debugging issues it's important to reference things like the register list defined by the ISA. But the ISA can be unwieldy, so alternate references can helpful... Wikipedia to the rescue! There is a RISC-V instruction list available there.

As I was working on an RP2350 program, adapting it from an existing ESP32-C3 code base, I ran into a number of errors. The error messages kept telling me that I had "illegal operands", which are the things on the right-hand side of most assembler operations. The error screen in the Arduino IDE was showing me these errors:

/Users/jamessmith/Documents/Arduino/eecs2021_LabP2_Task3pt1_soln_SerialMon/asmp2t3pt1.S:93: Error: illegal operands `mv t7,t1'
/Users/jamessmith/Documents/Arduino/eecs2021_LabP2_Task3pt1_soln_SerialMon/asmp2t3pt1.S:101: Error: illegal operands `beqz t7,end'
/Users/jamessmith/Documents/Arduino/eecs2021_LabP2_Task3pt1_soln_SerialMon/asmp2t3pt1.S:108: Error: illegal operands `li t8,DELAYCOUNT'
/Users/jamessmith/Documents/Arduino/eecs2021_LabP2_Task3pt1_soln_SerialMon/asmp2t3pt1.S:110: Error: illegal operands `addi t8,t8,-1'
/Users/jamessmith/Documents/Arduino/eecs2021_LabP2_Task3pt1_soln_SerialMon/asmp2t3pt1.S:111: Error: illegal operands `bnez t8,delay_on'
/Users/jamessmith/Documents/Arduino/eecs2021_LabP2_Task3pt1_soln_SerialMon/asmp2t3pt1.S:119: Error: illegal operands `li t8,DELAYCOUNT'
/Users/jamessmith/Documents/Arduino/eecs2021_LabP2_Task3pt1_soln_SerialMon/asmp2t3pt1.S:121: Error: illegal operands `addi t8,t8,-1'
/Users/jamessmith/Documents/Arduino/eecs2021_LabP2_Task3pt1_soln_SerialMon/asmp2t3pt1.S:122: Error: illegal operands `bnez t8,delay_off'
/Users/jamessmith/Documents/Arduino/eecs2021_LabP2_Task3pt1_soln_SerialMon/asmp2t3pt1.S:125: Error: illegal operands `addi t7,t7,-1'
Error messages in Arduino IDE

How did I tackle this errors? By looking at each one, one at a time, starting with the first one: mv, t7, t1.

Error: mv t7, t1

At first, it looks really weird because there should be no problem "moving" the contents of register t1 to register t7.

Error: illegal operands `mv t7,t1'

Move (mv) is supposed to take two registers as operands. T7 and T1 are both valid "temporary" registers, right? So, I pretended to be a student trying to figure out this kind of error. Where would a student turn to? Not the register list. Instead, I have a feeling that a typical student, today, would turn to Claude or ChatGPT because York University management has "embraced" AI. It has even set up both general principles and guidelines (Revision 2.1) and specific data classification categorizations. So I called up Microsoftt's version of ChatGPT (Bing), asked it what was up with the error. And this is what Bing replied with:

ChatGPT's (Bing's) explanation for why I got the assembler programming error.

But this isn't the actual reason. ChatGPT gave me a really confident answer, but it's wrong. The real reason for the error is because the compiler for the RP2350 knows that temporary register t7 doesn't exist. As shown on Wikichip, the only valid "temporary" registers are t0 to t6.

So the solution to this error is to choose other registers and to write something like:

  1. mv t1,t3 or
  2. addi t1,t3,0

    Next, what about the divu and remu (divide and remainder, unsigned) errors? They both have the same problem and also have the same solution.

    Error: divu t2,t0,10

    The error message looks like this:

    This one is easy: immediate values (like the number 10) aren't permitted with remu and divu. Instead, we need to use another register to hold 10 before calling remu or divu. The solution could look like this:

    li t4,10                # t4 holds a divisor valued at 10.
    divu t2, t0, t4         # t2 = t0 / 10 

    Next up is an error produced by the IDE and its underlying compiler framework rather than the specific code that we're working on.

    Error: "multiple definition of `_start'"

    Many examples of combining assembler and C (or C++) will tell you to create a _start label at the top of the your assembler file's .text section. That's fine if you're building the program mostly from scratch. But if you use a framework like the Arduino framework, then you cannot use _start as it is already defined as the entry point for its internal main function. Otherwise you'll get this error:

    To avoid this, define your starting point as something else, like myAsmFunction or asmp2t3pt1 like I do here:

    You'll also need to set up a definition in your C file and call it as a function from C (or C++).

    Conclusion

    Writing assembler code is hard. It requires patience and insight. You will encounter errors. Knowing how to read them and tackle them is going to be important. My recommendation is always: start small, compile often, be methodical. Errors are going to happen. Tackle the problems knowing that you'll run into errors -- program with debugging in mind. The chatbot results for debugging or code writing assembler are going to be hit-and-miss. It's important to have authoritative tools at your disposal... which means having access to register lists, the ISA, etc.

    Final Note: Print-outs for Better Error Sleuthing

    While I know it might not be a popular idea, I'm going to suggest it: consider printing out your assembler code and annotating it by hand. Assembler code is not nearly as compact as Java, Python or C. It takes more lines to express something in Assembler than in higher level languages. This can be challenging given the limitations for your laptop, tablet or phone's screen. On the other hand, you have virtually limitless real estate if you have a table and printouts. Printing out your code and stepping away from your computer to plan, analyze and debug your code is likely to be helpful and complementary approach to on-computer development.

    Print out of assembler code, showing pen and highlighter and annotations.  (c) James Andrew Smith
    Print out of assembler code, showing pen and highlighter and annotations. (c) James Andrew Smith

    a pen

    James Andrew Smith is a Professional Engineer and Associate Professor in the Electrical Engineering and Computer Science Department of York University’s Lassonde School, with degrees in Electrical and Mechanical Engineering from the University of Alberta and McGill University.  Previously a program director in biomedical engineering, his research background spans robotics, locomotion, human birth, music and engineering education. While on sabbatical in 2018-19 with his wife and kids he lived in Strasbourg, France and he taught at the INSA Strasbourg and Hochschule Karlsruhe and wrote about his personal and professional perspectives.  James is a proponent of using social media to advocate for justice, equity, diversity and inclusion as well as evidence-based applications of research in the public sphere. You can find him on Twitter.  You can find him on BlueSky. Originally from Québec City, he now lives in Toronto, Canada.