Lab10_Bit-pair Recoded Multiplier

Lab Overview

In this lab, you will implement a Bit-pair Recoded fixed point multiplier in Verilog. The top-level entity should be a structural design, and all lower-level components should be kept completely modular and use Verilog parameters. The processor should be tested in Vivado Simulator with appropriate testbench(es) that test for all cases (+/- and varying ranges) in an 8-bit multiplier. You will map your top-level entity to the board using a shortened 4-bit version of the multiplier.

Multiplier Design

You are to design an N-bit shift-add multiplier using bit-pair recoding. Each component in the design should be a separate module that you will connect to your top-level structural entity. You must set the design to 4 bits to program it to the board and 8 bits when performing test bench simulations.

Model your design based on the figure below. Multiplication will begin when the start signal is high, and the busy signal is low (note, busy and done are set by the state machine, Ctrl). The system will multiply through the following steps:

  • 1) Load the multiplicand and multiplier into their corresponding registers and activate the busy signal.

  • 2) Iteratively perform N/2 add/shift cycles. (N is the number of bits in the multiplicand and multiplier)

  • 3) Activate the done signal in the last clock cycle when the product is on the output.

  • 4) Deactivate the busy and done signals while the machine waits for the next multiplication.

drawing

Understanding Multiplication

This multiplier utilizes a very similar algorithm to the one we typically learn in grade school but for binary numbers. This algorithm works by partitioning the multiplier into its distinct digits and then iteratively performing (multiply => add => shifts) until all the digits of the multiplier have been used. Compare the following decimal and binary multiplication algorithms.

drawing

Moving to binary multiplication there are a few things we need to consider.

  • 1) Recoding – Rather than directly multiplying by the binary digit, we use booth encoding to determine the digit (-2, -1, 0, 1, 2) to multiply by.

  • 2) Shift – Shift by two binary digits rather than one. This implies N/2 iterations of the algorithm rather than N.

  • 3) Signs – The sign of a binary number is indicated by the leading digit rather than a - sign. Ensure that your values are preserved while performing shifts and additions. For example, in the 2nd Add, adding 2 negative numbers should never be positive; however, if we do not sign the extension properly, our leading bit will be 0. This is a common issue.

Implementation Details

Most of the components in this design should be fairly familiar to you by now. However, this design is fairly optimized, so here are a few clarifications to help you implement it.

Multiplication

This design decomposes the multiplier into a series of {-2, -1, 0, 1, 2} representing it. We do this because multiplying any binary number by these values is super easy to implement. All these implementation requires are shift («), NOT, sign extends, and +1 operations. Of these, the most difficult to perform is the +1. Luckily, since we already have an adder in our design, we can absorb this +1 operation into it. We do this by passing that +1 in as the carry-in bit of the adder when the partial product is added. See the figure for more details.

drawing

ProductReg

The product register is optimized to store the product, partial product, and multiplier at different times during the multiplication operation. The accompanying figure depicts how each flip-flop in the register is mapped to the other components. Upon starting a new multiplication, the system will initialize the most significant N+2 bits and the least significant bits of this register with 0’s and bits 1 to N with the multiplier.

drawing

Then, the three least significant bits will be used to determine the first partial product. This will be added to the running partial product contained in bits N+1 to 2N+1 and stored in bits N+1 to 2N+2. Then, each of the will be shifted rightward in the register, copying the sign bit for the top two bits. This will occur N/2 times until the multiplication is complete. The product will be contained in bits 1 to 2N after completion. The figure below shows how data would move through the Product Register as the product -6 x -5 is computed.

drawing

Ctrl and TimerReg

Similar to the previous lab, the control unit drives the control signals of the other components to orchestrate the multiplication. However, this time, it also comes with a TimerReg. This is because we know exactly how many add/shift cycles a week need to be completed to compute fixed width multiplication (N/2). This TimerReg is an alarm that signals the control unit is finished and needs to stop the added shift cycle. There are many ways to implement this alarm. But, one of the easiest is to utilize another shift register. When the multiplier starts an operation, this register is initialized to a specific N-bit value such as “100…00” then, after each cycle is completed you shift the bits by 1, “010…00”. After N cycles, the one will have shifted to the right, “000…01”, which can be easily used to send a signal to the controller to tell it to conclude the operation.

Demoing on the board

You are to map a 4-bit instance of your top-level entity to the board. You should test 4 different multiplications: (-2 x 7), (6 x 5), (5 x 6), and (4 x -7).

Add the source file

shift_reg.v

