Programming Assignment One: PA1

Due Wednesday night, April 22 @ 11:59pm)

The purpose of this assignment is to build your knowledge of using SPARC assembly language especially branching and looping logic, calling assembly routines from within a C program, calling C functions from within assembly routines, passing parameters and returning values, using command line arguments, and learning some useful Standard C Library routines.

You will be writing a program that takes 4 inputs from the command line.

    % pa1 width height border_ch diamond_ch
These inputs are parameters for a diamond pattern which will be printed to stdout. 'width' specifies the width of the rectangle border in which the diamond will be embedded. 'height' specifies the height of the diamond. 'border_ch' and 'diamond_ch' are the ASCII values for the characters which will make up the border and diamond. Appropriate error checking and reporting is required. Note that in most cases we will be displaying all related errors and not stopping after we encounter the first error. Example runs are given below.

Grading Breakdown

README (more info on details of README below): 10 points
Compiling (using our Makefile; no warnings): 10 points
Style (all requirements met, see details below): 30 points
Correctness: 50 points

-10 points (minus 10 points) for each module written in the wrong language
  (C vs. Assembly and vice versa)
Optimization: 5 points - Extra Credit
Must correctly pass at least 70% of test cases to be eligible for Extra Credit
Filling delay slots with useful instructions (eliminating nops)
At least 80% of nops need to be filled to get full "filling delay slots" Extra Credit
Sometimes there are many ways to perform a task, some ways are more optimal
(and worth more Extra Credit points) than others

NOTE: If what you turn in does not compile, you will receive 0 points for this assignment. The files you turn in must compile with the supplied template Makefile in order for your programming assignment to be graded.

Copy the new template Makefile for PA1 from ~/../public/Makefile-PA1 to your pa1 directory and name it Makefile.

Let us start off by looking at some example output:

Output Format

    ieng9.ucsd.edu% pa1 11 9 42 57
    ***********
    *****9*****
    ****999****
    ***99999***
    **9999999**
    *999999999*
    **9999999**
    ***99999***
    ****999****
    *****9*****
    ***********

    ieng9.ucsd.edu% pa1 19 15 35 45
    ###################
    #########-#########
    ########---########
    #######-----#######
    ######-------######
    #####---------#####
    ####-----------####
    ###-------------###
    ##---------------##
    ###-------------###
    ####-----------####
    #####---------#####
    ######-------######
    #######-----#######
    ########---########
    #########-#########
    ###################

    ieng9.ucsd.edu% pa1

    Usage: pa1 width height border_ch diamond_ch
        width      (must be odd within the range of [3 - 21])
        height     (must be odd within the range of [1 - 19])
                   (must be less than width)
        border_ch  (must be an ASCII value within the range [33 - 126])
                   (must be different than diamond_ch)
        diamond_ch (must be an ASCII value within the range [33 - 126])
                   (must be different than border_ch)

    ieng9.ucsd.edu% pa1 10 9 50 52

        width(10) must be an odd number.

    ieng9.ucsd.edu% pa1 21 23 38 48

        height(23) must be within the range of [1 - 19]

        height(23) must be less than width(21)

    ieng9.ucsd.edu% pa1 9999999999 123abc 6 6

        Converting "9999999999" base "10": Result too large

        "123abc" is not an integer

        border_ch(6) must be an ASCII code in the range [33 - 126]

        diamond_ch(6) must be an ASCII code in the range [33 - 126]

        border_ch(6) and diamond_ch(6) must be different

    ieng9.ucsd.edu% pa1 -2 0 5 5

        width(-2) must be within the range of [3 - 21]

        width(-2) must be an odd number.

        height(0) must be within the range of [1 - 19]

        height(0) must be an odd number.

        height(0) must be less than width(-2)

        border_ch(5) must be an ASCII code in the range [33 - 126]

        diamond_ch(5) must be an ASCII code in the range [33 - 126]

        border_ch(5) and diamond_ch(5) must be different

A sample stripped executable for you to try is available at


~/../public/pa1test

Strategy

