Skip to content

3. "Hello World" Tutorial

sy2002 edited this page Jul 2, 2022 · 32 revisions

This tutorial is actually a bit more than "just" the classical "Hello World". It contains six small sub-tutorials to get you started:

  1. Classical "Hello World" using the Welcome-Screen
  2. Add a new menu-item and show an "About & Help" menu
  3. Add two menu-items: Flip the joystick ports and mute the siren
  4. Add three menu items to change the color of the "ball"
  5. Filter files: Only show *.txt files in the file browser
  6. Understanding the QNICE debug console

[@lydon: Here we could advertize your YouTube video]

Please make sure that you have completed the First Steps before proceeding with the tutorials shown here. Use the MyFirstM2M clone you made there for the following exercises.

"Hello World!" using the Welcome-Screen

By default, the Welcome-Screen is shown after power-on (and/or loading the core, respectively) and after each reset. You can configure this behavior in the file CORE/vhdl/config.vhd:

-- show the welcome screen in general
constant WELCOME_ACTIVE    : boolean := true;

-- shall the welcome screen also be shown after the core is reset?
-- (only relevant if WELCOME_ACTIVE is true)
constant WELCOME_AT_RESET  : boolean := true;

For now, do not change anything here, yet, because we want to use the Welcome-Screen to show our "Hello World" message.

Search for the constant SCR_WELCOME in config.vhd. You will notice that this is quite a long, multi-line string constant. You will recognize the contents of the Welcome-Screen that the demo-core is showing. Please be aware that you can and need to use \n to add newlines so that you can format your welcome screen.

Go to the lines that contains this segment:

   "\n\nEdit config.vhd to modify welcome screen.\n\n" &
   "You can for example show the keyboard map.\n" &
   "Look at this example from Game Boy Color:\n\n\n" &

Replace the segment with your personal "Hello World!" message for example:

    "\n\nHello World! Hello M2M! Hello MEGA65!\n\n\n\n" &

Generate a bitstream and run the core.

After that, you might want to set the above-mentioned flags to false so that the Welcome-Screen disappears completely: It will neither be shown upon the initial startup of the core then nor will it be shown after a reset.

New menu-item to show "About & Help"

This mini-tutorial catches two birds with one stone:

  1. You'll get a first sense for how the on-screen-menu works
  2. You'll understand the help-system

M2M contains a help-system that allows you to have up to 15 help topics, where each help topic can have 256 screens. Let's add a menu item that shows some demo help content.

Search for the constant OPTM_SIZE in config.vhd and change its value from 25 to 27.

Add this to the string constant OPTM_ITEMS before the "Close Menu" item:

" About & Help\n"        &
"\n"                     &

The string constant should now look like this:

constant OPTM_ITEMS        : string :=

   " Demo Headline A\n"     &
   "\n"                     & 
   " Item A.1\n"            &
   " Item A.2\n"            &
   " Item A.3\n"            &
   " Item A.4\n"            &
   "\n"                     &
   " HDMI Frequency\n"      &
   "\n"                     &
   " 50 Hz\n"               &
   " 60 Hz\n"               &
   "\n"                     &
   " Drives\n"              &
   "\n"                     &
   " Drive X:%s\n"          &
   " Drive Y:%s\n"          &
   " Drive Z:%s\n"          &
   "\n"                     &
   " Another Headline\n"    &
   "\n"                     &
   " HDMI: CRT emulation\n" &
   " HDMI: Zoom-in\n"       &
   " Audio improvements\n"  &
   "\n"                     &
   " About & Help\n"        &
   "\n"                     &
   " Close Menu\n";

Now go to this block of constants:

constant OPTM_G_Demo_A     : integer := 1;
constant OPTM_G_HDMI       : integer := 2;
constant OPTM_G_Drive_X    : integer := 3;
constant OPTM_G_Drive_Y    : integer := 4;
constant OPTM_G_Drive_Z    : integer := 5;
constant OPTM_G_CRT        : integer := 6;
constant OPTM_G_Zoom       : integer := 7;
constant OPTM_G_Audio      : integer := 8;

