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.