Όπως και στα προηγούμενα εργαστήρια συνεχίζουμε στο περιβάλλον του online chisel bootcamp.
Πριν ξεκινήσετε, εκτελέστε τα επόμενα 2 κελιά:
val path = System.getProperty("user.dir") + "/source/load-ivy.sc"
interp.load.module(ammonite.ops.Path(java.nio.file.FileSystems.getDefault().getPath(path)))
import chisel3._
import chisel3.util._
import chisel3.tester._
import chisel3.tester.RawTester.test
import dotvisualizer._
Στη συνέχεια του εργαστηρίου θα χρειαστούμε μια μνήμη μόνο για ανάγνωση (ROM) για να αποθηκεύουμε τις εντολές προς εκτέλεση. Στην Chisel αυτό μπορεί να γίνει με τη χρήση του VecInit()
όπως στο επόμενο παράδειγμα:
val content = Vector(33.U,1256.U,555.U)
val rom = VecInit(content)
Ο πιο πάνω κώδικας, όταν θα μεταφερθεί η σχεδίαση σε ένα πραγματικό κύκλωμα, θα δημιουργήσει ένα τμήμα υλικού που δέχεται μια είσοδο επιλογής (διεύθυνση) και παράγει την αντίστοιχη τιμή. Στο προηγούμενο παράδειγμα θα είναι rom(0)=33, rom(1)=1256 και rom(2)=555.
Σημ.: Ανάλογα με την τεχνολογία κατασκευής, το κύκλωμα του VecInit θα υλοποιηθεί με ως πίνακας αναζήτησης (lookup table) ή με απλές πύλες.
Στη συνέχεια δίνεται ένα παραμετρικό module InstructionMemory
που περιέχει ένα τμήμα ROM για εντολές. Οι παράμετροι δημιουργίας είναι οι ακόλουθες:
addr_width
: εύρος διεύθυνσης (πόσα bits έχουν οι διευθύνσεις της μνήμης εντολών)
instr_width
: εύρος εντολών (πόσα bits έχει κάθε εντολή)
content
: ακολουθία με το περιεχόμενο της μνήμης εντολών (σε μορφή UInt)
Εκτελέστε το επόμενο κελί:
class InstructionMemory(addr_width: Int, instr_width: Int, content: Seq[UInt]) extends Module {
val io = IO(new Bundle {
val address = Input(UInt(addr_width.W))
val data_out = Output(UInt(instr_width.W))
})
val rom = VecInit(content)
io.data_out := rom(io.address)
}
Δοκιμάστε το επόμενο κελί, όπου δημιουργούμε ένα InstructionMemory instance με εύρος διευθύνσεων 8 bits, εύρος εντολών 16 bits και αρχικοποίηση από ένα Vector με 3 τιμές. Στη συνέχεια, για κάθε γραμμή της μνήμης ROM εισάγουμε τη διεύθυνσή της με poke και ελέγχουμε αν παίρνουμε το επιθυμητό αποτέλεσμα:
val instructions = Vector(33.U,1256.U,555.U)
test(new InstructionMemory(8,16,instructions)) { c =>
for (i <- 0 until instructions.length) {
c.io.address.poke(i.U)
c.io.data_out.expect(instructions(i))
}
}
println("SUCCESS!!")
Σκοπός της άσκησης είναι να κατασκευάσετε τη μονάδα
του program counter και της μνήμης εντολών της ΚΜΕ. Το module θα ονομάζεται FetchUnit
και θα σχεδιαστεί όπως στο επόμενο σχήμα:
Το module FetchUnit
θα υλοποιεί την εξής λειτουργικότητα:
addr_width
), το εύρος των εντολών (παράμετρος instr_width
) και το περιεχόμενο της μνήμης εντολών (παράμετρος content
).class FetchUnit(addr_width: Int, instr_width: Int, content: Seq[UInt]) extends Module {
pcReg
με εύρος λέξης addr_width
που θα κρατά την τιμή της διεύθυνσης της επόμενης εντολής. Με την έναρξη λειτουργίας ο καταχωρητής pcReg
θα αρχικοποιείται στην τιμή 0.U.val pcReg = RegInit(0.U(addr_width.W))
InstructionMemory
instance που θα περιέχεται στη μεταβλητή imem
:val iMem = Module(new InstructionMemory(addr_width,instr_width,content))
Η έξοδος του καταχωρητή pcReg
θα οδηγείται στο InstructionMemory
instance (βλ. προηγούμενο σχήμα).
Το module θα διαθέτει επίσης τις εξής εισόδους:
branch_pc
(εύρος addr_width)pc_sel
(εύρος 1 bit)Σε κάθε κύκλο ρολογιού, η νέα τιμή του καταχωρητή pcReg
θα ισούται:
pc_sel
είναι 0.branch_pc
, όταν το pc_sel
είναι 1.Παρατηρήστε στο σχήμα τη χρήση του σήματος addr_plus_1
για το σημείο του κυκλώματος μετά τον αθροιστή + 1. Στο σήμα addr_plus_1
μπορείτε να συνδέσετε άλλα σήματα όπως π.χ. την έξοδο next_pc
.
val addr_plus_1 = pcReg + 1.U
Συμπληρώστε τις συνδέσεις του module FetchUnit
σύμφωνα με το προηγούμενο σχήμα στο κελί που ακολουθεί:
class FetchUnit(addr_width: Int, instr_width: Int, content: Seq[UInt]) extends Module {
val io = IO(new Bundle {
val branch_pc = Input(UInt(addr_width.W))
val pc_sel = Input(UInt(1.W))
val next_pc = Output(UInt(addr_width.W))
val instruction = Output(UInt(instr_width.W))
})
// instruction memory instance
val iMem = Module(new InstructionMemory(addr_width,instr_width,content))
// pc register
val pcReg = RegInit(0.U(addr_width.W))
// output of pc+1 adder
val addr_plus_1 = pcReg + 1.U
// συμπληρώστε τις συνδέσεις των σημάτων σύμφωνα με το σχήμα
}
Δοκιμάστε την ορθή λειτουργία του κυκλώματός σας με τον παρακάτω κώδικα:
val instructions = Vector(33.U,1256.U,555.U)
test(new FetchUnit(8,16,instructions)) { c =>
// test instruction output when incrementing pc
c.io.pc_sel.poke(0.U)
for (i <- 0 until instructions.length) {
c.io.instruction.expect(instructions(i))
c.clock.step()
}
// test instruction output with input branch pc
c.io.pc_sel.poke(1.U)
c.io.branch_pc.poke(1.U)
c.clock.step()
c.io.instruction.expect(instructions(1))
}
println("SUCCESS!!")
Αποθηκεύστε το σημερινό notebook για να το έχετε στο αρχείο σας (θα χρειαστεί σε επόμενα εργαστήρια).
Δεν ανήκει στα παραδοτέα του εργαστηρίου.