And add one more:

constant OPTM_G_AboutHelp  : integer := 9;

After that, locate the constant OPTM_GROUPS and add two more line items right before this block:

     OPTM_G_LINE,                              -- Line
     OPTM_G_CLOSE                              -- Close Menu
   );

The line items that you should add are these:

     OPTM_G_LINE,                              -- Line that separates the Help menu
     OPTM_G_AboutHelp + OPTM_G_HELP,           -- Show help topic #1 

As a result, OPTM_GROUPS will look like this:

constant OPTM_GROUPS       : OPTM_GTYPE := ( OPTM_G_TEXT + OPTM_G_HEADLINE,            -- Headline "Demo Headline"
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_Demo_A + OPTM_G_START,             -- Item A.1, cursor start position
                                             OPTM_G_Demo_A + OPTM_G_STDSEL,            -- Item A.2, selected by default
                                             OPTM_G_Demo_A,                            -- Item A.3
                                             OPTM_G_Demo_A,                            -- Item A.4
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_TEXT,                              -- Headline "HDMI Frequency"
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_HDMI + OPTM_G_STDSEL,              -- 50 Hz, selected by default
                                             OPTM_G_HDMI,                              -- 60 Hz
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_TEXT,                              -- Headline "Drives"
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_Drive_X + OPTM_G_MOUNT_DRV,        -- Drive X
                                             OPTM_G_Drive_Y + OPTM_G_MOUNT_DRV,        -- Drive Y
                                             OPTM_G_Drive_Z + OPTM_G_MOUNT_DRV,        -- Drive Z
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_TEXT,                              -- Headline "Another Headline"
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_CRT     + OPTM_G_SINGLESEL,        -- On/Off toggle ("Single Select")
                                             OPTM_G_Zoom    + OPTM_G_SINGLESEL,        -- On/Off toggle ("Single Select")
                                             OPTM_G_Audio   + OPTM_G_SINGLESEL,        -- On/Off toggle ("Single Select")
                                             OPTM_G_LINE,                              -- Line that separates the Help menu
                                             OPTM_G_AboutHelp + OPTM_G_HELP,           -- Show help topic #1 
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_CLOSE                              -- Close Menu
                                           );

Please note that the items in OPTM_GROUPS are having a 1-to-1 relationship to each line (separated by \n) in OPTM_ITEMS. Also please note that OPTM_ITEMS now has 27 lines and OPTM_GROUPS has 27 array elements, just as specified by OPTM_SIZE.

That's it! You now have an "About & Help" menu item that opens demo content that is three screens long.

Try it: Generate a bitstream and play with it. You can open the "About & Help" menu using Return and you can browse through the multiple help pages using Cursor Left and Cursor Right. Press Space to close the help menu.

How it works

You will find a more in-depth description of how the Shell's menu system works here @TODO and all details about the help-system here @TODO. Nevertheless, here is the condensed overview:

  • OPTM_ITEMS defines the names of the menu items and OPTM_GROUPS defines the properties and behaviour of the menu items. There is a 1-to-1 relationship between both: A line in OPTM_ITEMS corresponds to an array element in OPTM_GROUPS.
  • Empty lines in OPTM_ITEMS will be shown as lines, when the corresponding entry in OPTM_GROUPS is set to OPTM_G_LINE.
  • Each menu item that is supposed to "do something" needs to be part of a "Menu Group". This is why you defined the constant OPTM_G_AboutHelp while following the recipe above.
  • Multi-select menu items need all to be in the same Menu Group. See how all the "Item A.*" menu items are part of OPTM_G_Demo_A.
  • Single-select menu items - such as the help menu item - need to have a unique identifier ("Group"). This is why you gave your constant OPTM_G_AboutHelp the unique value 9.
  • Attributes and properties can be added to Menu Groups using the + operator because the values are arranged in a way that the Shell can recognize single bits. This is why you added the OPTM_G_HELP attribute to the OPTM_G_AboutHelp menu item identifier.
  • The help system counts: The first menu item that has a OPTM_G_HELP attribute will show the first help topic. The second menu item that has a OPTM_G_HELP attribute will show the second help topic, and so on. The various help menu items do not need to be in proximity.
  • The example config.vhd file provided with M2M contains a three-page (three-screen) demo help topic. This tutorial does not explain more details how the help-system itself works, but you can look at the constants WHS_DATA and WHS to get a first overview or go to the reference page @TODO to learn more.

