Compare commits
7 Commits
07de394ddb
...
b0126a7d16
Author | SHA1 | Date |
---|---|---|
David Anderson | b0126a7d16 | |
David Anderson | a08fd421fe | |
David Anderson | 379ebf0411 | |
David Anderson | cea5fde170 | |
David Anderson | 1b85c3e216 | |
David Anderson | b527a62ab8 | |
David Anderson | d10a548bc1 |
|
@ -0,0 +1,243 @@
|
|||
package UART;
|
||||
|
||||
import Cntrs::*;
|
||||
import GetPut::*;
|
||||
import FIFOF::*;
|
||||
import SpecialFIFOs::*;
|
||||
import StmtFSM::*;
|
||||
import Connectable::*;
|
||||
|
||||
import PinSync::*;
|
||||
import GlitchFilter::*;
|
||||
import Strobe::*;
|
||||
|
||||
(* always_enabled *)
|
||||
interface UART_RX_PHY;
|
||||
(* prefix="" *)
|
||||
method Action rx_in((* port="rx_in" *) bit b);
|
||||
(* result="cts" *)
|
||||
method Bool stop_sending();
|
||||
endinterface
|
||||
|
||||
(* always_enabled *)
|
||||
interface UART_TX_PHY;
|
||||
(* result="tx_out" *)
|
||||
method bit tx_out();
|
||||
(* prefix="" *)
|
||||
method Action can_send((* port="rts" *) Bool send);
|
||||
endinterface
|
||||
|
||||
(* always_enabled *)
|
||||
interface UART_PHY;
|
||||
(* prefix="" *)
|
||||
method Action rx_in((* port="rx_in" *) bit b);
|
||||
(* result="tx_out" *)
|
||||
method bit tx_out();
|
||||
(* result="cts" *)
|
||||
method Bool stop_sending();
|
||||
(* prefix="" *)
|
||||
method Action can_send((* port="rts" *) Bool send);
|
||||
endinterface
|
||||
|
||||
interface UART_RX;
|
||||
interface UART_RX_PHY phy;
|
||||
interface Get#(Bit#(8)) receive;
|
||||
endinterface
|
||||
|
||||
interface UART_TX;
|
||||
interface UART_TX_PHY phy;
|
||||
interface Put#(Bit#(8)) send;
|
||||
endinterface
|
||||
|
||||
interface UART;
|
||||
interface UART_PHY phy;
|
||||
interface Put#(Bit#(8)) send;
|
||||
interface Get#(Bit#(8)) receive;
|
||||
endinterface
|
||||
|
||||
typedef enum {
|
||||
WaitIdle,
|
||||
Idle,
|
||||
Read,
|
||||
Stop,
|
||||
Cork
|
||||
} RXState deriving (Bits, Eq);
|
||||
|
||||
module mkUARTReceiver(Integer clock_frequency, Integer uart_bitrate, UART_RX ifc);
|
||||
Reg#(bit) rx_sync <- mkPinSync(0);
|
||||
let rx_in <- mkGlitchFilter(3, 0);
|
||||
mkConnection(toGet(asReg(rx_sync)), toPut(asReg(rx_in)));
|
||||
|
||||
Reg#(RXState) rx_state <- mkReg(WaitIdle);
|
||||
Strobe bit_16x_strobe <- mkStrobe(clock_frequency, 16*uart_bitrate);
|
||||
Count#(UInt#(4)) cnt <- mkCount(0);
|
||||
Reg#(Bit#(8)) shift_in <- mkReg(0);
|
||||
FIFOF#(Bit#(8)) rx <- mkBypassFIFOF();
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule rx_counter (bit_16x_strobe);
|
||||
cnt.incr(1);
|
||||
endrule
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule rx_wait_idle (rx_state == WaitIdle && bit_16x_strobe && rx_in == 1);
|
||||
rx_state <= Idle;
|
||||
endrule
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule rx_idle (rx_state == Idle && bit_16x_strobe && rx_in == 0);
|
||||
rx_state <= Read;
|
||||
cnt <= 1;
|
||||
shift_in <= 8'hFF;
|
||||
endrule
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule rx_read (rx_state == Read && bit_16x_strobe && cnt == 7);
|
||||
let shifted_out = shift_in[0];
|
||||
shift_in <= {rx_in, shift_in[7:1]};
|
||||
if (shifted_out == 0) begin // start bit reached end of shiftreg
|
||||
rx_state <= Stop;
|
||||
end
|
||||
endrule
|
||||
|
||||
(* fire_when_enabled *)
|
||||
rule rx_stop (rx_state == Stop);
|
||||
if (rx.notFull) begin
|
||||
rx.enq(shift_in);
|
||||
rx_state <= WaitIdle;
|
||||
end
|
||||
else
|
||||
rx_state <= Cork;
|
||||
endrule
|
||||
|
||||
(* fire_when_enabled *)
|
||||
rule rx_cork (rx_state == Cork);
|
||||
rx.enq(shift_in);
|
||||
rx_state <= WaitIdle;
|
||||
endrule
|
||||
|
||||
interface UART_RX_PHY phy;
|
||||
method rx_in = rx_sync._write;
|
||||
method Bool stop_sending();
|
||||
// Signal is active low, so 1 == "stop sending"
|
||||
return rx_state == Cork;
|
||||
endmethod
|
||||
endinterface
|
||||
|
||||
interface receive = toGet(rx);
|
||||
endmodule
|
||||
|
||||
typedef enum {
|
||||
Idle,
|
||||
Ready,
|
||||
Send
|
||||
} TXState deriving (Bits, Eq);
|
||||
|
||||
module mkUARTTransmitter(Integer clock_frequency, Integer uart_bitrate, UART_TX ifc);
|
||||
Reg#(bit) cts_sync <- mkPinSync(0);
|
||||
let cts_in <- mkGlitchFilter(3, 0);
|
||||
mkConnection(toGet(asReg(cts_sync)), toPut(asReg(cts_in)));
|
||||
|
||||
Reg#(TXState) tx_state <- mkReg(Idle);
|
||||
Strobe bit_strobe <- mkStrobe(clock_frequency, uart_bitrate);
|
||||
Reg#(UInt#(4)) cnt <- mkReg(0);
|
||||
Reg#(Bit#(9)) shift_out <- mkReg(9'h1FF);
|
||||
FIFOF#(Bit#(8)) tx <- mkPipelineFIFOF();
|
||||
|
||||
(* fire_when_enabled *)
|
||||
rule tx_idle (tx_state == Idle && tx.notEmpty);
|
||||
shift_out <= {tx.first, 0};
|
||||
tx.deq();
|
||||
tx_state <= Ready;
|
||||
bit_strobe.reset();
|
||||
endrule
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule rx_ready (tx_state == Ready && bit_strobe && cts_in == 1);
|
||||
tx_state <= Send;
|
||||
cnt <= 0;
|
||||
endrule
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule tx_send (tx_state == Send && bit_strobe);
|
||||
shift_out <= {1'b1, shift_out[8:1]};
|
||||
cnt <= cnt+1;
|
||||
if (cnt == 9)
|
||||
tx_state <= Idle;
|
||||
endrule
|
||||
|
||||
interface UART_TX_PHY phy;
|
||||
method bit tx_out();
|
||||
if (tx_state == Send)
|
||||
return shift_out[0];
|
||||
else
|
||||
return 1'b1;
|
||||
endmethod
|
||||
method Action can_send(b);
|
||||
cts_sync <= pack(b);
|
||||
endmethod
|
||||
endinterface
|
||||
|
||||
interface Put send = toPut(tx);
|
||||
endmodule
|
||||
|
||||
module mkUART(Integer clock_frequency, Integer uart_bitrate, UART ifc);
|
||||
let _rx <- mkUARTReceiver(clock_frequency, uart_bitrate);
|
||||
let _tx <- mkUARTTransmitter(clock_frequency, uart_bitrate);
|
||||
|
||||
interface UART_PHY phy;
|
||||
method rx_in = _rx.phy.rx_in;
|
||||
method tx_out = _tx.phy.tx_out;
|
||||
method stop_sending = _rx.phy.stop_sending;
|
||||
method can_send = _tx.phy.can_send;
|
||||
endinterface
|
||||
interface send = _tx.send;
|
||||
interface receive = _rx.receive;
|
||||
endmodule
|
||||
|
||||
typeclass FlowControlled#(type ifc);
|
||||
module disableFlowControl(ifc i, Empty ret);
|
||||
endtypeclass
|
||||
|
||||
instance FlowControlled#(UART_RX_PHY);
|
||||
module disableFlowControl(UART_RX_PHY phy, Empty ifc);
|
||||
endmodule
|
||||
endinstance
|
||||
|
||||
instance FlowControlled#(UART_TX_PHY);
|
||||
module disableFlowControl(UART_TX_PHY phy, Empty ifc);
|
||||
(* no_implicit_conditions,fire_when_enabled *)
|
||||
rule always_send;
|
||||
phy.can_send(True);
|
||||
endrule
|
||||
endmodule
|
||||
endinstance
|
||||
|
||||
instance FlowControlled#(UART_PHY);
|
||||
module disableFlowControl(UART_PHY phy, Empty ifc);
|
||||
(* no_implicit_conditions,fire_when_enabled *)
|
||||
rule always_send;
|
||||
phy.can_send(True);
|
||||
endrule
|
||||
endmodule
|
||||
endinstance
|
||||
|
||||
instance FlowControlled#(UART_RX);
|
||||
module disableFlowControl(UART_RX rx, Empty ifc);
|
||||
disableFlowControl(rx.phy);
|
||||
endmodule
|
||||
endinstance
|
||||
|
||||
instance FlowControlled#(UART_TX);
|
||||
module disableFlowControl(UART_TX tx, Empty ifc);
|
||||
disableFlowControl(tx.phy);
|
||||
endmodule
|
||||
endinstance
|
||||
|
||||
instance FlowControlled#(UART);
|
||||
module disableFlowControl(UART uart, Empty ifc);
|
||||
disableFlowControl(uart.phy);
|
||||
endmodule
|
||||
endinstance
|
||||
|
||||
endpackage
|
|
@ -0,0 +1,265 @@
|
|||
package UART_Test;
|
||||
|
||||
import Assert::*;
|
||||
import StmtFSM::*;
|
||||
import Connectable::*;
|
||||
import GetPut::*;
|
||||
import Probe::*;
|
||||
|
||||
import UART::*;
|
||||
import Testing::*;
|
||||
import Strobe::*;
|
||||
|
||||
interface Test;
|
||||
method Action start();
|
||||
method Bool done();
|
||||
endinterface
|
||||
|
||||
module mkTestReceiver(Test);
|
||||
Reg#(Bool) running <- mkReg(False);
|
||||
|
||||
let testflags <- mkTestFlags();
|
||||
let cycles <- mkCycleCounter();
|
||||
|
||||
let dut <- mkUARTReceiver(25_000_000, 115_200);
|
||||
Reg#(bit) tx <- mkReg(1);
|
||||
mkConnection(toGet(asReg(tx)), toPut(dut.phy.rx_in));
|
||||
|
||||
Probe#(Bit#(8)) read_probe <- mkProbe();
|
||||
Probe#(Bool) cork_probe <- mkProbe();
|
||||
rule record_cork;
|
||||
cork_probe <= dut.phy.stop_sending();
|
||||
endrule
|
||||
|
||||
Reg#(Bit#(40)) shift[2] <- mkCReg(2, 40'hFFFFFFFFFF);
|
||||
let shift_in_strobe <- mkStrobe(25_000_000, 115_200);
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule shift_in (running && shift_in_strobe && (tx == 0 || !dut.phy.stop_sending));
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): to UART: %0d", cycles.all, cycles, shift[0][0]);
|
||||
tx <= shift[0][0];
|
||||
shift[0] <= {1'b1, shift[0][39:1]};
|
||||
endrule
|
||||
|
||||
function Action start_tx(Bit#(40) val);
|
||||
return action
|
||||
shift[1] <= val;
|
||||
endaction;
|
||||
endfunction
|
||||
|
||||
function Bool tx_idle();
|
||||
return shift[0] == 40'hFFFFFFFFFF;
|
||||
endfunction
|
||||
|
||||
function Bool tx_is(Bit#(40) val);
|
||||
return shift[0] == val;
|
||||
endfunction
|
||||
|
||||
let fsm <- mkFSM(seq
|
||||
running <= True;
|
||||
// Let UART initialize and settle
|
||||
repeat (61) noAction;
|
||||
// desynchronize sender and receiver strobes, to emulate a
|
||||
// real setup.
|
||||
shift_in_strobe.reset();
|
||||
|
||||
action
|
||||
dynamicAssert(tx_idle(), "transmitter not idle");
|
||||
dynamicAssert(!dut.phy.stop_sending(), "receiver not ready to receive");
|
||||
endaction
|
||||
|
||||
// Single byte transmission
|
||||
action
|
||||
start_tx({10'h3FF, 10'h3FF, 10'h3FF, 1'b1, 8'd77, 1'b0});
|
||||
cycles.reset();
|
||||
endaction
|
||||
|
||||
// Read received byte
|
||||
action
|
||||
let got <- dut.receive.get();
|
||||
read_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): UART.rx = %0d, want %0d", cycles.all, cycles, got, 77);
|
||||
dynamicAssert(got == 77, "wrong byte received");
|
||||
dynamicAssert(cycles < 2100, "byte not received during stop bit");
|
||||
endaction
|
||||
dynamicAssert(!dut.phy.stop_sending(), "receiver not ready to receive");
|
||||
|
||||
await(shift_in_strobe);
|
||||
action
|
||||
dynamicAssert(tx_idle(), "transmitter not idle");
|
||||
dynamicAssert(!dut.phy.stop_sending(), "receiver not ready to receive");
|
||||
endaction
|
||||
await(shift_in_strobe);
|
||||
|
||||
// Send 4 bytes back to back, to check flow control
|
||||
action
|
||||
start_tx({
|
||||
1'b1, 8'd25, 1'b0,
|
||||
1'b1, 8'd14, 1'b0,
|
||||
1'b1, 8'd59, 1'b0,
|
||||
1'b1, 8'd42, 1'b0});
|
||||
cycles.reset();
|
||||
endaction
|
||||
|
||||
// Once two bytes are sent, the receiver should cork.
|
||||
await(tx_is({10'h3FF, 10'h3FF, 1'b1, 8'd25, 1'b0, 1'b1, 8'd14, 1'b0}));
|
||||
repeat (1000) noAction;
|
||||
dynamicAssert(dut.phy.stop_sending(), "receiver did not cork sender");
|
||||
|
||||
// Read out one byte, verify that one more byte can transmit.
|
||||
action
|
||||
let got <- dut.receive.get();
|
||||
read_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): UART.rx = %0d, want %0d", cycles.all, cycles, got, 42);
|
||||
dynamicAssert(got == 42, "wrong byte received");
|
||||
dynamicAssert(cycles < 5400, "byte not received during stop bit");
|
||||
endaction
|
||||
await(tx_is({10'h3FF, 10'h3FF, 10'h3FF, 1'b1, 8'd25, 1'b0}));
|
||||
dynamicAssert(dut.phy.stop_sending(), "receiver did not cork sender");
|
||||
|
||||
// Read out one more byte, check the final byte transmits.
|
||||
action
|
||||
let got <- dut.receive.get();
|
||||
read_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): UART.rx = %0d, want %0d", cycles.all, cycles, got, 59);
|
||||
dynamicAssert(got == 59, "wrong byte received");
|
||||
dynamicAssert(cycles < 7600, "byte not received during stop bit");
|
||||
endaction
|
||||
await(tx_is(40'hFFFFFFFFFF));
|
||||
repeat (1000) noAction;
|
||||
dynamicAssert(dut.phy.stop_sending(), "receiver did not cork sender");
|
||||
|
||||
// Read the final two bytes from the receiver.
|
||||
action
|
||||
let got <- dut.receive.get();
|
||||
read_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): UART.rx = %0d, want %0d", cycles.all, cycles, got, 14);
|
||||
dynamicAssert(got == 14, "wrong byte received");
|
||||
dynamicAssert(cycles < 10500, "byte not received during stop bit");
|
||||
endaction
|
||||
repeat (1000) noAction;
|
||||
dynamicAssert(!dut.phy.stop_sending(), "receiver did not uncork sender");
|
||||
|
||||
action
|
||||
let got <- dut.receive.get();
|
||||
read_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): UART.rx = %0d, want %0d", cycles.all, cycles, got, 25);
|
||||
dynamicAssert(got == 25, "wrong byte received");
|
||||
dynamicAssert(cycles < 11500, "byte not received during stop bit");
|
||||
endaction
|
||||
repeat (1000) noAction;
|
||||
dynamicAssert(!dut.phy.stop_sending(), "receiver did not uncork sender");
|
||||
running <= False;
|
||||
endseq);
|
||||
method start = fsm.start;
|
||||
method done = fsm.done;
|
||||
endmodule
|
||||
|
||||
module mkTestTransmitter(Test);
|
||||
let testflags <- mkTestFlags();
|
||||
let cycles <- mkCycleCounter();
|
||||
|
||||
let receiver <- mkUARTReceiver(25_000_000, 115_200);
|
||||
let dut <- mkUARTTransmitter(25_000_000, 115_200);
|
||||
mkConnection(toGet(dut.phy.tx_out), toPut(receiver.phy.rx_in));
|
||||
mkConnection(toGet(True), toPut(dut.phy.can_send));
|
||||
|
||||
Probe#(bit) tx_probe <- mkProbe();
|
||||
Probe#(Bit#(8)) recv_probe <- mkProbe();
|
||||
rule record_tx;
|
||||
tx_probe <= dut.phy.tx_out;
|
||||
endrule
|
||||
|
||||
let fsm <- mkFSM(seq
|
||||
// Let UART initialize and settle
|
||||
repeat (61) noAction;
|
||||
|
||||
action
|
||||
dut.send.put(42);
|
||||
cycles.reset();
|
||||
if (testflags.verbose)
|
||||
$display("%0d: send", cycles.all);
|
||||
endaction
|
||||
action
|
||||
let got <- receiver.receive.get();
|
||||
recv_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
|
||||
dynamicAssert(got == 42, "wrong value received");
|
||||
dynamicAssert(cycles < 2000, "value received too late");
|
||||
endaction
|
||||
|
||||
repeat(1000) noAction;
|
||||
par
|
||||
seq
|
||||
dut.send.put(1);
|
||||
dut.send.put(2);
|
||||
dut.send.put(5);
|
||||
dut.send.put(3);
|
||||
dut.send.put(4);
|
||||
endseq
|
||||
|
||||
seq
|
||||
action
|
||||
let got <- receiver.receive.get();
|
||||
recv_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
|
||||
dynamicAssert(got == 1, "wrong byte received");
|
||||
endaction
|
||||
action
|
||||
let got <- receiver.receive.get();
|
||||
recv_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
|
||||
dynamicAssert(got == 2, "wrong byte received");
|
||||
endaction
|
||||
action
|
||||
let got <- receiver.receive.get();
|
||||
recv_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
|
||||
dynamicAssert(got == 5, "wrong byte received");
|
||||
endaction
|
||||
action
|
||||
let got <- receiver.receive.get();
|
||||
recv_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
|
||||
dynamicAssert(got == 3, "wrong byte received");
|
||||
endaction
|
||||
action
|
||||
let got <- receiver.receive.get();
|
||||
recv_probe <= got;
|
||||
if (testflags.verbose)
|
||||
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
|
||||
dynamicAssert(got == 4, "wrong byte received");
|
||||
endaction
|
||||
endseq
|
||||
endpar
|
||||
endseq);
|
||||
|
||||
method start = fsm.start;
|
||||
method done = fsm.done;
|
||||
endmodule
|
||||
|
||||
module mkTB();
|
||||
let rx_test <- mkTestReceiver();
|
||||
let tx_test <- mkTestTransmitter();
|
||||
|
||||
runTest(30000,
|
||||
mkTest("UART", seq
|
||||
rx_test.start();
|
||||
await(rx_test.done);
|
||||
|
||||
tx_test.start();
|
||||
await(tx_test.done);
|
||||
endseq));
|
||||
endmodule
|
||||
|
||||
endpackage
|
|
@ -0,0 +1,33 @@
|
|||
package Top;
|
||||
|
||||
import GetPut::*;
|
||||
|
||||
import UART::*;
|
||||
|
||||
(* always_enabled *)
|
||||
interface Top;
|
||||
(* prefix="" *)
|
||||
method Action rx_in((* port="rx_in" *) bit b);
|
||||
method bit tx_out();
|
||||
method Bit#(8) leds();
|
||||
endinterface
|
||||
|
||||
(* synthesize *)
|
||||
module mkTop(Top);
|
||||
let _ret <- mkUART(100_000_000, 115_200);
|
||||
disableFlowControl(_ret);
|
||||
|
||||
Reg#(Bit#(8)) leds_out <- mkReg(0);
|
||||
|
||||
rule recv;
|
||||
let b <- _ret.receive.get();
|
||||
_ret.send.put(b);
|
||||
leds_out <= b;
|
||||
endrule
|
||||
|
||||
method rx_in = _ret.phy.rx_in;
|
||||
method tx_out = _ret.phy.tx_out;
|
||||
method leds = leds_out._read;
|
||||
endmodule
|
||||
|
||||
endpackage
|
|
@ -0,0 +1,33 @@
|
|||
BLOCK RESETPATHS;
|
||||
BLOCK ASYNCPATHS;
|
||||
|
||||
LOCATE COMP "CLK" SITE "G2";
|
||||
IOBUF PORT "CLK" PULLMODE=NONE IO_TYPE=LVCMOS33;
|
||||
FREQUENCY PORT "CLK" 150 MHZ;
|
||||
|
||||
SYSCONFIG CONFIG_IOVOLTAGE=3.3 COMPRESS_CONFIG=ON MCCLK_FREQ=62 SLAVE_SPI_PORT=DISABLE MASTER_SPI_PORT=ENABLE SLAVE_PARALLEL_PORT=DISABLE;
|
||||
|
||||
LOCATE COMP "tx_out" SITE "L4"; # FPGA transmits to ftdi
|
||||
LOCATE COMP "rx_in" SITE "M1"; # FPGA receives from ftdi
|
||||
IOBUF PORT "tx_out" PULLMODE=UP IO_TYPE=LVCMOS33 DRIVE=4;
|
||||
IOBUF PORT "rx_in" PULLMODE=UP IO_TYPE=LVCMOS33;
|
||||
|
||||
LOCATE COMP "leds[7]" SITE "H3";
|
||||
LOCATE COMP "leds[6]" SITE "E1";
|
||||
LOCATE COMP "leds[5]" SITE "E2";
|
||||
LOCATE COMP "leds[4]" SITE "D1";
|
||||
LOCATE COMP "leds[3]" SITE "D2";
|
||||
LOCATE COMP "leds[2]" SITE "C1";
|
||||
LOCATE COMP "leds[1]" SITE "C2";
|
||||
LOCATE COMP "leds[0]" SITE "B2";
|
||||
IOBUF PORT "leds[0]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
|
||||
IOBUF PORT "leds[1]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
|
||||
IOBUF PORT "leds[2]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
|
||||
IOBUF PORT "leds[3]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
|
||||
IOBUF PORT "leds[4]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
|
||||
IOBUF PORT "leds[5]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
|
||||
IOBUF PORT "leds[6]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
|
||||
IOBUF PORT "leds[7]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
|
||||
|
||||
LOCATE COMP "RST_N" SITE "B3";
|
||||
IOBUF PORT "RST_N" PULLMODE=DOWN IO_TYPE=LVCMOS33;
|
|
@ -24,7 +24,7 @@
|
|||
gotools
|
||||
gtkwave
|
||||
imagemagick
|
||||
nextpnr
|
||||
nextpnrWithGui
|
||||
openfpgaloader
|
||||
picocom
|
||||
(python3.withPackages (py-pkgs: [
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package GlitchFilter;
|
||||
|
||||
import Cntrs::*;
|
||||
|
||||
(* always_enabled="_write",always_ready="_read" *)
|
||||
module mkGlitchFilter(Integer hysteresis, bit init_value, Reg#(bit) ifc);
|
||||
Wire#(bit) in <- mkBypassWire();
|
||||
UCount cnt <- mkUCount(init_value == 0 ? 0 : hysteresis, hysteresis);
|
||||
Reg#(bit) out[2] <- mkCReg(2, init_value);
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule incr (cnt.isLessThan(fromInteger(hysteresis)) && in == 1);
|
||||
cnt.incr(1);
|
||||
endrule
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule decr (cnt.isGreaterThan(0) && in == 0);
|
||||
cnt.decr(1);
|
||||
endrule
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule set_out (cnt.isEqual(0) || cnt.isEqual(fromInteger(hysteresis)));
|
||||
out[0] <= cnt.isEqual(0) ? 0 : 1;
|
||||
endrule
|
||||
|
||||
method _write = in._write;
|
||||
method _read = out[1]._read;
|
||||
endmodule
|
||||
|
||||
endpackage
|
|
@ -0,0 +1,76 @@
|
|||
package GlitchFilter_Test;
|
||||
|
||||
import Assert::*;
|
||||
import StmtFSM::*;
|
||||
|
||||
import GlitchFilter::*;
|
||||
import Testing::*;
|
||||
|
||||
module mkTB();
|
||||
let testflags <- mkTestFlags();
|
||||
let cycles <- mkCycleCounter();
|
||||
|
||||
let dut <- mkGlitchFilter(3, 0);
|
||||
|
||||
// Slight indirection to appease the compiler: GlitchFilter
|
||||
// requires a write on every cycle, and with just the test FSM, the
|
||||
// compiler can't prove that this is satisfied. So, give it a
|
||||
// default value when the test isn't driving the input, on the
|
||||
// first few cycles of the test.
|
||||
Wire#(bit) pin_in <- mkDWire(0);
|
||||
(* no_implicit_conditions,fire_when_enabled *)
|
||||
rule push_in;
|
||||
dut <= pin_in;
|
||||
endrule
|
||||
|
||||
function Action check_dut(bit in, bit want_out);
|
||||
return action
|
||||
if (testflags.verbose)
|
||||
$display("%0d: GlitchFilter(%0d) => %0d, want %0d", cycles.all, in, dut, want_out);
|
||||
dynamicAssert(dut == want_out, "wrong GlitchFilter output");
|
||||
pin_in <= in;
|
||||
endaction;
|
||||
endfunction
|
||||
|
||||
runTest(100,
|
||||
mkTest("GlitchFilter", seq
|
||||
// Simple 0->1 transition
|
||||
check_dut(0, 0);
|
||||
check_dut(1, 0);
|
||||
check_dut(1, 0);
|
||||
check_dut(1, 0);
|
||||
check_dut(1, 1);
|
||||
check_dut(1, 1);
|
||||
|
||||
// Simple 1->0 transition
|
||||
check_dut(0, 1);
|
||||
check_dut(0, 1);
|
||||
check_dut(0, 1);
|
||||
check_dut(0, 0);
|
||||
check_dut(0, 0);
|
||||
|
||||
// Glitchy 0->1
|
||||
check_dut(1, 0);
|
||||
check_dut(1, 0);
|
||||
check_dut(0, 0);
|
||||
check_dut(1, 0);
|
||||
check_dut(0, 0);
|
||||
check_dut(1, 0);
|
||||
check_dut(1, 0);
|
||||
check_dut(1, 1);
|
||||
check_dut(1, 1);
|
||||
|
||||
// Glitchy 1->0
|
||||
check_dut(0, 1);
|
||||
check_dut(0, 1);
|
||||
check_dut(1, 1);
|
||||
check_dut(0, 1);
|
||||
check_dut(1, 1);
|
||||
check_dut(0, 1);
|
||||
check_dut(0, 1);
|
||||
check_dut(0, 0);
|
||||
check_dut(0, 0);
|
||||
endseq));
|
||||
endmodule
|
||||
|
||||
endpackage
|
|
@ -1,11 +1,5 @@
|
|||
package PinSync;
|
||||
|
||||
(* always_ready, always_enabled *)
|
||||
interface PinSync#(type val);
|
||||
method Action _write(val a);
|
||||
method val _read();
|
||||
endinterface
|
||||
|
||||
// mkPinSync builds a synchronizer for use with asynchronous inputs.
|
||||
//
|
||||
// You should only use this to capture asynchronous inputs coming from
|
||||
|
@ -24,7 +18,8 @@ endinterface
|
|||
// source domain. Conceptually, we assume that register exists outside
|
||||
// our design and is driving the input of mkPinSync, so we just need
|
||||
// the metastability mitigation within our own domain.
|
||||
module mkPinSync(val init_value, PinSync#(val) ifc)
|
||||
(* always_enabled="_write",always_ready="_read" *)
|
||||
module mkPinSync(val init_value, Reg#(val) ifc)
|
||||
provisos(Bits#(val, _));
|
||||
|
||||
Reg#(val) r1 <- mkReg(init_value);
|
||||
|
|
|
@ -10,7 +10,7 @@ module mkTB();
|
|||
let testflags <- mkTestFlags();
|
||||
let cycles <- mkCycleCounter();
|
||||
|
||||
PinSync#(UInt#(2)) dut <- mkPinSync(0);
|
||||
Reg#(UInt#(2)) dut <- mkPinSync(0);
|
||||
|
||||
function Action check_dut_val(UInt#(2) want_val);
|
||||
return action
|
||||
|
|
142
lib/Strobe.bsv
142
lib/Strobe.bsv
|
@ -2,6 +2,7 @@ package Strobe;
|
|||
|
||||
import Real::*;
|
||||
import Printf::*;
|
||||
import DReg::*;
|
||||
|
||||
// A Strobe provides a synchronization signal to other modules, when
|
||||
// an event happens at a cadence other than the module clock.
|
||||
|
@ -9,49 +10,134 @@ import Printf::*;
|
|||
interface Strobe;
|
||||
method Bool _read();
|
||||
// reset resets the strobe cycle, starting with a strobe on the
|
||||
// cycle following reset.
|
||||
// cycle following the reset.
|
||||
method Action reset();
|
||||
endinterface
|
||||
|
||||
module mkStrobeRaw(UInt#(sz) increment, UInt#(sz) max, Strobe ifc);
|
||||
Reg#(UInt#(sz)) cnt <- mkReg(0);
|
||||
PulseWire want_reset <- mkPulseWire();
|
||||
Reg#(Bool) strobe_out <- mkDReg(False);
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule increment;
|
||||
if (want_reset) begin
|
||||
cnt <= 0;
|
||||
strobe_out <= True;
|
||||
end
|
||||
else begin
|
||||
UInt#(TAdd#(sz, 1)) new_cnt = extend(cnt)+extend(increment);
|
||||
if (new_cnt >= extend(max)) begin
|
||||
new_cnt = new_cnt - extend(max);
|
||||
strobe_out <= True;
|
||||
end
|
||||
cnt <= truncate(new_cnt);
|
||||
end
|
||||
endrule
|
||||
|
||||
method Bool _read = strobe_out._read;
|
||||
method Action reset();
|
||||
want_reset.send();
|
||||
endmethod
|
||||
endmodule
|
||||
|
||||
module mkStrobeRawOld(UInt#(sz) increment, UInt#(sz) max, Strobe ifc);
|
||||
Reg#(UInt#(sz)) cnt[2] <- mkCReg(2, 0);
|
||||
Reg#(Bool) pulse <- mkDReg(False);
|
||||
PulseWire pw <- mkPulseWireOR();
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule increment;
|
||||
UInt#(TAdd#(sz, 1)) new_cnt = extend(cnt[0])+extend(increment);
|
||||
if (new_cnt >= extend(max)) begin
|
||||
cnt[0] <= truncate(new_cnt - extend(max));
|
||||
pulse <= True;
|
||||
end
|
||||
else
|
||||
cnt[0] <= truncate(new_cnt);
|
||||
endrule
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule strobe (pulse);
|
||||
pw.send();
|
||||
endrule
|
||||
|
||||
method _read = pw._read;
|
||||
method Action reset();
|
||||
cnt[1] <= increment;
|
||||
pw.send();
|
||||
endmethod
|
||||
endmodule
|
||||
|
||||
module mkStrobeRawRec(UInt#(n) hack, Integer increment, Integer max, Strobe ifc);
|
||||
let register_width = ceil(log2(fromInteger(max)));
|
||||
if (valueOf(n) < register_width) begin
|
||||
UInt#(TAdd#(n, 1)) hack2 = 0;
|
||||
let _ret <- mkStrobeRawRec(hack2, increment, max);
|
||||
return _ret;
|
||||
end
|
||||
else begin
|
||||
UInt#(n) hack2 = fromInteger(increment);
|
||||
let _ret <- mkStrobeRaw(hack2, fromInteger(max));
|
||||
return _ret;
|
||||
end
|
||||
endmodule
|
||||
|
||||
// mkStrobe returns a Strobe that triggers at the given
|
||||
// target_frequency, assuming mkStrobe is being clocked at the given
|
||||
// higher clock_frequency.
|
||||
module mkStrobe(Integer clock_frequency, Integer target_frequency, Strobe ifc);
|
||||
if (target_frequency > clock_frequency)
|
||||
error("mkStrobe target_frequency must be less than clock_frequency");
|
||||
let strobe_every = round(fromInteger(clock_frequency)/fromInteger(target_frequency));
|
||||
|
||||
// Because we're using integer counters to divide frequencies,
|
||||
// unless the clock and target frequencies divide cleanly we'll end
|
||||
// up with a small amount of error.
|
||||
// This is how many main clock ticks there are per strobe at the
|
||||
// target frequency. For all but the simplest cases, this will
|
||||
// include fractional clock cycles due to the two frequencies not
|
||||
// dividing evenly.
|
||||
Real target_ratio = fromInteger(clock_frequency)/fromInteger(target_frequency);
|
||||
|
||||
// At this point we could round the target ratio and instantiate a
|
||||
// counter for that amount, but the resulting strobe frequency can
|
||||
// be quite drastically off target (empirically I've seen up to 5%
|
||||
// without trying).
|
||||
//
|
||||
// Strobes like this tend to be used for relatively short
|
||||
// operations before some other synchronization event happens
|
||||
// (e.g. sending one byte on UART), so we can allow a small amount
|
||||
// of frequency error. For now, the target frequency error is fixed
|
||||
// at <=0.1%.
|
||||
Real actual_frequency = fromInteger(clock_frequency)/fromInteger(strobe_every);
|
||||
Real frequency_error_pct = abs(fromInteger(target_frequency)-actual_frequency) / fromInteger(target_frequency) * 100;
|
||||
if (frequency_error_pct > 0.1)
|
||||
error(sprintf("mkStrobe actual frequency is %0f, %0f%% error vs. requested %0d. Your clock_frequency and target_frequency are probably too near each other.", actual_frequency, frequency_error_pct, target_frequency));
|
||||
// We can improve on this by counting in larger increments modulo
|
||||
// some maximum, and strobing on every overflow. This will make the
|
||||
// strobe period vary slightly by the main clock's reckoning,
|
||||
// jittering around the "true" strobe time as error accumulates and
|
||||
// is then subtracted back out.
|
||||
Real target_error = 1/1000; // 0.1%
|
||||
|
||||
Reg#(UInt#(32)) cnt[2] <- mkCReg(2, 0);
|
||||
Integer incr;
|
||||
Integer max;
|
||||
Real actual_error = 1;
|
||||
for (incr=1; incr<=32 && actual_error > target_error; incr=incr+1) begin
|
||||
Real mul_ratio = fromInteger(incr)*target_ratio;
|
||||
max = round(mul_ratio);
|
||||
actual_error = abs(mul_ratio - fromInteger(max))/fromInteger(max);
|
||||
end
|
||||
|
||||
(* no_implicit_conditions, fire_when_enabled *)
|
||||
rule increment;
|
||||
if (cnt[0] == fromInteger(strobe_every-1))
|
||||
cnt[0] <= 0;
|
||||
else
|
||||
cnt[0] <= cnt[0]+1;
|
||||
endrule
|
||||
// Exited the loop, so either we found a good counter width to hit
|
||||
// the requested error, or we maxed out on a 64-bit counter.
|
||||
if (actual_error > target_error)
|
||||
error(sprintf("cannot construct a strobe running at %0dHz from a main clock of %0dHz without exceeding the target frequency error of %0d%%", target_frequency, clock_frequency, target_error*100));
|
||||
|
||||
method Bool _read();
|
||||
return cnt[0] == 0;
|
||||
endmethod
|
||||
// The loop over-incremented by 1 after finding an acceptable error amount.
|
||||
incr = incr-1;
|
||||
|
||||
method Action reset();
|
||||
cnt[1] <= 0;
|
||||
endmethod
|
||||
// Debug messages, uncomment if you're wondering what the logic
|
||||
// above decided for your main clock and target strobe rate.
|
||||
Bool show_debug = True;
|
||||
if (show_debug) begin
|
||||
Real actual_ratio = fromInteger(max)/fromInteger(incr);
|
||||
Real actual_freq_low = fromInteger(clock_frequency)/fromInteger(ceil(actual_ratio));
|
||||
Real actual_freq_high = fromInteger(clock_frequency)/fromInteger(floor(actual_ratio));
|
||||
Real actual_freq_avg = fromInteger(clock_frequency)/actual_ratio;
|
||||
messageM(sprintf("Strobe at %0dHz given clock at %0dHz:\n count by %0d mod %0d\n strobing every %d to %d cycles\n effective strobe frequency %0.2fHz to %0.2fHz\n avg frequency %0.2fHz (%0.2f%% error)", target_frequency, clock_frequency, incr, max, floor(actual_ratio), ceil(actual_ratio), actual_freq_low, actual_freq_high, actual_freq_avg, actual_error*100));
|
||||
end
|
||||
|
||||
let _ret <- mkStrobeRawRec(UInt#(1) ' (0), incr, max);
|
||||
return _ret;
|
||||
endmodule
|
||||
|
||||
endpackage
|
||||
|
|
|
@ -11,10 +11,12 @@ module mkTB();
|
|||
let cycles <- mkCycleCounter();
|
||||
|
||||
// For this test, we assume we're clocked at 25MHz, and want a
|
||||
// 115_200bps strobe for a serial port. That translates to a strobe
|
||||
// every 217 cycles.
|
||||
let dut <- mkStrobe(25_000_000, 115_200);
|
||||
let want_pulse_every = 217;
|
||||
// 16x115_200bps strobe for a serial port. That translates to a
|
||||
// strobe that happens every 13 or 14 cycles, depending on the
|
||||
// amount of accumulated error from the imprecise frequency
|
||||
// division.
|
||||
let dut <- mkStrobe(25_000_000, 115_200*16);
|
||||
let want_pulse_every = 14;
|
||||
|
||||
function Action check_dut(Bool want);
|
||||
return action
|
||||
|
@ -24,29 +26,39 @@ module mkTB();
|
|||
endaction;
|
||||
endfunction
|
||||
|
||||
function Stmt check_one_cycle();
|
||||
function Stmt check_one_cycle(Integer cycle_len);
|
||||
return seq
|
||||
action
|
||||
$display("%0d: cycle start", cycles.all);
|
||||
check_dut(True);
|
||||
cycles.reset();
|
||||
endaction
|
||||
while (cycles < want_pulse_every)
|
||||
while (cycles < fromInteger(cycle_len))
|
||||
check_dut(False);
|
||||
endseq;
|
||||
endfunction
|
||||
|
||||
runTest(2000,
|
||||
runTest(500,
|
||||
mkTest("Strobe", seq
|
||||
dut.reset();
|
||||
repeat(3) check_one_cycle();
|
||||
check_one_cycle(14);
|
||||
check_one_cycle(14);
|
||||
check_one_cycle(13);
|
||||
check_one_cycle(14);
|
||||
check_one_cycle(13);
|
||||
check_one_cycle(14);
|
||||
check_one_cycle(13);
|
||||
check_one_cycle(14);
|
||||
check_one_cycle(14);
|
||||
check_one_cycle(13);
|
||||
check_one_cycle(14);
|
||||
check_one_cycle(13);
|
||||
|
||||
// Reset should actually reset
|
||||
repeat(10) noAction;
|
||||
par
|
||||
check_dut(False);
|
||||
dut.reset();
|
||||
endpar
|
||||
repeat(3) check_one_cycle();
|
||||
check_one_cycle(14);
|
||||
check_one_cycle(14);
|
||||
endseq));
|
||||
endmodule
|
||||
|
||||
|
|
11
tasks.py
11
tasks.py
|
@ -137,7 +137,7 @@ def build(c, target):
|
|||
return verilog_files
|
||||
|
||||
@task
|
||||
def synth(c, target):
|
||||
def synth(c, target, gui=False):
|
||||
target = resolve_synth_target(target)
|
||||
|
||||
out_info, out_verilog, out_bsc, out_yosys, out_nextpnr = ensure_build_dirs(target, "info", "verilog", "bsc", "yosys", "nextpnr")
|
||||
|
@ -192,11 +192,16 @@ def synth(c, target):
|
|||
pin_map_arg = f"--lpf {pin_map}"
|
||||
else:
|
||||
print(f"WARNING: no pin map at {pin_map}, executing place&route with no constraints")
|
||||
pin_map_arg = f"--lpf-allow-unconstrained --lpf=lib/ulx3s_v20.lpf"
|
||||
pin_map_arg = f"--lpf-allow-unconstrained --lpf=lib/ulx3s_v20.lpf --freq 100"
|
||||
nextpnr_out_json = out_nextpnr / f"{module_name.stem}_routed.json"
|
||||
nextpnr_out = out_nextpnr / module_name.with_suffix(".pnr")
|
||||
nextpnr_log = out_nextpnr / module_name.with_suffix(".log")
|
||||
nextpnr_timing = out_nextpnr / module_name.with_suffix(".timing.log")
|
||||
out = c.run(f"nextpnr-ecp5 --85k --detailed-timing-report -l {nextpnr_log} --report {nextpnr_timing} --json {yosys_json} --package=CABGA381 {pin_map_arg} --speed=6 --textcfg {nextpnr_out}", hide='stderr')
|
||||
if gui:
|
||||
cmd = f"nextpnr-ecp5 --85k --detailed-timing-report --report {nextpnr_timing} --json {yosys_json} --gui --gui-no-aa --package=CABGA381 {pin_map_arg} --speed=6 --textcfg {nextpnr_out}"
|
||||
else:
|
||||
cmd = f"nextpnr-ecp5 --85k --detailed-timing-report -l {nextpnr_log} --report {nextpnr_timing} --json {yosys_json} --write {nextpnr_out_json} --package=CABGA381 {pin_map_arg} --speed=6 --textcfg {nextpnr_out}"
|
||||
out = c.run(cmd)
|
||||
|
||||
print_filtered_paragraphs(out.stderr, "Device utilisation", "Critical path", "Max frequency", "Max delay", common_prefix="Info: ")
|
||||
print(f" PNR : {nextpnr_out}")
|
||||
|
|
Loading…
Reference in New Issue