Skip to content

Latest commit

 

History

History
296 lines (271 loc) · 9.96 KB

File metadata and controls

296 lines (271 loc) · 9.96 KB

Author: Ali Imran

Registration: 2018-EE-062

Table of Contents

File Structure

We have the following file structure. All the test are written in the test.py structure. Run the makefile to start the simulation. All the results are in the Figures directory. In order to run the simulation, cocotb and iverilog is required to be installed. Run the simulation by running make command in the tests folder in terminal to run the makefile which will perform the simulation using cocotb and dump the signals in the dump.vcd file. Vivado project is also available in the Single Cycle Hardware folder which implements the processor on the FPGA board it takes the two inputs(num1 and num2) from the user with switches and displays the output on the seven segment when the reset signal(rst) is set. The Vivado project also contains the simulation which is done using System Verilog.

├── Figures
│   ├── ALU.png
│   ├── Branch.png
│   ├── Immediate_Generator.png
│   ├── Instruction.png
│   ├── PC.png
│   ├── Register.png
│   ├── ckt.png
│   ├── datamem.png
│   └── gcd.png
├── README.md
├── Single Cycle Hardware
├── srcs
│   ├── ALU.sv
│   ├── Branch_Condition.sv
│   ├── Controller.sv
│   ├── Data_Memory.sv
│   ├── Immediate_Generator.sv
│   ├── Instruction_Memory.sv
│   ├── Program_Counter.sv
│   ├── Register_File.sv
│   ├── instruction_mem.mem
│   └── top.sv
└── tests
    ├── Makefile
    └── test.py

Single Cycle RISC-V Processor

For this project, we are going to implement single cycle RISC-V processor as shown in figure below. Single Cycle Processor\label{abc} Note: This figure has been taken from the slides of Dr. Muhammad Tahir.

For simulation, cocotb is used with iverilog.

Top Module

The top module of our design takes two 5 bit numbers as the input and calculate their gcd. When the reset is set, the values are written to the data memory and then the assemblt instructions load those values to the register file and perform the calculations.

For testing the gcd code, following assembly has been created.

    lw x8, 0(x0)
    lw x9, 4(x0)
gcd:
    beq x8, x9, stop
    blt x8, x9, less
    sub x8, x8, x9
    j gcd
less:
    sub x9, x9, x8
    j gcd
stop:
    sw x8,8(x0)
    lw x10,8(x0)
end:
    j end

For the above assembly, we have the following machine code in the instruction_mem.mem.

00000000
00002403
00402483
00940c63
00944663
409c0c33
ff5ff06f
408484b3
fedff06f
00802423
00802503
0000006f

For the top module, we have the following testbench.

@cocotb.test()
async def gcd_Test(dut):
    clk = Clock(dut.clk,10,"ns")
    cocotb.fork(clk.start())
    await RisingEdge(dut.clk)
    dut.rst.value = 1
    dut.num1.value = 2
    dut.num2.value = 4
    await RisingEdge(dut.clk)
    dut.rst.value = 0
    await RisingEdge(dut.clk)
    await RisingEdge(dut.clk)
    dut.hard_write.value = 1
    await RisingEdge(dut.clk)
    dut.hard_write.value = 0
    for i in range(2): await RisingEdge(dut.clk)
    while(int(dut.PC) != 40): await RisingEdge(dut.clk)
    await RisingEdge(dut.clk)
    dut.rst.value = 1
    dut.num1.value = 15
    dut.num2.value = 30
    await RisingEdge(dut.clk)
    dut.rst.value = 0
    await RisingEdge(dut.clk)
    await RisingEdge(dut.clk)
    dut.hard_write.value = 1
    await RisingEdge(dut.clk)
    dut.hard_write.value = 0
    for i in range(2): await RisingEdge(dut.clk)
    while(int(dut.PC) != 40): await RisingEdge(dut.clk)

For the gcd code, we get the following results. GCD

Data Path

Register File

For register file, the data available at two specified addresses(raddr1 and addr2) will be available at the output(rdata1 and rdata2). And data(wdata) will be written at the specified address(waddr) when the register write control signal(reg_wr) is set. The 31 general purpose registers have been declared as the output for debugging purposes.

Using the following test bench for register file.

@cocotb.test()
async def Register_Test(dut):
    clk = Clock(dut.r1.clk,10,"ns")
    cocotb.fork(clk.start())
    await RisingEdge(dut.r1.clk)
    dut.r1.rst <= 1
    await RisingEdge(dut.r1.clk)
    dut.r1.rst <= 0
    await RisingEdge(dut.r1.clk)
    dut.r1.raddr1 <= 5
    dut.r1.reg_wr <= 1
    dut.r1.waddr <= 5
    dut.r1.wdata <= 10
    await RisingEdge(dut.r1.clk)
    dut.r1.reg_wr <= 0
    await RisingEdge(dut.r1.clk)

