
Dengan perkembangan mikroelektronika, desain RTL menjadi semakin banyak. Penggunaan kembali kode Verilog banyak ketidaknyamanan, bahkan dengan menghasilkan, makro dan chip sistem Verilog. Pahat, bagaimanapun, memungkinkan untuk menerapkan kekuatan penuh objek dan pemrograman fungsional untuk pengembangan rtl, yang merupakan langkah yang telah lama ditunggu yang dapat mengisi paru-paru pengembang ASIC dan FPGA dengan udara segar.
Artikel ini akan memberikan tinjauan singkat tentang fungsi utama dan mempertimbangkan beberapa kasus penggunaan pengguna, kami juga akan berbicara tentang kekurangan bahasa ini. Di masa depan, jika topiknya menarik, kami melanjutkan artikel dalam tutorial yang lebih rinci.
Persyaratan sistem
- tingkat dasar scala
- Verilog dan prinsip-prinsip dasar membangun desain digital.
- simpan dokumentasi pahat agar mudah digunakan
Saya akan mencoba memahami dasar-dasar pahat menggunakan contoh sederhana, tetapi jika ada sesuatu yang tidak jelas, Anda dapat melihatnya di sini .
Sedangkan untuk scala, lembar contekan ini dapat membantu untuk menyelam cepat.
Ada yang serupa untuk pahat .
Kode artikel lengkap (dalam bentuk proyek scala sbt) dapat ditemukan di sini .
Penghitung sederhana
Sesuai namanya, pahat 'Membangun Perangkat Keras Dalam scala Embedded Language' adalah bahasa deskripsi perangkat keras yang dibangun di atas scala.
Secara singkat tentang bagaimana semuanya bekerja, maka: grafik perangkat keras dibangun dari deskripsi rtl pada pahat, yang, pada gilirannya, berubah menjadi deskripsi perantara dalam bahasa firrtl, dan setelah itu interpreter dihasilkan dari firrtl Verilog setelah backend bawaan.
Mari kita lihat dua implementasi dari penghitung sederhana.
Verilog:
module SimpleCounter #( parameter WIDTH = 8 )( input clk, input reset, input wire enable, output wire [WIDTH-1:0] out ); reg [WIDTH-1:0] counter; assign out = counter; always @(posedge clk) if (reset) begin counter <= {(WIDTH){1'b0}}; end else if (enable) begin counter <= counter + 1; end endmodule
pahat:
class SimpleCounter(width: Int = 32) extends Module { val io = IO(new Bundle { val enable = Input(Bool()) val out = Output(UInt(width.W)) }) val counter = RegInit(0.U(width.W)) io.out <> counter when(io.enable) { counter := counter + 1.U } }
Sedikit tentang pahat:
Module
- container untuk deskripsi modul rtlBundle
adalah struktur data dalam pahat, terutama digunakan untuk mendefinisikan antarmuka.io
- variabel untuk menentukan portBool
- tipe data, sinyal bit tunggal sederhanaUInt(width: Width)
- bilangan bulat tidak bertanda, konstruktor menerima kedalaman bit sinyal sebagai input.RegInit[T <: Data](init: T)
adalah konstruktor register, dibutuhkan nilai reset pada input dan memiliki tipe data yang sama.<>
- operator koneksi sinyal universalwhen(cond: => Bool) { /*...*/ }
- analog if
di Verilog
Kita akan membicarakan Verilog mana yang menghasilkan pahat sedikit kemudian. Sekarang bandingkan saja kedua desain ini. Seperti yang Anda lihat, tidak ada sinyal clk
dan reset
di pahat. Faktanya adalah pahat menambahkan sinyal-sinyal ini ke modul secara default. Nilai reset untuk register counter
RegInit
konstruktor register dengan reset RegInit
. Pahat memiliki dukungan untuk modul dengan banyak sinyal clock, tetapi tentang hal itu nanti.
Penghitungnya sedikit lebih rumit
Mari kita melangkah lebih jauh dan menyulitkan tugas sedikit, misalnya - kita akan membuat penghitung multi-saluran dengan parameter input dalam bentuk urutan bit untuk setiap saluran.
Mari kita mulai sekarang dengan versi pahat
class MultiChannelCounter(width: Seq[Int] = Seq(32, 16, 8, 4)) extends Module { val io = IO(new Bundle { val enable = Input(Vec(width.length, Bool())) val out = Output(UInt(width.sum.W)) def getOut(i: Int): UInt = { val right = width.dropRight(width.length - i).sum this.out(right + width(i) - 1, right) } }) val counters: Seq[SimpleCounter] = width.map(x => Module(new SimpleCounter(x)) ) io.out <> util.Cat(counters.map(_.io.out)) width.indices.foreach { i => counters(i).io.enable <> io.enable(i) } }
Sedikit tentang scala:
width: Seq[Int]
- parameter input untuk konstruktor kelas MultiChannelCounter
, memiliki tipe Seq[Int]
- urutan dengan elemen integer.Seq
adalah salah satu jenis koleksi dalam skala dengan urutan elemen yang terdefinisi dengan baik..map
adalah fungsi yang umum untuk koleksi untuk semua orang, yang mampu mengonversi satu koleksi ke koleksi lain karena operasi yang sama pada setiap elemen, dalam kasus kami, urutan nilai integer berubah menjadi urutan SimpleCounter
dengan kedalaman bit yang sesuai.
Sedikit tentang pahat:
Vec[T <: Data](gen: T, n: Int): Vec[T]
- tipe data pahat, adalah analog dari array.Module[T <: BaseModule](bc: => T): T
adalah metode pembungkus yang diperlukan untuk modul yang mungkin tersedia.util.Cat[T <: Bits](r: Seq[T]): UInt
- fungsi gabungan, analog {1'b1, 2'b01, 4'h0}
di Verilog
Perhatikan porta:
enable
- sudah dikerahkan di Vec[Bool]
*, secara kasar, ke dalam array sinyal satu-bit, satu untuk setiap saluran, dimungkinkan untuk membuat UInt(width.length.W)
.
out
- diperluas ke jumlah lebar semua saluran kami.
counters
variabel adalah array dari penghitung kami. Kami menghubungkan sinyal util.Cat
dari setiap penghitung ke port input yang sesuai, dan menggabungkan semua sinyal menjadi satu menggunakan fungsi util.Cat
dan meneruskannya ke output.
Kami juga mencatat fungsi getOut(i: Int)
- fungsi ini menghitung dan mengembalikan kisaran bit dalam sinyal out
untuk saluran i
. Ini akan sangat berguna dalam pekerjaan lebih lanjut dengan penghitung seperti itu. Menerapkan sesuatu seperti ini di Verilog tidak akan berhasil
* Vec
tidak harus bingung dengan Vector
, yang pertama adalah array data di pahat, yang kedua adalah koleksi di scala.
Sekarang mari kita coba menulis modul ini di Verilog, untuk kenyamanan, bahkan di systemVerilog.
Setelah duduk berpikir, saya sampai pada opsi ini (kemungkinan besar itu bukan satu-satunya yang benar dan paling optimal, tetapi Anda selalu dapat menyarankan implementasi Anda di komentar).
Verilog module MultiChannelCounter #( parameter TOTAL = 4, parameter integer WIDTH_SEQ [TOTAL] = {32, 16, 8, 4} )(clk, reset, enable, out); localparam OUT_WIDTH = get_sum(TOTAL, WIDTH_SEQ); input clk; input reset; input wire [TOTAL - 1 : 0] enable; output wire [OUT_WIDTH - 1 :0] out; genvar j; generate for(j = 0; j < TOTAL; j = j + 1) begin : counter_generation localparam OUT_INDEX = get_sum(j, WIDTH_SEQ); SimpleCounter #( WIDTH_SEQ[j] ) SimpleCounter_unit ( .clk(clk), .reset(reset), .enable(enable[j]), .out(out[OUT_INDEX + WIDTH_SEQ[j] - 1: OUT_INDEX]) ); end endgenerate function automatic integer get_sum; input integer array_width; input integer array [TOTAL]; integer counter = 0; integer i; begin for(i = 0; i < array_width; i = i + 1) counter = counter + array[i]; get_sum = counter; end endfunction endmodule
Itu sudah terlihat jauh lebih mengesankan. Tetapi bagaimana jika, kita melangkah lebih jauh dan mengacaukan antarmuka wishbone yang populer dengan akses register.
Antarmuka bundel
Wishbone adalah bus kecil yang mirip dengan AMBA APB, terutama digunakan untuk ip core open source.
Lebih detail tentang wiki: https://ru.wikipedia.org/wiki/Wishbone
Karena pahat memberi kami wadah data tipe Bundle
, masuk akal untuk membungkus bus dalam wadah yang nantinya dapat digunakan dalam proyek pahat apa pun.
class wishboneMasterSignals( addrWidth: Int = 32, dataWidth: Int = 32, gotTag: Boolean = false) extends Bundle { val adr = Output(UInt(addrWidth.W)) val dat_master = Output(UInt(dataWidth.W)) val dat_slave = Input(UInt(dataWidth.W)) val stb = Output(Bool()) val we = Output(Bool()) val cyc = Output(Bool()) val sel = Output(UInt((dataWidth / 8).W)) val ack_master = Output(Bool()) val ack_slave = Input(Bool()) val tag_master: Option[UInt] = if(gotTag) Some(Output(Bool())) else None val tag_slave: Option[UInt] = if(gotTag) Some(Input(Bool())) else None def wbTransaction: Bool = cyc && stb def wbWrite: Bool = wbTransaction && we def wbRead: Bool = wbTransaction && !we override def cloneType: wishboneMasterSignals.this.type = new wishboneMasterSignals(addrWidth, dataWidth, gotTag).asInstanceOf[this.type] }
Sedikit tentang scala:
Option
- pembungkus data opsional dalam scala yang dapat berupa elemen atau None
, Option[UInt]
adalah Some(UInt(/*...*/))
atau None
, berguna saat parameterisasi sinyal.
Sepertinya tidak ada yang aneh. Hanya deskripsi antarmuka oleh penyihir, dengan pengecualian beberapa sinyal dan metode:
tag_master
dan tag_slave
adalah sinyal tujuan umum opsional dalam protokol wishbone, kita akan melihatnya jika parameter gotTag
true
.
wbTransaction
, wbWrite
, wbRead
- berfungsi untuk mempermudah bekerja dengan bus.
cloneType
- metode kloning tipe yang diperlukan untuk semua kelas [T <: Bundle]
diparameterisasi
Tetapi kita juga membutuhkan antarmuka slave, mari kita lihat bagaimana ini dapat diimplementasikan.
class wishboneSlave( addrWidth: Int = 32, dataWidth: Int = 32, tagWidht: Int = 0) extends Bundle { val wb = Flipped(new wishboneMasterSignals(addrWidth , dataWidth, tagWidht)) override def cloneType: wishboneSlave.this.type = new wishboneSlave(addrWidth, dataWidth, tagWidht).asInstanceOf[this.type] }
Metode membalik, seperti yang Anda tebak dari namanya, membalik antarmuka, dan sekarang antarmuka penyihir kami telah berubah menjadi budak, kami menambahkan kelas yang sama untuk penyihir.
class wishboneMaster( addrWidth: Int = 32, dataWidth: Int = 32, tagWidht: Int = 0) extends Bundle { val wb = new wishboneMasterSignals(addrWidth , dataWidth, tagWidht) override def cloneType: wishboneMaster.this.type = new wishboneMaster(addrWidth, dataWidth, tagWidht).asInstanceOf[this.type] }
Nah, itu saja, antarmuka sudah siap. Tetapi sebelum menulis sebuah handler, mari kita lihat bagaimana kita dapat menggunakan antarmuka ini jika kita perlu beralih atau sesuatu dengan sejumlah besar antarmuka wishbone.
class WishboneCrossbarIo(n: Int, addrWidth: Int, dataWidth: Int) extends Bundle { val slaves = Vec(n, new wishboneSlave(addrWidth, dataWidth, 0)) val master = new wishboneMaster(addrWidth, dataWidth, 0) } class WBCrossBar extends Module { val io = IO(new WishboneCrossbarIo(1, 32, 32)) io.master <> io.slaves(0)
Ini kosong kecil untuk sakelar. Lebih mudah untuk mendeklarasikan antarmuka bertipe Vec[wishboneSlave]
, dan Anda dapat menghubungkan antarmuka dengan operator <>
sama. Chip pahat yang berguna dalam mengelola sejumlah besar sinyal.
Pengontrol bus universal
Seperti yang disebutkan sebelumnya tentang kekuatan pemrograman fungsional dan objek, kami akan mencoba menerapkannya. Selanjutnya kita akan berbicara tentang implementasi pengontrol bus wishbone universal dalam bentuk trait
, ini akan menjadi semacam mixin untuk setiap modul dengan bus wishboneSlave
, untuk modul Anda hanya perlu mendefinisikan kartu memori dan mencampur pengontrol trait
untuk itu selama generasi.
Implementasi
Bagi yang masih antusiasMari beralih ke implementasi handler. Ini akan menjadi sederhana dan segera menanggapi transaksi tunggal, dalam kasus jatuh dari kumpulan alamat, mengembalikan nol.
Mari kita analisis dalam beberapa bagian:
setiap transaksi perlu dijawab dengan pengakuan
val io : wishboneSlave = val wb_ack = RegInit(false.B) when(io.wb.wbTransaction) { wb_ack := true.B }.otherwise { wb_ack := false.B } wb_ack <> io.wb.ack_slave
- Kami merespons membaca dengan data
val wb_dat = RegInit(0.U(io.wb.dat_slave.getWidth.W))
MuxCase[T <: Data] (default: T, mapping: Seq[(Bool, T)]): T
adalah skema koordinasi MuxCase[T <: Data] (default: T, mapping: Seq[(Bool, T)]): T
tipe case
dalam Verilog *.
Seperti apa bentuknya di Verilog:
always @(posedge clock) if(reset) wb_dat_o <= 0; else if(wb_read) case (wb_adr_i) `ADDR_1 : wb_dat_o <= data_1; `ADDR_2 : wb_dat_o <= data_2; `ADDR_3 : wb_dat_o <= data_3; default : wb_dat_o <= 0; endcase }
* Secara umum, dalam hal ini, ini adalah hack kecil demi parameterisasi, di pahat ada desain standar yang lebih baik untuk digunakan jika, menulis sesuatu yang lebih sederhana.
switch(x) { is(value1) {
Nah, catatannya
when(io.wb.wbWrite) { data_4 := Mux(io.wb.addr === ADDR_4, io.wb.dat_master, data_4) }
Mux[T <: Data](cond: Bool, con: T, alt: T): T
- multiplexer biasa
Kami menyematkan sesuatu yang mirip dengan konter multichannel kami, menutup register untuk manajemen saluran dan topi. Tapi di sini sudah dekat dengan pengontrol bus WB universal tempat kami akan mentransfer kartu memori semacam ini:
val readMemMap = Map( ADDR_1 -> DATA_1, ADDR_2 -> DATA_2 ) val writeMemMap = Map( ADDR_1 -> DATA_1, ADDR_2 -> DATA_2 )
Untuk tugas seperti itu, trait
akan membantu kita - sesuatu seperti mixin di Sala. Tugas utama adalah membuat readMemMap: [Int, Data]
terlihat seperti Seq( -> )
, dan akan lebih baik jika Anda dapat mentransfer alamat dasar dan larik data di dalam kartu memori
val readMemMap = Map( ADDR_1_BASE -> DATA_SEQ, ADDR_2 -> DATA_2 )
Apa yang akan diperluas menjadi sesuatu yang serupa, di mana WB_DAT_WIDTH adalah lebar data dalam byte
val readMemMap = Map( ADDR_1_BASE + 0 * (WB_DAT_WIDHT)-> DATA_SEQ_0, ADDR_1_BASE + 1 * (WB_DAT_WIDHT)-> DATA_SEQ_1, ADDR_1_BASE + 2 * (WB_DAT_WIDHT)-> DATA_SEQ_2, ADDR_1_BASE + 3 * (WB_DAT_WIDHT)-> DATA_SEQ_3 ADDR_2 -> DATA_2 )
Untuk mengimplementasikan ini, kami menulis fungsi konverter dari Map[Int, Any]
ke Seq[(Bool, UInt)]
. Anda harus menggunakan matematika pola scala.
def parseMemMap(memMap: Map[Int, Any]): Seq[(Bool, UInt)] = memMap.flatMap { case(addr, data) => data match { case a: UInt => Seq((io.wb.adr === addr.U) -> a) case a: Seq[UInt] => a.map(x => (io.wb.adr === (addr + io.wb.dat_slave.getWidth / 8).U) -> x) case _ => throw new Exception("WRONG MEM MAP!!!") } }.toSeq
Akhirnya, sifat kita akan terlihat seperti ini:
trait wishboneSlaveDriver { val io : wishboneSlave val readMemMap: Map[Int, Any] val writeMemMap: Map[Int, Any] val parsedReadMap: Seq[(Bool, UInt)] = parseMemMap(readMemMap) val parsedWriteMap: Seq[(Bool, UInt)] = parseMemMap(writeMemMap) val wb_ack = RegInit(false.B) val wb_dat = RegInit(0.U(io.wb.dat_slave.getWidth.W)) when(io.wb.wbTransaction) { wb_ack := true.B }.otherwise { wb_ack := false.B } when(io.wb.wbRead) { wb_dat := MuxCase(default = 0.U, parsedReadMap) } when(io.wb.wbWrite) { parsedWriteMap.foreach { case(addrMatched, data) => data := Mux(addrMatched, io.wb.dat_master, data) } } wb_dat <> io.wb.dat_slave wb_ack <> io.wb.ack_slave def parseMemMap(memMap: Map[Int, Any]): Seq[(Bool, UInt)] = { } }
Sedikit tentang scala:
io , readMemMap, writeMemMap
adalah bidang abstrak dari trait
kita, yang harus didefinisikan dalam kelas di mana kita akan mencampurnya.
Bagaimana cara menggunakannya
Untuk mencampurkan trait
kita dengan modul, beberapa kondisi harus dipenuhi:
io
harus mewarisi dari kelas wishboneSlave
- perlu mendeklarasikan dua kartu memori
readMemMap
dan writeMemMap
class WishboneMultiChannelCounter extends Module { val BASE = 0x11A00000 val OUT = 0x00000100 val S_EN = 0x00000200 val H_EN = 0x00000300 val wbAddrWidth = 32 val wbDataWidth = 32 val wbTagWidth = 0 val width = Seq(32, 16, 8, 4) val io = IO(new wishboneSlave(wbAddrWidth, wbDataWidth, wbTagWidth) { val hardwareEnable: Vec[Bool] = Input(Vec(width.length, Bool())) }) val counter = Module(new MultiChannelCounter(width)) val softwareEnable = RegInit(0.U(width.length.W)) width.indices.foreach(i => counter.io.enable(i) := io.hardwareEnable(i) && softwareEnable(i)) val readMemMap = Map( BASE + OUT -> width.indices.map(counter.io.getOut), BASE + S_EN -> softwareEnable, BASE + H_EN -> io.hardwareEnable.asUInt ) val writeMemMap = Map( BASE + S_EN -> softwareEnable ) }
Kami membuat register softwareEnable
, ditambahkan ke 'dan' oleh sinyal input hardwareEnable
dan pergi untuk mengaktifkan counter[MultiChannelCounter]
.
Kami mendeklarasikan dua kartu memori untuk membaca dan menulis: readMemMap
writeMemMap
, untuk detail lebih lanjut tentang struktur, lihat bab di atas.
Dalam kartu memori baca, kami mentransfer nilai penghitung setiap saluran *, softwareEnable
, dan hardwareEnable
. Dan sebagai catatan kami hanya memberikan daftar softwareEnable
.
* width.indices.map(counter.io.getOut)
- desain yang aneh, kami akan menganalisisnya dalam beberapa bagian.
width.indices
- akan mengembalikan array dengan indeks elemen, mis. jika width.length == 4
maka width.indices = {0, 1, 2, 3}
{0, 1, 2, 3}.map(counter.io.getOut)
- memberikan sesuatu seperti ini:
{ counter.io.getOut(0), counter.io.getOut(1), /*...*/ }
Sekarang untuk setiap modul pada pahat dengan kita dapat mendeklarasikan kartu memori untuk membaca dan menulis dan cukup menghubungkan pengontrol bus wishbone universal kami saat membuat, sesuatu seperti ini:
class wishbone_multicahnnel_counter extends WishboneMultiChannelCounter with wishboneSlaveDriver object countersDriver extends App { Driver.execute(Array("-td", "./src/generated"), () => new wishbone_multicahnnel_counter ) }
wishboneSlaveDriver
- ini persis campuran sifat yang kami jelaskan di bawah spoiler.
Tentu saja, versi pengontrol universal ini jauh dari final, tetapi sebaliknya kasar. Tujuan utamanya adalah untuk menunjukkan salah satu pendekatan yang mungkin untuk mengembangkan rtl pada pahat. Dengan semua kemampuan scala, pendekatan seperti itu bisa jauh lebih besar, sehingga setiap pengembang memiliki bidang kreativitas masing-masing. Benar, tidak ada tempat untuk terinspirasi, kecuali:
- pustaka utils library asli, tentang yang sedikit lebih jauh, di sana Anda dapat melihat warisan modul dan antarmuka
- https://github.com/freechipsproject/rocket-chip - risc-v seluruh kernel diimplementasikan pada pahat, asalkan Anda tahu scala dengan sangat baik, untuk pemula tanpa setengah liter, seperti kata mereka, Anda akan membutuhkan waktu yang sangat lama untuk memahaminya. tidak ada dokumentasi resmi tentang struktur internal proyek.
MultiClockDomain
Bagaimana jika kita ingin mengontrol jam secara manual dan mengatur ulang sinyal di pahat. Sampai saat ini, ini tidak dapat dilakukan, tetapi dengan salah satu rilis terbaru, dukungan withClock {}
, withReset {}
dan withClockAndReset {}
. Mari kita lihat sebuah contoh:
class DoubleClockModule extends Module { val io = IO(new Bundle { val clockB = Input(Clock()) val in = Input(Bool()) val out = Output(Bool()) val outB = Output(Bool()) }) val regClock = RegNext(io.in, false.B) regClock <> io.out val regClockB = withClock(io.clockB) { RegNext(io.in, false.B) } regClockB <> io.outB }
regClock
- register yang akan di-clock oleh sinyal clock
standar dan reset oleh reset
standarregClockB
- register yang sama diberi clock, Anda dapat menebaknya, dengan sinyal io.clockB
, tetapi reset standar akan digunakan.
Jika kita ingin menghapus clock
standar dan reset
sinyal sepenuhnya, maka kita dapat menggunakan fitur eksperimental - RawModule
(modul tanpa jam standar dan sinyal reset, semua orang harus dikendalikan secara manual). Contoh:
class MultiClockModule extends RawModule { val io = IO(new Bundle { val clockA = Input(Clock()) val clockB = Input(Clock()) val resetA = Input(Bool()) val resetB = Input(Bool()) val in = Input(Bool()) val outA = Output(Bool()) val outB = Output(Bool()) }) val regClockA = withClockAndReset(io.clockA, io.resetA) { RegNext(io.in, false.B) } regClockA <> io.outA val regClockB = withClockAndReset (io.clockB, io.resetB) { RegNext(io.in, false.B) } regClockB <> io.outB }
Utilitas perpustakaan
Bonus pahat yang menyenangkan tidak hanya sampai di situ. Pembuatnya bekerja keras dan menulis perpustakaan kecil tapi sangat berguna untuk fungsi, antarmuka, modul, kecil. Anehnya, tidak ada deskripsi perpustakaan di wiki, tetapi Anda dapat melihat tautan lembar contekan yang pada awalnya (ada dua bagian terakhir)
Antarmuka:
DecoupledIO
adalah antarmuka siap / valid yang umum digunakan.
DecoupledIO(UInt(32.W))
- akan mengandung sinyal:
val ready = Input(Bool())
val valid = Output(Bool())
val data = Output(UInt(32.W))
ValidIO
- sama seperti DecoupledIO
saja tanpa ready
Modul:
Queue
- modul FIFO sinkron adalah hal yang sangat berguna. Antarmuka terlihat seperti
val enq: DecoupledIO[T]
- DecoupledIO
terbalik
val deq: DecoupledIO[T]
- DecoupledIO
biasa
val count: UInt
- jumlah data dalam antrian- Modul
Pipe
- tunda, memasukkan jumlah irisan register yang ke-n Arbiter
- arbiter pada antarmuka DecoupledIO
, memiliki banyak subspesies yang berbeda dalam jenis arbitrase
val in: Vec[DecoupledIO[T]]
- array antarmuka input
val out: DecoupledIO[T]
val chosen: UInt
- menampilkan saluran yang dipilih
Sejauh yang dapat Anda pahami dari diskusi tentang github - dalam rencana global terdapat ekstensi signifikan modul perpustakaan ini: seperti FIFO asinkron, LSFSR, pembagi frekuensi, templat PLL untuk FPGA; berbagai antarmuka; pengendali untuk mereka dan banyak lagi.
Pahat tes-io
Kemungkinan pengujian di pahat harus disebutkan, saat ini ada dua cara untuk menguji ini:
peekPokeTesters
- tes simulasi murni yang menguji logika desain AndahardwareIOTeseters
sudah lebih menarik sejak itu dengan pendekatan ini Anda akan mendapatkan bangku teset yang dihasilkan dengan tes yang Anda tulis di pahat, dan bahkan jika Anda memiliki verilator, Anda bahkan akan mendapatkan timeline.
Namun sejauh ini, pendekatan untuk pengujian belum selesai, dan diskusi masih berlangsung. Di masa depan, kemungkinan besar alat universal akan muncul, untuk pengujian dan pengujian juga dimungkinkan untuk menulis di pahat. Tetapi untuk sekarang, Anda dapat melihat apa yang sudah ada di sana dan bagaimana menggunakannya di sini .
Kekurangan pahat
Ini bukan untuk mengatakan bahwa pahat adalah alat universal, dan bahwa setiap orang harus beralih ke pahat itu. Dia, seperti, mungkin, semua proyek pada tahap pengembangan, memiliki kekurangannya, yang layak disebut demi kelengkapan.
Kelemahan pertama dan mungkin yang paling penting adalah kurangnya flush asynchronous. Cukup berat, tetapi bisa diselesaikan dengan beberapa cara, dan salah satunya adalah skrip di atas Verilog, yang mengubah pengaturan ulang sinkron menjadi asinkron. Ini mudah dilakukan karena semua konstruksi di Verilog yang dihasilkan dengan always
cukup seragam.
Kelemahan kedua, menurut banyak orang, adalah ketidak terbaca dari Verilog yang dihasilkan dan, sebagai konsekuensinya, komplikasi dari debugging. Tapi mari kita lihat kode yang dihasilkan dari contoh dengan penghitung sederhana
Verilog yang dihasilkan `ifdef RANDOMIZE_GARBAGE_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_INVALID_ASSIGN `define RANDOMIZE `endif `ifdef RANDOMIZE_REG_INIT `define RANDOMIZE `endif `ifdef RANDOMIZE_MEM_INIT `define RANDOMIZE `endif module SimpleCounter( input clock, input reset, input io_enable, output [7:0] io_out ); reg [7:0] counter; reg [31:0] _RAND_0; wire [8:0] _T_7; wire [7:0] _T_8; wire [7:0] _GEN_0; assign _T_7 = counter + 8'h1; assign _T_8 = _T_7[7:0]; assign _GEN_0 = io_enable ? _T_8 : counter; assign io_out = counter; `ifdef RANDOMIZE integer initvar; initial begin `ifndef verilator #0.002 begin end `endif `ifdef RANDOMIZE_REG_INIT _RAND_0 = {1{$random}}; counter = _RAND_0[7:0]; `endif // RANDOMIZE_REG_INIT end `endif // RANDOMIZE always @(posedge clock) begin if (reset) begin counter <= 8'h0; end else begin if (io_enable) begin counter <= _T_8; end end end endmodule
Sekilas, Verilog yang dihasilkan dapat mendorong, bahkan dalam desain berukuran sedang, tapi mari kita lihat.
- RANDOMIZE mendefinisikan - (mungkin berguna ketika pengujian dengan pahat-penguji) - umumnya tidak berguna, tetapi mereka tidak terlalu mengganggu
- Seperti yang kita lihat nama port kita, dan register dipertahankan
- _GEN_0 adalah variabel yang tidak berguna bagi kami, tetapi perlu firrtl ke interpreter untuk menghasilkan Verilog. Kami juga tidak memperhatikannya.
- Masih ada _T_7 dan _T_8, semua logika kombinasional dalam Verilog yang dihasilkan akan disajikan langkah demi langkah dalam bentuk variabel _T.
Yang paling penting, semua port, register, kabel yang diperlukan untuk debugging menjaga nama mereka dari pahat. Dan jika Anda melihat tidak hanya pada Verilog tetapi juga pada pahat, maka segera proses debugging akan semudah dengan Verilog murni.
Kesimpulan
Dalam realitas modern, pengembangan RTL, baik asic atau fpga di luar lingkungan akademik, telah lama berubah dari hanya menggunakan kode Verilog tulisan tangan murni menjadi satu atau beberapa jenis skrip generasi, baik itu skrip tcl kecil atau seluruh IDE dengan banyak fitur.
Pahat, pada gilirannya, adalah pengembangan logis dari bahasa untuk pengembangan dan pengujian logika digital. Misalkan pada tahap ini ia jauh dari sempurna, tetapi sudah mampu memberikan peluang yang bisa Anda hadapi dengan kekurangannya. , .