Tuesday 14 April 2020

Mapping joystick bits from Linux

Foreword

Previously we managed to load a tape image from the Linux File system, and got our C64 core to load it.

In this post we will add some more key mappings for a joystick, so we can play the game that loads.

Making our C64 design accept two joystick inputs

Currently our C64 FPGA design have only implemented Joystick Port #2. Having implemented functionality to rapidly switch between different game tape images, it actually becomes more of a requiremnt to implement Joystick Port #1 as well.

So, let us start with this requirement by looking into the C64 FPGA design.

Our starting point is our IP block that contains both an Master and Slave AXI port. This block we need to modify so that it can get an additional output port for Joystick Port #1:


To wire up this port, we need to make the following adjustments to the user logic of our AXI Slave block:

 // Add user logic here
    assign slave_reg_0 = slv_reg0;
    assign slave_reg_1 = slv_reg1;
    assign restart = slv_reg2[1];
    assign tape_button = slv_reg2[2];
    assign joybits = slv_reg2[8:4];
    assign c64_mode = slv_reg2[9];
    assign joybits2 = slv_reg2[14:10];

 // User logic ends

The bits of the two joystick ports are not consecutive to each other, due to the c64_mode in between. This will be a source of interesting bit manipulation in our driver, as will be seen later.

On our C64 core IP, we need to add an extra input port to accept this extra Joystick input. In our C64 core we supply the bits of the second joystick port to port B of CIA#1.

Port B of CIA#1, however, is also used as an from our keyboard. Thus we need to merge the lower four bits of the keyboard input bits with the bits from our second joystick port.

We do this as follows:

...
    assign key_joy_merged = ~(~keyboard_result[4:0] | ~joybits2);
