Copyright 1997, 1998, 1999, 2000, 2001  Providenza & Boekelheide, Inc.

July 25, 2001
March 23, 2000
Oct 28,1999
Sep 9, 1999
April 26, 1999
February 24, 1999
August 26, 1998

What's New
Environment variables can now be used to select the PCI device.
Check out Extending DBG for a new method for adding your own customized DBG commands.
Forth Cheat Sheet  has been added.

DBG is a hardware debugger designed for use on a x86 based PCI system. It includes an extensive set of debug commands as well as a Forth interpreter to allow flexible scripting and interactive diagnostic development.

DBG is a DOS based application designed to run in a variety of OS configurations. We’ve successfully used DBG in:

Why doesn’t DBG use a fancy Windows interface with dialog boxes and pop-up menus? DBG needs to be able to run in a simple environment to help debug hardware. The Windows OS may make the debug environment too fragile to use. Speed is also a concern. We’ve used DBG on PC’s where the Pentium is running at about 2% of its normal speed - you just don’t want to wait for Windows to boot when the computer is this slow.

DBG is constantly in development - your feedback is welcome.  The revision history section can show you what's new.

Throughout this document, hex numbers are used unless otherwise noted. Note that DBG and its Forth interpreter are case insensitive. We’ll frequently use the style $reg to emphasize that the parameter us a hex value.

Bytes are 8 bits, Words are 16 bits, Dwords are 32 bits.

Note that when specifying a memory address...

Thanks to Phil Burk for the Pforth interpreter source code.

Also thanks to the folks who created the PMODE/W DOS extender used by DBG.

Starting DBG
DBG is started from the DOS prompt by typing:

dbg [options]
If no options are specified, DBG finds all the devices on the PCI bus and prompts the user to select one for debugging. DBG then uses the selected device to initialize internal Base Address Registers (BARs) that are used to access the selected device. You can skip this interactive prompt by using both the -d and -v options to manually specify a PCI device or bye setting a couple of environment variables. Multiple command line options can be used.
Command Line Options
Flag Description
-d$pci_device_id specify the target PCI device ID
-v$pci_vendor_id specify the target PCI vendor ID
-nfunction specify the target PCI function
-t$slot specify a PCI device via slot number
-ccommand run a debugger command
-f start in Forth mode
-ffilename load the specified Forth file, and start in Forth mode
-sfilename specify a script to run
-q quit the debugger

Command Line Examples
dbg -d1234 -v5678 Start the debugger and use the PCI device with vendor ID 5678 (hex) and device ID 1234 (hex).
dbg "-c 1dmem 0" -cbye Start the debugger, display dword 0 in BAR 1, then stop the debugger. Note the quotes.
dbg -d1234 -v5678 -f Start the debugger and use the PCI device with vendor ID 5678 (hex) and device ID 1234 (hex). Start in Forth mode.

PCI Vendor/Device ID
Command line options can be used to specify the desired target PCI device.  The two methods are:

The use of -v/-d and -t are mutually exclusive - it just doesn't make sense to specify both slot and vendor/device IDs.

The vendor and device IDs can also be specified using the environment variables DBG_VENDOR and DBG_DEVICE.  These would typically be set using the DOS 'set' command:

set DBG_VENDOR=1131
set DBG_DEVICE=8500
If you're using a PCI slot number to specify the PCI board, you can use the DBG_SLOT environment variable.

If neither valid command line options or environment variables have been used to specify the PCI device/vendor values, DBG reverts to a simple interactive prompt to force the user to select an appropriate device.

Using DBG
DBG can either operate in native mode or in forth mode. Native mode presents a traditional user interface with a limited command set. Forth mode provides a direct interface to Forth - a interpreted, stack based programming language.

Core to DBG is the concept of  Base Address Registers (BARs).  DBG uses BARs to access the different memory spaces that many PCI devices utilize. For example, many graphics devices will use one BAR for registers and another BAR to access the video memory. DBG’s BAR registers allow you to specify offsets into each BAR rather than having to specify an actual physical address for each command. This is very useful since the PCI BIOS is allowed to map these two BARs in different memory locations each time the system is booted. Thus, if you know that the FOO register is at offset $14 in BAR 1, you can access FOO by simply using the number $14 rather than an absolute physical address. DBG treats BARs 7 and 9 specially. BAR 9 is pre-initialized to allow access to the bottom 1MB of PC address space - this allows easy access to peripherals (such as VGA) that use portions of the old DOS memory area. BAR 7 is used to allow access to other areas of physical memory as specified by the user - see the BAR7 command below.