module shift_reg #(
    parameter N = 8  // N is the number of bits in the shift register.
)(
    input clk,                     // Clock input: triggers the shifting or setting actions.
    input set,                     // Set control: if high, load 'din' into the register.
    input shift,                   // Shift control: if high, perform a right shift on the register content.
    input [1:0] shift_in,          // 2-bit input value to be shifted into the register from the left.
    input [N-1:0] din,             // Data input: value to load into the register when 'set' is high.
    output reg [N-1:0] dout        // Data output: the current value of the shift register.
);
    
    // Sequential logic block triggered by the rising edge of the clock.
    always @(posedge clk) begin
        if (set == 1'b1) begin
            // When 'set' is high, load 'din' into the register.
            dout <= din; 
        end 
        else if (shift == 1'b1) begin
            // When 'shift' is high, perform a right shift of the register content by two bits,
            // and insert 'shift_in' in the two bits on the leftmost side.
            dout <= {shift_in, dout[N-1:2]};  // Correct for a 2-bit right shift
        end
    end

endmodule


MUX_multiplicand.v

// Define a module for a multiplexer that selects one of five multiplicand values.
// The width of each multiplicand and the output is parameterizable through N.
module MUX_multiplicand #(parameter N = 8)(
    input [2:0] sel,         // 3-bit select input to choose which data line is output.
    input [N-1:0] x1,        // Multiplicand option +0 (no change).
    input [N-1:0] x2,        // Multiplicand option +1 (original value).
    input [N-1:0] x3,        // Multiplicand option -1 (two's complement or negated value).
    input [N-1:0] x4,        // Multiplicand option +2 (double the original value).
    input [N-1:0] x5,        // Multiplicand option -2 (negated double value).
    output reg [N-1:0] y     // Output of the multiplexer, selected based on `sel`.
    );
    
    // Combinational logic to select the output based on the select signal.
    always @(*) begin
        case(sel)
            3'b000: y = x1; // Selects +0 multiplicand, often used for no operation.
            3'b001: y = x2; // Selects +1 multiplicand, equivalent to the original number.
            3'b101: y = x3; // Selects -1 multiplicand, which is the negated original number.
            3'b010: y = x4; // Selects +2 multiplicand, double the original number.
            3'b110: y = x5; // Selects -2 multiplicand, negated and double the original number.
            // Note: Other case values like 3'b011, 3'b100, and 3'b111 are not covered.
            // Depending on the design, you may want to handle these undefined states.
        endcase    
    end
endmodule

adder.v

// Parameterizable N-bit adder module.
module adder #(parameter N = 8)(
    input cin,                    // Carry-in input: Adds an extra bit to the sum if set to 1.
    input [N-1:0] x0,              // First N-bit input operand.
    input [N-1:0] x1,              // Second N-bit input operand.
    output reg extend,            // Output flag for sign extension (carry-out or overflow indication).
    output reg [N-1:0] y          // N-bit output sum of the inputs.
);

    // Combinational logic block to compute the sum.
    always @(*) begin
        // If carry-in (cin) is high, add both operands and an additional 1.
        // Otherwise, just add the two operands.
        if(cin == 1)
            y = x0 + x1 + 1;  // Add with carry
        else
            y = x0 + x1;      // Regular addition
    end

    // Combinational logic block to determine the need for sign extension.
    always @(*) begin
        // Check combinations of the most significant bits (MSBs) of inputs for sign extension.
        if (x0[N-1] == 1'b1 & x1[N-1] == 1'b1)
            extend = 1'b1;  // Both MSBs are 1, indicating potential overflow for positive numbers.
        else if (x0[N-1] == 1'b0 & x1[N-1] == 1'b0)
            extend = 1'b0;  // Both MSBs are 0, with no overflow for positive numbers.
        else if (x0[N-1] == 1'b1 & x1[N-1] == 1'b0)
            extend = y[N-1];  // Mixed MSBs, check MSB of result for overflow indication.
        else if (x0[N-1] == 1'b0 & x1[N-1] == 1'b1)
            extend = y[N-1];  // Mixed MSBs, check MSB of result for overflow indication.
        else
            extend = 1'b0;  // Default case, should logically never occur due to covered cases.
    end

endmodule

RESET.v

