9 Roll Your Own (Hardware Diagnostics, that is) 9 9 Mitch Bradley 9 Here's how you can easily write programs to diagnose your hardware. The programs can range from very simple things like reading a register to very complicated things like performing communications protocols. Each higher level of tests can build on the lower levels. At any time, you can interactively invoke any part of the test, without having to build a command inter- preter into your program. This document describes how to do very simple things interac- tively. Another document describes how to save your work in a file and how to make a sequence of tests run automatically. Yes, this is Forth I'm describing, but PLEASE don't quit reading yet, just because of that. Forth really isn't hard to learn or unreadable or un-American, it is just different. Read on and see for yourself! All you need is a 680x0 system of some sort. Any of the sys- tems we have at Sun is fine - the same programs will run on the Sun Multibus CPU board, the Sun 2050 single-board, the Astraea VME 68000 board, the Motorola VME CPU board, the Carrera, the Model 25, the Turbo, etc. They will run either under Unix* or stand-alone. Warning The examples in the rest of this document assume that you are running on a stand-alone system, and that there really are I/O devices located at the addresses mentioned. If you are running Forth under Unix, Unix won't allow you to access device regis- ters, so the examples in this paper will cause a core dump of the forth process. If you want to try out this stuff under Unix, always use addresses in the range 2a000 to 2e000 and all will be well. In fact, most of these examples assume that you are trying to debug a board whose registers begin at virtual address (hex) _________________________ You can't do many hardware tests under Unix, because the memory protection prevents you from accessing most of the hardware, but you can conveniently edit your code using your favorite editor. You can also compile and check for syntax errors. March 20, 1986 - 2 - ee2000. This address isn't likely to be the real address of your board, especially since that address is already used for the (obsolete) parallel port. If you just want to play around on a random system, use instead the address 2a000, which happens to be in accessible memory for any configuration that I can think of. Getting started The major difference between the various kinds of 68000 sys- tems that we have, as far as the Forth user is concerned, is how to boot. On a Sun system, boot from the Ethernet: >bie(,3)stand/forth This monitor command boots the stand-alone forth system from venus. If you are using a 3Com Multibus Ethernet board, replace `ie' with `ec'. The "1a" is the network address for venus. If you're on a different network, replace "1a" with the appropriate address for your server. You may have to copy venus:/pub/stand/forth to your /pub/stand on your server first. You can also use one of the Forth diagnostic programs, such as ether.forth, which include the complete Forth system as part of the the diagnostic. If you have a set of Forth PROMs for your CPU, all you have to do is plug them in, hook up a terminal, and turn on the power. These Model 50 PROMs are primarily intended for debugging a flaky CPU board, so they use video memory for their RAM. This means that they will frequently work on a single-board whose main memory is broken or flaky. Forth PROMs are available for the Model 25 and the Turbo too. If the CPU board you are using works, you should go ahead and boot Forth over the net rather than using the PROMs. When the Forth system has booted, it will prompt with `ok'. Poking at registers. The first hurdle to get over when debugging a new board is usually reading and writing the device registers. Of course you can poke at them from the monitor, but when they don't work, you are sort of stuck. Suppose that you have a register which is at address (hex) ee2000. You can read it with: ee2000 w@ . `w@' says to fetch a word from the preceding address. `.' says to print it. There must be one or more spaces separating each symbol!* _________________________ The Forth parser is really simple; it just grabs the next sequence of non-blank characters (called a `word' in the jargon) from the terminal and looks up that word in its March 20, 1986 - 3 - The most likely result of trying this exercise on a prototype peripheral board is that the board won't respond to the cycle, so the CPU will get a bus error and abort. Don't worry; just type >g10004 to the monitor to get back into Forth. Scope Loops No, you don't have to get out your 68000 programmer's refer- ence manual and try to figure out how to poke in a tiny loop. Just do this. ee2000 constant reg-addr : test begin reg-addr w@ drop again ; This makes a new command called `test' which will loop on trying to read the register. `begin ... again' is a loop-forever con- struct. The `cleanup' is necessary to get rid of the value that was read from the register, which is left on a stack. That stack would overflow before forever, even on a 4-Meg machine. The `constant' line defines the new word `reg-addr' as a constant whose value is `ee2000'. We could have just used the value inside the test word, but we all know that it's bad form to bury magic numbers inside programs. Don't execute the new command yet, because we still haven't coped with the bus error problem yet. Type catchberr which tells Forth to catch the bus errors and ignore them. Now you can execute your scope-loop test, just by typing test In general, the way to execute a Forth command is by typing its name. So now the machine is sitting there banging away at your register. Now you can try to find a scope that still has some probes attached and figure out why your register isn't respond- ing. Remember that you can interrupt the loop with a L1-A, which gets you back to the monitor, then g10004 which gets you back to Forth. If you're running under UNIX just type ^C. _________________________ internal dictionary. If it finds the word, it executes some associated code. If it doesn't find the word, it tries to parse the word as a number. If that fails, it complains. March 20, 1986 - 4 - Writing to registers Now that you can read your register, no doubt you want to write to it too. 1234 reg-addr w! writes the word `1234' (hex) to the address left by the word `reg-addr' (which we defined earlier). If you want to write a byte, use `c!'. Longs may be written with `l!'. You can read bytes and longs with the obvious operators `c@' and `l@', respec- tively. reg-addr w@ . reads back the register and prints the value, so you can verify that the write actually worked. Do Loops An obvious thing to do now is to write a bunch of different values to the register and see if they all work. : test-loop ffff 0 do i reg-addr w! ( write a value to the register ) reg-addr w@ ( read it back ) ( register value on stack ) i <> ( see if the value read back is different from the one written ) if ." Error - wrote " i . ." read " reg-addr w@ . cr then loop ; The indentation is optional. If you were writing this test `on- the-fly' while sitting in the lab, you would probably not bother with indentation. Similarly, everything inside parentheses is a comment and may be omitted. If you are constructing this test in a Unix file to save, don't omit the comments or the indentation. One primary reason that people claim that Forth code is hard to read is that some Forth programmers tend to cram too much on one line and neglect to comment. This is historically understand- able, because the editors and filing systems supplied with many Forth systems are very painful. It should not apply to Forth programs written at Sun, because our Forth system uses normal Unix files for program storage, which may be edited with vi or Emacs or whatever. How does this test-loop work? Let's go over it line-by-line. The `ffff 0' are the arguments to the `do ... loop' construct. The loop will start at 0 and will end when the loop index reaches (hex) ffff. The last time through the loop the index will have the value (hex) fffe. Inside the loop, we start with `i reg-addr w!'. Previously we used the literal number `1234' as the value March 20, 1986 - 5 - to store into location reg-addr. This time we use the loop index `i'. The loop index is ALWAYS called `i'. This may sound like it restricts you to only using one level of loop nesting, but it doesn't. If loops are nested, the index of the next outer loop is called `j'. The next thing we do inside the loop is read back the regis- ter. Previously we printed the value as soon as we read it; this time we'll let the program look at and decide if it's okay. But where is the value kept? It's on the stack, just like on an HP calculator. In fact almost every operator in Forth takes its operands from the stack and leaves its results on the same stack. I'll assume that this concept is familiar to you; if it isn't, let me know and I'll either expalin it to you or loan you a book which does so. Anyway, the register value is now sitting on the stack. The next thing we do is compare that value to the loop index `i'. The operator `<>' (not-equal) compares the top two things on the stack and leaves true if they are not equal or false if they are equal. If the numbers are equal, all is well. If they are dif- ferent, we need to print an error message. That is where the `if ... then' construct comes in. Here is the strange part: The stuff you want to do if the condition is true goes BETWEEN `if' and `then', not after `then' as one would expect. This is unfor- tunate, but it is not the end of the world. The condition that is tested comes BEFORE the `if'; in this case the condition is the true/false value left on the stack by the `<>' operator. If this seems strange to you, consider that it is very simple, yet completely general. It is also possible to specify an `else' clause (details later). The only thing remaining for this test-loop is to describe how the error message is printed. The construct `." ... "', properly pronounced `print-string' but often called `dot-quote', just prints whatever is inside the quotes. The first space after the first quote is mandatory and is not printed. Any subsequent spaces before the next quote are part of the string and are printed. Next we print the loop index with `i .'. As you have probably guessed, `.' just means print whatever number is on the stack. Next we print another string, followed by the value read back from the register. Finally, `cr' prints a carriage-return and linefeed. Extensibility Earlier we saw how to make a word called `test' which could then be executed by typing its name. Once you have made a word, you can then use it as part of another word, thus building on top of your previous work. For example, suppose that there is a dma address register on your board, and that its address is (hex) ee2804. You can define a word to store a value into that regis- ter as follows: March 20, 1986 - 6 - : dma! ee2004 l! ; This defines a new word called `dma!' which takes an argument and stores it into the prescribed location. This word can be used as: f00000 dma! which will store f00000 into the dma register. Now, suppose that as part of a test, you need to automatically set the dma regis- ter. You can use your word dma! as part of another word. : init-dma f00000 dma! ; This is a trivial example, but it serves to illustrate the style of building up your application in small incremental steps. Don't hesitate to build words which only have a few components; the overhead of calling a word from one at higher level is quite small, and the advantages of small words are many. Variables Define a variable with variable foo The new variable `foo' has space for a 32-bit word. Put a number in the variable with: 129876 foo l! and get it back with foo l@ The number to be stored is taken from the stack, and the number fetched is left on the stack. When you typed the 129876, that number was actually left on the stack, and `foo l!' picked it up and put in the variable foo. `foo l@' retrieved it from the variable and returned it to the stack. Constants A constant is a symbolic name for a number. In other words, when you type the name of a constant, it just leaves its number on the stack. One way of making a constant is the obvious: : mem-base 100000 ; Now the word mem-base is equivalent to the number 100000. A slightly more efficient form of this is: 100000 constant mem-base March 20, 1986 - 7 - A word defined with `constant' will execute somewhat faster than one defined the other way (but you would probably never notice the difference). C Language Analogies C Forth while( condition ) { begin condition while loop body loop body } repeat do { begin loop body loop body condition until ( condition ) until for( i=start value; end value i end value; start value i = i + increment ) { do loop body loop body increment } +loop for( i=start value; end value i end value; start value i = i++ ) { do loop body loop body } loop if ( condition ) { condition true clause if true clause } else { else false clause false clause then } if ( condition ) { condition true clause if true clause } then "Forth Notes:" condition is any sequence of forth words that has the effect of leaving a number on the stack. If the number the stack is 0, the condition value is false, otherwise it is true. Within a do loop, the word `i' will put the loop index on the stack. March 20, 1986 - 8 - On More Thing ... You may want to do a scope loop which can be easily inter- rupted. You can always abort back to the Sun monitor with "L1- A", and then return to Forth with g10004. A nicer way, however, is the following: : scope-loop begin 1234 ee2000 w! key? until ; This word will continuously write `1234' to location `ee2000' until you type any key. The word `key?' returns true (which hap- pens to be equal to -1) if a key has been depressed, and false (0) if not. Other Wonderful Features Forth includes, among other things, a resident 68000 assem- bler, so you can write little bits of assembly code if you need to. It has a built-in visual line editor, so you can edit com- mand lines as you type them. There are packages for defining structures and bit fields, similar to C. A built-in decompiler allows you to interactively decompile any forth word that you have previously defined. Documentation to be provided as time permits. Line Editing While you're typing a forth command line, you can move around in the line and edit it. Here are the editing commands: (^X means hold down the CTRL key and type x; ^[x means type the ESC key then type x). : ^F forward-character ; : ^B backward-character ; : ^A beginning-of-line ; : ^E end-of-line ; : ^D erase-next-character ; : ^H erase-previous-character ; : ^K kill-to-end-of-line ; : ^M finish-line ; : ^@ expand-word ; : ^_ show-matches ; : ^Q quote-next-character ; : ^L retype-line ; : ^[h erase-previous-word ; : ^[d erase-next-word ; : ^[f forward-word ; : ^[b backward-word ; : ^[= grab-last-line ; March 20, 1986 - 9 - "Grab-last-line" means repeat the previous command. It only works right when you haven't yet typed anything on the new line. You can edit the grabbed line before hitting carriage return if you want. "Expand-word" is command completion. If you have typed part of a forth word and are too lazy to finish typing it, just type ^J and forth will try to complete the word for you. If there are several choices, forth will complete the word as far as it can and then beep at you. "Show-matches" (^_) will then show you what all the choices are. Examples and Other Tidbits 1000 300 dump ( dump 300 bytes of memory starting at 1000 ) 1000 300 aa fill ( fill 300 bytes of memory starting at 1000 with "aa" ) : scan ( end start -- ) ( scan memory for bytes not equal to "aa" ) do i c@ aa <> if i . then loop ; 1300 1000 scan ( scan from 1000 to 1300 ) 55 aa npatch scan ( change the number "aa" in scan to "55" ) March 20, 1986