Saturday 15 February 2020

Toggling between C64 video output and the Linux console

Foreword

In the previous pot we wrote a small program running under Linux that captures the Scan codes that gets pressed and released on a keyboard.

This paved way for a future post where we will be delegating key presses in Linux to the C64 module.

In this post we will start with some Linux/C64 module integration.

In particular, we will be developing some functionality that allows us to toggle video output to the VGA screen between the Linux console and the video output from the C64 FPGA module.

Overview

Let us start by having an overview of what we want to achieve.

The last time we work on our C64 CPGA module was in this post, where we have added a scaler for scaling up the video frames our C64 module produces, so that it fills the screen.

Obviously, when we toggle from our C64 video output to the Linux console, we will need to bypass this scaler. This bypass is depicted by the illustration below:


The red arrow indicates when we want to display the Linux console on the VGA screen, we need to bypass the video scaler, as well as the succeeding buffer. So, in this scenario we effectively outputting pixels directly from  the first pixel buffer to the VGA screen.

Apart from bypassing the video scaler related components, we will also need to adjust the visible portion of the screen when toggling between the C64 video output and the Linux console. The Linux console will fill the whole screen, wheras the C64 module wont fit the complete width, e.g. a black band on the left and the right.

To trigger a change of video output source I will be utilising one of the push buttons on the Zybo board. When this button isn't pressed, we will show the Linux console on the VGA screen. If you push this button, the C64 screen will be shown. As soon as you release the button, the Linux console will be shown again.

In a future post we will be writing a program running under Linux that will automatically switch to the C64 screen when we run it, and switching back to the Linux console when we terminate this program.

Changing Framebuffer Addresses

Having to cater for two possible video sources (e.g. Linux console and C64), we need to assign a separate framebuffer area in memory for each.

From a previously post we have seen that it is better to host a framebuffer area at the top of the Zybo RAM and permit Linux to only use memory below this region. This just avoid headaches of trying to get Linux allocating large chunks of physical contiguous memory.

To accommodate the Linux framebuffer console, we have decided to limit allowable Linux memory to 500MB and put the framebuffer at address 507MB, resolving to physical address 0x1fb00000.

For the C64 video output we can reserve a framebuffer at address 503MB. This in turn resolves to physcial address 0x1f700000.

In previous posts C64 video output was always written to address 0x200000, so we need to change this in the burst write unit of our C64 module

always @(negedge clk)
if (!reset | (next_frame & count_in_buf == 0))
begin
  axi_start_address <= /*32'h200000*/32'h1f700000;
  axi_data_inc <= 0;