Add two menu-items: Flip the joystick ports and mute the siren

Adding the menu-items to config.vhd

Now we are ready to add two menu items that change the actual state of the core: The first one will flip the joystick ports. The demo core's "BreakOut paddle" can not only be controlled by the cursor keys Left and Right but they can be also controlled by a joystick that sits in port #1. If you did not try this, yet, please do right now. After that, connect your joystick to port #2 and see how it is not working. We will fix that with our new menu-item.

The second menu item that we will add will be used to toggle the siren. By default it will be on so that the siren will be on. The flip joystick ports menu item will be off by default; so we will also learn how to work with default values.

If you followed the mini tutorial above, then you already know the trick of how to add menu items to OPTM_ITEMS and OPTM_GROUPS. Let's get started:

Here are two new constants for our list of Menu Group constants:

constant OPTM_G_FlipJoys   : integer := 10;
constant OPTM_G_Siren      : integer := 11;

You can highlight headlines or sections in the menu using the OPTM_G_HEADLINE attribute. Let's do that and create a completely new section at the very top of the menu directly after the first stand-alone "\n" (after " Demo Headline A\n"). Make sure that the first 10 lines of OPTM_ITEMS look like this:

constant OPTM_ITEMS        : string :=

   " Demo Headline A\n"     &
   "\n"                     &
   " My Switches\n"         &
   "\n"                     &
   " Flip Joysticks\n"      &
   " Siren\n"               &
   "\n"                     &  
   " Item A.1\n"            &
   " Item A.2\n"            &
   " Item A.3\n"            &

What we did: We added a headline called "My Switches" that is embedded between two new-lines ("empty lines"). We will make sure that these empty lines will be displayed as lines. We will also make sure that the headline "My Switches" will be displayed differently than the rest of the menu.

Before we proceed, let's count how many lines we added: We added five new lines, starting with "My Switches" and ending with "\n". So let's update OPTM_SIZE:

constant OPTM_SIZE         : natural := 32;  -- amount of items including empty lines:

Last-but-not least, we need to define the semantics. Make sure that the first 10 lines of OPTM_GROUPS look like this (and make sure that you remove the + OPTM_G_HEADLINE after the very first OPTM_G_TEXT just as shown here):