// Module definition for a resettable register.
// Parameter N specifies the width of the register.
module RESET #(
	parameter N = 8  // Width of the register and the inputs/outputs.
)(
	input s,                // Reset signal: when high, output follows input; when low, output is cleared.
	input [N-1:0] x1,       // Data input: value to be loaded into the register when 's' is high.
	output reg [N-1:0] y    // Data output: the current value of the register.
);

    // The always block triggers on any change of inputs.
	always @(*) 
	begin
		if (s == 1'b1)
			y = x1;  // If reset signal 's' is high, load 'x1' into the output 'y'.
		else if (s == 1'b0)
			y = 0;   // If reset signal 's' is low, clear the output 'y'.
	end
	
endmodule


MultiReg.v

// Define a module for a multi-functional register with parameterizable width N.
module MultiReg #(
    parameter N = 8  // N is the number of bits in the register.
)(
    input clk,                    // Clock input: triggers the operations within the register.
    input set,                    // Set control: if high, load 'din' into the register.
    input shift,                  // Shift control: if high, perform a shift operation on the register content.
    input [1:0] shift_in,         // 2-bit input value to be shifted into the register from the left.
    input [N-1:0] din,            // Data input: value to load into the register when 'set' is high.
    output reg [N-1:0] dout       // Data output: the current value of the register.
);
    
    // Sequential logic block triggered by the rising edge of the clock.
    always @(posedge clk) begin
        if (shift == 1'b1) begin
            // When 'shift' is high, perform a shift operation.
            // The content of 'dout' is updated to include the 2-bit 'shift_in' value at the left
            // and the existing 'din' data shifted right by two positions.
            dout <= {shift_in, din[N-1:2]};
        end 
        else if (set == 1'b1) begin
            // When 'set' is high, load the 'din' value directly into 'dout'.
            // This operation overrides the shifting behavior if both 'shift' and 'set' are high.
            dout <= din;  
        end
    end

endmodule

Counter.v

// Define a module for a countdown counter with a parameterizable width N.
module Counter #(
    parameter N = 8  // N defines the total count range; actual counter width is N/2.
)(
    input start,          // Start signal: when high, resets or initializes the counter.
    input tick,           // Tick signal: serves as a clock input, driving the counter's decrement.
    output done           // Done signal: goes high when the counter reaches zero.
);

    // Internal counter register with a width of N/2 bits.
    // The counter size is halved presumably to fit specific design constraints or requirements.
    reg [N/2-1:0] count;
    
    // Sequential logic block triggered by the rising edge of 'tick' or 'start'.
    // This block is responsible for resetting the counter on 'start' and decrementing it on 'tick'.
    always @(posedge tick or posedge start) begin
        if (start)
            count <= -1; // Reset the counter to its maximum value (all bits set due to -1 in two's complement).
        else 
            count <= {1'b0, count[N/2-1:1]}; // Perform a right shift, effectively decrementing the counter.
    end
    
    // The 'done' signal is assigned the value of the least significant bit of the counter.
    // When the counter reaches zero, 'count[0]' would be 0, indicating the countdown is not yet complete.
    // There seems to be a logical discrepancy here: typically, 'done' might be expected to signal completion.
    // You may need to revise this based on your intended counter behavior.
    assign done = count[0];

endmodule

CTRL.v

// Control module for managing operations such as counting, shifting, and loading,
// parameterized by bit width N.
module CTRL #(
    parameter N = 8  // Bit width for shift_data.
)(
    input clk,              // Clock input for synchronous operation.
    input rst,              // Active low reset to initialize the state machine.
    input start,            // Start signal to initiate counting or processing.
    input alarm,            // Alarm or external signal to transition from counting.
    input [1:0] bits,       // Input bits for shifting into shift_data.
    output reg busy,        // Busy signal indicating the machine is in operation.
    output reg done,        // Done signal indicating completion of a cycle.
    output reg count,       // Count signal to trigger counting (unused in current context).
    output reg tick,        // Tick signal, typically used for timing or synchronization.
    output reg addReg,      // Control signal to trigger addition operation.
    output reg shiftReg,    // Control signal to trigger shifting operation.
    output reg loadReg,     // Control signal to trigger loading operation.
    output reg [N-1:0] shift_data  // Data register for holding or shifting data.
);

    // State definitions for the state machine.
    reg [2:0] state, next_state;  // Current and next state variables.
    
    // State constants for readability and maintainability.
    parameter RESET = 2'b00;  // State constant for the reset state.
    parameter COUNT = 2'b11;  // State constant for the counting state.
    
    // Shift operation for shift_data, happens every positive clock edge or when reset.
    always @(posedge clk or negedge rst) begin
        if (!rst)
            shift_data <= 0;  // Clear shift_data on reset.
        else
            shift_data <= {bits, shift_data[N-1:2]};  // Shift in new bits on clock edge.
    end
    
    // State transition logic, triggered on clock edge or reset.
    always @(posedge clk or negedge rst) begin
        if (!rst)
            state <= RESET;  // Move to RESET state on reset.
        else
            state <= next_state;  // Transition to next state on clock edge.
    end
    
    // State behavior and output logic based on current state and inputs.
    always @(*) begin
        case (state)
            RESET: begin  // In RESET state...
                if (start == 1'b1) begin
                    next_state = COUNT;  // Move to COUNT state if start is high.
                    busy = 1'b1;  // Indicate that machine is busy.
                    done = 1'b0;  // Not done yet.
                end else begin
                    next_state = RESET;  // Stay in RESET if start is not high.
                    busy = 1'b0;  // Indicate machine is not busy.
                    done = 1'b0;  // Not done as we are in reset.
                end
            end
            COUNT: begin  // In COUNT state...
                if (alarm == 1'b0) begin
                    next_state = RESET;  // Return to RESET state if alarm is low.
                    busy = 1'b1;  // Still busy as we were counting.
                    done = 1'b1;  // Indicate completion as we leave COUNT state.
                end else begin
                    next_state = COUNT;  // Stay in COUNT state if alarm is high.
                    busy = 1'b1;  // Indicate machine is busy.
                    done = 1'b0;  // Not done as counting continues.
                end
            end
            default: begin  // In default case...
                next_state = RESET;  // Return to RESET for any undefined states.
                busy = 1'b0;  // Not busy in undefined state.
                done = 1'b0;  // Not done as this is an unexpected state.
            end
        endcase
    end
    
    // Output logic for control signals based on current state.
    always @(*) begin
        if (rst == 1'b0) begin  // If in reset...
            loadReg = 1'b1;  // Prepare to load data.
            addReg = 1'b0;  // No addition.
            shiftReg = 1'b0;  // No shifting.
            tick = 1'b0;  // No tick signal.
        end else begin
            case (state)
              RESET: 
		begin		
			if (start == 1'b1)
				loadReg 	= 1'b1;
			else
				loadReg 	= 1'b0;
			addReg 	= 1'b0;
			shiftReg = 1'b0;
			tick 	= 1'b0;					
		end
		COUNT: 
		begin
			loadReg  = 1'b0;
			addReg 	= 1'b1;
			shiftReg = 1'b1;
			tick 	= ~clk;
		end
		default: 
			begin
			loadReg  = 1'b0;
			addReg 	= 1'b0;
			shiftReg = 1'b0;
			tick 	= 1'b0;
			end
	endcase	
	end
end

endmodule

MUX_recoded.v

// Module for recoding multiplier data according to Booth's encoding.
// This is typically used to reduce the number of operations in binary multiplication.
module MUX_recoded(
    input [2:0] mul_data,       // 3-bit input representing a part of the multiplier.
    output reg [2:0] recoded_data // 3-bit output representing the recoded multiplier data.
);

    // Combinational logic to recode the input data based on typical Booth's algorithm patterns.
    always @(*) begin
        case (mul_data)
            // No change needed: Represents multiplication by 0.
            3'b000: recoded_data = 3'b000; // +0

            // Standard positive multipliers: Represents multiplication by +1.
            3'b001: recoded_data = 3'b001; // +1
            3'b010: recoded_data = 3'b001; // +1 (redundant, but aligns with Booth for certain implementations)

            // Special case for handling +2 (not typically standard Booth but may represent a double operation).
            3'b011: recoded_data = 3'b010; // +2

            // Negative multipliers: Represents multiplication by -1.
            3'b100: recoded_data = 3'b110; // -2 (In two's complement, '110' represents -2)
            3'b101: recoded_data = 3'b101; // -1
            3'b110: recoded_data = 3'b101; // -1 (redundant, but aligns with Booth for certain implementations)

            // Represents a return to zero or no operation needed at the end of a cycle.
            3'b111: recoded_data = 3'b000; // +0
            
            // Default cases can be added if required, especially for handling unexpected inputs.
            // default: recoded_data = 3'bxxx; // Undefined or X state for illegal inputs.
        endcase    
    end
endmodule

Bitpair_Mult.v

// Parameterized module for bit-pair multiplication, typically used in DSPs or ALUs.
module Bitpair_Mult #(
    parameter N = 8  // Specifies the bit width for the multiplicand and multiplier.
)(
    input clk,                         // Clock input for synchronization.
    input rst,                         // Asynchronous reset signal.
    input start,                       // Start signal to initiate the multiplication process.
    input [N-1:0] multiplican,         // Input multiplicand.
    input [N-1:0] multiplier,          // Input multiplier.
    output [2*N-1:0] product,          // Result of the multiplication.
    output busy,                       // Flag indicating the module is currently processing.
    output done                        // Flag indicating completion of multiplication.
);

    // Control signals for internal operations.
    wire load, shift, add, count, tick, LorA, sign_extend;
    wire [2:0] code;                   // Encoded value for multiplication steps.

    // Intermediate values and registers.
    wire [N:0] product_1, product_0;   // Extended product registers for accumulation.
    wire [N-1:0] multiplican_save;     // Register to store the multiplicand.
    wire [N:0] multi_multiplican;      // Result of selected operation on multiplicand.
    wire [N:0] partial;                // Partial sum/product for each step.
    wire [N:0] reset;                  // Reset value for the adder block.
    wire [N-1:0] data;                 // Additional data, possibly for future use or debugging.

    // Register to store and potentially modify the multiplicand.
    shift_reg #(
        .N(N)
    ) multiplican_Reg (
        .clk(clk),
        .set(load),
        .shift(),              // Not used here as multiplicand does not need shifting.
        .shift_in(),           // No input needed as there's no shift operation.
        .din(multiplican),
        .dout(multiplican_save)
    );
    
    // Register to store and shift the multiplier, integrating partial products.
    shift_reg #(
        .N(N+1)
    ) multiplier_Reg (
        .clk(clk),
        .set(load),
        .shift(shift),
        .shift_in(product_1[1:0]),  //Feedback lower bits of product for Booth encoding.
        .din({multiplier,1'b0}),    // Initialize with multiplier and an added zero bit.
        .dout(product_0)
    );	
    wire [N:0] x3;
    // Compute complementary version of the multiplicand for subtraction.
    assign x3 = (multiplican_save[N-1] == 1'b1) ? {1'b0, ~multiplican_save} : {1'b1, ~multiplican_save};	

    // Multiplexing different operation results based on Booth encoding.
    MUX_multiplicand #(
        .N(N+1)
    ) multi_multi (
        .sel(code),                           // Selection based on Booth recoding.
        .x1(0),                               // No operation (mult by 0).
        .x2({1'b0, multiplican_save}),        // Addition (mult by +1).
        .x3(x3),                              // Subtraction (mult by -1).
        .x4({multiplican_save, 1'b0}),        // Double (mult by +2).
        .x5({~multiplican_save, 1'b1}),       // Negative double (mult by -2).
        .y(multi_multiplican)                 // Output selected operation result.
    );
    
    // Adder for accumulating the multiplication results.
adder #(
		.N(N + 1)
	) adder_inst (
		.cin(code[2]),//+1
		.extend(sign_extend),
		.x0(multi_multiplican),
		.x1(product_1),
		.y(partial)
	);
    // Reset block to clear or set the partial sum/product.
    RESET #(
        .N(N+1)
    ) Reset_blk (
        .s(!load),
        .x1(partial),
        .y(reset)
    );

    // Logic to determine load or add operation.
    assign LorA = load | add;
    
    // Register block used for updating and shifting the partial product.
    MultiReg #(
        .N(N+1)  // Parameter for bit width, extended by one for overflow.
    ) product_Reg (
        .clk(clk),                           // Clock signal to synchronize operations.
        .set(LorA),                          // Signal to set (load) or add to the register.
        .shift(shift),                       // Signal to shift the contents of the register.
        .shift_in({sign_extend, sign_extend}), // Input bits for shifting, based on sign extension.
        .din(reset),                         // Data input, coming from the RESET module or partial sum.
        .dout(product_1)                     // Output of the register, fed back into the system.
    );
    
    // Counter used for controlling the number of multiplication cycles.
    Counter#(
        .N(N)  // Using the same parameter N for consistency with bit width.
    ) timer (
        .start(load),   // Start signal for the counter, initiates counting.
        .tick(count),   // Tick signal used to increment the counter.
        .done(tick)     // Done signal, indicates counting is complete.
    );
    
    // Control unit orchestrates the overall operation of the multiplication.
    CTRL #(
        .N(N)  // Parameter for matching bit width.
    ) ctrl_unit (
        .clk(clk),         // Clock signal for synchronization.
        .rst(rst),         // Reset signal for initializing the control unit.
        .start(start),     // Start signal to kick-off the multiplication process.
        .alarm(tick),      // Alarm or trigger from the counter indicating a cycle is complete.
        .bits(reset[1:0]), // Bits used for control decisions, derived from the RESET module.
        .shiftReg(shift),  // Signal to control shifting operation.
        .addReg(add),      // Signal to control addition operation.
        .loadReg(load),    // Signal to control data loading operation.
        .tick(count),      // Signal to manage timing and counting.
        .busy(busy),       // Output signal indicating the module is processing.
        .done(done),       // Output signal indicating the multiplication is complete.
        .shift_data(data)  // Data involved in shifting operations.
    );  
    
    // Booth recoding unit for transforming multiplier bits into control signals.
    MUX_recoded booth (
        .mul_data(product_0[2:0]),  // Input data from the shifted multiplier.
        .recoded_data(code)         // Output recoded data for controlling the multiplication steps.
    );

    // Final product assembly, combining sign extension, partial product, and data.
    // This forms the complete output product from the multiplication process.
    assign product = {sign_extend, product_1[N:0], data};  // Concatenate to form the full product.

endmodule

We will map clk port to the button, so we also need the debouncing module like below:

btn_lab4.v


// Module definition for lab 4 button interface with parameterizable data width N.
module btn_lab4 #(
    parameter N = 4  // Define the data width for the system.
)(
    input clk,               // Clock input for synchronization.
    input rst,               // Global reset signal.
    input key,               // Input signal from a physical button.
    input start,             // Control signal to start the processor.
    input [N-1:0] multiplican,    // Data input port for the multiplicand.
    input [N-1:0] multiplier,       // Data input port for the multiplier.
    output [2*N-1:0] product,       // Output port for the product result.
    output Busy,                     // Processor's busy status output.
    output Done                      // Signal indicating completion of the operation.
);
    
    // Internal signals for debouncing logic.
    wire key_pulse;          // Pulse signal generated after key debounce.
    reg [7:0] key_buffer;    // Buffer to store states of the key for debouncing.
    reg key_reg;             // Register to hold the debounced key value.
    
    // Debounce logic for the key input, triggered on clock edge or reset.
    always @(posedge clk or negedge rst) begin
        if (!rst)
            key_reg <= 1'b0;  // Clear the key register on reset.
        else if (key == 1'b1)
            key_reg <= 1'b1;  // Set the key register if key input is high.
        else 
            key_reg <= 1'b0;  // Clear the key register if key input is low.
    end

integer i; 
    // Update key buffer on each clock cycle, used for edge detection.
    always @(posedge clk or negedge rst) begin
        if (!rst)
            key_buffer <= 8'b0;  // Reset the key buffer on reset.
        else begin
            // Sequentially fill the buffer based on the debounced key value.
            for (i = 0; i < 8; i = i + 1) begin
                if (key_reg == 1'b1)
                    key_buffer[i] <= 1'b1;  // Set buffer bits if key is pressed.
                else
                    key_buffer[i] <= 1'b0;  // Clear buffer bits if key is not pressed.
            end            
        end  
    end
    
    // Generate a single-cycle pulse when the key is pressed.
    // This is a simplification; typically, edge detection logic would be used.
    assign key_pulse = &key_buffer;  // AND all bits to generate a pulse.
    
    // Instantiation of the system_processor (or Bitpair_Mult) module 
    // with the debounced key_pulse signal as the clock input.
    Bitpair_Mult #(
        .N(N)  // Pass the parameter N to the instantiated module.
    ) dut (
        .clk(key_pulse),        // Use the debounced key signal as a clock for simulation purposes.
        .rst(rst),              // Pass the reset signal.
        .start(start),          // Pass the start control signal.
        .multiplican(multiplican), // Pass the multiplicand input.
        .multiplier(multiplier),   // Pass the multiplier input.
        .product(product),         // Connect to the product output.
        .busy(Busy),               // Connect to the busy status output.
        .done(Done)                // Connect to the done signal output.
    );
    
endmodule

Now we can see the Schematic under the RTL ANALYSIS part like below:

drawing

For the system module, we can see:

drawing

Creating a testbench

Then we can run a Simulation to check the code of the Bitpair_Mult module.

tb.v

// Define a testbench module for the Bitpair_Mult
module BitPairMult_tst();

    // Define variables for loop iterations and their maximum values
    integer i;
    integer j;
    integer max_i = 4; // Maximum value for i loop
    integer max_j = 4; // Maximum value for j loop
    
    parameter N = 4; // Bit width for multiplicand and multiplier

    // Testbench control signals
    reg clk;        // Clock signal for synchronization
    reg rst;        // Reset signal to initialize the module
    reg start;      // Start signal to trigger multiplication

    // Input values for the multiplier
    reg [N-1:0] multiplican; // Input multiplicand
    reg [N-1:0] multiplier;  // Input multiplier
    
    // Outputs from the multiplier module
    wire busy;               // Indicates if the module is currently processing
    wire done;               // Indicates if the multiplication is complete
    wire [2*N-1:0] product;  // The result of the multiplication

    // Instantiate the Bitpair_Mult module with parameter N
    Bitpair_Mult #(
        .N(N)
    ) dut (
        .clk(clk),
        .rst(rst),
        .start(start),
        .multiplican(multiplican),
        .multiplier(multiplier),
        .product(product),
        .busy(busy),
        .done(done)
    );
    
    // Clock generation block
    initial begin
        clk <= 1'b0; // Initialize the clock to 0
        // Toggle clock every 5-time units to create a clock signal
        repeat (2*(2 + max_i * max_j * 3 + 3)) begin
            #5 clk <= ~clk;
        end
    end
    
    // Test sequence block
    initial begin
        $display("Running testbench"); // Display message on simulation start
        
        // Initialize inputs
        rst <= 1'b0;         // Assert reset initially
        start <= 1'b0;       // Ensure start is low
        multiplican <= 0;    // Initialize multiplicand
        multiplier <= 0;     // Initialize multiplier
        #10;                 // Wait for two clock cycles
        rst <= 1'b1;         // De-assert reset to start module operation
        #10;                 // Wait for two more clock cycles before starting test

        // Set test values for multiplicand and multiplier
        multiplican <= 4'b0100; // Set multiplicand to 4
        multiplier <=  4'b1001;  // Set multiplier to -7
    //  multiplican	<= 4'b1110;//-2
    //  multiplier	<= 4'b0111;//7
    //  multiplican	<= 4'b0110; //6
    //  multiplier	<= 4'b0101;//5
    //  multiplican	<= 4'b1010; //-6
    //  multiplier	<= 4'b1011;//-5
        #2;                      // Wait a short time
        start <= 1'b1;           // Assert start to begin multiplication
        #4;                      // Wait two clock cycles
        start <= 1'b0;           // Deassert start to simulate single pulse
        #4;                      // Wait two more clock cycles for results
    end   
endmodule

We can run a Simulation to check the code by clicking the Run Simulation under SIMULATION and choosing the first Run Behavioral Simulation. Here, the function of the clk is the same as the submit of the lab8.

drawing

The multiplicand is 4 and the multiplier is -7, then the result is -28.

drawing

The multiplicand is -2 and the multiplier is 7, then the result is -14.

drawing

The multiplicand is 6 and the multiplier is 5, then the result is 00111110, which is equal to the 30.

drawing

The multiplicand is -6 and the multiplier is -5, then the result is 30.

For the showing the real data we want, you can set the radix to the signed like below:

drawing

Implementation

The part can reference the Generate Bitstream in lab1.

The block design is shown below:

drawing

For the value of start multiplican multiplier ports can be read from the AXI_GPIO IP, and we need to write data to the product input ports by AXI_GPIO and the key port will map to the button(L19) and the Done port will map to the LED(M14) and the Busy will map to the LED(R14) of the board.

Download the bitstream file to PYNQ

We need to download the design_1_wrapper.bit to the local machine. Go to Lab9/project_1/project_1.runs/impl_1, download design_1_wrapper.bit, and upload the file to the PYNQ. And we also need to upload the design_1.hwh file which is in the Lab9/project_1/project_1.gen/sources_1/bd/design_1/hw_handoff.

from pynq import Overlay
from pynq import Bitstream
bit = Bitstream("design_1.bit")
bit.download()
bit.bitfile_name

from pynq import MMIO 
GPIO_BASE_ADDRESS = 0X41200000
GPIO_RANGE = 0x1000
start_write = MMIO(GPIO_BASE_ADDRESS, GPIO_RANGE)
GPIO_BASE_ADDRESS = 0X41210000
GPIO_RANGE = 0x1000
multiplican = MMIO(GPIO_BASE_ADDRESS, GPIO_RANGE)
GPIO_BASE_ADDRESS = 0X41220000
GPIO_RANGE = 0x1000
multiplier = MMIO(GPIO_BASE_ADDRESS, GPIO_RANGE)
GPIO_BASE_ADDRESS_r = 0X41230000
GPIO_RANGE = 0x1000
product = MMIO(GPIO_BASE_ADDRESS_r, GPIO_RANGE)
representations = {
    '0': ('###', '# #', '# #', '# #', '###'),
    '1': ('  #', '  #', '  #', '  #', '  #'),
    '2': ('###', '  #', '###', '#  ', '###'),
    '3': ('###', '  #', '###', '  #', '###'),
    '4': ('# #', '# #', '###', '  #', '  #'),
    '5': ('###', '#  ', '###', '  #', '###'),
    '6': ('###', '#  ', '###', '# #', '###'),
    '7': ('###', '  #', '  #', '  #', '  #'),
    '8': ('###', '# #', '###', '# #', '###'),
    '9': ('###', '# #', '###', '  #', '###'),
    '-': ('   ', '   ', '###', '   ', '   '),
    '.': ('   ', '   ', '   ', '   ', '  #'),
}

def seven_segment(number):
    # treat the number as a string, since that makes it easier to deal with
    # on a digit-by-digit basis
    digits = [representations[digit] for digit in str(number)]
    # now digits is a list of 5-tuples, each representing a digit in the given number
    # We'll print the first lines of each digit, the second lines of each digit, etc.
    for i in range(5):
        print("  ".join(segment[i] for segment in digits))
DATA_OFFSET = 0X0
DATA_0 = 0x06
DATA_1 = 0X05
multiplican.write(DATA_OFFSET,DATA_0)
multiplier.write(DATA_OFFSET,DATA_1)
start_write.write(0x0,1)

Then we need to press the button(L19) until you can see the LED(M14) is on which means the process is done. We need to press the button again and you can see the LED is off we can see the result below:

# Decimal value
decimal_value = product.read(0x0)

# Convert the decimal value to an 8-bit binary string
binary_str = format(decimal_value, '08b')

# Convert to signed decimal
if binary_str[0] == '1':  # Check if the number is negative in two's complement
    # Invert the digits
    inverted_str = ''.join('1' if bit == '0' else '0' for bit in binary_str)
    # Convert to decimal and subtract 1 to get the magnitude
    signed_decimal = -1 * (int(inverted_str, 2) + 1)
else:
    # If the number is positive, just convert it directly
    signed_decimal = int(binary_str, 2)

seven_segment(signed_decimal)
drawing
DATA_OFFSET = 0X0
DATA_0 = 0x05
DATA_1 = 0X06
multiplican.write(DATA_OFFSET,DATA_0)
multiplier.write(DATA_OFFSET,DATA_1)
start_write.write(0x0,1)
# Decimal value
decimal_value = product.read(0x0)

# Convert the decimal value to an 8-bit binary string
binary_str = format(decimal_value, '08b')

# Convert to signed decimal
if binary_str[0] == '1':  # Check if the number is negative in two's complement
    # Invert the digits
    inverted_str = ''.join('1' if bit == '0' else '0' for bit in binary_str)
    # Convert to decimal and subtract 1 to get the magnitude
    signed_decimal = -1 * (int(inverted_str, 2) + 1)
else:
    # If the number is positive, just convert directly
    signed_decimal = int(binary_str, 2)

seven_segment(signed_decimal)
drawing
DATA_OFFSET = 0X0
DATA_0 = 0xe   #-2*7
DATA_1 = 0X07
multiplican.write(DATA_OFFSET,DATA_0)
multiplier.write(DATA_OFFSET,DATA_1)
start_write.write(0x0,1)

Then we need to press the button(L19) until you can see the LED(M14) is on which means the process is done. We need to press the button again and you can see the LED is off we can see the result below:

# Decimal value
decimal_value = product.read(0x0)

# Convert the decimal value to an 8-bit binary string
binary_str = format(decimal_value, '08b')

# Convert to signed decimal
if binary_str[0] == '1':  # Check if the number is negative in two's complement
    # Invert the digits
    inverted_str = ''.join('1' if bit == '0' else '0' for bit in binary_str)
    # Convert to decimal and subtract 1 to get the magnitude
    signed_decimal = -1 * (int(inverted_str, 2) + 1)
else:
    # If the number is positive, just convert it directly
    signed_decimal = int(binary_str, 2)

seven_segment(signed_decimal)
drawing
DATA_OFFSET = 0X0
DATA_0 = 0x04 #4*-7
DATA_1 = 0X09
multiplican.write(DATA_OFFSET,DATA_0)
multiplier.write(DATA_OFFSET,DATA_1)
start_write.write(0x0,1)

Then we need to press the button(L19) until you can see the LED(M14) is on which means the process is done. We need to press the button again and you can see the LED is off we can see the result below:

# Decimal value
decimal_value = product.read(0x0)

# Convert the decimal value to an 8-bit binary string
binary_str = format(decimal_value, '08b')

# Convert to signed decimal
if binary_str[0] == '1':  # Check if the number is negative in two's complement
    # Invert the digits
    inverted_str = ''.join('1' if bit == '0' else '0' for bit in binary_str)
    # Convert to decimal and subtract 1 to get the magnitude
    signed_decimal = -1 * (int(inverted_str, 2) + 1)
else:
    # If the number is positive, just convert directly
    signed_decimal = int(binary_str, 2)

seven_segment(signed_decimal)
drawing
DATA_OFFSET = 0X0
DATA_0 = 0x0a #-6*-5
DATA_1 = 0X0b
multiplican.write(DATA_OFFSET,DATA_0)
multiplier.write(DATA_OFFSET,DATA_1)
start_write.write(0x0,1)

Then we need to press the button(L19) until you can see the LED(M14) is on which means the process is done. And we need to press the button again and you can see the LED is off and we can see the result below:

seven_segment(product.read(0x0))# Decimal value
decimal_value = product.read(0x0)

# Convert the decimal value to an 8-bit binary string
binary_str = format(decimal_value, '08b')

# Convert to signed decimal
if binary_str[0] == '1':  # Check if the number is negative in two's complement
    # Invert the digits
    inverted_str = ''.join('1' if bit == '0' else '0' for bit in binary_str)
    # Convert to decimal and subtract 1 to get the magnitude
    signed_decimal = -1 * (int(inverted_str, 2) + 1)
else:
    # If the number is positive, just convert it directly
    signed_decimal = int(binary_str, 2)

seven_segment(signed_decimal)
drawing