end
else if (state == INIT_CMD)
begin
  axi_start_address <= axi_start_address + axi_data_inc;
  axi_data_inc <= {BURST_THRES,2'b0};
end    


We have covered the core surrounding this code in a post a year or so ago. Basically this snippet keeps track of the address to use for each AXI burst. This gets reset at the beginning of each frame to display.

For our C64 video output we can always just use the same address. In case we change video source to Linux console, our C64 can continue writing its value output to its memory area without causing any harm.

So, our C64 module doesn't need much changes to accommodate two video sources. Our VGA module, however, needs to be able to read framebuffer data from two different areas.

For starters, we need to tell our VGA module which display mode we are in. We do this with an input port:

module vga(
  input wire clk,
  input wire clk_axi,
  input wire reset,
  output wire vert_sync,
  output wire horiz_sync,
  output wire [4:0] red,
  output wire [5:0] green,
  output wire [4:0] blue,  
  output wire [31:0] ip2bus_mst_addr,
  output wire [11:0] ip2bus_mst_length,
  input wire [31:0] ip2bus_mstrd_d,
  output wire [4:0] ip2bus_inputs,
  input wire [5:0] ip2bus_otputs,
  input wire c64_mode_in,
...
    );
...
reg c64_mode;
...
always @(posedge clk_axi)
  c64_mode <= c64_mode_in;
...


Later in this post we will be connecting c64_mode_in to a toggle button present on the Zybo board.

I am sampling this input port to a flip-flop just to cater for potential button bounces. This is probably an overkill, but I am just doing this in case...

Next, we need to make the following modifications to our AXI burst read block within our VGA module:

burst_read_block my_read_block(
          .clk(clk_axi),
          .reset(reset),
          .restart(trigger_restart_state == RESTART_STATE_RESTART),
          .count_in_buf(),
          .ip2bus_mst_addr(ip2bus_mst_addr),
          .ip2bus_mst_length(ip2bus_mst_length),
          .ip2bus_mstrd_d(ip2bus_mstrd_d),
          .ip2bus_inputs(ip2bus_inputs),
          .ip2bus_otputs(ip2bus_otputs),
          .axi_d_out(axi_read_data),
          .empty(axi_buffer_empty_temp),
          .read(read_from_axi),
          .start_address(c64_mode ? 32'h1f700000 : 32'h1fb00000)          
            );


We have added the start_address port in a previous post. Now we just take it one step further and provide a different adddress depending on whether c64_mode is enabled or not.

Different visible regions

Another difference between C64 video output and the Linux console we should address, is the difference between visible regions. The Linux console fills the screen whereas the C64 video output doesn't fill the whole width of the screen.

Let us start by defining a computational logic output for indicating when we are in a visible portion of the screen:

...
assign visible_region_vga = (vert_pos_next > 0)  & (vert_pos_next < 766) &
                           (horiz_pos_next > 0) & (horiz_pos_next < 1361);
                           
assign visible_region_c64 = (vert_pos > 20)  & (vert_pos < 760) &                               
                            (horiz_pos > 100) & (horiz_pos < 1175); 

assign visible_region = c64_mode ? visible_region_c64 : visible_region_vga;
...

Together with the different visible regions, we also need to able to select pixel display data from different sources:

assign pixel_display_data = c64_mode ? fifo_data_read : out_pixel_buffer;

So, in C64 mode we output the FIFO pixel buffered data from the scalar unit. With the Linux console mode, we output directly from the 16-bit asynchronous FIFO buffer, which buffer data from the AXI burst unit.

All this we can combine into the following, which will give us a black border for the portion of the screen which the applicable video output doesn't fill:

 assign out_pixel_buffer_final = visible_region ? pixel_display_data : 0;


The Final touches

We have developed the majority of functionality so that our FGPA design can display video from different sources.

There is a couple of remaining things that needs to be implemented.

First thing we need to have a look at is controlling the reading of data from the Asynchronous FIFO.

In C64 mode the Scalar unit will control the reading from the Asynchronous FIFO, whereas in Linux Console mode we will trigger reads from this FIFO when we are in the visible portions of the screen.

This translates to the following change:

aFifo
  #(.DATA_WIDTH(16))
  my_fifo
    (.Data_out(out_pixel_buffer), 
     .Empty_out(async_empty),
           .ReadEn_in(c64_mode ? (nextDIn & data_valid_in) : visible_region_vga),

     .RClk(clk),        
     .Data_in(shift_reg_16_bit[31:16]),  
     .Full_out(buffer_full),
     .WriteEn_in((state_shift_reg == STATE_16_SHIFT_STORED | state_shift_reg == STATE_16_SHIFT_SHIFTED) & !buffer_full),
     .WClk(clk_axi),
  
     .Clear_in(trigger_restart_state == RESTART_STATE_RESTART)
     

     );


The final thing we should address is the different orders pixels are stored for the two video sources for each 32-bit word received from the AXI bus. For instance, on the C64 module each 32-bit AXI word has the first pixel in line in the higher 16 bits, and the next pixel in line as the lower 16-bits.

On the Linux console video source has the first pixel in line in the lowest 16 bits of the 32-bit word, whereas the next pixel in line as the higher order 16-bits.

We need to account for this in our pixel splitter:

always @(posedge clk_axi)
  if (read_from_axi)
    shift_reg_16_bit <= c64_mode ? {axi_read_data[31:16], axi_read_data[15:0]} : 
        { axi_read_data[15:0], axi_read_data[31:16]};
  else if (state_shift_reg == STATE_16_SHIFT_STORED & !buffer_full)
    shift_reg_16_bit <= {shift_reg_16_bit[15:0], 16'b0};


Testing the setup

Let us test the setup.

We still need to link up the C64_mode pin on our VGA module to one of the push buttons.

Usually this involves adding an extra constraint to our constraint file (e.g. the XDC file).

Luckily we don't need to figure out these constraints from scratch, since the Home website of the Zybo board provides us with a template XDC file.

Scrolling down this template XDC, we eventually find the set of lines we are looking for:

##Buttons
#set_property -dict { PACKAGE_PIN R18   IOSTANDARD LVCMOS33 } [get_ports { btn[0] }]; #IO_L20N_T3_34 Sch=BTN0
#set_property -dict { PACKAGE_PIN P16   IOSTANDARD LVCMOS33 } [get_ports { btn[1] }]; #IO_L24N_T3_34 Sch=BTN1
#set_property -dict { PACKAGE_PIN V16   IOSTANDARD LVCMOS33 } [get_ports { btn[2] }]; #IO_L18P_T2_34 Sch=BTN2
#set_property -dict { PACKAGE_PIN Y16   IOSTANDARD LVCMOS33 } [get_ports { btn[3] }]; #IO_L7P_T1_34 Sch=BTN3


This declares a potential input port we can add to our design called btn.

This port, however, is declared as a 4 bit vector. From this 4 bit vector we only need one bit, meaning that we would need to write some extra Verilog code to split this vector into individual bits.

We could, however, simplify our life by not declaring these set of buttons as a vector constraint, but as individual ports:

##Buttons
#set_property -dict { PACKAGE_PIN R18   IOSTANDARD LVCMOS33 } [get_ports { btn_0 }]; #IO_L20N_T3_34 Sch=BTN0
#set_property -dict { PACKAGE_PIN P16   IOSTANDARD LVCMOS33 } [get_ports { btn_1 }]; #IO_L24N_T3_34 Sch=BTN1
#set_property -dict { PACKAGE_PIN V16   IOSTANDARD LVCMOS33 } [get_ports { btn_2 }]; #IO_L18P_T2_34 Sch=BTN2
#set_property -dict { PACKAGE_PIN Y16   IOSTANDARD LVCMOS33 } [get_ports { btn_3 }]; #IO_L7P_T1_34 Sch=BTN3


We can now just choose the applicable button we want and add it to our block design.

After synthesis, creating the bitstream, and creating a new boot.bin, we are now ready to run a test.

The following video shows the outcome:


We start off with the Linux console. As we press and release one of the push buttons, the C64 screen pops up and go back to the Linux console.

In Summary

In this post we have implemented functionality within our FPGA design in which we can switch between C64 video output and the Linux console.

For now this switching can only be done via a push button on the Zybo board.

In the next post we will be redirecting keystrokes from Linux to our C64 FPGA module.

Till next time!


Thursday 6 February 2020

Capturing KeyUp/KeyDown events in Linux

Foreword

In the previous post we finally got to a point where we can view a Linux console from a Zybo board on a VGA screen, as well as interacting with it via a USB keyboard.

For all practical reasons, Linux can now now run completely on its own a Zybo board, without the need of been connected to a PC.

Having manged to run Linux on the Zybo board, the next step would be to able for this running instance of Linux to be able to interact with our C64 FPGA module. This interaction will involve the following:


  • Redirecting keystrokes to the C64 FPGA module
  • Switching between C64 video output and the Linux Console
  • Loading .TAP images from the Linux file system and transferring it to the C64 FPGA module
This is quite a lot of functionality and we will definitely not be able tackle it all in one post, but as usual, we will tackle it like eating an elephant: bit by bit, or in our case, post by post :-)

In this particular post will start to tackle the redirection of keystrokes to the C64 FPGA module. This by itself needs two pieces of functionality:

  • Writing a user program in C running under Linux, carefully monitoring when the user pressed a key or released a key.
  • Writing a Kernel Driver driver that receives the key events from the user program mentioned in the previous point, and forwarding it to our C64 FPGA module.
One might wonder why it would be necessary to write a Kernel driver for forwarding the keystrokes to our C64 FPGA module. Why couldn't the mentioned user program take care of this as well?

The big reason for this is is because because interaction between our C64 FPGA module and Linux will happen via specific physical locations in memory space. When working with physical locations in memory it is always better to delegate these actions to a Kernel Driver.

In this post we will only be dealing with creating the user program for capturing the KeyUp/KeyDown events and in a later post we will be developing the associated Kernel Driver.

Watching the keyboard like a Hawk


When one writes an emulator for the Commodore 64, one of the first things one realise is that at any instant in time, you need to exactly know the state of the keyboard: Which keys are currently been held down and the moment one of them is released.

Knowing this kind if information just makes your emulator appear more in sync with your keyboard.

If one will be getting these keyboard changes from the Linux console, as we will be doing with our Zybo board, one will be facing a couple of frustrations. One of these frustrations is that by default you will not receive any key release events, no matter what you try.

This issue has to do with the default mode the Linux console is in, which is called cooked mode.

Let us analyse this problem by first looking at what cooked mode is.

Cooked mode actually provides a lot of functionality in the background when requesting a line of text. In cooked all edits from the user will be performed in the background and the final result will be send when you hit enter.

However, when we want to catch both keyup and keydown events, we need to switch the console from cooked mode to raw mode.

To set the right options to switch to raw mode takes quite a bit of fiddling.

Luckily someone on the Internet did a nice write-up on how to switch to raw mode: 

http://www.gcat.org.uk/tech/?p=70

In this blog post, the following method takes care of setting the console into raw mode:

#include "unistd.h"
#include "linux/kd.h"
#include "termios.h"
#include "fcntl.h"
#include "sys/ioctl.h"

static struct termios tty_attr_old;
static int old_keyboard_mode;

int setupKeyboard()
{
    struct termios tty_attr;
    int flags;

    /* make stdin non-blocking */
    flags = fcntl(0, F_GETFL);
    flags |= O_NONBLOCK;
    fcntl(0, F_SETFL, flags);

    /* save old keyboard mode */
    if (ioctl(0, KDGKBMODE, &old_keyboard_mode) < 0) {
 return 0;
    }

    tcgetattr(0, &tty_attr_old);

    /* turn off buffering, echo and key processing */
    tty_attr = tty_attr_old;
    tty_attr.c_lflag &= ~(ICANON | ECHO | ISIG);
    tty_attr.c_iflag &= ~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF);
    tcsetattr(0, TCSANOW, &tty_attr);

    ioctl(0, KDSKBMODE, K_RAW);
    return 1;
}

