replaced build instructions with link to repo; and other minor changes

This commit is contained in:
sejo 2021-12-13 14:57:08 -06:00
parent 79e97ec6f6
commit 55cc7cd59c
1 changed files with 79 additions and 91 deletions

View File

@ -16,21 +16,23 @@ i invite you to read "why create a smol virtual computer" from the 100R site, as
=> https://100r.co/site/uxn.html 100R - uxn
uxn is the core of the varvara virtual (for the moment?) computer. it is simple enough to be emulated by many old and new computing platforms, and to be followed by hand.
uxn is the core of the varvara virtual computer. it is simple enough to be emulated by many old and new computing platforms, and to be followed by hand.
personally, i see in it the following features:
* built for the longterm
* built at a human-scale
* built for audiovisual interactive applications
* simple architecture and instruction set (only 32 instructions!)
* offline-first: it works locally and you only need a couple of documentation files to get going
* practice and experimentation ground for computing within limits
* ported already to several years old and modern computing platforms
all these concepts sound great to me, and hopefully to you too! however, i see in it a couple of aspects that may make it seem not too very approachable:
all these concepts sound great to me, and hopefully to you too!
however, i see in it a couple of aspects that may make it seem not too very approachable:
* it is programmed in an assembly language, uxntal
* it uses the {postfix} notation (aka reverse polish notation) / it is inspired by forth machines
* it uses the {postfix} notation (aka reverse polish notation) and it is inspired by forth machines
the idea of this tutorial is to explore these two aspects and reveal how they play along to give uxn its power with relatively little complexity.
@ -128,7 +130,7 @@ we'll be covering these instructions slowly over this tutorial.
memory in a uxn computer consists in four separate spaces:
* main memory, with 65536 bytes
* i/o memory, with 256 bytes divided in 16 sections (or devices) of 16 bytes each: 8 bytes for inputs and 8 bytes for outputs.
* i/o memory, with 256 bytes divided in 16 sections (or devices) of 16 bytes each
* working stack, with 256 bytes
* return stack, with 256 bytes
@ -138,7 +140,7 @@ the first 256 bytes of the main memory constitute a section called the zero page
there are different instructions for interacting with each of these memory spaces.
the main memory stores the program to be executed, starting at the 257th byte (address 0100 in hexadecimal). it can also store data.
the main memory stores the program to be executed, starting at the 257th byte (address 0100 in hexadecimal, or 256 in decimal). it can also store data.
the stacks cannot be accessed randomly; the uxn machine takes care of them.
@ -152,85 +154,62 @@ once the cpu reads a byte, it decodes it as an instruction and performs it.
the instruction will normally imply a change in the stack(s), and sometimes it may imply a change of the normal flow of the program counter: instead of pointing to the next byte in memory, it can be made to point elsewhere, "jumping" from a place in memory to another.
# installation and toolchain
## learn-uxn site
## local install
you can try and experiment with all the materials in the tutorial with the learn-uxn site by metasyn:
in order to run varvara locally and off the grid, we'd have to get the uxn assembler (uxnasm) and emulator (uxnemu) from their git repository:
=> https://metasyn.github.io/learn-uxn/ learn-uxn by metasyn
=> https://git.sr.ht/~rabbits/uxn ~rabbits/uxn
## locally
you can either build these tools from source, or download pre-compiled binaries for multiple platforms.
in order to run varvara locally and off the grid, let's get the uxn assembler (uxnasm) and emulator (uxnemu) from their git repository:
=> https://git.sr.ht/~rabbits/uxn ~rabbits/uxn - sourcehut git
these instructions are for linux-based systems.
you can find the installation instrutions in the repository.
if you need a hand, find us in #uxn on irc.esper.net :)
## install SDL2
### using the toolchain
in order to build uxnemu, we need to install the SDL2 library.
you'll see that when building or downloading uxn, you will get three executable files:
in a terminal in debian/ubuntu, do:
* uxnemu: the emulator
* uxnasm: the assembler
* uxncli: a non-interactive console-based emulator
``` sudo apt install libsdl2-dev
$ sudo apt install libsdl2-dev
```
in principle you can double click uxnemu and have it run.
or in guix:
however, we'll use these programs from the command line.
``` guix install sdl2
$ guix install sdl2
```
the idea is that in order to run a program written in uxntal, the uxn assembly language, first you have to assemble it into a "rom" with uxnasm. then you can run this rom with uxnemu.
## get and build uxn
for example, in order to assemble and run {darena} that is in projects/examples/demos/ :
let's get and build uxnemu and uxnasm:
assemble:
```
$ git clone https://git.sr.ht/~rabbits/uxn
$ cd uxn
$ ./build.sh
$ ./uxnasm darena.tal darena.rom
```
if everything went alright, you'll see many messages in the terminal and a little new window with the title uxn, and a demo application: uxnemu is now running a "rom" corresponding to that application.
run:
## uxnemu controls
```
$ ./uxnemu darena.rom
```
take a look at the available demos! (or not, and let's start programming ours!)
### uxnemu controls
* F1 circles between different zoom levels
* F2 shows the on-screen debugger
* F3 takes a screenshot of the window
* F4 loads a boot.rom that lets you browse and open roms in the current directory
## using the toolchain
## learn-uxn site
you'll see that after building uxn, you have three new executable files in the bin/ directory:
alternatively, you can try and experiment with all the materials in the tutorial with the learn-uxn site by metasyn:
* uxnemu: the emulator
* uxnasm: the assembler
* uxncli: a non-interactive console-based emulator
you can adjust your $PATH to have them available anywhere.
the idea is that in order to run a program written in uxntal (the uxn assembly language), first you have to assemble it into a "rom", and then you can run this rom with the emulator.
for example, in order to run {darena} that is in projects/examples/demos/ :
```
assemble darena.tal into darena.rom
$ ./bin/uxnasm projects/examples/demos/darena.tal bin/darena.rom
run darena.rom
$ ./bin/uxnemu bin/darena.rom
```
take a look at the available demos! (or not, and let's start programming ours!)
=> https://metasyn.github.io/learn-uxn/ learn-uxn by metasyn
# uxntal and a very basic hello world
@ -256,7 +235,7 @@ now that we are at it, there's a complementary instruction, SUB (opcode 19), tha
SUB ( a b -- a-b )
```
note that the order of the operands is similar to the division we discussed above when talking about postfix notation.
note that the order of the operands in the subtraction is similar to the order for the division as we discussed above when talking about postfix notation: it is as if we moved the operator from between operands, to the end after the second operand.
## a first program
@ -267,13 +246,13 @@ let's write the following program in our favorite text editor, and save it as he
|0100 LIT 68 LIT 18 DEO
```
let's assemble it and run it:
save it, and then let's assemble it and run it:
```
$ ./bin/uxnasm hello.tal bin/hello.rom && ./bin/uxnemu bin/hello.rom
$ ./uxnasm hello.tal hello.rom && ./uxnemu hello.rom
```
we will see an output that looks like the following:
a black window will open, and in the console we will see an output that looks like the following:
```
Assembled tmp/test.rom in 5 bytes(0.40% used), 0 labels, 0 macros.
@ -281,9 +260,11 @@ Loaded hello.rom
h
```
the last 'h' we see is the output of our program. change the 68 to, for example, 65, and now you'll see an 'e'.
the last 'h' we see is the output of our program.
so what is happening?
edit the code changing the 68 to, for example, 65, and now you'll see an 'e'.
interesting! so what is happening?
## one instruction at a time
@ -299,7 +280,7 @@ the first line is a comment: comments are enclosed between parenthesis and there
the second line has several things going on:
* |0100 : you may remember this number from before - this is the initial value of the program counter; the address of the first byte that the cpu reads. we use this notation to indicate that whatever is written afterwards, will be written in memory starting at this address.
* LIT : this appears twice, and is an uxn instruction with the following actions: it pushes the next byte in memory down onto the stack, and makes the program counter skip that byte.
* LIT : this appears twice; it is an uxn instruction that performs the following actions: it pushes the next byte in memory down onto the stack, and it makes the program counter skip that byte.
* 68 : an hexadecimal number, that corresponds to the ascii code of the character 'h'
* 18 : an hexadecimal number, that corresponds to an i/o address: device 1, port 8.
* DEO : another uxn instruction, that we could define as the following: output the given value (1 byte) into the given device address, both taken from the stack ( value address -- )
@ -320,7 +301,7 @@ looking at the devices table from the varvara reference, we can see that the dev
so, device address 18 corresponds to "console write", or standard output.
our program is sending the hexadecimal value 68 (character 'h') to the standard output!
our program is sending the hexadecimal value 68 (character 'h') to standard output!
you can see the hexadecimal values of the ascii characters in the following table:
@ -339,14 +320,14 @@ when we assembled our program, we saw that it was 5 bytes in size.
we can confirm it using the wc (word count) program:
```
$ wc --bytes bin/hello.rom
5 bin/hello.rom
$ wc --bytes hello.rom
5 hello.rom
```
for the curious (like you!), we could use a tool like hexdump to see its contents:
```
$ hexdump -C bin/hello.rom
$ hexdump -C hello.rom
00000000 80 68 80 18 17 |.h...|
00000005
```
@ -355,7 +336,7 @@ $ hexdump -C bin/hello.rom
so, effectively, our assembled program matches one-to-one the instructions we just wrote!
actually, we could have written our program with these hexadecimal numbers (the machine code), and it would have worked the same:
actually, we could have written our program using these hexadecimal numbers, i.e. the machine code, and it would have worked the same way:
```
( hello.tal )
@ -384,11 +365,9 @@ we could expand our program to print more characters:
if we assemble and run it, we'll now have a 'hello' in our terminal, using 30 bytes of program :)
ok, so... do you like it?
ok, so... do you like it? does it look straightforward? maybe unnecessarily complex?
it looks unnecessarily complex?
we'll look now at some features of uxntal that make writing and reading code more "comfy".
we'll look now at some features of uxntal that make writing and reading code a more "comfy" experience.
# runes, labels, macros
@ -396,10 +375,9 @@ runes are special characters that indicate to uxnasm some pre-processing to do w
## absolute pad rune
we saw already the first of them: | defines an "absolute pad": the address where the next written elements will be located in memory.
we already saw the first of them: | defines an "absolute pad", i.e. the address where the next written items will be located in memory.
if the address is 1-byte long, it is assumed to be an address of the i/o memory space or of the zero page.
if the address is 2-bytes long, it is assumed to be an address for the main memory.
## literal hex rune
@ -415,24 +393,26 @@ using this rune, we could re-write our first program as:
|0100 #68 #18 DEO
```
note that you can only use this rune to write the contents of either one or two bytes (two or four nibbles).
the following would have the same behavior as the program above, but using one less byte (in the next section/day we'll see why)
the following would have the same behavior as the program above, but using one less byte (in the next day of the tutorial we'll see why)
```
( hello.tal )
|0100 #6818 DEO
```
important: remember that this rune (and the others with the word "literal" in their names) is a shorthand for the LIT instruction. this can lead to confusion in some cases :)
note that you can only use this rune to write the contents of either one or two bytes, i.e. two or four nibbles.
important: remember that this rune (and the others with the word "literal" in their names) is a shorthand for the LIT instruction. this implies that uxn will push these values down into the stack.
if we just want to have a specific number in the main memory, without pushing it into the stack, we would just write the number as is, "raw". this is the way we did it in our first programs above.
## raw character rune
this is the raw character rune: '
it allows us to have uxnasm decode the numerical value of an ascii character.
uxnasm reads the ascii character after the rune, and decodes its numerical value.
our "hello program" would look like the following, using the new runes we just learned:
using this rune, our "hello program" would look like the following:
```
( hello.tal )
@ -444,11 +424,13 @@ our "hello program" would look like the following, using the new runes we just l
#0a #18 DEO ( newline )
```
the "raw" in the name of this rune indicates that it's not literal, i.e. that it doesn't add a LIT instruction.
note the "raw" in the name of this rune indicates that it's not literal, i.e. that it doesn't add a LIT instruction by itself.
that's why we need to include a LIT instruction.
## runes for labels
even though right now we know that #18 corresponds to pushing the console write device address down onto the stack, for readability and future-proofing of our code it is a good practice to assign a set of labels that would correspond to that device and sub-address.
even though right now we know that #18 corresponds to pushing the console write device port down onto the stack, for readability and future-proofing of our code it is a good practice to assign a set of labels that would correspond to that device and port.
the rune @ allows us to define labels, and the rune & allows us to define sub-labels.
@ -458,9 +440,9 @@ for example, for the console device, the way you would see this written in uxnta
|10 @Console [ &vector $2 &read $1 &pad $5 &write $1 &error $1 ]
```
we can see an absolute pad to address 10, that assigns the following elements to that address. because the address consists of one byte only, uxnasm assumes it is for the i/o memory space or the zero page.
we can see an absolute pad to address 10, that assigns the following items to that address. because the address consists of one byte only, uxnasm assumes it is for the i/o memory space or the zero page.
then we see a label @Console: this label will correspond to address 10.
then we see a label @Console: this label is assigned to address 10.
the square brackets are ignored, but included for readability.
@ -503,9 +485,9 @@ now this starts to look more like the examples you might find online and/or in t
## macros
following the forth heritage (?), in uxntal we can define our own "words" in macros that allow us to group and reuse instructions.
following the forth heritage, in uxntal we can define our own "words" as macros that allow us to group and reuse instructions.
during assembly, these macros are (recursively) replaced by the contents in their definitions.
during assembly, these macros are recursively replaced by the contents in their definitions.
for example, we can see that the following piece of code is repeated many times in our program:
@ -513,12 +495,14 @@ for example, we can see that the following piece of code is repeated many times
.Console/write DEO ( equivalent to #18 DEO, or LIT 18 DEO )
```
we could define a macro called EMIT that will take from the stack a byte corresponding to a character, and print it to standard output. for this, we need the % rune, and curly brackets for the definition.
we could define a macro called EMIT that will take from the stack a byte corresponding to a character, and print it to standard output.
for this, we need the % rune, and curly brackets for the definition.
don't forget the spaces!
```
( print a character to standard output )
( macro: print a character to standard output )
%EMIT { .Console/write DEO } ( character -- )
```
@ -570,7 +554,7 @@ we could "improve" this program by having a loop printing the characters, but we
in our previous program, the EMIT macro is called just after pushing a character down onto the stack.
how would you rewrite the program so that you push all the characters first, and then "EMIT" all them with a sequence like this one?
how would you rewrite the program so that you push all the characters first, and then "EMIT" all of them with a sequence like this one?
```
EMIT EMIT EMIT EMIT EMIT
@ -586,7 +570,11 @@ define a PRINT-DIGIT macro that takes a number (from 0 to 9) from the stack, and
%PRINT-DIGIT { } ( number -- )
```
remember that the number would have to be written as a complete byte in order to be valid uxntal. if you wanted to test this macro with e.g. number 2, you would have to write it as 02.
remember that the number would have to be written as a complete byte in order to be valid uxntal. if you wanted to test this macro with e.g. number 2, you would have to write it as 02:
```
#02 PRINT-DIGIT
```
# instructions of day 1
@ -599,7 +587,7 @@ these are the instructions we covered today:
# day 2
well done!
well done! hope you had a great start today!
in {uxn tutorial day 2} we start exploring the visual aspects of the varvara computer: we talk about the fundamentals of the screen device so that we can start drawing on it!