diff --git a/build.sbt b/build.sbt index d0b1bbe..87ec4f9 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ ThisBuild / scalaVersion := "2.13.12" ThisBuild / version := "0.1.0" -ThisBuild / organization := "com.github.hyperdbg" +ThisBuild / organization := "org.hyperdbg" val chiselVersion = "6.0.0" diff --git a/generated/Buffer.sv b/generated/Buffer.sv new file mode 100644 index 0000000..893768a --- /dev/null +++ b/generated/Buffer.sv @@ -0,0 +1,33 @@ +// Generated by CIRCT firtool-1.62.0 +module Buffer( + input clock, + reset, + output io_in_ready, + input io_in_valid, + input [7:0] io_in_bits, + input io_out_ready, + output io_out_valid, + output [7:0] io_out_bits +); + + reg stateReg; + reg [7:0] dataReg; + always @(posedge clock) begin + if (reset) begin + stateReg <= 1'h0; + dataReg <= 8'h0; + end + else begin + if (stateReg) + stateReg <= ~io_out_ready & stateReg; + else + stateReg <= io_in_valid | stateReg; + if (~stateReg & io_in_valid) + dataReg <= io_in_bits; + end + end // always @(posedge) + assign io_in_ready = ~stateReg; + assign io_out_valid = stateReg; + assign io_out_bits = dataReg; +endmodule + diff --git a/generated/BufferedTx.sv b/generated/BufferedTx.sv new file mode 100644 index 0000000..b980704 --- /dev/null +++ b/generated/BufferedTx.sv @@ -0,0 +1,33 @@ +// Generated by CIRCT firtool-1.62.0 +module BufferedTx( + input clock, + reset, + output io_txd, + io_channel_ready, + input io_channel_valid, + input [7:0] io_channel_bits +); + + wire _buf_io_out_valid; + wire [7:0] _buf_io_out_bits; + wire _tx_io_channel_ready; + Tx tx ( + .clock (clock), + .reset (reset), + .io_txd (io_txd), + .io_channel_ready (_tx_io_channel_ready), + .io_channel_valid (_buf_io_out_valid), + .io_channel_bits (_buf_io_out_bits) + ); + Buffer buf_0 ( + .clock (clock), + .reset (reset), + .io_in_ready (io_channel_ready), + .io_in_valid (io_channel_valid), + .io_in_bits (io_channel_bits), + .io_out_ready (_tx_io_channel_ready), + .io_out_valid (_buf_io_out_valid), + .io_out_bits (_buf_io_out_bits) + ); +endmodule + diff --git a/generated/Sender.sv b/generated/Sender.sv new file mode 100644 index 0000000..736f9ea --- /dev/null +++ b/generated/Sender.sv @@ -0,0 +1,43 @@ +// Generated by CIRCT firtool-1.62.0 +module Sender( + input clock, + reset, + output io_txd +); + + wire _tx_io_channel_ready; + wire [15:0][6:0] _GEN = + '{7'h48, + 7'h48, + 7'h48, + 7'h48, + 7'h21, + 7'h64, + 7'h6C, + 7'h72, + 7'h6F, + 7'h57, + 7'h20, + 7'h6F, + 7'h6C, + 7'h6C, + 7'h65, + 7'h48}; + reg [7:0] cntReg; + wire _tx_io_channel_valid_T = cntReg != 8'hC; + always @(posedge clock) begin + if (reset) + cntReg <= 8'h0; + else if (_tx_io_channel_ready & _tx_io_channel_valid_T) + cntReg <= cntReg + 8'h1; + end // always @(posedge) + BufferedTx tx ( + .clock (clock), + .reset (reset), + .io_txd (io_txd), + .io_channel_ready (_tx_io_channel_ready), + .io_channel_valid (_tx_io_channel_valid_T), + .io_channel_bits ({1'h0, _GEN[cntReg[3:0]]}) + ); +endmodule + diff --git a/generated/Tx.sv b/generated/Tx.sv new file mode 100644 index 0000000..fe73d14 --- /dev/null +++ b/generated/Tx.sv @@ -0,0 +1,39 @@ +// Generated by CIRCT firtool-1.62.0 +module Tx( + input clock, + reset, + output io_txd, + io_channel_ready, + input io_channel_valid, + input [7:0] io_channel_bits +); + + reg [10:0] shiftReg; + reg [19:0] cntReg; + reg [3:0] bitsReg; + wire _io_channel_ready_T = cntReg == 20'h0; + always @(posedge clock) begin + if (reset) begin + shiftReg <= 11'h7FF; + cntReg <= 20'h0; + bitsReg <= 4'h0; + end + else if (_io_channel_ready_T) begin + if (|bitsReg) begin + shiftReg <= {1'h1, shiftReg[10:1]}; + bitsReg <= bitsReg - 4'h1; + end + else begin + shiftReg <= io_channel_valid ? {2'h3, io_channel_bits, 1'h0} : 11'h7FF; + if (io_channel_valid) + bitsReg <= 4'hB; + end + cntReg <= 20'h1B1; + end + else + cntReg <= cntReg - 20'h1; + end // always @(posedge) + assign io_txd = shiftReg[0]; + assign io_channel_ready = _io_channel_ready_T & ~(|bitsReg); +endmodule + diff --git a/generated/UartMain.sv b/generated/UartMain.sv new file mode 100644 index 0000000..412de5a --- /dev/null +++ b/generated/UartMain.sv @@ -0,0 +1,15 @@ +// Generated by CIRCT firtool-1.62.0 +module UartMain( + input clock, + reset, + io_rxd, + output io_txd +); + + Sender s ( + .clock (clock), + .reset (reset), + .io_txd (io_txd) + ); +endmodule + diff --git a/src/main/scala/hwdbg/lib/uart/Uart.scala b/src/main/scala/hwdbg/lib/uart/Uart.scala new file mode 100644 index 0000000..b0b8029 --- /dev/null +++ b/src/main/scala/hwdbg/lib/uart/Uart.scala @@ -0,0 +1,222 @@ +/* + * + * A UART is a serial port, also called an RS232 interface. + * + * Author: Martin Schoeberl (martin@jopdesign.com) + * + */ +package hwdbg.lib.uart + +import chisel3._ +import circt.stage.ChiselStage +import chisel3.util._ + +class UartIO extends DecoupledIO(UInt(8.W)) + +/** + * Transmit part of the UART. + * A minimal version without any additional buffering. + * Use a ready/valid handshaking. + */ +class Tx(frequency: Int, baudRate: Int) extends Module { + val io = IO(new Bundle { + val txd = Output(UInt(1.W)) + val channel = Flipped(new UartIO()) + }) + + val BIT_CNT = ((frequency + baudRate / 2) / baudRate - 1).asUInt + + val shiftReg = RegInit(0x7ff.U) + val cntReg = RegInit(0.U(20.W)) + val bitsReg = RegInit(0.U(4.W)) + + io.channel.ready := (cntReg === 0.U) && (bitsReg === 0.U) + io.txd := shiftReg(0) + + when(cntReg === 0.U) { + + cntReg := BIT_CNT + when(bitsReg =/= 0.U) { + val shift = shiftReg >> 1 + shiftReg := Cat(1.U, shift(9, 0)) + bitsReg := bitsReg - 1.U + }.otherwise { + when(io.channel.valid) { + shiftReg := Cat(Cat(3.U, io.channel.bits), 0.U) // two stop bits, data, one start bit + bitsReg := 11.U + }.otherwise { + shiftReg := 0x7ff.U + } + } + + }.otherwise { + cntReg := cntReg - 1.U + } +} + +/** + * Receive part of the UART. + * A minimal version without any additional buffering. + * Use a ready/valid handshaking. + * + * The following code is inspired by Tommy's receive code at: + * https://github.com/tommythorn/yarvi + */ +class Rx(frequency: Int, baudRate: Int) extends Module { + val io = IO(new Bundle { + val rxd = Input(UInt(1.W)) + val channel = new UartIO() + }) + + val BIT_CNT = ((frequency + baudRate / 2) / baudRate - 1).U + val START_CNT = ((3 * frequency / 2 + baudRate / 2) / baudRate - 1).U + + // Sync in the asynchronous RX data, reset to 1 to not start reading after a reset + val rxReg = RegNext(RegNext(io.rxd, 1.U), 1.U) + + val shiftReg = RegInit(0.U(8.W)) + val cntReg = RegInit(0.U(20.W)) + val bitsReg = RegInit(0.U(4.W)) + val valReg = RegInit(false.B) + + when(cntReg =/= 0.U) { + cntReg := cntReg - 1.U + }.elsewhen(bitsReg =/= 0.U) { + cntReg := BIT_CNT + shiftReg := Cat(rxReg, shiftReg >> 1) + bitsReg := bitsReg - 1.U + // the last shifted in + when(bitsReg === 1.U) { + valReg := true.B + } + }.elsewhen(rxReg === 0.U) { // wait 1.5 bits after falling edge of start + cntReg := START_CNT + bitsReg := 8.U + } + + when(valReg && io.channel.ready) { + valReg := false.B + } + + io.channel.bits := shiftReg + io.channel.valid := valReg +} + +/** + * A single byte buffer with a ready/valid interface + */ +class Buffer extends Module { + val io = IO(new Bundle { + val in = Flipped(new UartIO()) + val out = new UartIO() + }) + + val empty :: full :: Nil = Enum(2) + val stateReg = RegInit(empty) + val dataReg = RegInit(0.U(8.W)) + + io.in.ready := stateReg === empty + io.out.valid := stateReg === full + + when(stateReg === empty) { + when(io.in.valid) { + dataReg := io.in.bits + stateReg := full + } + }.otherwise { // full + when(io.out.ready) { + stateReg := empty + } + } + io.out.bits := dataReg +} + +/** + * A transmitter with a single buffer. + */ +class BufferedTx(frequency: Int, baudRate: Int) extends Module { + val io = IO(new Bundle { + val txd = Output(UInt(1.W)) + val channel = Flipped(new UartIO()) + }) + val tx = Module(new Tx(frequency, baudRate)) + val buf = Module(new Buffer()) + + buf.io.in <> io.channel + tx.io.channel <> buf.io.out + io.txd <> tx.io.txd +} + +/** + * Send a string. + */ +class Sender(frequency: Int, baudRate: Int) extends Module { + val io = IO(new Bundle { + val txd = Output(UInt(1.W)) + }) + + val tx = Module(new BufferedTx(frequency, baudRate)) + + io.txd := tx.io.txd + + val msg = "Hello World!" + val text = VecInit(msg.map(_.U)) + val len = msg.length.U + + val cntReg = RegInit(0.U(8.W)) + + tx.io.channel.bits := text(cntReg) + tx.io.channel.valid := cntReg =/= len + + when(tx.io.channel.ready && cntReg =/= len) { + cntReg := cntReg + 1.U + } +} + +class Echo(frequency: Int, baudRate: Int) extends Module { + val io = IO(new Bundle { + val txd = Output(UInt(1.W)) + val rxd = Input(UInt(1.W)) + }) + // io.txd := RegNext(io.rxd) + val tx = Module(new BufferedTx(frequency, baudRate)) + val rx = Module(new Rx(frequency, baudRate)) + io.txd := tx.io.txd + rx.io.rxd := io.rxd + tx.io.channel <> rx.io.channel +} + +class UartMain(frequency: Int, baudRate: Int) extends Module { + val io = IO(new Bundle { + val rxd = Input(UInt(1.W)) + val txd = Output(UInt(1.W)) + }) + + val doSender = true + + if (doSender) { + val s = Module(new Sender(frequency, baudRate)) + io.txd := s.io.txd + } else { + val e = Module(new Echo(frequency, baudRate)) + e.io.rxd := io.rxd + io.txd := e.io.txd + } + +} + +object Main extends App { + // These lines generate the Verilog output + println( + ChiselStage.emitSystemVerilog( + new UartMain(50000000, 115200), + firtoolOpts = Array( + "-disable-all-randomization", + "-strip-debug-info", + "--split-verilog", // The intention for this argument (and next argument) is to separate generated files. + "-o", + "generated/", + ) + ) + ) +} diff --git a/src/test/scala/gcd/GCDSpec.scala b/src/test/scala/gcd/GCDSpec.scala deleted file mode 100644 index 4103d61..0000000 --- a/src/test/scala/gcd/GCDSpec.scala +++ /dev/null @@ -1,68 +0,0 @@ -// See README.md for license details. - -package gcd - -import chisel3._ -import chisel3.experimental.BundleLiterals._ -import chisel3.simulator.EphemeralSimulator._ -import org.scalatest.freespec.AnyFreeSpec -import org.scalatest.matchers.must.Matchers - -/** - * This is a trivial example of how to run this Specification - * From within sbt use: - * {{{ - * testOnly gcd.GCDSpec - * }}} - * From a terminal shell use: - * {{{ - * sbt 'testOnly gcd.GCDSpec' - * }}} - * Testing from mill: - * {{{ - * mill hwdbg.test.testOnly gcd.GCDSpec - * }}} - */ -class GCDSpec extends AnyFreeSpec with Matchers { - - "Gcd should calculate proper greatest common denominator" in { - simulate(new DecoupledGcd(16)) { dut => - val testValues = for { x <- 0 to 10; y <- 0 to 10} yield (x, y) - val inputSeq = testValues.map { case (x, y) => (new GcdInputBundle(16)).Lit(_.value1 -> x.U, _.value2 -> y.U) } - val resultSeq = testValues.map { case (x, y) => - (new GcdOutputBundle(16)).Lit(_.value1 -> x.U, _.value2 -> y.U, _.gcd -> BigInt(x).gcd(BigInt(y)).U) - } - - dut.reset.poke(true.B) - dut.clock.step() - dut.reset.poke(false.B) - dut.clock.step() - - var sent, received, cycles: Int = 0 - while (sent != 100 && received != 100) { - assert(cycles <= 1000, "timeout reached") - - if (sent < 100) { - dut.input.valid.poke(true.B) - dut.input.bits.value1.poke(testValues(sent)._1.U) - dut.input.bits.value2.poke(testValues(sent)._2.U) - if (dut.input.ready.peek().litToBoolean) { - sent += 1 - } - } - - if (received < 100) { - dut.output.ready.poke(true.B) - if (dut.output.valid.peekValue().asBigInt == 1) { - dut.output.bits.gcd.expect(BigInt(testValues(received)._1).gcd(testValues(received)._2)) - received += 1 - } - } - - // Step the simulation forward. - dut.clock.step() - cycles += 1 - } - } - } -} diff --git a/src/test/scala/hwdbg/lib/uart/UartTester.scala b/src/test/scala/hwdbg/lib/uart/UartTester.scala new file mode 100644 index 0000000..8cfde35 --- /dev/null +++ b/src/test/scala/hwdbg/lib/uart/UartTester.scala @@ -0,0 +1,77 @@ +import chisel3._ +import chisel3.experimental.BundleLiterals._ +import chisel3.simulator.EphemeralSimulator._ +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.must.Matchers + +import hwdbg.lib.uart._ + + +class UartTxTests extends AnyFreeSpec with Matchers { + "UartTx should work" in { + simulate(new Tx(10000, 3000)) { dut => + dut.clock.step(2) + // ready/valid handshake the first character + dut.io.channel.valid.poke(true.B) + dut.io.channel.bits.poke('a'.toInt.U) + while (!dut.io.channel.ready.peek().litToBoolean) { + dut.clock.step(1) + } + dut.clock.step(1) + dut.io.channel.valid.poke(false.B) + dut.io.channel.bits.poke(0.U) + + // wait for start bit + while (dut.io.txd.peek().litValue != 0) { + dut.clock.step(1) + } + // to the first bit + dut.clock.step(3) + + for (i <- 0 until 8) { + dut.io.txd.expect((('a'.toInt >> i) & 0x01).U) + dut.clock.step(3) + } + // stop bit + dut.io.txd.expect(1.U) + } + } +} + +class UartSenderTests extends AnyFreeSpec with Matchers { + "UartSender should work" in { + simulate(new Sender(10000, 3000)) { dut => + dut.clock.step(300) + } + } +} + +class UartRxTests extends AnyFreeSpec with Matchers { + "UartRx should work" in { + simulate(new Rx(10000, 3000)) { dut => + dut.io.rxd.poke(1.U) + dut.clock.step(10) + // start bit + dut.io.rxd.poke(0.U) + dut.clock.step(3) + // 8 data bits + for (i <- 0 until 8) { + dut.io.rxd.poke(((0xa5 >> i) & 0x01).U) + dut.clock.step(3) + } + // stop bit + dut.io.rxd.poke(1.U) + while (!dut.io.channel.valid.peek().litToBoolean) { + // wait on valid + dut.clock.step(1) + } + dut.io.channel.bits.expect(0xa5.U) + + // read it out + dut.io.channel.ready.poke(true.B) + dut.clock.step(1) + dut.io.channel.ready.poke(false.B) + dut.clock.step(5) + } + } +}