constant OPTM_GROUPS       : OPTM_GTYPE := ( OPTM_G_TEXT,                              -- "Demo Headline" (standard look)
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_TEXT + OPTM_G_HEADLINE,            -- Headline "My Switches": Yellow
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_FlipJoys + OPTM_G_SINGLESEL,       -- Single select item: Flip joysticks; not selected by default
                                             OPTM_G_Siren + OPTM_G_SINGLESEL + OPTM_G_STDSEL, -- Single select item: Siren; selected by default
                                             OPTM_G_LINE,                              -- Line
                                             OPTM_G_Demo_A + OPTM_G_START,             -- Item A.1, cursor start position
                                             OPTM_G_Demo_A + OPTM_G_STDSEL,            -- Item A.2, selected by default
                                             OPTM_G_Demo_A,                            -- Item A.3

A single-select menu item is being created by using OPTM_G_SINGLESEL, and adding OPTM_G_STDSEL makes sure that a menu item is on by default.

Synthesize the core, run it and check - using the Help key - that the menu is showing the expected menu items. Please note, that the headline "Demo Headline" is no longer shown in yellow (as we removed + OPTM_G_HEADLINE) while "My Switches" is indeed shown in yellow. As soon as this is successful, please continue with the next steps.

Applying the user's on-screen-menu choices to the core in mega65.vhd

The user's menu choices are available in real-time in mega65.vhd via the register qnice_osm_control_i, which is an input port to mega65.vhd:

 -- On-Screen-Menu selections
 qnice_osm_control_i     : in std_logic_vector(255 downto 0);

The prefix qnice_ means, that this signal / register is in the "Clock Domain" of the QNICE System-on-a-Chip. At this moment you neither need to understand what a "Clock Domain" (or what Clock Domain Crossing (CDC)) actually is, nor do you need to fully understand how QNICE works. The only important thing for now to know right now is:

  1. Always connect signals of the same clock domain with each other.
  2. Do not connect signals of different clock domains with each other.

In our very example this means: It is no problem to wire qnice_osm_control_i to the flip-joystick-port signal called qnice_flip_joyports_o. But it would be a problem, if you propagated qnice_osm_control_i into for example main.vhd that runs in the core's clock domain, and used it there. (Actually it is not difficult to do that, if you work out the Clock Domain Crossing (CDC), but for now, just ignore this and continue with the tutorial's track.)

Go back to the file config.vhd and count (starting from zero) where the two menu items we added are located. You need to count each line of OPTM_ITEMS including text-only items and empty lines. After that, create two constants in mega65.vhd that contain the position. Don't forget to adjust the positions of the other, already existing demo-core menu items: They changed when you added your new menu items. When you are done, the section -- Democore menu items in mega65.vhd should look like this:

-- Democore menu items
constant C_MENU_FLIPJOYS      : natural := 4;
constant C_MENU_SIREN         : natural := 5;
constant C_MENU_HDMI_60HZ     : natural := 15;
constant C_MENU_CRT_EMULATION : natural := 25;
constant C_MENU_HDMI_ZOOM     : natural := 26;
constant C_MENU_IMPROVE_AUDIO : natural := 27;

Now let's make sure that M2M is informed about the user's intention to flip the joystick ports; then M2M will do the rest for you. The qnice_osm_control_i contains this information at position C_MENU_FLIPJOYS so all we need to do is is to change this code here

-- Flip joystick ports (i.e. the joystick in port 2 is used as joystick 1 and vice versa)
qnice_flip_joyports_o      <= '0';

into this code here:

-- Flip joystick ports (i.e. the joystick in port 2 is used as joystick 1 and vice versa)
qnice_flip_joyports_o      <= qnice_osm_control_i(C_MENU_FLIPJOYS);

Next, let's make sure that the siren can be muted. Find the line that assigns a zero to qnice_audio_mute_o and change it to this code:

qnice_audio_mute_o         <= not qnice_osm_control_i(C_MENU_SIREN);

We are using not because the semantics is: If the Siren menu item is on then the siren should be audible.

Done. Pretty elegant, isn't it? Make a bitstream, run it and test it:

  1. The siren is on. Press Space and play a bit. Your joystick should be in port #2, so you can move the paddle using the cursor keys but not using the joystick.

  2. Open the menu by pressing Help and unselect "Siren": The siren should be immediatelly muted.

  3. Choose "Flip Joysticks" and note, how the menu item is visually selected in the menu. If you move your joystick now, it will not yet work. The reason is: The menu is still open and JOY_1_AT_OSD in config.vhd is set to false which means: While the menu is open, the joystick is disconnected from the core.

  4. Close the menu by pressing Help. Move your joystick to the left and to the right and note how the joystick moves the paddle.

Add three menu items to change the color of the "ball"

@TODO @TODO @TODO

@TODO @TODO @TODO

Filter files: Only show *.txt files in the file browser

@TODO @TODO @TODO

@TODO @TODO @TODO

Understanding the QNICE debug console

@TODO @TODO @TODO

@TODO @TODO @TODO