Start Early! You will write a C main() driver to read in the command line arguments representing the # of columns (width), # of diamond rows(height), ASCII code for border character (border_ch), and ASCII code for diamond character (diamond_ch) and convert them to long ints utilizing the Standard C Library routine strtol() (done in strToLong()). In main() you will also check that each of the parameters are within their required ranges by calling checkRange(). By calling isOdd() you will check that the height and width parameters are odd numbers. Also, check that width is greater than height. Finally, make sure that the border and diamond characters are not equal. Then if everything is fine display a diamond shape (displayDiamond() with the user-supplied parameters) by outputing a single character at a time with printChar().

Function Prototypes of the C functions you will write:

int main( int argc, char *argv[] );
long strToLong( char* str, int base );

Function Prototypes of the SPARC Assembly functions you will write:

int checkRange( long value, long minRange, long maxRange );
void displayDiamond( long width, long height, long borderCh, long diamondCh );
int isOdd( long value );
void printChar( char ch );

Assignment Details

Part 1: The "C" Modules

(1): main.c

To read the arguments from the command line, you will use argc and argv as defined below. The signature for main() should be:

int main (int argc, char *argv[])

argc is the integer count for the number of arguments. Remember that the name of the program is included in this. argv[0] is the name of the program. For example, pa1 13 4 -17 25 19 has argc set to 6.

argv is the vector (array) of string inputs. argv[0] is the string "pa1"; argv[1] is the string "13"; etc. using the above example.

More details of using command line arguments in C are discussed in section 9.10 (page 262) of the text and at Discussion Sections.

You will pass the command line arguments to a C function you will write called strToLong() that will perform the actual conversions, error checking, and error reporting (see below). If there are no strToLong() conversion errors, then check the converted values to make sure that width and height are odd (isOdd()) and in the ranges [3 - 21] and [1 - 19] respectively (checkRange()). Also, make sure that width is greater than height. Finally, check that the ASCII codes for the border and diamond characters are within the range [33 - 126] (checkRange()), and are different. Then output the specified diamond pattern (displayDiamond()).

Use a local header file pa1.h to hold the function prototypes for the functions you write that are used in any of the C modules. [Note: printChar() is only used in the assembly routine displayDiamond() so the function prototype for printChar() should be included in pa1.h for documentation purposes but commented out.] Include this local header file after any Std C Header files in your C modules. Also put any #defines to aid in program management -- so you can make a single change to the BASE, WIDTH_MIN, WIDTH_MAX, HEIGHT_MIN, HEIGHT_MAX, ASCII_MIN, or ASCII_MAX values here and recompile to reflect these changes throughout your C modules. So your pa1.h should look something like this:

    #ifndef PA1_H       /* Macro Guard */
    #define PA1_H
    
    #define BASE 10
    #define WIDTH_MIN 3
    #define WIDTH_MAX 21
    #define HEIGHT_MIN 1
    #define HEIGHT_MAX 19
    #define ASCII_MIN 33
    #define ASCII_MAX 126
    
    /* Local function prototypes for PA1 (written in Assembly or C) */
    
    long strToLong( char* str, int base );
    int checkRange( long value, long minRange, long maxRange );
    void displayDiamond( long width, long height, long borderCh, long diamondCh );
    int isOdd( long value );
    
    /*
     * void printChar( char ch );
     *
     * Only called from an Assembly routine. Not needed in any C routine.
     * Would get a lint message about function declared but not used.
     */
    
    #endif /* PA1_H */
Use double quotes vs. less-than/greater-than characters for local header file includes in your C modules:
#include "pa1.h"
Also, you should get in the habit of using a macro guard for your header files similar to what is used in the Standard C header files.

(2): strToLong.c
Function Prototype:

long strToLong( char *str, int base );

Command line arguments are strings. To convert the strings into integers, you will use the Standard C Library routine strtol(). See man strtol for details. strtol() converts a string to a long int in a given base. The function prototype for the strtol() function is found in the header file <stdlib.h>:

#include <stdlib.h>
long strtol(const char *str, char **endptr, int base);

The first argument str, is a string, which in our case will be one of the command line arguments.