...     
    cia cia_1(
          .port_a_out(keyboard_control),
          .port_a_in({3'b111, joybits}),
          .port_b_in({keyboard_result[7:5],key_joy_merged}),
          .addr(addr[3:0]),
          .we(we & io_enabled & (addr[15:8] == 8'hdc)),
          .clk(clk_1_mhz),
          .chip_select(addr[15:8] == 8'hdc & io_enabled),
          .data_in(ram_in),
          .data_out(cia_1_data_out),
          .flag1(flag1 & !flag1_delayed),
          .irq(irq)
            );
...

This is all the changes we require for the second joystick port for our FPGA design

Changes to the driver

Let us see what changes is required to our driver to accommodate two joystick ports.

Firstly we need to modify our record structure for input events as follows:

struct keyboard 
{
           u32 word1;
           u32 word2;
           u32 joybits;
};


Joybits will store the bits for both joystick ports.

Next, I will make a couple of changes to our write method:

static ssize_t dev_write(struct file *filep, const char * keys, size_t len, loff_t *offset){
   struct keyboard temp[1];
   copy_from_user(temp, keys, 12);
   iowrite32(temp[0].word1, c64_reg_keyboard_0);
   iowrite32(temp[0].word2, c64_reg_keyboard_1);
   unsigned int tempjoy = temp[0].joybits & 0x3ff;
   tempjoy = ~tempjoy & 0x3ff;
   unsigned int joy_high = (tempjoy << 1) & 0x7c0;
   unsigned int joy_low = tempjoy & 0x1f;
   joy_high = joy_high | joy_low;
   joy_high = joy_high << 4;
   unsigned int screenread = ioread32(c64_reg_screen_mode) & 0xffff820f;
   screenread = screenread | joy_high;
   iowrite32(screenread, c64_reg_screen_mode);
   
   return 12;
}


So, we isolate the bits of both ports and shift them to the correct position. We also need to shift the data of the two joystick ports one bit apart, because they are separated by the c64_mode bit.

Changes to the application

In our application, we make use of a file called sym.txt to indicate the resulting c64 scancode for each mapped key.

The real question here is: How do we indicate that a set of mapped keys is purposed for a Joystick? For this purpose, I am going to use the value -3:

84 -3 0 0                                            
17 -3 1 0                                              
87 -3 2 0                                              
89 -3 3 0                                              
19 -3 4 0                                              

The third value indicates the relevant bit that should be set for the joystick.

You might also remember that we use the file sym.txt to populate an array called key_map, containing c64 scancode, where the row and column values are reduced to a single scan code value between the range 0 to 63.

For the joystick, we can just extend the range, so scancode 64 can be joystick bit 0, 65 joystick bit 1 and so on.

With this in mind, we can change the method init_table as follows:

void init_table() {
  for (int i = 0; i < 256; i++) {
    key_map[i][0] = -1;
    key_map[i][1] = -1;
    key_map[i][2] = -1;
    key_map[i][3] = -1;
  }
  FILE *fp;
  fp = fopen("sym.txt", "r");
  char input[80];
  int num1, num2, num3, num4;
  while (1) {
    int status = fscanf(fp,"%d", &num1);
    fscanf(fp,"%d", &num2);
    fscanf(fp,"%d", &num3);
    fscanf(fp,"%d", &num4);
    fscanf(fp,"%[^\n]",input);
    if (status == EOF)
      break;
    if (num2 == -3) {
      key_map[num1][0] = num3 + 64;
      key_map[num1][1] = 0;
    } else if (num4 == 8) {
      key_map[num1][0] = (num2 << 3) | num3;
      key_map[num1][1] = 0;
      key_map[num1][2] = (num2 << 3) | num3;
      key_map[num1][3] = 1;
    } else if (num4 == 1) {
      key_map[num1][0] = (num2 << 3) | num3;
      key_map[num1][1] = 1;
    } else if (num4 & 32) {
      key_map[num1][0] = (num2 << 3) | num3;
      key_map[num1][1] = num4 & 1;
      fscanf(fp,"%d",&num1);
      fscanf(fp,"%d",&num2);
      fscanf(fp,"%d",&num3);
      fscanf(fp,"%d",&num4);
      fscanf(fp,"%[^\n]",input);
      key_map[num1][2] = (num2 << 3) | num3;
      key_map[num1][3] = num4 & 1;
    }
  }
}


We handle these codes in the key processing loop as follows:

    for (int i = 0; i < num_keys_to_process; i++) {
      int offset = shifted << 1;
      if (keys_to_process[i] == 0x18) {
        ioctl(fd,3);
        continue;
      }
      int c64_scan_code = key_map[keys_to_process[i] & 0xff][offset];
      if (c64_scan_code == -1)
        continue;
      if (c64_scan_code > 63) {
        keyToProcess.joybits = keyToProcess.joybits | (1 << (c64_scan_code - 64));
      } else if (c64_scan_code < 32) {
        keyToProcess.word1 = keyToProcess.word1 | (1 << c64_scan_code);
      } else {
        c64_scan_code = c64_scan_code - 32;        
        keyToProcess.word2 = keyToProcess.word2 | (1 << c64_scan_code);
      }
      if (key_map[keys_to_process[i] & 0xff][offset+1]) {
        keyToProcess.word1 = keyToProcess.word1 | (1<<15);
      }
    }

How do we switch between different joystick ports? A very simple mechanism would be to specify an extra parameter on the commandline. We can then just test for the number of arguments:

    for (int i = 0; i < num_keys_to_process; i++) {
      int offset = shifted << 1;
      if (keys_to_process[i] == 0x18) {
        ioctl(fd,3);
        continue;
      }
      int c64_scan_code = key_map[keys_to_process[i] & 0xff][offset];
      if (c64_scan_code == -1)
        continue;
      if (c64_scan_code > 63) {
        if (argc == 3)
          c64_scan_code = c64_scan_code + 5;
        keyToProcess.joybits = keyToProcess.joybits | (1 << (c64_scan_code - 64));
      } else if (c64_scan_code < 32) {
        keyToProcess.word1 = keyToProcess.word1 | (1 << c64_scan_code);
      } else {
        c64_scan_code = c64_scan_code - 32;        
        keyToProcess.word2 = keyToProcess.word2 | (1 << c64_scan_code);
      }
      if (key_map[keys_to_process[i] & 0xff][offset+1]) {
        keyToProcess.word1 = keyToProcess.word1 | (1<<15);
      }
    }

This conclude the code changes we should make to implement joystick ports.

In Summary

In this post we have implemented the necessary code changes for mapping keys to joystick ports in Linux.

In the next post we will write some extra code for our driver to initialise the sound system  so we can hear sound generated by our C64 core!

Till next time!

No comments:

Post a Comment