The device model of Pochi bears resemblance with uxn’s Varvara. The advantages of such a system is to separate all the external interactions between the core of the vm and the system. Usually the devices’ implementation will be different based on the platform (e.g. the screen will be different on desktop than on web) while the core of the vm will remain almost unchanged.
The difference with uxn is that access to devices is the same as a memory access with the difference that
the address that is accessed is in the HIGHMEM (0x10000 - 0x1FFFF). This aspect is more similar to
how the GreenArrays’ F18a chip
does I/O.
Each device has 16 ports (0x0 - 0xF) and the device id is specified by the rest of the address. Concretely
in the following address D represent the device id and P the port: 0x1DDDP.
This means that there are potentially 4095 different devices but we will strive to use way less than that.
Port name with a * usually represent addresses in memory, otherwise port holds/store integer values.
Disclaimer: As we are very early in the design phase, things may change a lot. The documentation can also lag behind. I strongly encourage you to have a look at the code.
Each device usually has a vector on port 0x0. When set, a vector contains the address of a word
that will be executed when the required conditions are met. The conditions for the vector to be triggered
will be specified in the relevant device sections.
| id | addr | device |
|---|---|---|
| 0 | 0x10000 | System |
| 1 | 0x10010 | Console |
| 2 | 0x10020 | Files 1 |
| 3 | 0x10030 | Files 2 |
| 4 | 0x10040 | Datetime |
| 5 | 0x10050 | Screen |
| 6 | 0x10060 | Grid |
| 7 | 0x10070 | Controller |
| 8 | 0x10080 | Mouse |
The system device is a general device that manages the Pochi vm.
| port | addr | name | type |
|---|---|---|---|
| 0 | 0x10000 | *vector |
rw |
| 1 | 0x10001 | halt |
rw |
| 2 | 0x10002 | debug |
-w |
The system vector is triggered once an error occurs, right before exiting the program. It is not triggered when the program exits normally.
The errors are the following:
When an error occurs and if the system’s vector is defined, the word pointed vector execute with the
working stack filled like so: ( addr inst err )
addr is the address in memory of the instruction that failed.inst is the opcode that failed.err is the error value as indicated above.When a non-zero value is written to the halt port, the vm will stop and the program will exit.
The value written to the debug port is a bitmask where the bits have the following meaning:
debug[0] prints the state of the stack to stderr.debug[1] prints the state of the execution (value of registers etc..) to stderr.debug[2] prints a dump of the RAM to stderr.Note that the range for the RAM addresses to be dumped is currently hard coded in the devices/system.c file.
The console device, as its name suggests deals with I/O within the console/terminal.
| port | addr | name | type |
|---|---|---|---|
| 0 | 0x10010 | *vector |
rw |
| 1 | 0x10011 | stdin/stdout |
rw |
| 2 | 0x10012 | cmdin/cmdout |
rw |
| 3 | 0x10013 | stderr |
-w |
The console vector is triggered whenever something is available to be read on the stdin or cmdin port.
Reading gets a char from stdin. Writing writes a char to stdout.
The cmdin/cmdout port behave the same as stdin/stdout except that it reads/write a Pochi instruction.
An instruction is 32 bit unsigned integer that has 6 slots (5x6 bits + 1x2 bits).
Writes a char to stderr.
The files device provide an interface to interact with the host’s file system.
| port | addr | name | type |
|---|---|---|---|
| 0 | 0x100?0 | unused | – |
| 1 | 0x100?1 | flag |
r- |
| 2 | 0x100?2 | nread |
r- |
| 3 | 0x100?3 | *path |
-w |
| 4 | 0x100?4 | *addr |
rw |
| 5 | 0x100?5 | maxread |
rw |
| 6 | 0x100?6 | append |
rw |
| 7 | 0x100?7 | action |
-w |
| 8 | 0x100?8 | read/write |
rw |
| 9 | 0x100?9 | delete |
-w |
| A | 0x100?A | tell/seek |
rw |
Where ? in the addr is either 2 or 3.
Note that there are 2 files devices to facilitate operations between two files.
Reading from the flag port indicates whether a previous read/write operation failed, succeeded or
if there is more data to read. The following values are possible:
The value read from the nread port is the number of “units” that were read/written.
It is usually chars, but in the case of a directory listing it’ll be the number of entries.
The value written to the *path port is an address to a Pochi string.
When written, it will close any previously opened file.
The string can be a path to a file as well as a directory.
An address in memory to/from which any read/write operation will operate.
In other words it’s the address of a buffer. See the description for action to
see how the data that is written to the *addr will look like.
The maximum number of bytes that can be read into the buffer at *addr.
The flag will be useful to detect if the buffer is full and there is more to be read.
Any non-zero value written to the append port will enable append mode, which means that writing
in a file won’t override it’s content.
Note that even if the value in append is 0, the file won’t be truncated, you should first delete
the file and then write to it if you want to emulate “truncating”.
Before writing a value to the action port you want to make sure that *path and *addr are set
as well as optionally make sure that append is set as desired and the position in the file is
good.
There are 3 different values that can be written to the action port. The result will always
be written to *addr up to maxread bytes. After writing to the action port, the
flag and nread will be set accordingly.
Since the value in *path can be both a file or a directory, we need to specify the
different behaviors depending on the type.
The possible values are as follow:
*addr.*addr.*addr.For read and write actions, nread will be set to the number of bytes read/written.
What is written to *addr is the same as a Pochi string, 1 cell whose value is the number of
bytes in the string that follows.
For stat nread will be 1 (entry) in case of success or 0 in case of failure.
The entry will look like this in addr:
0001 size in bytes
0002 num of chars in the name
0003 char string of the name
...
Each cell (line) is 32 bits (4 bytes).
We can’t write to a directory, so there are only two actions possible:
*addr.Both read and stat will set nread to the number of entries that where read.
(stat will be 1 in case of success)
When reading a directory, what is written to *addr is in the form of multiple stat entries
following each other:
0001 size in bytes of the 1st entry
0002 num of chars in the name of the 1st entry
0003 char string of the name of the 1st entry
... ...
000N size in bytes of the 2nd entry
000N+1 num of chars in the name of the 2nd entry
000N+2 char string of the name of the 2nd entry
... etc...
Read/write a 32 bits unsigned integer from/to the file, at the position of the cursor
given/set by tell/seek and move that cursor forward.
This can be useful to read a file containing instructions, for instance, one could jump to this port and
“execute” the file directly by reading it instruction by instruction.
Writing a non-zero value to the delete port will delete the file/directory pointed to by *path.
Get/set the cursor position in a file or in a directory by reading/writing to this port.
The datetime device provides a way to obtain date and time information as well as a convenient timer.
| port | addr | name | type |
|---|---|---|---|
| 0 | 0x10040 | *vector |
rw |
| 1 | 0x10041 | interval |
rw |
| 2 | 0x10042 | timestamp |
r- |
| 3 | 0x10043 | year |
r- |
| 4 | 0x10044 | month |
r- |
| 5 | 0x10045 | day |
r- |
| 6 | 0x10046 | hour |
r- |
| 7 | 0x10047 | min |
r- |
| 8 | 0x10048 | sec |
r- |
| 9 | 0x10049 | wday |
r- |
| A | 0x1004A | yday |
r- |
| B | 0x1004B | isdst |
r- |
It is worth noting that most of the read only values come from the localtime(3) function from
the time.h header in C. The interface should feel familiar to C programmers,
just be careful with year and month which are slightly different.
The datetime vector is triggered each time the timer “ticks”.
The timer is controlled by the interval port.
The interval in milliseconds between each tick of the timer. Initially the timer is disabled.
Writing a non-zero value to it will activate the timer, writing 0 to it will disable the timer.
Reading will return the amount of milliseconds left until the next tick.
The usual UNIX timestamp, aka number of seconds from the 1st January 1970. Don’t rely on it too much since 32 bits integers will overflow in 2038. I suggest to use it just to get “unique” timestamps if needed.
The current year.
Note that unlike localtime(3) the year is not 1900 - current_year.
The month of this year, it’s a value between 1 (for January) and 12 (for December).
This is slightly different than localtime(3).
The day of the month, the value is between 1 and 31.
The usual hours, minutes and seconds, nothing surprising here.
The day of the week. Note that Sunday is 0.
The day of the year, the value is from 1 to 365.
A value of 1 indicates daylight savings (summer time).
The screen is a bitmap graphics screen that works very similarly to the uxn screen.
| port | addr | name | type |
|---|---|---|---|
| 0 | 0x10050 | *vector |
rw |
| 1 | 0x10051 | palette |
-w |
| 2 | 0x10052 | width |
rw |
| 3 | 0x10053 | height |
rw |
| 4 | 0x10054 | auto |
rw |
| 5 | 0x10055 | x |
rw |
| 6 | 0x10056 | y |
rw |
| 7 | 0x10057 | *addr |
rw |
| 8 | 0x10058 | pixel |
-w |
| 9 | 0x10059 | sprite |
-w |
| A | 0x1005A | poly |
-w |
The sprite port works similarily to uxn’s screen
sprite port with a little extension
to benefit from Pochi’s 16 colors while remaining compatible with uxn’s
1bpp and
2bpp sprite format.
The lowest significant byte works exactly like it does in uxn.
The 2 following bytes select the colors from Pochi’s palette. This means that when
drawing a 2bpp sprite in the foreground by using the first 4 color (same behavior as
uxn), you can either set the extension bytes to 0 or set them to 0x0123 which will
select Pochi’s color 0 for the sprite’s color 0 etc..
The two folowing value for the sprite port are equivalent:
0xC10x0123C1The first 4 digits selects the “sprite palette” from the Pochi palette of 16 colors. From left to right they select colors from 0 to 4.
The poly port draws a polygon when written to. The x and y ports don’t matter,
the coordinates of the corners of the polygon are absolute and (0,0) is the top left
corner of the screen. The two ports that matter are *addr and auto.
The *addr port is an address in memory that points to a cell containing the number
of corner the polygon has followed by that number of cells containing the coordinates
of the corners. A cell containing the coordinate has the 16 least significant bits represent
the y coordinate and the 16 most significant bits represent the x coordinate.
The (40,125) coordinate would then look like this in memory: 0x0028007D. With 0x28 = 40
and 0x7D = 125.
The auto byte is only used when the autoaddr bit is set, which will then move *addr right
after the cell containing the last corner coordinate of the polygon.
The value written to the poly port is used to specify how the polygon will be drawn:
* O L F C C C C
| | | +-- Fill | | | +-- Color
| | +---- Layer | | +---- Color
| +------ Openshape | +------ Color
+-------- Unused +-------- Color
The Openshape bit will be 1 when we want the shape to be open (aka only a path and
not a polygon). This will work only when the Fill bit is 0, a polygon cannot be open
and filled.
We assume that it is more common to draw closed polygons, thus the “default” is to close
the shapes, conveniently the Layer, Fill and Color bits fit in 6 bits and this
makes it possible to encode the value in a gray number instead
of a regular green number.
| port | addr | name | type |
|---|---|---|---|
| 0 | 0x10060 | unused | – |
| 1 | 0x10061 | cols |
r- |
| 2 | 0x10062 | rows |
r- |
| 3 | 0x10063 | *font |
-w |
| 4 | 0x10064 | fontheight |
rw |
| 5 | 0x10065 | auto |
rw |
| 6 | 0x10066 | pos |
rw |
| 7 | 0x10067 | x |
rw |
| 8 | 0x10068 | y |
rw |
| 9 | 0x10069 | *straddr |
rw |
| A | 0x1006A | char |
rw |
| B | 0x1006B | str |
-w |
* * * * * * A P
| | | +-- Unused | | | +-- Auto pos
| | +---- Unused | | +---- Auto addr
| +------ Unused | +------ Unused
+-------- Unused +-------- Unused
The controller device is pretty straigthforward and again very similar to the uxn controller.
| port | addr | name | type |
|---|---|---|---|
| 0 | 0x10070 | *vector |
rw |
| 1 | 0x10071 | key |
r- |
| 2 | 0x10072 | buttons |
rw |
The mouse device is pretty straigthforward and again very similar to the uxn mouse.
| port | addr | name | type |
|---|---|---|---|
| 0 | 0x10080 | *vector |
rw |
| 1 | 0x10081 | x |
rw |
| 2 | 0x10082 | y |
rw |
| 3 | 0x10083 | scrollx |
r- |
| 4 | 0x10084 | scrolly |
r- |
| 5 | 0x10085 | buttons |
rw |
Not yet implemented