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
.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