The second argument endptr is the address of a local variable you define as a pointer to a char. This is used to store the address (pointer) to the character in the string that stopped the conversion to a long and is very useful in error checking. To check if the string was a valid input, you just need to check if the character pointed to by the endptr (*endptr) is the nul character ('\0'). For example, if the input is something like "334g56", then the string to long conversion will stop at the 'g' and endptr will point to the 'g' in the original string.

Another erroneous situation could be where the input is a big number like 999999999999. In this case, strtol() will still be able to parse the string up to the ending nul character, but the number will be out of the range of numbers that can be represented by a long (meaning, there are not enough bits in the long (32) to represent the number 99999999999999) . In this case strtol() sets the variable errno to ERANGE which signifies that the number supplied to it is out of range. To use errno include <errno.h>.

The standard structure to use errno with a function that may set errno is


	errno = 0;           /* Clear errno */
	num = strtol( ... ); /* Make the function call that may set errno */
	if ( errno != 0 ) {  /* Check if errno was set indicating there was an error */
	    /* Deal with the error generically with perror() */
	}

To report error conditions that have set errno, you will use perror(). More detailed information on how to use errno and perror() will be given in Discussion Section. You will probably want to use snprintf() to construct a string specifying the string that caused the error (argv[i]) and the BASE used for the conversion, and then use this constructed string as the parameter to perror(). The function prototype for snprintf() in <stdio.h> is

int snprintf(char *s, size_t n, const char *format, /* args */ ...);

When other errors are encountered, such as (*endptr != '\0'), you output an error message to the user using fprintf(). The more specific an error message is the more useful and therefore better it is. When printing error messages use fprintf() to stderr rather than printf(). The function prototype for fprintf() is in <stdio.h>

int fprintf(FILE *stream, const char *format, /* args */ ... );

This is the same as printf() with the exception of the first argument. This argument defines to which output stream to print the statement. You should specify stderr which is standard error ... this ensures that the message is output immediately rather than being buffered, and it is preferred convention to send error/debugging messages to stderr and normal output to stdout. More information can be found by typing man fprintf at the Unix prompt.

Be sure to make error messages clear and informative. Error messages will be separated from the normal output with extra newlines around them and indented with a tab so they stick out. Try the sample pa1test for output formatting.

To communicate back to main() whether an error occurred (either errno set to non-zero after the call to strtol() or *endptr != '\0') and thus the return value is not valid, be sure to set errno to a non-zero value before returning. In main() you should check errno to see if it is 0 (no error) or non-zero (error). Think of errno as indicating "Was there an error?" such that 0 means "No there was not an error" and a non-zero value means "Yes there was an error."

Part 2: The Assembly Modules

(1): isOdd.s
Function prototype:

int isOdd( long num );

This assembly module will implement the logic for checking if the long int argument value is odd or not. Return 0 if not odd to indicate false; return a non-zero value if it is odd to indicate true.

(2): printChar.s
Function Prototype:

void printChar( char ch );

This assembly module will print the character argument to stdout. This is very similar to the assembly module printHello.s given as part of PA0 -- use printf(). But printChar() just prints a single character.

(3): checkRange.s
Function Prototype:

int checkRange( long value, long minRange, long maxRange );

This assembly module will check to make sure the value of the first argument is within the range of minRange and maxRange, inclusive. Return 0 for false; return non-zero for true.

(4): displayDiamond.s
Function Prototype:

void displayDiamond( long width, long height, long borderCh, long diamondCh );

This assembly module will perform the actual outputting of individual characters (via calls to printChar()) such that a diamond pattern is displayed with the user-supplied arguments. You are given a C version here:

#define NL '\n'