We get the following output waveform.

Output of Register File

Instruction Memory

The instruction memory will read instructions from the instruction_mem.mem file. And the output(Instruction) will be available based on the instruction's address which is the input(Address).

The instructions are being read from instruction_mem.mem file with following test contents.

00b58513
40b50533
00b55533

We have the following testbench.

@cocotb.test()
async def Instruction_Test(dut):
    dut.i1.Address <= 8
    await Timer(2,'ns')
    dut.i1.Address <= 4
    await Timer(2,'ns')
    dut.i1.Address <= 0
    await Timer(2,'ns')

We get the following output waveform.

Output of Register File

Program Counter

The program counter will provide the address(PC) of the instruction to be executed and the next value of the program counter will depend on the br_taken flag which decides whether the next instruction will be executed or we will jump to some other instruction.

We have the following testbench.

@cocotb.test()
async def PC_Test(dut):
    clk = Clock(dut.p1.clk,10,"ns")
    cocotb.fork(clk.start())
    await RisingEdge(dut.p1.clk)
    dut.p1.ALU_out <= 10
    dut.p1.br_taken <= 0
    dut.p1.rst <= 1
    await RisingEdge(dut.p1.clk)
    dut.p1.rst <= 0
    await RisingEdge(dut.p1.clk)
    for i in range(2): await RisingEdge(dut.p1.clk)
    dut.p1.br_taken <= 1
    await RisingEdge(dut.p1.clk)
    dut.p1.br_taken <= 0
    for i in range(2): await RisingEdge(dut.p1.clk)

We get the following output waveform. Program Counter output

Immediate Generator

The Immediate Generator will generate the Immediate Value based on the type of Instruction.

We have the following testbench.

@cocotb.test()
async def Imm_Gen(dut):
    # addi x10,x11,21
    dut.ig.Instruction <= 22381843
    await Timer(2,'ns')
    # sw x10,4(x0)
    dut.ig.Instruction <= 10494499
    await Timer(2,'ns')
    # beq x0,x0,-8
    dut.ig.Instruction <= 4261416163
    await Timer(2,'ns')
    # jal x0,-12
    dut.ig.Instruction <= 4284477551
    await Timer(2,'ns')
    # lui x10,1234 = 5054464
    dut.ig.Instruction <= 5055799
    await Timer(2,'ns')

We get the following output waveform. Immediate generator

ALU

The ALU will perform different operations on the two inputs(A and B) based on the control signal(alu_op).

Following is the testbench code.

@cocotb.test()
async def alu(dut):
    dut.al.A <= 10
    dut.al.B <= 10
    dut.al.alu_op <= 3
    await Timer(2,'ns')
    dut.al.alu_op <= 0
    await Timer(2,'ns')

We get the following outputs for xor and add operations. ALU output

Branch and Jump Module

The Branch_Condition module performs different comparisons for the B-type based on the br_type control signal to generate the control signal(br_taken) for the B-type and J-type instructions which will be the input to the Program_Counter module.

We have the following testbench.

@cocotb.test()
async def cond(dut):
    dut.bcond.A <= 10
    dut.bcond.B <= 10
    dut.bcond.br_type <= 0
    await Timer(2,'ns')
    dut.bcond.br_type <= 1
    await Timer(2,'ns')
    dut.bcond.br_type <= 6
    await Timer(2,'ns')

We get the following output waveform. Branch Condition

Data Memory

The Data_Memory module creates the data memory from which data can be accessed or can be written onto based on the control signals(wr_en and rd_en).

We have the following testbench.

@cocotb.test()
async def Data_Test(dut):
    clk = Clock(dut.dmem.clk,10,"ns")
    cocotb.fork(clk.start())
    await RisingEdge(dut.dmem.clk)
    dut.dmem.rst <= 1
    await RisingEdge(dut.dmem.clk)
    dut.dmem.rst <= 0
    await RisingEdge(dut.dmem.clk)
    dut.dmem.addr <= 10
    dut.dmem.wdata <= 100
    dut.dmem.wr_en <= 1
    await RisingEdge(dut.rf.clk)
    dut.dmem.wr_en <= 0
    await RisingEdge(dut.rf.clk)
    dut.dmem.rd_en <= 1
    await RisingEdge(dut.rf.clk)
    dut.dmem.rd_en <= 0
    await RisingEdge(dut.rf.clk)

We get the following output waveform. DataMemory

Controller

For this datapath, all the RV32I instructions have been implemented except for lb, lh, lbu, lhu, sb and sh. The Controller generates different control signals(alu_op, reg_wr, sel_A, sel_B, wr_en, rd_en, unsign, br_type, wb_sel) for the different datapath modules based on the Instruction available at the input.