'Native' DBG Commands
DBG has numerous 'native' commands in the non-Forth mode of operation.  These commands allow you to examine memory, IO, and configuration spaces, test memory, etc.  The commands can be broken into the following categories:

The native mode DBG prompt looks like:
The [0] in this case indicates that, by default, BAR 0 will be used for all memory oriented commands. You can change this default by using the BAR command.

Sometimes, you want to access a memory location in a different BAR than the default for only one or two commands. DBG allows you to do this by prepending the desired BAR to the command. For example:

[0]cmd> 1dmem 0
will use BAR 1 for the dmem command.

Basic Access Commands
The following are basic memory, I/O, and PCI commands. Each command begins with either a b (byte), w (word), or d (dword) to specify the size of the access to perform. Thus, the bpci command is used to access individual bytes of pci configuration registers for the current device.

The pci, io, and mem commands are used to access a single memory, I/O, or PCI configuration location. Each command is followed by an address and, optionally, a data value. If no data value is specified, the value at the specified address is displayed. If a data value is specified, it is written to the location.

Command Action
bpci $reg <$data> byte read or write a PCI register 
wpci $reg <$data> word read or write a PCI register
dpci $reg <$data> dword read or write a PCI register
bio $port <$data> byte read/write a I/O port
wio $port <$data> word read/write a I/O port
dio $port <$data> dword read/write a I/O port
bmem $addr <$data> byte read/write a memory location
wmem $addr <$data> word read/write a memory location
dmem $addr <$data> dword read/write a memory location

The compare commands are used to compare memory to an expected value.  If the value does not match, an error message is printed.  If the halt flag is set, repeat_loops and scripts will also be aborted on an error.

Command Action
bcmp $addr $data compare byte memory location to an expected value
wcmp $addr $data compare word memory location to an expected value
wcmp $addr $data compare dword memory location to an expected value

The wait commands are used to wait for an event to occur as indicated by a value in memory changing to an expected value.  The wait can be interrupted by a keyboard hit - if so, an error message is printed and repeat_loops/scripts will be aborted if the halt flag is set.
Command Action
bwait $addr $value $mask wait until ( (byte_memory & $mask) == $value || kbhit() )
wwait $addr $value $mask wait until ( (word_memory & $mask) == $value || kbhit() )
dwait $addr $value $mask wait until ( (dword_memory & $mask) == $value || kbhit() )

The examine command allows you to interactively examine and modify memory locations. Like the previous commands, byte, word, and dword variants allow access to different sized locations.
Command Action
bx $addr examine bytes in memory
wx $addr examine words in memory
dx $addr examine dwords in memory