If one wants to set the console back to cooked mode, the following method will do the trick:

void restoreKeyboard()
{
    tcsetattr(0, TCSAFLUSH, &tty_attr_old);
    ioctl(0, KDSKBMODE, old_keyboard_mode);
}

This is quite a bit of code to get into raw mode and it might be easier just to use the SDL library that makes it much easier to capture keyup and keydown events.

However, to get the SDL library on the Zybo board, one would need to jump through a couple of cross compile hoops.

So, for now the setupKeyboard method will do it for me.

The previous mentioned post also provides a method for reading the keystrokes when we are in raw mode:

void readKeyboard()
{
    char buf[1];
    int res;

    /* read scan code from stdin */
    res = read(0, &buf[0], 1);
    /* keep reading til there's no more*/
    while (res >= 0) {
 switch (buf[0]) {
 case 0x01:
            /* escape was pressed */
            break;
        case 0x81:
            /* escape was released */
            break;
        /* process more scan code possibilities here! */
 }
 res = read(0, &buf[0], 1);
    }
}

Putting everything together

The mentioned Blog post in the previous section didn't provide a main method, so let us quickly create one:

int main(int argc, char * argv[])
{
   //code
  setupKeyboard();
  while(1) {
    usleep(20000);
    readKeyboard();
  }

}

