Skip to content

Polling, Glue Logic and an example application

In this section, we will construct a complete ecosystem around the bird CPU in SystemVerilog.  We will do this by writing a verilog module which connects the three verilog modules

  • Bird CPU
  • a 4×7 segment display as an output device,
  • a switchbank as an input device

and allows them to pass information among each other.  Such modules whose function is only to join and connect many other modules are sometimes known as  “glue logic”.  Alternatively, such modules are referred as the “top module” and the modules it connects as “submodules”.

A Pollable Switchbank

The first order of business is to write a switchbank module which can be polled by the CPU. This switchbank must have 16 switches and an enter button. Internally, it must have data and status registers.

In the code below, data_reg and status_reg act as  data and status register.  Ready bit is the least significant bit of status register. a0 correspons to the least significant bit of the address bus of the CPU and it chooses whether the data_reg or status_reg is read. When CPU reads any of the registers of the switchbank, ack becomes one.

  • When the switchbank does not have new data (ready bit=0) and the enter key is pressed (ie, enter key goes from 0 to 1), new data is loaded into data register and ready bit becomes 1.
  • When the CPU reads the data register of the switchbank (ack=1 and a0=0), ready bit becomes 0
module switchbank_poll(
                   input clk,
                   //--user side
                   input  [15:0] switches,
                   input enter_key,
                   //--cpu side
                   input a0,
                   input ack,
                   output [15:0] data_out
                   ); //; 

logic [1:0] pressed;
logic [15:0] status_reg; 
logic [15:0] data_reg ; 

always_ff @(posedge clk)// added @(posedge clk)
    begin
        pressed <= {pressed[0],enter_key};
        if ( ( pressed == 2'b10 ) && ( status_reg[0] == 1'b0 ) )
        begin
             status_reg  <= 16'b1;
             data_reg    <= switches;
        end
        else if ( ack & !a0  )
          begin
             status_reg <=16'b0;
          end
    end
     
    always_comb
    begin
        if (a0)
             data_out = status_reg;
        else
             data_out = data_reg;
    end
     assign ack_sw = ack;
     
initial begin
status_reg = 16'b0;
end
endmodule

 

 

Note that all in assignments should be done to the variables of the top module. Also, submodules can only exchange information over the top module..

Seven segment display

Seven segment display is unsynchronized, ie, it does not need a status register and ready bit. Hence it can be used as-is from the previous chapter.

Top module (or glue logic)

The basic duty of the top module is to implement the memory map.

Pin assignment

module main_module( input clk,
                        //---input from switchboard
                        input [7:0]  switches,            //input from 16-bit switchboard
                        input  enter_key,              //enter button
                        //---output to seven segment display
                        output logic [3:0] grounds,
                        output logic [6:0] display
                                );



//memory map is defined here
localparam    BEGINMEM=12'h000,
                  ENDMEM=12'h1ff,
                  SWITCHBANK_DATA=12'h900,
                  SWITCHBANK_STATUS=12'h901,
                  SEVENSEG=12'hb00;
//  memory chip
logic [15:0] memory [0:127]; 
 
// cpu's input-output pins
logic [15:0] data_out;
logic [15:0] data_in;
logic [11:0] address;
logic memwt;


//======ss7 and switchbank=====
logic [15:0] ss7_out, switch_in;
logic ackx;