After entering the command, DBG displays the current value in the memory location, then waits for user input:

  • hex numbers are used to alter the value in memory
  • space  advances to the next location. If any hex numbers have been entered prior to the space key, they are written to the current location prior to advancing.
  • enter  re-reads the current location. If any hex numbers have been entered prior to the enter key, they are written to the current location prior to re-reading.
  • back-space  advances to the previous location. If any hex numbers have been entered prior to the space key, they are written to the current location prior to advancing.
  • other aborts current examine command.
  • This command does not have a graceful way to correct typing errors - be careful! If you mis-type a hex value, type eight zeroes then the value you intended.

    The dump command allows you to dump a range of memory locations. Again, bytes, words, and dwords variants are supported.

    Command Action
    bdmp $addr1 [$addr2] dump bytes of memory
    wdmp $addr1 [$addr2] dump words of memory
    ddmp $addr1 [$addr2] dump dwords of memory

    If no second address is specified, it is assumed to be 3Fh greater than addr1.

    The fill command is used to fill a range of memory locations with a constant value. Again, bytes, words, and dwords variants are supported.

    Command Action
    bfil $addr1 $addr2 $data fill byte memory range with data
    wfil $addr1 $addr2 $data fill word memory range with data
    dfil $addr1 $addr2 $data fill dword memory range with data


    DBG has a set of commands designed to help in testing memory and registers.

    The test commands (byte, word, dword) provide rudimentary memory tests.

    Command Action
    btst [$addr1 $addr2] run byte oriented memory tests
    wtst [$addr1 $addr2] run word oriented memory tests
    bdst [$addr1 $addr2] run dword oriented memory tests

    The memory tests operate in several phases:

    If any errors are found during testing, the error message is displayed with the address that failed, the data read from memory, the expected data, a -re-read of the bad location, and an XOR of the bad data against the expected data.

    The crc command is provided to compute a 32 bit CRC of the specified memory range.

    Command Action
    bcrc $addr $addr [$value] compute byte CRC, optionally compare to expected value
    wcrc $addr $addr [$value] compute word CRC, optionally compare to expected value
    dcrc $addr $addr [$value] compute dword CRC, optionally compare to expected value

    The csum command is used to compute a 16 bit checksum of the specified memory range.

    Command Action
    csum $addr1 $addr2 compute 16 bit csum on the range of bytes


    Script/DOS Commands
    DBG can read commands from a script file. The following commands are useful when invoking or writing scripts.
    Command Action
    loop rewind the current script and run it again
    loop $value $mask loop script if ((last_read & mask) == value) This is not well supported since Forth is far more flexible for looping, etc.
    s file_name run the script
    p <name> run the DOS program. If no name, shell to a DOS prompt


    Misc. Commands
    The following commands are useful for you to know.
    Command Action
    help print a help menu
    h print a help menu
    ? print a help menu
    alias name string define an alias for 'name'. This is a nice way to create a short-cut to avoid re-typing the same long boring string over and over.
    bar $reg specify default BAR register for memory base
    bar7 $phys_addr $size create pseudo BAR 7 for system memory access
    log <name> open/close log file
    bye quit the debugger
    quit quit the debugger
    q quit the debugger
    halt 0|1 set/clear halt on error flag.  If an error is detected, scripts and repeat loops will be aborted if this flag is set.
    stat print status (error count, pass count, etc.)
    kwait wait for keyboard hit
    count [$value] set or increment pass counter
    test $value $mask set error flag if ((last_read & mask) != value)
    f transfer into forth interpreter
    f   forth_words_here have the foth interpreter execute the forth words

    Several of these commands deserve a little extra description.

    BAR7 is used to access physical memory - it is very dangerous if you’re not careful. When working on devices that perform bus mastering, you frequently need to obtain access to a chunk of physical memory. BAR7 does not allocate memory - it simply lets you access it. You can do bad things to the system memory with this. The command takes two parameters: a physical address to access, and the size of the area to access. Once you have initialized BAR7, you can use normal DBG commands to examine and change values in the physical memory. You cannot use BAR7 a second time to change the memory aperture - get it right the first time!

    One trick we’ve used when working with bus mastering devices is to use a PC with a reasonable amount of memory, say 32MB. We can guess that DOS will probably not be using the upper 16MB, so, big_gulp, we assume that we can use this area as a scratch-pad for bus mastering experiments using BAR 7 to peek and poke the locations. You can get in big trouble doing this, so good luck! Remember, BAR7 just provides a way to access physical memory, it doesn’t allocate it.
    It’s easy to forget that BAR7 is a different command from BAR   7. The former creates an aperture to access physical memory, the latter specifies a default BAR for subsequent commands.


    ALIAS is a way to create text short-cuts. For example, if you are repeatedly typing:

    ddmp 13478 134ff
    You could create an alias to allow less typing:
    alias foo ddmp 13478 134ff
    Typing ‘foo’ will now execute the memory dump command.

    HALT allows a ‘halt on error’ flag to be set or cleared. This will terminate commands that have been invoked with the ‘!’ prefix.

    LAST_READ is a variable set during the execution of several of the commands. It is useful in aborting scripts, but has been largely supplanted by the flexibility provided by the Forth interpreter. The following commands set last_read:

    Command Value set in last_read
    bio, wio, dio value read from IO port
    bpci, wpci, dpci value read from PCI cfg register
    bmem, wmem, dmem value read from memory
    bcrc, wcrc, dcrc crc computed
    csum checksum computed
    btst, wtst, dtst number of memory test errors
    bcmp, wcmp, dcmp value read from memory
    bwait, wwait, dwait value read from memory

    Command Prefixes
    Commands can have simple prefixes prepended to them to slightly modify the normal behavior of the command.
    The valid prefixes are:
    Prefix Action
    ! repeat command until keyboard hit or error
    0..9 over-ride default PCI BAR for this command
    / start of non-printing comment (useful in scripts)
    # start of printing comment (useful in scripts)

    Graphics Commands
    DBG includes a number of commands to assist us in debugging graphics cards.  One of these days I'll document them here.  To work properly, you need to use the mode command to set the screen width (stride) in pixels, height, and color depth.  Good Luck.  If you actually try to use these commands, let me know and I'll try to help you out.

    DBG contains a Forth interpreter that is extremely useful for writing routines to aid in hardware debug. The f command is used to switch from the normal debugger into Forth while the dbg command switches back.  In addition, the f command can be used to pass one line to the forth interpreter to execute.  This provides an interesting method for extending core debugger functionality.  Also note that Forth can be used to add new commands to the native debugger command set - see the section on Extending DBG.

    Forth is a stack based language using Reverse Polish Notation (RPN) similar to an HP calculator. Many tutorials are available describing the forth language - it is beyond the scope of this document to teach forth. The Pforth web page maintained by Phil Burk contains a simple tutorial that can help get you started.  Forth operations are usually called words.  The simple program

    1 2 +
    consists of 3 words.

    When forth is active, the debugger prompt changes to:
    The <16> indicates that forth is using hexadecimal for numbers. If any data is on the stack, it is printed after the Stack <16> portion of the prompt.

    Forth has frequently been described as a write only language - easy to write, impossible to read.  To some extent, this is true.  You can minimize this by (gasp!) liberally using comments.  What a concept!  Since Forth is a stack based language, a commenting style has evolved to match the architecture.  You will frequently see Forth comments in the style:
            ( in_value -- out_value )

    This indicates that in_value is on the stack before the Forth word is invoked and that out_value is on the stack after the word finishes. It is a very handy way of describing input and output parameters to Forth words.

    The following words have been added as extensions to Forth to aid in accessing hardware.

    Forth Word Description
    dbg return to normal debugger
    dbg" cmd_string" ask the normal debugger to execute cmd_string. Note that dbg" is one word followed by whitespace. The quote marks are required for this Forth word.
    inb ( port -- data ) read byte IO port
    inw ( port -- data ) read word IO port
    ind ( port -- data ) read dword IO port
    outb ( data port -- ) write byte IO port
    outw ( data port -- ) write word IO port
    outd ( data port -- ) write dword IO port
    cfgrdb ( port -- data ) read byte pci cfg reg
    cfgrdw ( port -- data ) read word pci cfg reg
    cfgrdd ( port -- data ) read dword pci cfg reg
    cfgwrb ( data reg -- ) write byte pci cfg reg
    cfgwrw ( data reg -- ) write word pci cfg reg
    cfgwrd ( data reg -- ) write dword pci cfg reg
    bar ( bar_number -- bar_value) retrieve specified PCI linear base address value. This is not a physical address -- it is the virtual address corresponding to the specified PCI BAR. This address can be used by Forth primitives such as "!" and "@" to access memory.
    time ( -- current_time) retrieve the current time.  The time is in 1/100 of a second.  Note that  most DOS system clocks run at about 18Hz, hence the granularity is between 5 and 6 hundreths of a second.
    int386 ( in_array out_array -- ) perform a DOS int.  Each array is 32 bytes long (8 dwords) arranged as follows:
    Interupt number
    :: (double colon) Same as the standard : (colon) word used to define new words EXCEPT that it doesn't complain if you are redefining a pre-exisiting word.  Use ;; (double semi-c) to end the word definition!
    ;; (double semi-c) Same as the standard ; (semi-colon) word EXCEPT it is usually used to terminate a :: defined word.

    The  Forth Cheat Sheet gives some examples of standard popular/useful Forth words.


    Forth Example
    The following example gives a flavor of using Forth to aid in debug.

    \ Forth comments start with a back-slash

    \ make sure we’re using hex

    \ define a constant to allow us to access memory in BAR 1
    1 bar Constant MyRegs

    \ define a word to display the 1st 16 (decimal) bytes in BAR 1
    : first16
        10 0 do \ start of a loop - I is iteration value
            MyRegs i + \ get address of next byte
            c@ \ get the byte data value
            u. \ print the value
        loop \ end of loop
        cr \ print a carriage return
    ; \ end of our new word

    \ define a word to change the 100th byte to a 0
    : Change100
        0 \ get the 0 to write on the stack
        MyRegs 64 + \ get address onto the stack
        c! \ write a 32 bit byte

    \ define a word to loop until the dword at 0x1234 is non-0 or a
    \ key is hit on the keyboard
    : Wait1234
        MyRegs 1234 + @ \ get the dword
        key? \ get the keyboard status
        or \ OR the 2 things together
    until \ loop until top-of-stack is non-0

    key? if \ check if we need to get & drop a key
        key drop

    \ Routine to Clr the screen via int386 call

    \ create two 8 dword arrays for passing int386 parameters
    create in_regs  20 allot
    create out_regs 1c allot

    : my_clr
        \ init the input array for Int386 call
        0010 in_regs  0 + !  \ int10
        0600 in_regs  4 + !  \ AX = 0600
        0600 in_regs  8 + !  \ BX = 0700
        0000 in_regs  c + !  \ CX = 0000
        2B50 in_regs 10 + !  \ DX = 2B50

        \ perform the INT command to clear the screen
        in_regs out_regs int386

    Forth Cheat Sheet
    If you don't use Forth regularly, this cheat sheet may help you to remember interesting words and constructs...
    dup ( n -- n n )
    swap ( n1 n2 -- n2 n1 )
    over ( n1 n2 -- n1 n2 n1 )
    rot ( n1 n2 n3 -- n2 n3 n1 )
    -rot ( n1 n2 n3 -- n3 n1 n2 )
    ?dup ( n - n n ) if n is non-zero
    ( n - n ) if n is zero
    2over ( n1 n2 n3 n4 - n1 n2 n3 n4 n1 n2 )
    2dup ( n1 n2 n3 -- n1 n2 n3 n2 n3 )
    Program Flow
    last+1 first do ...  leave ...  loop \ print 1 to 4
    5 1 do
     i . cr

    \ print 2 and 3
    5 2 do
      i 4 = if
      i . cr

    Basic iteration loop. Note that leave
    will prematurely break out of the loop.
    If the end <= start, this loop will 
    run a long time
    do ... leave ... n1 +loop 5 1 do
      i . 
    2 +loop
    Iteration loop with a different increment value.
    This loop prints 1 and 3
    begin ... until begin
    loop until the until sees a non-0 on the stack
    begin .... while .... repeat   while will break out of the loop if it sees a 0
    on the stack.  The 2nd code block will always
    execute one time less than the first block.
    if ... else ... then 0 if
      ." no print"
      ." I print"
     n1 case
    n2 of  ... endof
    n3 of  ... endof
    3 dup case
      7 of . endof
      2 of . endof
      3 of . endof
      5 of . endof
      \default words
    case statement.  n1 on the stack controls the
    flow.  the 'of's don't have to be sequential
    since this is basically a set of nested if/else/then
    statements.  The example prints 3.
    or ( n1 n2 -- n3 ) bitwise inclusive or
    and ( n1 n2 -- n3 ) bitwise and
    xor ( n1 n2 -- n3 ) bitwise exclusive or
    = ( n1 n2 -- n3 ) equality comparison
    <> ( n1 n2 -- n3 ) inequality comparison
    0= ( n1 -- n2 ) true if TOS was 0
    > ( n1 n2 -- n3 ) true if TOS is less than TOS-1
    > ( n1 n2 -- n3 ) true if TOS is greater than TOS-1
    >= ( n1 n2 -- n3 ) true if TOS is greater than or equal TOS-1
    <= ( n1 n2 -- n3 ) true if TOS is less than or equal TOS-1
    + ( n1 n2 -- n3 ) addition
    - ( n1 n2 -- n3 ) subtraction
    * ( n1 n2 -- n3 ) multiplication
    / ( n1 n2 -- n3 ) division
    1+ ( n1 -- n2 ) increment
    2+ ( n1 -- n2 ) increment by 2
    1- ( n1 -- n2 ) decrement
    2- ( n1 -- n2 ) decrement by 2
    @  w@  c@ ( n1 -- n2 ) retrieve dword/word/bye pointed to by n1 
    ! w! c! (n1 n2 -- ) store dword/word/byte n1 at address n2
    -> l_var (n1 -- ) store n1 into local variable named l_var
    +! (n1 n2 -- ) add n1 into the memory at n2
    constant 0 constant foo create a constant named foo with a value of 0.
    foo will now place 0 on the stack.
    variable variable foo create a variable named foo.
    !/w!/c! are used to store into the variable,
    @/w@/c@ are used to read the variable.
    value 0 value foo create an initialized variable named foo
    to read, just use the name - "foo u. cr"
    the -> operator is used to write - "0 -> foo"
    trace ( i*x <name> -- , setup trace for Forth word )
    s ( -- , step over )
    sm ( many -- , step over many times )
    sd ( -- , step down )
    g ( -- , go to end of word )
    gd ( n -- , go down N levels from current level, stop at end of this level )


    Extending DBG with Forth
    Under certain circumstances, it is useful to add custom commands to DBG.  Recently, one of our clients was debugging an I2C device. By writing a simple set of Forth words, we were able to extend DBG with a simple user interface to allow peeking and poking registers in his device.

    As of V2.11, if the 'native' debugger doesn't understand a command that you've entered, it tries to find a corresponding Forth word that is prefixed by 'dbg_'.  If it finds the word, it passes the command to Forth to execute.  For example

    You Type DBG tells Forth
    foo 1 2 3
    1 2 3 dbg_foo

    To assist this process, DBG automatically tries to read the file dbg_xtnd.f into the Forth interpreter when starting up.  DBG first looks for the extension file in the current directory in which DBG was invoked.  If the file is not found the current directory, the directory holding the DBG executable is then checked.

    Some things to note...

    Revision History
      Initial release to web
    Changed PCI code to scan all busses
    Deleted forth word 'foo'.
    Turned of floating point support
    Upgrade to PForth 2.1
    Changed 'bus' to 'slot' for initial PCI prompt
    Revised forth hashing algorithm
    Added 'hash' command to dbg core cmds
    Changed the 'bar8' cmd to be 'bar7' since the rombase address reg is actually bar 8!
    Fixed the forth 'key?' to map to 'kbhit' - This got broken when we ported to PForth 2.1
    Added rudimentary support for multi-function devices
    Added 'time' word to forth
    Added bcmp/wcmp/dcmp commands
    Enhanced halt flag to kill scripts on error.
    Added bwait/wwait/dwait commands
    Added kwait command
    Enabled 'f' command to forward a single line into the forth interpreter
    10-28-99 Added debug extensions and dbg_xtnd.f auto reading
    Misc Forth bug fixes:
        fixed bugs in forth trace code (stack corruption)
        fixed bug in (quit) - didn't clean stack properly
        fixed bug in forth local variables - { -- lout } caused errors
        fixed RI to remember the outmost name for nested includes
        include statistics are now in decimal
    Added int386 word to forth
    1-14-00 More Forth cleanups -
          Removed extra CR after a dbg_extension is executed
          Re-enabled calling native dbg cmds from compiled 4th words
          REALLY fixed forth RI to remember outermost file name
          Fixed nested CASE statements
          Added warning for long (non 8.3) script names
          Increased forth dictionary size
          Added :: and ;; words to allow word redfinition without complaints
    4-14-00 Added -t command line option to specify a PCI slot
    Fixed bug in identifying multi-function PCI devices
    Forth cleanups
          Allow \ comment inside local variable
          Allow Carriage Return inside local variable declaration
          Removed word 'test1'
    V2.14 7-25-00 Added support for specifying pci_vendor/pci_device or pci_slot from env. variables


    Bug Reports & Comments

    Please email bug reports or comments to johnp (@t) probo dot com