Πριν προχωρήσετε, εκτελέστε τα επόμενα δύο κελιά.
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._
Στην Chisel σπάνια θα χρειαστεί να περιγράψουμε τη συμπεριφορά ενός module στο χαμηλό επίπεδο των λογικών πράξεων με πύλες. Στο εργαστήριο αυτό θα δούμε πώς μπορούμε να περιγράψουμε ένα κύκλωμα μέσα από την αναμενόμενη συμπεριφορά του. Όπως και σε όλες τις άλλες περιπτώσεις, η Chisel θα κατασκευάσει μια περιγραφή κυκλώματος, η οποία όταν υλοποιηθεί με κάποια τεχνολογία θα έχει τη ζητούμενη συμπεριφορά.
Η περιγραφή ενός κυκλώματος μέσω της αναμενόμενης συμπεριφοράς του είναι η προτιμώμενη μέθοδος στην Chisel.
Ο πολυπλέκτης 4-σε-1 είναι ένα κύκλωμα με:
in
με εύρος 4 bits.sel
με εύρος 2 bits.out
με εύρος 1 bit.Ανάλογα με την τιμή της εισόδου sel
, η έξοδος out
ισούται με κάποιο από τα bits της εισόδου in
σύμφωνα με τον πίνακα:
sel | out |
---|---|
0.U | in(0) |
1.U | in(1) |
2.U | in(2) |
3.U | in(3) |
Στο παράδειγμα που ακολουθεί περιγράφουμε τον πολυπλέκτη 4-σε-1 με τρεις διαφορετικούς τρόπους:
when..elsewhen..otherwise
.switch
.Στο επόμενο κελί εμφανίζεται μια πιθανή υλοποίηση του πολυπλέκτη 4-σε-1 με πύλες (λογικές πράξεις):
Και ακολουθεί η υλοποίηση του πολυπλέκτη 4-σε-1 με λογικές πύλες στην Chisel στο επόμενο κελί (εκτελέστε το):
class Mux4to1 extends Module {
val io = IO(new Bundle {
val in = Input(UInt(4.W))
val out = Output(UInt(1.W))
val sel = Input(UInt(2.W))
})
val p0 = io.in(0) & ~io.sel(0) & ~io.sel(1)
val p1 = io.in(1) & io.sel(0) & ~io.sel(1)
val p2 = io.in(2) & ~io.sel(0) & io.sel(1)
val p3 = io.in(3) & io.sel(0) & io.sel(1)
io.out := p0 | p1 | p2 | p3
}
Στο επόμενο κελί, ο έλεγχος ορθότητας εξετάζει για κάθε συνδυασμό του io.sel
(εξωτερικό for από 0 έως 3) αν εμφανίζεται στην έξοδο η κατάλληλη είσοδος, για κάθε πιθανή τιμή εισόδου io.in
(εσωτερικό for από 0 έως 15).
Παρατηρήστε ότι χρησιμοποιούμε την υποκείμενη γλώσσα (Scala) για να αυτοματοποιήσουμε τον έλεγχο. Η μεταβλητή mask
χρησιμοποιείται για να απομονώνουμε την επιλεγμένη είσοδο io.in(s)
και να ελέγχουμε αν αυτή η είσοδος ταιριάζει με την έξοδο.
(εκτελέστε το επόμενο κελί)
test(new Mux4to1()) { c =>
for (s <- 0 to 3) {
val mask = 1 << s
c.io.sel.poke(s.U)
for (i <- 0 to 15) {
c.io.in.poke(i.U)
c.io.out.expect(if ((i&mask)!=0) 1.U else 0.U)
}
}
}
println("SUCCESS!!")
Ο ίδιος πολυπλέκτης 4-σε-1 με τη δομή when..elsewhen..otherwise
.
Παρατηρήστε ότι η ισότητα ελέγχεται με το ===
(τριπλό ίσον)!
Εκτελέστε το επόμενο κελί που επανακαθορίζει το module Mux4to1
. Στη συνέχεια τρέξτε το προηγούμενο test
για να βεβαιωθείτε ότι και αυτή η εκδοχή του πολυπλέκτη δουλεύει κατά το αναμενόμενο.
class Mux4to1 extends Module {
val io = IO(new Bundle {
val in = Input(UInt(4.W))
val out = Output(UInt(1.W))
val sel = Input(UInt(2.W))
})
when(io.sel===0.U) {
io.out := io.in(0)
}.elsewhen (io.sel===1.U) {
io.out := io.in(1)
}.elsewhen (io.sel===2.U) {
io.out := io.in(2)
}.otherwise {
io.out := io.in(3)
}
}
Μια δεύτερη δομή της Chisel για την περιγραφή συμπεριφοράς είναι το switch
.
Στο επόμενο κελί ο ίδιος πολυπλέκτης 4-σε-1 με τη δομή switch
.
Προσοχή! Η Chisel απαιτεί την αρχική τιμή (safeguard) του io.out
(ίση με 0.U) για να μπορέσει να υλοποιήσει (elaborate) το κύκλωμα!
Ελέγξτε και πάλι το αποτέλεσμα με το test
που δόθηκε προηγουμένως.
class Mux4to1 extends Module {
val io = IO(new Bundle {
val in = Input(UInt(4.W))
val out = Output(UInt(1.W))
val sel = Input(UInt(2.W))
})
io.out := 0.U // safeguard
switch(io.sel) {
is(0.U) { io.out := io.in(0) }
is(1.U) { io.out := io.in(1) }
is(2.U) { io.out := io.in(2) }
is(3.U) { io.out := io.in(3) }
}
}
Με βάση τα προηγούμενα, κατασκευάστε module Alu
με τη δομή when..elsewhen..otherwise
το οποίο θα έχει:
a
και b
εύρους 1 bit η καθεμία.out
εύρους 1 bit.s
εύρους 2 bits.Το module Alu
θα λειτουργεί σύμφωνα με τον παρακάτω πίνακα:
s(1) | s(0) | out |
---|---|---|
0 | 0 | a AND b |
0 | 1 | a OR b |
1 | 0 | a XOR b |
1 | 1 | a + b |
Χρησιμοποιήστε τους γνωστούς τελεστές λογικών πράξεων της Chisel και τον τελεστή πρόσθεσης +
(θυμηθείτε ότι η Chisel δεν κάνει πρόσθεση αλλά δημιουργεί περιγραφή κυκλώματος αθροιστή) σύμφωνα με τον πίνακα:
Τελεστής | Πράξη μεταξύ bits |
---|---|
& | AND |
| | OR |
^ | XOR |
+ | πρόσθεση (χωρίς τελικό κρατούμενο) |
class Alu extends Module {
val io = IO(new Bundle {
val a = // ..συμπληρώστε..
val b = // ..συμπληρώστε..
val out = // ..συμπληρώστε..
val s = // ..συμπληρώστε..
})
when // ..συμπληρώστε..
}
Ελέγξτε την ορθότητα της υλοποίησής σας εκτελώντας το επόμενο κελί:
test(new Alu()) { c =>
for (aval <- 0 to 1) { // για τις 2 πιθανές τιμές του io.a
c.io.a.poke(aval.U)
for (bval <- 0 to 1) { // για τις 2 πιθανές τιμές του io.b
c.io.b.poke(bval.U)
// έλεγχος εξόδων κάνοντας τις αντίστοιχες πράξεις με τη Scala
c.io.s.poke(0.U) // AND
c.io.out.expect((aval & bval).U)
c.io.s.poke(1.U) // OR
c.io.out.expect((aval | bval).U)
c.io.s.poke(2.U) // XOR
c.io.out.expect((aval ^ bval).U)
c.io.s.poke(3.U) // +
val result = (aval+bval) & 0x1 // αφαίρεση πιθανού κρατουμένου από το αποτέλεσμα της Scala
c.io.out.expect(result.U)
}
}
}
println("SUCCESS!!")
Περιγράψτε στη συνέχεια το ίδιο module Alu
με τη βοήθεια της δομής switch
:
class Alu extends Module {
val io = IO(new Bundle {
val a = // ..συμπληρώστε..
val b = // ..συμπληρώστε..
val out = // ..συμπληρώστε..
val s = // ..συμπληρώστε..
})
io.out := 0.U // safeguard
switch // ..συμπληρώστε..
}
Δοκιμάστε με το προηγούμενο test
την ορθότητα και αυτού του κυκλώματος.
Μπορείτε να κατεβάσετε και να πάρετε μαζί σας το σημερινό notebook για να το έχετε στο αρχείο σας.
Δεν ανήκει στα παραδοτέα του εργαστηρίου.