Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

THE DOT COMMAND

The last part is deciding on how the dot command should be invoked.  The idea here is to support three options:

-R to reset the XADC

-d to read the DNA

-x to read the XADC temperature and supply voltages

We'll follow standard unix convention by allowing both of these types of invocations:

.xper -R -d -x
.xper -Rdx

If xper is entered on its own, help text should be printed.  If an unknown option is specified, an error should be generated.

For C, we're using Z88DK to compile the program.  It knows about dot commands and how to automatically produce them.  By default, it will properly parse the command line for us into C's standard argc/argv interface and it will arrange to exit properly back to basic, including registering an error intercept in case basic generates an error while the dot command runs.  This happens, for example, if the program printing causes scroll? to appear and the user presses space to break.  This case has to be taken care of for reliable return to basic.

C Programs start at main():

unsigned char old_cpu_speed;
void cleanup(void)
{
   ZXN_NEXTREGA(0x07, old_cpu_speed);          // restore original cpu speed
}
unsigned char board_issue;
int main(unsigned int argc, char **argv)
{
   // speed up
   
   old_cpu_speed = ZXN_READ_REG(0x07) & 0x03;  // remember the current cpu speed
   ZXN_NEXTREG(0x07, 0x03);                    // run at 28 MHz
   
   atexit(cleanup);                            // always run cleanup when program terminates

We're going to run this dot command at 28 MHz (why not?) but we'll restore the cpu speed on exit.  To do that, we store the current cpu speed in a variable and then set the speed to 28 MHz.  A single exit function is registered with atexit().  The startup code guarantees that this function will be run when the program exits no matter what happens.  The cleanup() function registered will restore the proper cpu speed.

Options parsing is straightforward:

   // check options & help
   strupr(argv[0]);                            // capitalize name of dot command
   board_issue = (ZXN_READ_REG(0x0f) & 0x0f) + 2;
   if ((argc == 1) || (board_issue < 4))       // if no options or board issue too low
   {
      printf("\n"
             "%s 1.0\n\n"
             "Read Xilinx Peripherals\n\n"
             "* Xilinx DNA\n\n"
             "-d  reports the fpga unique id\n\n"
             "* Xilinx XADC\n\n"
             "-R  resets the xadc\n"
             "-x  reports status of sensors\n\n"
             ".%s -dx\n\n"
             "running on an issue %u board\n"
             "%s",
             argv[0], argv[0], board_issue,
             (board_issue < 4) ? "ISSUE 4 REQUIRED\n\n" : "\n"
            );
   
      exit(0);
   }
   // parse command line
   
   for (unsigned char i = 1; i < (unsigned char)argc; ++i)
      option_parse(strrstrip(strstrip(argv[i])));   // remove spaces around option (bizarre case)

   

ESXDOS starts dot commands with the HL register pointing at the string following the dot command name.  NEXTZXOS does that as well for compatibility but it also passes the whole dot command line string, beginning with the dot command name, in BC.  Z88DK uses the latter for command line parsing so that argv[0] will contain the dot command name as is normal for command line invocations in unix.

The command line parsing puts the number of words in argc.  There is always at least one word (the dot command name).  Words are space separated text in the command line string.  Quotes can be used to group together text that contains spaces.  The array argv[] contains pointers to each word on the command line.  argv[0] is always the dot command name.

The board issue is read from the new nextreg 0x0F and if the program is not running on an issue 4 board or if the dot command is started without any arguments then the help text is printed.  The help text uses argv[0] for the dot command name so it is possible to rename the dot command and still have the help text print properly.

The code for parsing the command line is in a separate file "options.c".  The main code simply goes through each word, one at a time and passes a pointer to the word to the option_parse() function.  The word might be modified by removing leading and trailing whitespace.  We're protecting against wise guys who might invoke with quoted options as in .xper "  -Rdx  ".  Because it's quoted, Z88DK will include those spaces in the first word it parses.

struct flag
{
   unsigned char xdna;
   unsigned char xadc_reset;
   unsigned char xadc;
};
struct flag flags = { 0, 0, 0 };
void option_parse(unsigned char *s)
{
   if (*s == '-')
   {
      while (*++s)
      {
         switch (*s)
         {
            case 'd':
               flags.xdna = 1;
               break;
            
            case 'R':
            case 'r':
               flags.xadc_reset = 1;
               break;
            
            case 'x':
               flags.xadc = 1;
               break;
            default:
               exit(ESX_ENONSENSE);            
         }
      }
      
      return;
   }
   exit(ESX_ENONSENSE);
}

A single byte flag for each of -R, -d, -x is created and initialized with 0 (false).  option_parse() examines each word it is passed from the main program and sets the flags appropriately.  If there's no leading - or the option is not recognized then a canned error is generated via exit().  Returning a 0 value indicates success to NextBASIC and non-zero indicates an error.  ESX_NONSENSE is a canned error message provided by NextZXOS.

Back to main, the last part is calling the implementation functions for each option selected:

   // actions
   
   if (flags.xdna)
   {
      read_xilinx_dna();
   
      printf("\n"
             "*** Xilinx DNA ***\n\n"
             "length    = %u bits\n"
             "unique id = %016llX\n",
             xdna_len, xdna
            );
   }
   
   if (flags.xadc_reset)
   {
      printf("\nXADC RESET\n");
      reset_xilinx_xadc();
      z80_delay_ms(100*8);   // some time for new data to be collected (*8 for 28 MHz)
   }
   
   if (flags.xadc)
   {
      printf("\n*** Xilinx XADC ***\n");
      read_xilinx_xadc();
   }
   
   printf("\n");
   return 0;
}