void
displayDiamond( long width, long height, long borderCh, long diamondCh ) {
  int row, col, outer, inner;

  char outerCh = (char)borderCh;
  char innerCh = (char)diamondCh;

  /*
   * First display the top row of the outer border
   */
  for ( col = 1; col <= width; ++col)
    printChar( outerCh );

  printChar( NL );

  /*
   * Now display the top half of the diamond pattern
   */
  for ( row = 1; row <= height - 2; row += 2) {

    /*
     * First inner loop to print the outer border char
     */
    for ( outer = (width - row) / 2; outer >= 1; --outer )
       printChar( outerCh );
 
    /*
     * Second inner loop to print the inner/diamond char
     */
    for ( inner = 1; inner <= row; ++inner ) 
       printChar( innerCh );
 
    /*
     * Third inner loop to print the outer border char
     */
    for ( outer = (width - row) / 2; outer >= 1; --outer )
       printChar( outerCh );
 
    printChar( NL );
  }
  
  /*
   * Now for the middle row and lower half of diamond pattern
   */
  for ( row = height; row >= 0; row -= 2) {
     
    /*
     * First inner loop to print the outer border char
     */
     for ( outer = (width - row) / 2; outer >= 1; --outer )
        printChar( outerCh );
  
    /*
     * Second inner loop to print the inner/diamond char
     */
     for ( inner = 1; inner <= row; ++inner )
        printChar( innerCh );
  
    /*
     * Third inner loop to print the outer border char
     */
     for ( outer = (width - row) / 2; outer >= 1; --outer )
        printChar( outerCh );
  
     printChar( NL );
  }

  /*
   * And lastly display the bottom row of the outer border
   */
  for ( col = 1; col <= width; ++col)
    printChar( outerCh );

  printChar( NL );
} 
Just a bunch of nested for loops. Chapter 2 in the textbook covers just about everything you will need to code these assembly modules. We will be covering all these assembly constucts in class.

There are many ways to code displayDiamond(). You are not limited to using the above algorithm, but one of the purposes of this programming assignment is to have you write looping constructs (branches) and use the simple .mul/.div/.rem subroutines with parameter passing and return values and perform simple arithmetic instructions (add, sub) in assembly. You may output only a single character at a time in the fashion of the algorithm above.

We would prefer you use the above algorithm for these reasons, but we also do not want to suppress creative thinking that may lead to alternative solutions. You must use the "preferred" style of coding loops as detailed in the class Notes #4 -- set up an opposite logic branch to jump over the loop body and a positive logic branch to jump backwards to the loop body.

Part3: the README file

Your README file for this and all assignments should contain:

Requirements for Style

You will be graded for the style of programming on this assignment. A few suggestions/requirements for style are given below. These guidelines for style will have to be followed for all the remaining assignments. So read them carefully.

Code Style:

Part 4: Unit Testing

Provided in the Makefile for PA1 are rules for compiling and running tests on individual functions that are part of this assignment. You are given the source for testchechRange.c used to test your checkRange() function. Use testcheckRange.c as a template for you to write modules to test your isOdd() and strToLong() functions.

These kinds of test programs are called Unit Tests because they test a function independently as a single unit of code separate from the whole program. This allows you to develop and more importantly test individual functions more or less in any order, and allows you to have a high level of confidence each function is correct when you put them together to form the whole program.

Unit tests you need to write:

testisOdd.c
teststrToLong.c

Think of how to test each of these functions -- boundary cases, special cases, general cases, extreme limits, error cases, etc. as appropriate for each function.

As part of the grading, we will run all the unit test targets in the Makefile and manually grade your unit test programs. For example:

make runtestisOdd

Copy ~/../public/test.h and ~/../public/testcheckRange.c to your pa1 directory.


Helpful Information:

Start early! Remember that you can and should use man in order to lookup information on specific C functions. For example, if you would like to know what type of parameters strtol() takes, or what strtol() does to errno, type man strtol. Also, take advantage of the tutors in the lab. They are there to help you learn more on your own and get through the course!

File Headers and Function Headers

See PA0 handout for examples of File Headers and Function Headers.
Remember to complete the Sources of Help: comment for each file.

Turn In, Due Wednesday night, April 22 @ 11:59pm

Use the turnin program to turn in the following:

Your code (C and assembly and header files -- *.c, *.s, *.h)
A README file that describes how to run your code and also how you tested the code
Makefile (Copy the template Makefile in ~/../public to your pa1 directory.)

To reiterate the turnin process, in your pa1 directory:

make clean
cd
turnin pa1
verify pa1

Subversion/CVS

You are highly encouraged to use Subversion or CVS with all your programming assignments. See the Subversion Notes / CVS Notes for this class.

A Subversion module setup script is available at

~/../public/cse30_svnrepo.sh