Visa Digital Calendar
The digital calendar is the main program for which we developed the visa assembly language and microprocessor in the first place. It was part of the original class assignment that sparked the beginning of the bopkit project.
The goal was to write an assembly program to drive the display of a digital calendar that showed the date and the time of day.
7-segment display
The 7-segment calendar display is an OCaml Graphics application. It is implemented here.
It is available in the command line as:
$ visa digital-calendar display
This opens up a OCaml Graphics window, and displays a 7-segment calendar that looks like this:
The application expects 91 bits on stdin, and responds by a blank line on stdout (this is the protocol for an external bopkit app of that interface).
You may try to feed a few inputs to play around with it. It's possible to use 7-segment to display some letters too!
$ visa digital-calendar display
0000000101111110110001011000111100111101100000000000000000000000000000000000000000000000000 <-- to enter on stdin
Testing the display
In order to test the display we added a command called gen-input
that will
generate valid inputs based on the current date and time of day. You may simply
connect the two applications together using a unix pipe:
$ visa digital-calendar gen-input | visa digital-calendar display --no-output
Using a textual format
So we can check in the output of calendar simulations into textual regression tests, we added a command that consumes the same input as the graphic calendar and output the date and time in a textual format.
$ visa digital-calendar gen-input | visa digital-calendar print
22/04/23 - 15:34:06
...
Microprocessor output vs Calendar input
The microprocessor visa uses an output device to communicate with the outside world. In order to drive the digital calendar, we'll make use of 6 bytes of the output device, each encoding a value on 8 bits (between 0-255) for each of the digits of the time of day (sec, min, hour) and the date (day, month, year).
However, these bytes cannot be directly connected to the calendar display, because its input is not directly compatible. Indeed, the display expects 7-segment codes.
During the hardware simulation, as you'll see we'll use a small circuit that makes the connection between the two components (the microprocessor and the calendar display) and maps the output of the microprocessor into an input directly consumable by the display.
In the meantime, we want to be able to test the same operation implemented fully
in the software world. So we've added two more commands, gen-raw-input
and
map-raw-input
. The first one simulates what the microprocessor will output,
while the second simulate what the translating circuit will have to do.
To exercise this all, run:
$ visa digital-calendar gen-raw-input \
> | visa digital-calendar map-raw-input \
> | visa digital-calendar display --no-output
Assembly program
We're now ready to implement an visa-assembly program to drive the digital-calendar!
We're using internal memory addresses for each of the sec, min, hour, day,
month, and year, which we named using the define
construct. We have part of
the code responsible for computing the expected number of days on a given month,
and make sure to call that code whenever we're beginning a new month. The code
starts by computing the expected number of days in February, and this code is
executed again each time the year changes.
The digital calendar program implemented in visa-assembly.
// A visa-assembly program to drive the display of a digital-calendar. This
// program was originally implemented by Mathieu Barbin & Ocan Sankur in 2007.
// GLOBAL VARIABLE DECLARATIONS (ADDRESSES).
define february 7
define year 5
define month 4
define day 3
define hour 2
define min 1
define sec 0
define days_in_current_month 8
// For a constant [x], [minus x] stores [-x] into [R1].
macro minus x
load $x, R0
not R0
load #1, R1
add
end
// Increment [var] by 1. If it equals [modulo] goto [carry_label],
// otherwise goto [return_label].
macro increment var, modulo, return_label, carry_label
load $var, R0
load #1, R1
add
store R1, $var
load $modulo, R0
cmp
jmn $carry_label
jmp $return_label
end
macro write_to_device_out local_address, device_address
load $local_address, R0
write R0, $device_address
end
// Computing the number of days in february:
COMPUTE_FEBRUARY:
load year, R0
load #3, R1
and
// If it is divisible by 4
jmz @29
jmn @28
29:
load #29, R0
jmp @FEB_WRITE
28:
load #28, R0
FEB_WRITE:
store R0, february
// MAIN PROGRAM
UPDATE_SEC:
sleep
write_to_device_out sec, 0
write_to_device_out min, 1
write_to_device_out hour, 2
write_to_device_out day, 4
write_to_device_out month, 5
write_to_device_out year, 6
// COUNT_SEC
increment sec, #60, @UPDATE_SEC, @COUNT_MIN
COUNT_MIN:
load #0, R0
store R0, sec
increment min, #60, @UPDATE_SEC, @COUNT_HOUR
COUNT_HOUR:
load #0, R0
store R0, min
increment hour, #24, @UPDATE_SEC, @COUNT_DAY
COUNT_DAY:
load #0, R0
store R0, hour
// Calculate days_in_current_month
load month, R0
// Is it February?
load #1, R1
cmp
jmn @FEBRUARY
// Else: (month <= 6) ==> (even month <=> 31)
// and : (month > 6) ==> (even month <=> 30)
minus #6
load month, R0
add
// If it's zero, then month == 6
jmz @LE6
// Otherwise we check bit 2^7. If it's 1, this means the result was negative,
// thus month < 6
load #128, R0
and
cmp
jmn @LE6
jmp @G6
DONE:
increment day, days_in_current_month, @UPDATE_SEC, @COUNT_MONTH
COUNT_MONTH:
load #0, R0
store R0, day
increment month, #12, @UPDATE_SEC, @COUNT_YEAR
COUNT_YEAR:
load #0, R0
store R0, month
increment year, #100, @COMPUTE_FEBRUARY, @NEW_CENTURY
NEW_CENTURY:
load #0, R0
store R0, year
jmp @UPDATE_SEC
// Functions (with Labels) to compute the number of days in the current month:
FEBRUARY:
load february, R0
store R0, days_in_current_month
jmp @DONE
// Case if month > 6.
G6:
load month, R0
load #1, R1
and
jmz @F30
F31:
load #31, R0
jmp @W
F30:
load #30, R0
W:
store R0, days_in_current_month
jmp @DONE
// Case if month <= 6.
LE6:
load month, R0
load #1, R1
and
jmn @F30
jmp @F31
Assembled
For the program to be able to fit into the microprocessor ROM code, it has to have a binary representation that fits on 256 bytes.
$ visa assemble calendar.asm | wc -l
210
We're in good shape here!
Simulation
Now that we have the assembly program, we can simulate its execution with the
visa simulator. Whether we're running the simulator, or executing the
microprocessor on the executable won't change that the output will be the raw
output device one. To connect that output to a calendar display, we'll need to
map it. For now, we're still doing that with our software version
map-raw-input
.
$ visa run circuit/calendar.asm \
| visa digital-calendar map-raw-input \
| visa digital-calendar print --clear-on-reprint
00/01/00 - 00:00:00
00/01/00 - 00:00:00
00/01/00 - 00:00:00
00/01/00 - 00:00:04
Initializing a different date
The microprocessor memory can be initialized with values, in which case the
calendar will start incrementing from that date rather than from
00/01/00 -00:00:00
. We made sure this would work, so we were careful not to erase the
initial memory contents in the executable.
We implemented a small executable whose job is to create the initial memory in
the format that's expected by the calendar.asm
program. That's what
get_date.exe
is about. Here is an example of usage:
$ ./get_date.exe '23:59:58' '2023/01/30'
// Initial memory contents for Visa
// Generated by: ./get_date.exe '23:59:58' '2023/01/30'
01011100 // sec
11011100 // min
11101000 // hou
10111000 // day
00000000 // mon
11101000 // yea
To be able to feed this contents to the simulation, we simply save it to a file, and supply the filename to the simulator:
$ ./get_date.exe '23:59:58' '2023/01/30' > my-initial-memory.txt
$ visa run calendar.asm \
> --sleep false \
> --stop-after-n-outputs 20 \
> --initial-memory my-initial-memory.txt \
> | visa digital-calendar map-raw-input \
> | visa digital-calendar print
01/01/00 - 00:00:58
01/01/00 - 00:59:58
01/01/00 - 23:59:58
30/01/00 - 23:59:58
30/01/23 - 23:59:58
30/01/23 - 23:59:59
30/01/23 - 23:59:00
30/01/23 - 23:00:00
30/01/23 - 00:00:00
31/01/23 - 00:00:00
31/01/23 - 00:00:01
31/01/23 - 00:00:02
31/01/23 - 00:00:03
31/01/23 - 00:00:04
31/01/23 - 00:00:05
31/01/23 - 00:00:06
31/01/23 - 00:00:07
31/01/23 - 00:00:08
31/01/23 - 00:00:09
31/01/23 - 00:00:10
We'll dive into this kind of simulation and the output that this produces in more details in the section dedicated to testing visa.
Debugging
As we've seen in the Assembler section of the doc, the visa project comes with a debugger, so we've used that to feel more confident making changes to the file!
$ visa-debugger bogue calendar.asm
The whole program can be tested quite easily by tweaking the contents of the
register R1
right before it is checked to determine if the increment operation
reached the maximum value allowed for each of the sec, min, hour, etc.