Here we are in an endless loop, waiting 20milliseconds at each cycle before we read the keyboard.

Currently the readKeyboard method doesn't give any visual feedback on the keystrokes it gets. For this one could probably do a printf after each read. This would, however, clutter the display with repeated scancodes at a rate of whatever the keyboard repeat rate is been set at.

It would be so much nicer if we could just print output if the state of a key changes.

Let us achieve this by keeping at hand an array of keys that is currently been held down together with an applicable utility method:

...
int keys[6];
...
int getCodeInList(int code) {
  if (code == 0xff) {
    return -1;
  }
  code = code & 0x7f;

  for (int i = 0; i < 6; i++) {
    if (keys[i] == code)
      return i;
  }
  
  return -1;
}
...

With this code we can have up to 6 keys that are simultaneously been held down. You will also realise that we are masking off the most siginicant bit of the scancode to test. This is because both a press and release will yield the same value in lower 7 bits, but a release will have bit 7 set to one.

Next, let us write a method for when the key pressed, we insert the code into the array:

void doKeyDown(int scanCode) {
  int index = getCodeInList(scanCode);
  if (index != -1)
    return;
  for (int i = 0; i < 6; i++) {
    if (keys[i] == 0) {
      keys[i] = scanCode;
      if (scanCode == 0x1e) {
        printf("A pressed\n");
      } else if (scanCode == 0x1f) {
        printf("S pressed\n");
      }
      break;
    }
  }
}


