How should I modify this RISC-V code to generate a symmetric diamond with the middle row width of N?

100 views Asked by At

I'm asked to write a RISC-V assembly code to generate a symmetric diamond made up of asterisks which has a middle row width of N. N is the odd number taken as input from the user.

Example: Inputting 5 into the terminal should yield the result:

  *
 ***
*****
 ***
  *

Due to my relative inexperience in working with RISC-V as I am a beginner, I couldn't fully accomplish this.

What I have written and tested in RARS is this:

# CONST
.eqv SYSTEM_EXIT, 93
.eqv SYSTEM_READ, 63
.eqv SYSTEM_WRITE, 64
.eqv STDIN, 0
.eqv STDOUT, 1

# DATA
.data

space:
    .asciz " "
asterisk:
    .asciz "*"
newline:
    .asciz "\n"
in_buffer:
    .space 16

# TEXT
.text
.global _start

_start:

    li a0, STDIN
    la a1, in_buffer
    li a2, 16
    li a7, SYSTEM_READ
    ecall

    la a1, in_buffer
    li t0, 0
    li t1, 10

convert_input:

    lbu t2, 0(a1)
    beqz t2, input_done
    beq t2, t1, input_done
    sb t2, 0(a1)
    addi t2, t2, -48
    mul t0, t0, t1
    add t0, t0, t2
    addi a1, a1, 1
    j convert_input

input_done:

    andi t3, t0, 1
    beqz t3, not_odd

    li t4, 0
    li t5, 0

diamond_outer_loop:

    bge t4, t0, end_pattern

    li t6, 0

print_spaces_before_asterisks:

    blt t5, t0, check_asterisks
    j next_row

check_asterisks:

    bge t5, t6, print_asterisks
    j inc_space

print_asterisks:

    li a0, STDOUT
    li a2, 1
    la a1, asterisk
    li a7, SYSTEM_WRITE
    ecall

    addi t6, t6, 2
    j next_asterisk

inc_space:

    li a0, STDOUT
    li a2, 1
    la a1, space
    li a7, SYSTEM_WRITE
    ecall

    addi t5, t5, 1
    j print_spaces_before_asterisks

next_asterisk:

    li a0, STDOUT
    li a2, 1
    la a1, newline
    li a7, SYSTEM_WRITE
    ecall

    addi t4, t4, 1
    addi t5, t5, -1
    j diamond_outer_loop

next_row:

    li a0, STDOUT
    li a2, 1
    la a1, newline
    li a7, SYSTEM_WRITE
    ecall

    addi t5, t5, 2
    j diamond_outer_loop

not_odd:

    addi t0, t0, -1
    
end_pattern:

    li a0, 0
    li a7, SYSTEM_EXIT
    ecall

What this program yields as a result for example input of 5 is:

*
 *
 *
 *
 *

It correctly changes size according to the input number (7 lines if the input 7 for example) but the number of stars is always 1 and is formatted incorrectly.

1

There are 1 answers

1
Erik Eidt On

First and foremost you need a working algorithm.  If you have it already (i.e. in your head or in C or a language you know well), then you need to debug this.  The way to debug is to single step and observe what it is doing at each instruction.  Since the output is wrong immediately (it should print some spaces first), you have something to debug right away.

It is most helpful to have an algorithm using C or a language you know well — before doing the assembly version.  (In other words, you don't have to think in assembly language to develop an algorithm.)  Once you have the algorithm, take it to assembly language as literally as possible.  Assembly language is so much easier if you already know what the code should do (the variables and the control flow that you need).

And if you transcribe a working C code to assembly language, then you'll know that any bugs are assembly translation problems not algorithmic problems, and those are much easier to debug.  (Debugging a broken algorithm is hard in assembly.)

When you have an algorithm in C or other language, you have something you can compare with during debugging, which will tell you exactly where your assembly code is going wrong.  Single step each, both the C and the assembly, and you'll see where the assembly is going wrong.

You may be thinking that you don't need a C version, but let's note that it will only be a 3-5 of lines of code, and it is practically mandatory for you to make progress with this.


I suggest getting it working in C, then take it in C to "if-goto-label" style.

For example, a nested for-loop like this (for printing a simple square matrix):

for ( i = 0; i < 5; i++ ) {
    for ( j = 0; j < 5; j++ ) {
        print ( "*" );
    }
    print ( "\n" );
}
...

Can be transformed, still in C, into if-goto-label:

    i = 0;
Loop1:
    if ( i >= 5 ) goto EndLoop1;
    j = 0;

Loop2:
    if ( j >= 5 ) goto EndLoop2;
    print ( "*" );
    j++;
    goto Loop2;
EndLoop2:

    i++;
    goto Loop1;
EndLoop1:
    ...

This is an accurate transcription of the control structures used in that simple nested for loop example.  This transformation will run the same as the for-loop version.

Translating controls structures properly takes some work as assembly language will allow many useless or wrong things.  :But rest assured that each control flow construct (if-then-else, for/while, sequential statements, nested statements) have a simple pattern in if-goto-label.  Follow the proper pattern and your control flow in assembly will run the same as the C version.

(It is easy here to try to optimize something during translation to assembly, but resist that urge, use the simple patterns even if they appear optimizable.  My preference is to apply optimization to the C if-goto-label code first, then if that works, take that to assembly.)