This is all very straightforward and 0 is returned to indicate success.

COMPILING USING Z88DK

It's all done in one line for this project since it's so simple.

The pragmas controlling the compile are placed at the top of the main xper.c file:

#pragma printf = "%s %f %llX %u"
#pragma output CLIB_EXIT_STACK_SIZE = 1

The size of printf is controlled by only including the converters used in the program.  It's doubly necessary because float code and 64-bit integer code is not included by default.

The exit stack size is set to 1, meaning only one function can be registerd with atexit().  This saves a few bytes in the atexit() table of functions.

The compile line comes down to this:

zcc +zxn -v -startup=30 -clib=sdcc_iy -SO3--max-allocs-per-node200000 --opt-code-size xper.c option.c -o xper -lm -subtype=dot -Cz"--clean" -create-app

+zxn : choose zx next target

-v : be verbose in reporting on steps taken in the compile process

-startup=30 : use rst 16 for printing (stdout) and input is unconnected (stdin)

-clib=sdcc_iy : use zsdcc as c compiler

-SO3 : use aggressive peephole optimization

--max-allocs-per-node200000 : high depth for zsdcc code optimizer

--opt-code-size : use rules that reduce code size, especially for 32-bit and above integers

xper.c option.c : the list of files to compile

-o xper : the root name of output files

-lm : link the math48 floating point library code (40-bit mantissa, 8-bit exponent)

-subtype=dot : the type of compile

-Cz"--clean" : tell appmake to erase intermediate files it uses in forming the output binary

-create-app : invoke appmake to automatically build the output file (in this case a dot command)

The output file will be "XPER" without any extension.  This can be placed in the /dot directory and invoked with ".xper" from basic or the command line.  If you save it as "XPER.DOT" you can invoke it from another directory with syntax like this:  "../xper.dot".  The first dot introduces the dot command; what follows must be a path to the dot command, in this case "./xper.dot" (./ means the current directory).  Having the DOT extension also means the program can be launched by the browser.  However the browser will launch it without arguments which produces the useless help text.  A dot command intended to be invoked by the browser should be written to do something useful when no arguments are given.

EXAMPLE RUN

Let's save that for the next post with a screenshot showing how hot the fpga gets after a gaming session.