Here we only print something to the console if the scan code wasn't in the key array.

Similarly, the following method will remove an element from the array:

void doKeyUp(int scanCode) {
  for (int i = 0; i < 6; i++) {
    if (keys[i] == scanCode) {
      keys[i] = 0;
      if (scanCode == 0x1e) {
        printf("A released\n");
      } else if (scanCode == 0x1f) {
        printf("S released\n");
      }
      break;
    }
  }
}


Once again, we only print something if this code were previously in the array.

Next, let us write another method that will call either doKeyDown or doKeyUp, depending on the scanCode given:

void processKey(int scanCode) {
  if (scanCode == 0xff) {
    return;
  }

  if ((scanCode & 0x80)) {
    //do key release
    doKeyUp(scanCode & 0x7f);
  } else {
    //do key down
    doKeyDown(scanCode);
  }
}


Finally, we need to modify the readKeyboard method as follows:

void readKeyboard()
{
    char buf[1];
    int res;

    /* read scan code from stdin */
    res = read(0, &buf[0], 1);
    if(buf[0] == 1) {
      restoreKeyboard();
      exit(0);
    }
    processKey(buf[0]);
    /* keep reading til there's no more*/
    while (res >= 0) {
 res = read(0, &buf[0], 1);
        if (buf[0] == 1) {
          restoreKeyboard();
          exit(0);
        }
        processKey(buf[0]);
    }
}

You can now test the whole program. This program, however, will only work if you use a virtual console. If you do it over an SSH channel or terminal window in an X-Windows session, it will not work.

As you keep pressing and releasing the A ans S keys, you will see statements  like A pressed and A released.

In Summary

In this post we have implemented functionality to capture KeyUp and KeyDown events.

In the next post we will be implementing functionality within our C64 FPGA module for switching between the video output of the C64 module and the Linux console.

Till next time!

Saturday 1 February 2020

Rendering Linux Console to VGA screen: Part 2

