Tag: technology

  • chastdin for FreeBASIC

    I have rewritten my chastdin program (the stack based calculator that reads keyboard input from standard input) into the FreeBASIC programming language. I did it as an exercise to prepare myself for a future book on the BASIC programming language which was my first programming language. FreeBASIC is compatible with QBASIC which is what I first started on. Luckily, BASIC is not so different from C but I had to spend a lot of time on the documentation to refresh my memory in how I used to do things with it.

    Eventually I would like to make a GUI version of this command line calculator. I am trying to take baby steps in working my way into programs that the average person would use. However, command line utilities are still the easiest to build and that is an acceptable place to start.

    main.bas

    #include "chastelib.bi"
    #include "chastdin.bi"
    
    dim shared as integer chastack(256)
    dim shared as integer csi=0 'Chastity's Stack Index
    
    radix=10
    
    dim as integer a,b
    dim shared as string s
    
    sub help()
    ?  "chastdin is a stack based interactive calculator"
    ?  "Numbers are pushed on the stack and commands can do math."
    ?  "It is a fork of chastack that reads from stdin instead of arguments."
    ?  "Each line can contain multiple numbers or commands."
    ?
    ?  "Math commands are add,sub,mul,div,rem"
    ?  "And use the top two stack numbers for their operations"
    ?
    ?  "The setradix command uses the top of stack as the new radix"
    ?  "The exit command ends the program"
    ?  "The ? command prints the entire stack"
    ?
    end sub
    
    sub stack_check()
     if csi>0 then
      chastack(csi+1)=0 /'erase old top of stack because command was successful'/
     else
      print "Error: two numbers required for command: ";s
      csi+=1 /'increment the pointer to what it was before the failed command'/
     end if
    end sub
    
    help():
    
    while s<>"exit"
    
    s=""
    
     s=getstr() 'read and ignore empty strings
    
    'print entire stack
    if s="?" or s="print" then
     b=csi
     while csi>0
      print intstr(chastack(csi))
      csi-=1
     wend
     csi=b
    
    elseif s="exit" then
    exit while
    
    elseif s="help" then
    help()
    
    elseif s="setradix" then
     if csi>0 then
     radix=chastack(csi)
     chastack(csi)=0
     csi-=1
     else
      print "Error: need one number on stack for command: ";s
     end if
    
    elseif s="add" then
    b=chastack(csi)
    csi-=1
    a=chastack(csi)
    a+=b
    chastack(csi)=a
    stack_check()
    
    elseif s="sub" then
    b=chastack(csi)
    csi-=1
    a=chastack(csi)
    a-=b
    chastack(csi)=a
    stack_check()
    
    elseif s="mul" then
    b=chastack(csi)
    csi-=1
    a=chastack(csi)
    a*=b
    chastack(csi)=a
    stack_check()
    
    elseif s="div" then
    b=chastack(csi)
    csi-=1
    a=chastack(csi)
    a\=b
    chastack(csi)=a
    stack_check()
    
    elseif s="rem" then
    b=chastack(csi)
    csi-=1
    a=chastack(csi)
    a=a mod b
    chastack(csi)=a
    stack_check()
    
    else
    
    'try to interpret string as a number if not empty
     a=strint(s)
     if strint_errors<>0 or len(s)=0 then
     'print s;" cannot be added to the stack because it is not a valid number"
     else
     csi+=1
     chastack(csi)=a
     print intstr(a);" was added to the stack"
     end if
    
    end if
    
    wend
    
    /'
     This is a FreeBASIC program.
    
     compile and run as:
    
     fbc main.bas && ./main
    '/
    
    

    chastelib.bi

    /'
     global variables to define radix and formatting
     for the intstr function
    '/
    dim shared as integer radix=2
    dim shared as integer int_width=1
    
    /'
     translation of intstr function for FreeBASIC
     by original C programmer Chastity White Rose
    '/
    function intstr(i as uinteger) as string
     dim as string s=""
     dim as integer w=0
     dim as byte c
    
     while i<>0 or w<int_width 
    
      c=i mod radix                  
      i\=radix                     
    
      if c<10 then 
      c+=48
      else
      c+=55
      end if
    
      s=chr(c)+s
    
      w+=1                     
     wend
    
    return s
    end function
    
    /'
     global variable for error detection in strint function
     this variable will be zero if last string was a number
    '/
    dim shared as integer strint_errors=0
    
    /'
     translation of strint function for FreeBASIC
     by original C programmer Chastity White Rose
    '/
    function strint(s as string) as uinteger
    dim as uinteger i=0
    dim as integer x=0,y=len(s)
    dim as byte c
    
    strint_errors = 0 /' clear errors '/
    
    while x<y
    
     /' read digit from string '/
     c=s[x]
    
     /' 0 to 9 '/
     if c >= 48 and c <= 57 then
     c-=48
     /' A to Z '/
     elseif c >= 65 and c <= 90 then
     c-=65
     c+=10
     /' a to z '/
     elseif c >= 97 and c <= 122 then
     c-=97
     c+=10
     /' whitespace '/
     elseif c >= 0 and c <= 32 then
      exit while /' exit correctly at string end '/
     else
      strint_errors+=1
      print "Error: ";chr(s[x]);" is not an alphanumeric character!"
      exit while /' exit at invalid character '/
     end if
    
     if c>=radix then
      strint_errors+=1
      print "Error: ";chr(s[x]);" is not a valid character for radix ";radix
      exit while /' exit at digit wrong for radix '/
     end if
    
     /'multiply by radix then add digit'/
     i*=radix
     i+=c
    
    x+=1
    wend
    
    return i
    end function
    

    chastdin.bi

    dim shared as string stdin_buf
    dim shared as integer stdin_buf_index
    dim shared as integer stdin_buf_length=0
    
    function getstr() as string
    dim as string s=""         'create empty string
    dim as byte c              'temporary byte/char variable
    
    /'
    this section gets a line of text
    if the length of the string/buffer is 0
    '/
    
    if stdin_buf_length=0 then      'check if there are characters in the buf
    input "-> ",stdin_buf           'if not, read a line of text
    stdin_buf_index=0               'set index to zero
    stdin_buf_length=len(stdin_buf) 'set the length
    end if
    
    /'
    regardless of whether input was added above
    or if it still had bytes from the last input
    we then extract characters one at a time into the
    substring s to be returned from the function
    '/
    
    while stdin_buf_index<stdin_buf_length
    c=stdin_buf[stdin_buf_index]
    stdin_buf_index+=1
    if(c>=33) and (c<=126) then
    s=s+chr(c)
    else
    exit while
    endif
    wend
    
    /'
    if the index matches the length of buffer
    set length to zero so that more will be read
    next time this function is called
    '/
    
    if stdin_buf_index=stdin_buf_length then
    stdin_buf_length=0
    end if
    
    return s
    end function
    
    /'
    the getline function always gets an entire line of text
    I don't really need it but it is here as a reminder of
    how to use the input statement in FreeBASIC
    '/
    
    function getline() as string
    dim as string s=""
    input "-> ",stdin_buf
    s=stdin_buf
    return s
    end function
    

  • chastelib for Pascal Programming Language

    I managed to hack my four functions from chastelib into the Pascal programming language. This program includes the functions and the test suite which works just like the C version. The code is a bit more complex than the C version because strings and characters are handled very differently than they are in the C programming language. The strint function was the hardest to write but it seems to be working according to the standards I require.

    I am doing this for education and possibly a future book on old programming languages. Pascal is nice but I will also be studying BASIC again.

    program chastelib;
    
    const
     string0='Official test suite for the Pascal version of chastelib.'#10;
    
    var //this is the global variable section
     a:integer;
     b:integer;
     
     radix:integer;       //current radix being used
     int_width:integer=1; //global integer width
     strint_errors:integer=0; //error result for strint function
    
    (*
    A function to print a string using Pascal's write function.
    *)
    procedure putstr(s:string);
    begin
     write(s);
    end;
    
    (*
     a function to return a string form of an integer
     using the global radix variable
    *)
    function intstr(i:integer):string;
    var
     s:string=''; //string that will be built and returned from this function
     width:integer=0; //the current width
     c:integer;
     ch:char;
    begin
     while (i>0) or (width<int_width) do
     begin
    
      c:=i mod radix; //get integer division modulus or remainder
      i:=i div radix; //get integer division quotient
    
      (*turn remainder c into character ch for digit in this radix*)
      if c<10 then
      begin
       ch:=chr(c+48);
      end
      else
      begin
       ch:=chr(c+55);
      end;
    
       s:=ch+s; //prefix the string with this character
       width+=1;
    
     end;
    
     intstr:=s; //return this string from the function
    
    end;
    
    (*use both putstr and intstr to print an integer*)
    procedure putint(i:integer);
    begin
     putstr(intstr(i));
    end;
    
    (*
    Because characters and integers are separate types in Pascal,
    it is required to get the ASCII value of characters in the string
    for the strint function so I can do the math the same way
    as I did in the C version of the function.
    *)
    
    function strint(s:string):integer;
    var
     i:integer=0; //integer that will be built and returned from this function
     x:integer=1; //index used to scan forward through the string
     c:integer=0;
    begin
     strint_errors := 0; (*set zero errors before we parse the string*)
     if (radix<2) or (radix>36 ) then
     begin
      strint_errors+=1;
      writeln('Error: radix ',radix,' is out of range!');
     end;
     while(x<=length(s)) do
     begin
      c:=ord(s[x]);
      if (c>=ord('0')) and (c<=ord('9')) then 
      begin
       c-=ord('0')
      end
      else if (c>=ord('A')) and (c<=ord('Z')) then
      begin
       c-=ord('A');
       c+=10;
      end
      else if (c>=ord('a')) and (c<=ord('z')) then
      begin
       c-=ord('a');
       c+=10;
      end
      
      else if (c < $21 ) then
      begin
       break; (*end loop because we have found whitespace*)
      end
      
      else
      begin
       strint_errors+=1;
       writeln('Error: ',s[x],' is not an alphanumeric character!');break;
      end;
      
      if(c>=radix) then
      begin
       strint_errors+=1;
       writeln('Error: ',s[x],' is not a valid character for radix ',radix);
       break;
      end;
      
      i*=radix; //multiply by the radix
      i+=c;     //add the digit from the character processed
    
      x:=x+1;
     end;
     strint:=i;
    end;
    
    
    
    
    begin
     radix:=16; //set the radix used by both intstr and strint functions
    
     a:=0;
     b:=strint('100');
    
     putstr(string0);
    
     while a<b do
     begin
      radix:=2;
      int_width:=8;
      putint(a);
      putstr(' ');
      radix:=16;
      int_width:=2;
      putint(a);
      putstr(' ');
      radix:=10;
      int_width:=3;
      putint(a);
    
      if (a>=$20) and (a<=$7E) then
      begin
       putstr(' ');
       putstr(chr(a));
      end;
    
      putstr(#10);
      a+=1;
     end;
     
     putstr(string0);
    
    end.
    
    (*
     fpc main.pas && ./main
    *)
    
  • Getting Started with Linux

    This is chapter 19 of my Linux Assembly book. However, it has a ton of information about Linux in general. I also cover how to run the Tiny Core Linux distribution inside of the QEMU emulator. This will give current Windows users a way to try out Linux without having to give up Windows until they are sure they no longer need it. Even if you don’t plan to do Assembly programming, this is a great way to get a retro computing feel because a terminal only Linux system is very similar to DOS, except the names of all the commands are different!

    Chapter 19: Getting Started with Linux

    This whole book was written on Debian version 12 (bookworm). This has been my Linux distribution on my desktop computer since at least 2024 when Microsoft Windows decided to overwrite my bootloader when I had a dual boot between Windows and Ubuntu. I installed the newest version of Debian available at the time and have never looked back.

    I got my start in the Linux world back in 2005 during the time of Ubuntu 5.10 (Breezy Badger). Ubuntu was my first distribution and I used it without problem for years. But since Ubuntu was based on Debian, I decided to go with the original distribution. I highly recommend either of them because both of these projects have great websites with detailed installation guides.

    https://www.debian.org/
    https://ubuntu.com/

    If you have an old machine running Windows but it is kind of slow, I recommend following the official instructions and installing one of these distributions of Linux to your hard disk by booting either a live CD or USB drive and following the instructions. I figured it out with no background in Linux when I was 18 years old. They have done great work making Linux easy for beginners.

    But what I am going to do is share a method of trying out Linux for people who don’t have a space computer in their house and they can’t risk deleting Windows because their job or school requires specific Windows software.

    Just as I used a DOS emulator to emulate DOS for the DOS edition of Assembly Arithmetic Algorithms, it is also possible to run Linux in an emulator.

    There are some things you should know about running Linux in an emulator.

    1. Emulators are slower than real hardware and cannot be used to judge Linux. Just because you ran Linux in an emulator and it was slow does not mean that the operating system is a failure or that you should give up the idea.

    2. Emulators have many different options that you should read about and the reason I recommend learning how to use an emulator is because it teaches you to understand hardware better. Files are used as virtual compact disks or hard disks but the emulators see them as being the same as real hardware.

    3. Portability is the best reason to use an emulator. Because an emulator can run software for the same machine type or even a different architecture, you can use the information in this book even if you are running an Intel emulator on an ARM or RISC-V processor.

    The emulator I will be using for this example is called QEMU.

    https://www.qemu.org/
    https://www.qemu.org/docs/master/system/invocation.html

    And the distro I will be using is called Tiny Core Linux.

    http://tinycorelinux.net/

    You will want to go to the downloads page and get the smallest of the three files. It will be named “Core-current.iso”.

    Install QEMU on whatever OS you are currently using. QEMU is available for Windows, Mac, and Linux.

    Once you have QEMU installed and downloaded “Core-current.iso” to a directory somewhere, you can launch it with QEMU by using this command.

    qemu-system-x86_64 -drive file=Core-current.iso,media=cdrom
    

    You can also use the shorter form of the command which means the same thing.

    qemu-system-x86_64 -cdrom Core-current.iso
    

    Either way, it will boot into a tiny Linux system that is only a command line. You can use standard Linux commands such as ls,cat,sed,cp,rm,cd, and exit. There is even the “vi” editor built in. If you have not used the vi or vim editors before, don’t worry about it. However, if you have used them, go ahead and practice by making some text files and saving them.

    When you are done using Tiny Core Linux, just use this command:

    sudo poweroff
    

    How does Tiny Core work?

    Everything will shut down and the emulator will also stop. If you clicked on the QEMU Window and your mouse cursor disappeared, press Ctrl+Alt+G on your keyboard to grab it back from the emulator so you can do other things while the emulator is still running Tiny Core Linux.

    Tiny Core Linux runs entirely in RAM when you run it using only the ISO image for the cdrom drive. Any changes you make will not be saved when you exit the emulator.

    I am sure you are wondering what good an operating system is that doesn’t save your work. For that, I have 2 answers.

    1. Because Tiny Core starts fresh each time using only the files in the CD image, it means each time you have a new chance to do something new and test things without any fear of messing up your system.

    2. There is a way to install it to a hard disk and configure it to save changes. I will be explaining more about this in this chapter.

    Installing Software Temporarily

    Before trying to permanently install Tiny Core to a hard disk, it is important to understand the package manager that it uses. It is called tce-ab. You can run it by its name.

    tce-ab
    

    It has a lot of text with instructions that let you search for and install packages. For example, on my machine when I booted into Tiny Core on QEMU, I was able to search for and install the “nano” text editor and the “nasm” assembler.

    You can also directly install programs with the tce-load program. This is often faster than navigating the menus of tce-ab.

    tce-load -wi nano
    

    However, because even previously installed programs will be gone on the next reboot, it is time to teach you how to create a virtual hard disk and reboot into the emulator with a hard disk that we will install to.

    Installing to a Hard Disk

    First, exit the emulator and then use the qemu-img command below to create a 1 Gigabyte empty file that will be used as a hard disk.

    qemu-img create harddisk.img 1G
    
    qemu-system-x86_64 -drive file=Core-current.iso,media=cdrom -drive file=harddisk.img,format=raw,media=disk
    

    The Tiny Core install program is ironically not included in the cdrom file we are booting from. Therefore, it becomes necessary to install the installer!

    While you are booted into QEMU with both the cdrom and harddisk images, install the install script with the tce-load command below.

    tce-load -wi tc-install
    

    It will take some time to install the dependencies of the Tiny Core install script. Notable dependencies are Perl (a popular scriping language) and syslinux (the bootloader).

    After it finished installing, run this command:

    sudo tc-install.sh
    

    The installer asks a lot of questions about what options you want when you want to install.

    • i for installing from booted cdrom
    • f for frugal hard drive installation
    • 1 for whole disk installation
    • 2 for sda (first hard disk)
    • y for installing the bootloader
    • 3 for the ext4 file system

    Most other options you can leave blank and press enter.

    I know that process may have been a little complex but it highlights the difference between installing Linux and installing Windows.

    You don’t install Windows. It was already on your computer when you bought it. This means somebody else chose all the options for you.

    When you install Linux, you need to know some basic facts or at least google them when in doubt. This takes some time to learn but it gives you unrestricted control in the kind of system you are building for your software development.

    In fact Tiny Core provides a helpful book that explains some of these options.

    http://www.tinycorelinux.net/book.html

    The book covers the graphical interface which is available on a much larger ISO file. I chose to go the pure command line only route and adapt the instructions for the tc-install.sh script which only got a passing mention in the book.

    When you have completed those steps, it is now possible to boot directly into the hard disk without the cdrom!

    qemu-system-x86_64 -drive file=harddisk.img,format=raw,media=disk
    

    The main benefit of installing to a hard disk instead of just booting from the cd image each time is that any programs you install with tce-ab or tce-load will stay there and be available to use each time you reboot.

    Making a Persistent Home Directory

    However, the files in your home directory are deleted each time you reboot. However, there is an easy fix for this. We will open the bootloader configuration and add an option to restore the backup from sda1.

    First, open the config file:

    nano /mnt/sda1/tce/boot/extlinux/extlinux.conf
    

    And add this option to the line that starts with APPEND.

    home=sda1
    

    This means that the home directory will become an actual directory on the hard disk instead of a temporary location only loaded into RAM. With this step completed, you can begin programming in Assembly on your Tiny Core Linux system! Time to install the tools.

    NASM is already in the repository and this is the standard way to install it.

    tce-load -wi nasm
    

    However, FASM is not available in the repository but it can be easily downloaded from the official website. However, this system only has a terminal with no web browser. Don’t worry, there is a way! We can use the wget command that is already in Tiny Core Linux!

    wget http://flatassembler.net/fasm-1.73.35.tgz
    

    After the TGZ (Tar with GZip compression) file downloads, we need to extract the files from it.

    tar -xf fasm-1.73.35.tgz
    

    Now you can change to the fasm directory that was just created.

    cd fasm
    

    Inside are several files including “fasm” (the actual executable that can assembly all the programs in this book), “fasm.txt” the manual in plain text that describes both how to use FASM and also introduces the Intel instruction set. The “license.txt” may also be of interest.

    To install fasm, we should add it to somewhere where it can be in the current path. Check what the path is currently with this command.

    echo $PATH
    

    One of the directories that is in the path is “/home/tc/.local/bin”. This gives us the information we need to install fasm permanently there. Just copy fasm there like this:

    cp fasm ~/.local/bin
    

    Now enter “fasm” as a command and see what happens. If you see the following output, then it was done correctly!

    flat assembler  version 1.73.35
    usage: fasm <source> [output]
    optional settings:
     -m <limit>         set the limit in kilobytes for the available memory
     -p <limit>         set the maximum allowed number of passes
     -d <name>=<value>  define symbolic variable
     -s <file>          dump symbolic information for debugging
    
    

    This means that you can assembly and run any program that I have included in this book, Assembly Arithmetic Algorithms for Linux.

    Keep in mind however that programming in this terminal based environment may feel different because you have to use terminal text editors like vi,vim, or nano. These text editors are just as good as graphical ones except that there is no mouse support. However, I was hoping to give you the retro feeling of programming like a crazy person in the 1980s.

    This is just the tip of the iceberg about what Tiny Core Linux can do. However, I wanted to at least spend this chapter introducing this lesser known distro because it is small enough that it emulates with QEMU flawlessly.

    Although Debian, Ubuntu, Linux Mint, and many others are better than Tiny Core because they have more programs in their repositories, they are too large to emulate because their graphical X Windows System displays use a lot more memory and it is harder for a PC emulator like QEMU to emulate them properly.

    Consider Tiny Core Linux as a way to test the waters and get used to running commands in a Linux terminal before you are ready to install a more mainstream distro like Debian. However, once you have made the jump into the wonderful world of Linux, you will not be disappointed as a programmer because there is no shortage of text editors, assemblers, compilers, and even video games. For example, Final Fantasy 6 and Chrono Trigger from my Steam game collection both work on my Debian Linux system. Ironically, my Windows 11 laptop could not run Chrono Trigger.

    I wrote this chapter to do my small part in the world to help people discover the things I like about Linux and the freedom it offers.

    Tiny Core Linux makefile

    The following makefile is something I use to run commands for emulating Tiny Core Linux inside QEMU. I use it to remind myself what commands I use to set up a new installation of Tiny Core Linux.

    Core-harddisk:
    	qemu-system-x86_64 -drive file=harddisk.img,format=raw,media=disk
    Core-cdrom:
    	qemu-system-x86_64 -drive file=Core-current.iso,media=cdrom
    Core-cdrom-short:
    	qemu-system-x86_64 -cdrom Core-current.iso
    Core-cdrom_and_harddisk:
    	qemu-system-x86_64 -drive file=Core-current.iso,media=cdrom -drive file=harddisk.img,format=raw,media=disk
    harddisk:
    	qemu-img create harddisk.img 1G
    

    Of course, this chapter also serves as a reminder to myself about how to use Tiny Core Linux and also why it is one of my favorite distributions of Linux. I like it because sometimes I just want to have a text-only system upon which I can improve my scripting and command line usage skills. Modern Linux distros include Graphical User Interfaces where you can point and click your way to doing anything, but from the beginning it was not this way. Therefore, I always promote learning the Linux terminal because it is the one thing that stays consistent and trustworthy no matter which distro you are using at the time.

    I will admit that using the command line is a huge learning curve when you are first getting started. Perhaps I found it easier because I grew up with MS-DOS where text commands were all I had. In any case, knowing how to navigate directories on Linux to edit, copy, rename, and delete files is helpful because there are so many different graphical user interfaces and I can’t remember which menus to click on.

    But the real reason text commands are the best is because text can be copy pasted and then I am able to give you the exact commands to run so that you don’t have to spend months google searching for the right documentation on all these different things!

  • Assembly Arithmetic Algorithms RISC-V

    Assembly Arithmetic Algorithms

    RISC-V Edition

    Preface

    This book is the RISC-V edition of Assembly Arithmetic Algorithms. This is the third book in the series but it is a completely acceptable way to learn programming, even for beginners.

    The first book was for 16-bit DOS programming using Assembly. The second book was for 32-bit Linux programming using the same assembly language for Intel machines.

    But the book you are reading now, is fundamentally different in nature. It uses simulators that run in any operating system to teach a new type of Assembly language for the RISC-V (Reduced Instruction Set Computing-Generation 5) processor.

    RISC-V is important because it is a royalty free open standard. This means that as it catches on, more companies will be able to make cheaper computers because they will not have to pay royalties to the Intel or ARM companies. I guess you could say that it is Open Source Hardware, which in my opinion is the final piece we need to match the Open Source Software people like me already have been enjoying with GNU and Linux.

    Introduction

    First, let me introduce this book by telling you what I will teach you. By the end of this book, you will have enough information to write small programs that will run on your machine but also allow you to bypass complicated languages like C++, Java, or even the Bash Linux shell. Arguably, the beautiful thing about Assembly is its simplicity.

    RISC-V is great because it is a Reduced Instruction Set Computing device. It has fewer instructions to learn and remember, especially when compared to Intel. It also has more registers available for use. Each of them have special purposes and conventions for how they are used. However, as the programmer, you are allowed to break these conventions because right now, your focus should be on learning it for fun!

    Required Knowledge

    To get the most out of this book, some background on the Binary and Hexadecimal numeral systems is going to be helpful, but this is not strictly required because I will be providing functions you can use in your code that will convert between decimal (base ten), binary (base two), and hexadecimal (base sixteen).

    However, I would say that experience in at least one programming language is necessary for an understanding of terminology like “arrays”, “pointers”, “addresses”, “integers”, etc. I recommend the C Programming Language as a start. C++ is also a good starting language, but it tends to abstract details away that directly apply to Assembly Language, which is the lowest level a human can go for understanding a computer.

    But I think it is perfectly okay for this Assembly language to be your introductory language for the world of computer programming. The language is simple, beautiful, and in my opinion, was designed better than Intel x86. The makers of the RISC-V architecture saw what other processors were doing and in some ways tried to avoid making the same mistakes as previous generations did.

    RISC-V is a new processor type and philosophy that was created in 2010 at University of California, Berkeley. The V in its name is actually the Roman number 5. That means there were apparently those named I,II,III,VI in the past.

    Many people have high hopes about this new computer architecture that is also old enough to trust. Because it is 16 years old at this time and has been standardized, there are many simulators available. This book will present two of these simulators and perhaps a passing mention of others as I learn about them myself and can provide more links.

    But because RISC-V is still relatively young compared to Intel 8086 (year 1978) and ARM (year 1983), I expect that the timing of this book will be of use to people who have heard about RISC-V and wonder what it is about.

    Low Level

    Low level is a term that confuses people. People think something high-level is better than low-level. In simple terms, humans consider themselves superior to machines and therefore think themselves higher or more important because of their abstract thought.

    A computer thinks only in terms of numbers. A computer may not understand “high-level” abstractions such as love, religion, philosophy, etc, but that is not its job. A computer must add, subtract, multiply, and divide. These are the four arithmetic functions that many humans struggle with.

    Therefore, I ask you, between a human and a computer, who is really low level or high level? In the age of Artificial Intelligence taking over human jobs and beating humans at Chess, we would all do well to take this question seriously.

    I wrote this book because I think like a machine, and I hope to help others think this way because it is the best way to learn programming and control your computer by writing Assembly Language programs, or to go back to your favorite programming language with a greater understanding of why things work as they do.

    Why RISC-V

    After writing 3 programming books, I decided I wanted to learn a language that offered the things I love about C and Assembly but was also new and exciting at the time.

    I first learned RISC-V from Robert Winker’s book.

    https://leanpub.com/riscvassemblyprogramming

    It was also available on Leanpub and I thought his teaching style is a standard that I hope to live up to. Obviously he is more experienced and his book is probably better than mine. Even so, I wanted to do my own part to promote education about RISC-V because I want to see a world where programming is easier and computers are cheaper. As much as I love Intel machines because I grew up with them, RISC-V provided me a new learning experience that I hope others discover and enjoy as much as I have!

    Chapter 1: The First Program

    For this chapter, I will explain give the source code of an example program that works in both RARS and riscemu simulators. Take a good look at it and the comments I included. I will be explaining this in detail. I also recommend you type or copy and paste this code into a text editor and save the file as “main.s” in a place on your computer that you prefer.

    Hello World

    .data
    
    string0: .asciz "Hello World!\n"
    
    .text
    
    li a0, 1       # STDOUT file number
    la a1, string0 # address of string 
    li a2, 13      # length of string
    li a7, 64      # write call number
    ecall          # environment call
    
    li a0, 0       # status
    li a7, 93      # exit
    ecall          # environment call
    

    There are two sections named “.data” and “.text”. All variables used in the program should be defined in the .data section. The .text section is where your code starts executing. This organization scheme is required for most simulators and assemblers.

    This program prints “Hello World” followed by a newline. It uses only 3 kinds of instruction and is fairly easy to follow.

    • li = load integer (or immediate value)
    • la = load address (a special kind of integer)
    • ecall = enviroment call (or syscalls)

    The reason that sometimes li is used to load a register and other times la is used is because technically, there is a limit on the size of integers that can be part of an instruction. Instructions like “la” are technically macros for multiple instructions that load upper and lower bits of a register.

    But besides that, having la for memory addresses and li for regular numbers makes the code more readable in my opinion.

    ecall is the how you would call the system calls of your operating system. It is the same idea as “int 0x80” was in my Linux Intel Assembly book. However, in RISC-V, there are different registers and different system call numbers than you would expect on Intel CPUs.

    But more importantly, this program was written for simulators and not a real operating system. The call numbers, which are loaded into the a7 register, are specific to the RARS and riscemu simulators. In the next chapter, I will explain the differences between these two simulators and how to run the program.

    Chapter 2: Simulator Choices

    There are two simulators I personally use and test my code against. Both of them are Free Software and available to download from their Github repositories. I will offer some information about them so you can choose which to use when following along with this book.

    RARS

    RARS is a port of the MARS simulator for MIPS processors. It is written in Java and is downloadable as a

    https://github.com/rarsm/rars

    This book favors RARS because it is easy to use and is reliable enough to use from any operating system. It also supports a lot of system calls and is the simulator I learned to use first. Because it runs using Java, you must have the Java Runtime installed. However, since Java can be installed and run on any operating system, you can be sure that it will work no matter whether your host operating system is Windows, Linux, or Mac.

    On my system, I usually have the file named “rars.jar” in my home directory. This allows me to run my source file like this:

    java -jar ~/rars.jar main.s
    

    Because ~ is a shortcut for the home directory, this allows me to run it from whichever directory I happen to be in that contains my source file, which in this case is named “main.s”.

    riscemu

    Riscemu is a simulator written in Python. It does not have as many system calls built in but it is a good option for people who have Python installed but cannot, or don’t want to install Java.

    https://github.com/AntonLydike/riscemu

    Running a source file with riscemu is even easier that with RARS.

    riscemu main.s
    

    In my experience, riscemu also launches faster than RARS does, mostly just because RARS requires Java to load the whole virtual machine before it starts. Because riscemu is written in Python, and Python is written in C, it is only natural that it might be faster.

    However, riscemu is really picky about having a space after commas in your instructions. It will fail with cryptic error messages if you don’t have proper spacing between your arguments to an instruction. This is probably a bug but it allows for consistency in formatting if you just remember to add a space after each comma. If you don’t like to do this, just forget riscemu and stick with RARS.

    Why use a simulator?

    You might be wondering why I am recommending using these simulators rather than a real assembler for a real machine with an operating system. There are 3 reasons.

    • I don’t have a computer with a RISC-V processor.
    • Simulators can be used by more people because they don’t require buying new hardware.
    • No “risk” in trying out this new RISC based machine.

    In short, I use these simulators or emulators for the same reason I used DOSBox in the DOS version of Assembly Arithmetic Algorithms.

    An emulator allows people to learn the language a Central Processing Unit uses before they have invested time or money into learning it. This means no investment or sacrifice from you.

    In fact, you might decide to forget learning RISC-V Assembly and learn Java or Python instead. Even so, I wrote this book so that I can share what a beautiful language the RISC-V Assembly language is and why I enjoy coding in it.

    Chapter 3: System Calls

    There are many more system calls for RARS than I will be teaching in this book. riscemu has less than RARS but the table below shows the calls that these two simulators both have in common. The 3 calls are enough for most programs.

    System Calls for RARS and riscemu

    Name a7 a0 a1 a2
    exit 93 status
    read 63 fd buf count
    write 64 fd buf count

    For more advanced programs that open and close files, you will need to use different call numbers and also different mode numbers. Unlike Linux and the POSIX system calls, the simulators are meant for teaching the language but you can’t count on them being compatible with each other in the same way. It is pure luck that the system calls for the 3 calls above matched. It is precisely this reason that I chose RARS and riscemu as the simulators for this book.

    Registers on RISC-V

    There are a LOT more registers than there were on Intel machines. This makes using RISC-V very easy because you don’t have to use memory locations as often.

    Name meaning/usage
    zero always = zero
    ra return address
    a0->a7 arguments
    s0->s11 saved values
    t0->t6 temporary

    The zero register is just a register that always equals zero. It may seem weird but remember than RISC-V does not allow comparing a register directly with a number. Normally you need to load a number into another register. But because comparing with zero is a common operation, this zero register is available to be compared or copied any time!

    The ra register is important for function calls. It is the register that keeps where you will return to when the function is done. Because of this, you will run into trouble if you try to do something else with it.

    The other registers can technically be used any way you want except that those starting with ‘a’ are used for arguments to environment calls. For this reason, they are used more than others.

    The registers that begin with ‘s’ or ‘t’ are technically the same but it is a convention or tradition that t0 to t6 are used for temporary cases where you need to add, subtract, multiply, or divide numbers. After you are done with them, you use them for something else and forget what you did with them last time.

    The s0 to s11 registers are the most abundant and you are expected to keep things that endure most or all of the program in them. For example, you might keep a file descriptor in one of them so that you could copy it to the correct ‘a’ register when you need to.

    Because of the fact that you cannot compare or otherwise perform math on memory addresses directly, you have to always load everything into registers. However, because RISC-V has more registers, you can also write programs that run faster because you access memory less often.

    However, you will be loading and saving memory for variables stored in the data section. Usually these will be strings you are printing.

    In the next chapter I will be introducing a function that can print any string by automatically calculating its length. I will also be using the calling convention of the recommended register usage.

    To be continued

  • new program: chastdin

    I wrote another program which is actually a modification of chastack. This gets input from a user while it is running. Despite how simple it may seem, I had to work at reading from the keyboard because there are multiple ways to read a string from the user. I may add more to this program later, but it has all the important functions of a stack based calculator. Here is a screenshot that shows me using it. You can probably figure out what the commands do based on their name and the numbers printed. I have also attached the full assembly source code to this post.

    main.asm

    format ELF executable
    entry main
    
    include 'chastelib32.asm'
    include 'chastdin32.asm'
    
    main:
    
    mov dword[radix],10    ;I can choose the radix for integer output!
    mov dword[int_width],1 ;and the width of each integer for padded zeros
    
    mov ebp,chastack       ;mov the address of the beginning of the stack to ebp registers
    
    ;this program does not read command line arguments
    ;it always displays a message to tell user what the program does
    mov eax,string_help
    call putstring
    
    mov [last_char],0xA ;set newline as last_char so prompt will display
    
    main_loop:
    
    ;show the arrow indicating we wait for the user to enter something
    ;but only show it when the last character is a newline
    ;otherwise it will print too many if multiple commands were entered on the same line
    cmp [last_char],0xA
    jnz skip_prompt
    mov eax,string_prompt
    call putstring
    skip_prompt:
    
    call getstring ;get string and return address in eax
    
    ;we must restart the loop in case of an empty string
    ;if we didn't, strint would read the empty string and return 0
    ;then zero would be pushed to the stack, which is not what we want
    
    cmp dword[count],0 ;were there zero characters read?
    jz main_loop ;if yes, this was an empty string, retry input
    
    mov esi,eax    ;mov string to esi for string comparison
    
    ;Now we process the string the user entered
    ;First, we will try testing for commands
    ;If any of the predefined strings match the string in esi
    ;We jump to the label for that command
    
    mov edi,string_add
    call strcmp
    jz command_add
    
    mov edi,string_sub
    call strcmp
    jz command_sub
    
    mov edi,string_mul
    call strcmp
    jz command_mul
    
    mov edi,string_div
    call strcmp
    jz command_div
    
    mov edi,string_rem
    call strcmp
    jz command_rem
    
    mov edi,string_query
    call strcmp
    jz command_query
    
    mov edi,string_clear
    call strcmp
    jz command_clear
    
    mov edi,string_exit
    call strcmp
    jz command_exit
    
    ;The default command is to turn the argument into a number and push to stack
    command_num:
    
    mov eax,esi          ;mov the string to eax for processing numbers
    call strint          ;try to get a number from the string pointed to by eax
    cmp [strint_error],0 ;did we have zero errors in the strint function?
    jz num_push          ;if there were no errors, push this to stack
    
    mov eax,string_err
    call putstring
    mov eax,esi
    call putstring
    call putline
    jmp num_push_end ;skip the push because this can't be used
    
    num_push:        ;push the number to the fake stack
    add ebp,4
    mov [ebp],eax
    num_push_end:
    jmp main_loop
    
    ;These are the labels and code for each of the commands
    ;When a command is done, we jump back to the beginning of the loop
    
    command_add:
    mov eax,[ebp]
    mov dword[ebp],0
    sub ebp,4
    add [ebp],eax
    jmp main_loop
    
    command_sub:
    mov eax,[ebp]
    mov dword[ebp],0
    sub ebp,4
    sub [ebp],eax
    jmp main_loop
    
    command_mul:
    mov ebx,[ebp]
    mov dword[ebp],0
    sub ebp,4
    mov eax,[ebp]
    mov edx,0     ;zero edx before multiply
    mul ebx       ;multiply eax with value in ebx
    mov [ebp],eax
    jmp main_loop
    
    command_div:
    mov ebx,[ebp]
    mov dword[ebp],0
    sub ebp,4
    mov eax,[ebp]
    mov edx,0 ;zero edx before divide
    div ebx   ;divide eax with value in ebx
    mov [ebp],eax ;store quotient on stack
    jmp main_loop
    
    command_rem:
    mov ebx,[ebp]
    mov dword[ebp],0
    sub ebp,4
    mov eax,[ebp]
    mov edx,0 ;zero edx before divide
    div ebx   ;divide eax with value in ebx
    mov [ebp],edx ;store remainder on stack
    jmp main_loop
    
    command_query: ;print all numbers on the stack
    push ebp ;save value of ebp
    command_query_loop:
    cmp ebp,chastack ;is ebp equal to the address of stack start?
    jz command_query_end  ;if it is, end the putstack loop
    mov eax,[ebp]
    sub ebp,4
    call putint_and_line
    jmp command_query_loop
    command_query_end:
    pop ebp ;restore ebp to what it was before this command
    jmp main_loop
    
    command_clear: ;erase all numbers on the stack
    command_clear_loop:
    cmp ebp,chastack ;is ebp equal to the address of stack start?
    jz command_clear_end  ;if it is, end the putstack loop
    mov dword[ebp],0
    sub ebp,4
    jmp command_clear_loop
    command_clear_end:
    jmp main_loop
    
    command_exit: ;end the program
    
    main_loop_end:
    
    mov eax,1        ;exit (kernel opcode 1 on 32 bit systems)
    mov ebx,0        ;return 0 status on exit - 'No Errors'
    int 80h          ;system call for 32-bit Linux kernel
    
    argc dd 0
    
    string_err db 'Error: invalid number or command: ',0 ;Generic error message
    string_add db 'add',0
    string_sub db 'sub',0
    string_mul db 'mul',0
    string_div db 'div',0
    string_rem db 'rem',0
    string_exit db 'exit',0
    string_query db '?',0
    string_clear db 'clear',0
    
    string_prompt db '-> ',0
    
    string_help db 'chastdin is a stack based interactive calculator',0xA
                db 'Numbers are pushed on the stack and commands can do math.',0xA
                db 'It is a fork of chastack that reads from stdin instead of arguments.',0xA
                db 'Each line can contain multiple numbers or commands.',0xA
                db 'Math commands are add,sub,mul,div,rem',0xA
                db 'The exit command ends the program',0xA
                db 'The ? command prints the entire stack',0xA,0xA,0
    
    ;This program uses a virtual stack for convenience and portability
    ;I allocate memory for a virtual stack that we can index as if it was the real stack
    ;I name it "chastack" for Chastity's stack.
    
    db 6 dup 0 ;extra padding bytes
    chastack: rd 0x100
    

    chastdin32.asm

    ;Chastity's Standard Input header file
    ;The functions here are designed to read strings and numbers from standard input.
    
    ;getstring ;read characters from stdin until the first whitespace
    ;getline   ;read characters from stdin until the first newline,EOF,tab,etc.
    ;strcmp    ;compare two strings similar to the same function in C
    
    ;these variables are used as the default controllers
    ;for the getstring and getline functions
    ;buf stores keyboard input during those functions
    ;count stores how many bytes were read
    ;last_char stores the last character read
    ;usually this will be a space, tab, or newline
    
    buf db 0x100 dup '?'
    count dd 0
    last_char db 0
    
    ;summary
    ;the getstring function is the reverse function of putstring
    ;instead of printing a string to standard output
    ;it reads a string from standard input (AKA the keyboard)
    
    ;details
    ;the getstring function is designed to get a string of text
    ;which is terminated by whitespace or any non printable character
    ;the idea is that multiple strings can be passed on one line
    ;separated by spaces, similar to command line arguments
    ;this function was written for the specific purpose of converting any of
    ;my programs that used command line arguments to read from stdin instead
    
    getstring:
    
    mov [count],0 ;set count of characters read during this function to zero
    mov edx,1     ;number of bytes to read
    mov ecx,buf   ;address to store the bytes
    
    getstring_chars:
    
    mov ebx,0     ;read from stdin
    mov eax,3     ;invoke SYS_READ (kernel opcode 3)
    int 80h       ;call the kernel
    
    cmp eax,1     ;was 1 character read?
    jnz getstring_end ; if not, then end this loop
    
    mov al,[ecx]  ;mov last character read into al register
    
    ;check if this character is in the proper range to be part of the string
    
    cmp al,0x21      ;compare with 0x21 (!=exclamation)
    jb getstring_end ;jump if below to getstring_end label
    cmp al,0x7E      ;compare with 0x7E (tilde)
    ja getstring_end ;jump if above to getstring_end label
    
    ;if neither jump happened, keep the character and
    
    inc [count]   ;increment how many characters we have read
    inc ecx       ;increment address where next byte is read from
    jmp getstring_chars ;jump back to start of loop and keep reading
    
    getstring_end:
    
    mov [last_char],al ;save the last character read
    mov byte[ecx],0 ;terminate this string with a zero
    
    mov eax,buf ;mov the buffer address to eax for returning the string
    
    ret
    
    ;the getline function gets an entire line of text from the keyboard
    ;calling this function allows for a string that can contain spaces
    ;it considers as anything outside the range of 0x20 to 0x7E as the end of line character
    ;this is because the end of the line might be 0x0A on Linux
    ;or it might be 0x0D,0x0A on DOS or Windows.
    ;technically, it means tab will also terminate a line
    ;the intended use of this function is to read a filename
    ;filenames can contain spaces
    
    getline:
    
    mov [count],0 ;set count of characters read during this function to zero
    mov edx,1     ;number of bytes to read
    mov ecx,buf   ;address to store the bytes
    
    getline_chars:
    
    mov ebx,0     ;read from stdin
    mov eax,3     ;invoke SYS_READ (kernel opcode 3)
    int 80h       ;call the kernel
    
    cmp eax,1     ;was 1 character read?
    jnz getline_end ; if not, then end this loop
    
    mov al,[ecx]  ;mov last character read into al register
    
    ;check if this character is in the proper range to be part of the string
    
    cmp al,0x20    ;compare with 0x20 (space)
    jb getline_end ;jump if below to getstring_end label
    cmp al,0x7E    ;compare with 0x7E (tilde)
    ja getline_end ;jump if above to getstring_end label
    
    ;if neither jump happened, keep the character and
    
    inc [count]       ;increment how many characters we have read
    inc ecx           ;increment address where next byte is read from
    jmp getline_chars ;jump back to start of loop and keep reading
    
    getline_end:
    
    mov byte[ecx],0 ;terminate this string with a zero
    
    mov eax,buf ;mov the buffer address to eax for returning the string
    
    ret
    
    ;summary
    ;strcmp compares the string at esi to the one at edi
    ;eax returns 0 if the strings are the same and 1 if different
    ;the algorithm is simple but I will explain it for those who are confused
    
    ;details
    ;eax is initialized to zero
    ;a byte from each string is loaded into the al and bl registers
    ;the bytes are compared. if they are different, then we jump to the end
    ;However, if they are the same, then we check if one of them is zero
    ;for this purpose it doesn't matter whether we compare al or bl with zero
    ;because it is known that they are the same if the jnz did not take place
    ;if it is zero, this also jumps to the end of the function
    ;If neither jump took place, then we jump to the start of the loop
    ;but when the function finally ends bl will be subtracted from al
    ;this ensures that the function returns zero if the final characters are the same
    ;ebx,esi,and edi are preserved but eax is the return value
    ;also, the sub instruction at the end of the function also updates the flags
    ;so you can "jz" or "jnz" to a label after calling this function based on results
    
    strcmp:
    
    push ebx
    push esi
    push edi
    
    mov eax,0
    
    strcmp_start:
    
    ;read a byte from each string
    mov al,[edi]
    mov bl,[esi]
    cmp al,bl
    jnz strcmp_end
    
    cmp al,0
    jz strcmp_end
    
    inc edi
    inc esi
    
    jmp strcmp_start
    
    strcmp_end:
    sub al,bl
    
    pop edi
    pop esi
    pop ebx
    
    ret