sevensegment ss1(.datain(ss7_out),.grounds(grounds),.display(display),.clk(clk));
switchbank_poll  sw1(.clk(clk), .switches(16'(switches)), .enter_key(enter_key), .a0(address[0]), .ack(ackx), .data_out(switch_in));
bird br1 (.clk(clk),.data_in(data_in), .data_out(data_out),.address(address),.memwt(memwt));

//multiplexer for cpu input
always_comb
begin
ackx =0;
    if ( (BEGINMEM<=address) && (address<=ENDMEM) )
        begin
            data_in=memory[address];
        end
          else if ((address==SWITCHBANK_STATUS)|| (address==SWITCHBANK_DATA))
            begin
                 ackx = 1;              //with appropriate a0 resets the ready flag    
                 data_in = switch_in;   //a0 will determine if we read data or status
            end
    else
        begin
            data_in=16'hf345; //any number
        end
end
//multiplexer for cpu output 

always_ff @(posedge clk) //data output port of the cpu
    if (memwt)
        if ( (BEGINMEM<=address) && (address<=ENDMEM) )
            memory[address]<=data_out;
        else if ( SEVENSEG==address) 
            ss7_out<=data_out;

            
initial 
    begin
        ss7_out=0;
        $readmemh("ram.dat", memory);
    end



endmodule

 

A simple assembly language program which polls the switchbank in an infinite loop and writes whatever it reads into the 4×7 segment display is given below: You can use this program as a test to see whether everything in hardware works properly.
.data
.code       
 loop    ldi 0 0x901
         ldi 5 0x0001
         ld 1 0
         and 3 1 5
         jz loop
         ldi 0 0x900
         ld  0 0
         ldi 2 0xb00
         add 6 0 5
         st 2 6
         jmp loop

Pseudocode for a calculator

Below, we give the pseudocode for a simple calculator as an application.

  • The calculator takes as input digits 0-9 and binary operands + and –
int number, operand;

main() {
    char number, operand, old_number, old_operand;

    read_number_and_operand(); //sets the variables number and operand
    old_number = number;
    old_operand = operand; 
    while(1) {
         read_number_and_operand();
         if (old_operand === '+'){
               old_number += number;
               ss7(old_number);
         }
         elseif (old_operand == '-'){
               old_number -= number;
               ss7(old_number);
         }
         old_operand = operand;
    }
}

read_number_and_operand() {
     char xx;

     number = 0;
     operand = 0;
     do
           poll_the_switchboard_for_digit_or_operand()   
           if (xx>=0)&&(xx<=9){
                 number = 10*number + xx;
                 ss7(number);
           }
           else
                 operand = xx;
      while ( operand == 0) 
} 

poll_the_switchboard_for_digit_or_operand() {
    //do your polling here
    //set the variable xx to the digit/operand when polling is done.
}

ss7(number) {
    int number, number_bcd;

    number_bcd = double_dabble(number);
    // send number_bcd to seven segment monitor here tp be displayed;
}

double_dabble(number)
     int number, number_bcd, i;

     number_bcd = 0;
     for ( i=0, i<16; i++){
         carry = number & 0x8000;
         number = number << 1;
         number_bcd = number_bcd << 1;
         if (carry != 0)
                number_bcd++;
         if (number_bcd & 0x000f > 0x0004)
                number_bcd += 0x0003;
         if (number_bcd & 0x00f0 > 0x0040)
                number_bcd += 0x0030;
         if (number_bcd & 0x0f00 > 0x0400)
                number_bcd += 0x0300;
         if (number_bcd & 0xf000 > 4000)
                number_bcd += 0x3000; 
         }
}                

A few words about the double-dabble algorithm.

  • We know that it is possible to multiply any binary number with two by a simple left shift, which is very efficient.
  • We would also like to multiply bcd numbers with 2 by a left shift. Alas, this is not possible.
  • What double dabble algorithm achieves is to multiply bcd numbers with two by mostly left shifts, plus a little bit of additional work, ie, dabbling.

Let us start with the case where pure left shifts work. Consider the bcd number 231402 = 0010 0011 0001 0100 0000 0010. If we want to multiply this number by two, a simple left shift will work: 2×231402 = 462804 = 0100 0110 0010 1000 0000 0100.

Let us now consider the bcd number 271564. The same logic will obviously wont work: 271564 = 0010 0111 0001 0101 0110 0100. When we left shift, what we get is 0100 1110 0010 1010 1100 1000 = 8e2ac, which is obviously not bcd.

This two examples forces us to conclude that the digits 0, 1, 2, 3, and 4 do not constitute a problem, because when we shift them to left the result is still a bcd digit: 0, 2, 4, 6, 8.  But when we shift the digits 5, 6, 7, 8, 9 the result is no longer bcd: a, c, e, 10, 12.

The solution is to add 3 to these digits before shifting. To return back to our example, bcd representation of 271564 is

2       7       1        5      6       4
0010 0111 0001 0101 0110 0100

Now we ad 3=0011 to every digit larger than 4.

2      10      1       8       9       4
0010 1010 0001 1000 1001 0100

Now shift left will give the correct bcd result

0101 0100 0011 0001 0010 1000  =  543128

Why this works is explained in the following website: https://www.youtube.com/watch?v=eXIfZ1yKFlA