Foreword

In the previous post we managed to compile the Simple Frambuffer driver and got it load on Linux startup on a Zybo board.

We also found that once this framebuffer  driver has loaded, Linux automatically utilises this framebuffer as part of a new console. We confirmed this by inspecting the contents of the framebuffer in memory, finding that Linux rendered the contents of the startup console into it.

In this post we will be developing the FPGA side of the solution that will render the console to a VGA screen.

FPGA code tweaks

If we have a look at our current C64 FPGA module, we see that there already exist code that will render the contents of a framebuffer in memory to a VGA screen.

Throughout the course of this Blog-series I haven't really provided full source code listings for the C64 module as I went along. One can, however, get a big chunk of the code on the following github repo: https://github.com/ovalcode/c64fpga.

The code in previous mentioned Github Repo, doesn't contain the code explained in recent posts, but is enough to render contents from a framebuffer in memory and rendering it to a VGA screen.

In addition, this code will only render on a small portion of a screen big enough to fit the contents of a C64 screen. Preferably we would want to render on the full screen when rendering a Linux console, so we need to make some minor tweaks to the code on this Github Repo.

Let us start by having a look at the files in this Github Repo that are relevant to VGA rendering. All of these files are located in ip/vga_block_c64 and are as follows:

  • GrayCounter.v
  • aFifo.v
  • burst_read_block.v
  • fifo.v
  • sync_dual_port_ram.v
  • vga.v

From this list of files it is only the file vga.v that we need to make changes to. Let us have a look at the extend of the changes we need to make to this file.

First change that we should do is to make the image to display fill the whole screen, which ,in my case, is a screen with resolution 1360x768:

...
aFifo
  #(.DATA_WIDTH(16))
  my_fifo
     //Reading port
    (.Data_out(out_pixel_buffer), 
           .ReadEn_in((vert_pos_next > 0)  & (vert_pos_next < 766) &
                           (horiz_pos_next > 0) & (horiz_pos_next < 1361)),

     .RClk(clk),        
     //Writing port.  
     .Data_in(/*out_pixel*/shift_reg_16_bit[31:16]),  
     .Full_out(buffer_full),
     .WriteEn_in((state_shift_reg == STATE_16_SHIFT_STORED | state_shift_reg == STATE_16_SHIFT_SHIFTED) & !buffer_full),
     .WClk(clk_axi),
  
     .Clear_in(trigger_restart_state == RESTART_STATE_RESTART)
     
     );
...
 assign out_pixel_buffer_final = (vert_pos > 0)  & (vert_pos < 766) &
                                (horiz_pos > 0) & (horiz_pos < 1361)
                                ? out_pixel_buffer : 0;
...

These changes will read and display image data when we are in the full visible area of the screen and will render images correctly with resolution similar to that of my screen.

One problem I had when displaying a picture full screen, was that the first pixel always showd a garbage pixel value, causing the whole image to be displayed incorrectly on the screen.

This garbled pixel was caused by reading from the AXI buffer during the period when this buffer transitions from been empty to been filled with one item.

We can remedy the situation by waiting for a couple of clock cycles when the AXI buffer have been filled with the first item:

...
always @(posedge clk_axi)
begin
  axi_buffer_empty3 <= axi_buffer_empty_temp;
  axi_buffer_empty2 <= axi_buffer_empty3;
  axi_buffer_empty1 <= axi_buffer_empty2;
  axi_buffer_empty <= axi_buffer_empty1;
end
...
burst_read_block my_read_block(
          .clk(clk_axi),
          .reset(reset),
          .restart(trigger_restart_state == RESTART_STATE_RESTART),
          //.write_data(ram[current_address]), 
          .count_in_buf(),
          //output src ready
          //-----------------------------------------
          .ip2bus_mst_addr(ip2bus_mst_addr),
          .ip2bus_mst_length(ip2bus_mst_length),
          .ip2bus_mstrd_d(ip2bus_mstrd_d),
          .ip2bus_inputs(ip2bus_inputs),
          .ip2bus_otputs(ip2bus_otputs),
          .axi_d_out(axi_read_data),
          .empty(axi_buffer_empty_temp),
          .read(read_from_axi),
            );
...
assign read_from_axi = !axi_buffer_empty & !buffer_full & (state_shift_reg == STATE_16_SHIFT_IDLE | state_shift_reg == STATE_16_SHIFT_SHIFTED) ? 1 : 0;
...

Here we see that axi_buffer_empty forms part of the decision when to trigger a read action on the AXI_BUFFER. By delaying this signal with the help of a couple of cascaded flip-flops, we effectively delaying reading from this buffer when it transitions to non-empty.

One final thing we should change is the start address of the frame we read from SDRAM for display on screen.

The read addreses to SDRAM is also generated within burst_read_block.v. If you have a look within this module you will see that it is currently been hardcoded to 0x200000.

From the previous post, you will remember that the start address of the framebuffer we use is at address 0x1fb00000.

So, we can either change this hardcoded value in burst_read_block.v, or you can add a port to this module in which you you provide this address:

burst_read_block my_read_block(
          .clk(clk_axi),
          .reset(reset),          
          .restart(trigger_restart_state == RESTART_STATE_RESTART),           
          .count_in_buf(),
          //output src ready
          //-----------------------------------------
          .ip2bus_mst_addr(ip2bus_mst_addr),
          .ip2bus_mst_length(ip2bus_mst_length),
          .ip2bus_mstrd_d(ip2bus_mstrd_d),
          .ip2bus_inputs(ip2bus_inputs),
          .ip2bus_otputs(ip2bus_otputs),
          .axi_d_out(axi_read_data),
          .empty(axi_buffer_empty_temp),
          .read(read_from_axi),
          .start_address(32'h1fb00000)
            );

One could go one step further where you service this port out of the C64 module, and then add the necessary logic where we could set the address from Linux. We will however not do it in this post.

Loading the FPGA design on Zybo board at startup

With the FPGA design created in the previous section, let us see if we can get it loaded when we boot the Zybo board into Linux.

To start, a bit bitstream will need to be created for the FPGA design and exported to an SDK project.

Next we need to create an FSBL as I explain in a previous post.

With the FSBL created, we need to encapsulate it together with our FPGA bistream, as well the ubootloader into a single file.

We have done something similar in a previous post, as explained here. The resulting boot.bin file, however, didn't contain an FPGA bistream file.

To create a boot.bin file containing an FPGA bitstream, we need to use a boot.bif file that looks as follow:

image : {
        [bootloader]fsbl_vga.elf
        design_1_wrapper.bit
        u-boot.elf
}

Of course, design_1_wrapper.bit is your FPGA bitstream.

As previously, we create the boot.bin file with the following command:

bootgen -image boot.bif -o i boot.bin

Copy the resulting boot.bin back to the SDCard from which your Zybo boards boots from.

The End Result

Let us have a look at the end result.

I have inserted the SDCard back into the Zybo board, hooked it up to a VGA screen and attached a USB keyboard.

I got hold of a 5V power adaptor which attached to the Zybo board via its barrel connector. I have also set the appropriate jumper setting so that the Zybo board draws power from this device.

The following video shows the end result:


In this video I start off by switching on the Zybo board and a couple of seconds later some boot messages appear, after which we are presented with the Linux command prompt.

At the command prompt I clear the screen with the clear command, after which I show directory contents of the /etc directory.

In Summary

In this post we managed to boot the Zybo board completely standalone and watch the console output on an attached VGA monitor.

In the next post we will investigate how to capture key-up and key-down events in Linux from a keyboard.

We will use this information in coming posts so we can interface the Linux running on the Zybo board with our C64 FPGA module, so that Linux can delegate keystrokes to it.

Till next time!