Add AXIS-Wishbone bridge
This adds the core of the UART-Wishbone bridge. The protocol has a variable-length address phase to help reduce overhead. Multiple in-flight commands are not supported, although this could be resolved with some FIFOs. Signed-off-by: Sean Anderson <seanga2@gmail.com>
This commit is contained in:
parent
0f6d4b166f
commit
75142311f2
|
@ -18,3 +18,6 @@ __pycache__
|
||||||
*.fst
|
*.fst
|
||||||
results.xml
|
results.xml
|
||||||
test_profile.pstat
|
test_profile.pstat
|
||||||
|
|
||||||
|
# doc artifacts
|
||||||
|
*.html
|
||||||
|
|
14
Makefile
14
Makefile
|
@ -2,6 +2,7 @@
|
||||||
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
||||||
|
|
||||||
Q = 1
|
Q = 1
|
||||||
|
ADOC = asciidoctor
|
||||||
SYNTH = yosys
|
SYNTH = yosys
|
||||||
PNR = nextpnr-ice40
|
PNR = nextpnr-ice40
|
||||||
ICARUS = iverilog
|
ICARUS = iverilog
|
||||||
|
@ -124,6 +125,7 @@ endef
|
||||||
|
|
||||||
MODULES += axis_replay_buffer
|
MODULES += axis_replay_buffer
|
||||||
MODULES += axis_mii_tx
|
MODULES += axis_mii_tx
|
||||||
|
MODULES += axis_wb_bridge
|
||||||
MODULES += descramble
|
MODULES += descramble
|
||||||
MODULES += hub
|
MODULES += hub
|
||||||
MODULES += hub_core
|
MODULES += hub_core
|
||||||
|
@ -153,6 +155,17 @@ test: $(addsuffix .fst,$(MODULES)) $(addsuffix .synth.fst,$(MODULES))
|
||||||
.PHONY: asc
|
.PHONY: asc
|
||||||
asc: $(addprefix rtl/,$(addsuffix .asc,$(MODULES)))
|
asc: $(addprefix rtl/,$(addsuffix .asc,$(MODULES)))
|
||||||
|
|
||||||
|
doc/output:
|
||||||
|
mkdir -p $@
|
||||||
|
|
||||||
|
doc/output/%.html: doc/%.adoc doc/docinfo.html | doc/output
|
||||||
|
$(ADOC) -o $@ $<
|
||||||
|
|
||||||
|
DOCS += uart_wb_bridge
|
||||||
|
|
||||||
|
.PHONY: htmldocs
|
||||||
|
htmldocs: $(addprefix doc/output/,$(addsuffix .html,$(DOCS)))
|
||||||
|
|
||||||
CLEAN_EXT := .json .asc .pre .vvp .d .synth.v .place.v .sdf .bin
|
CLEAN_EXT := .json .asc .pre .vvp .d .synth.v .place.v .sdf .bin
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
|
@ -161,3 +174,4 @@ clean:
|
||||||
rm -rf log
|
rm -rf log
|
||||||
rm -f $(addprefix rtl/*,$(CLEAN_EXT))
|
rm -f $(addprefix rtl/*,$(CLEAN_EXT))
|
||||||
rm -f $(addprefix examples/*/*,$(CLEAN_EXT))
|
rm -f $(addprefix examples/*/*,$(CLEAN_EXT))
|
||||||
|
rm -rf doc/output
|
||||||
|
|
|
@ -77,6 +77,15 @@ preamble/SFD and appends a 4-byte FCS to the data. It currently only supports
|
||||||
100M ethernet, although 10M would be easy to add. I have no plans to support
|
100M ethernet, although 10M would be easy to add. I have no plans to support
|
||||||
1000M.
|
1000M.
|
||||||
|
|
||||||
|
=== `axis_wb_bridge`
|
||||||
|
|
||||||
|
This module implements an AXI Stream to Wishbone bridge. This is not a
|
||||||
|
more-typical DMA bridge, where streaming data is written in a fixed pattern.
|
||||||
|
Rather, this module allows interactive or scripted examination and readout
|
||||||
|
of a Wishbone bus. For more details on the protocol implemented by this bridge,
|
||||||
|
refer to the xref:doc/uart_wb_bridge.adoc#protocol[UART-Wishbone Bridge
|
||||||
|
documentation].
|
||||||
|
|
||||||
=== `descramble`
|
=== `descramble`
|
||||||
|
|
||||||
This implements a descrambler as specified in ANSI X3.264-1995 section 7.2.3. It
|
This implements a descrambler as specified in ANSI X3.264-1995 section 7.2.3. It
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/wavedrom/3.1.0/skins/default.js" type="text/javascript"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/wavedrom/3.1.0/wavedrom.min.js" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript">document.addEventListener('DOMContentLoaded', WaveDrom.ProcessAll)</script>
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
= UART-Wishbone Bridge
|
||||||
|
:docinfo: shared
|
||||||
|
|
||||||
|
[[protocol]]
|
||||||
|
== Protocol
|
||||||
|
|
||||||
|
The following sections outline the protocol used to communicate with the UART
|
||||||
|
half of the bridge.
|
||||||
|
|
||||||
|
=== Overview
|
||||||
|
|
||||||
|
The UART protocol uses a request/response format. Each wishbone transaction
|
||||||
|
corresponds to one request and one response. Each request begins with a command
|
||||||
|
byte; an optional, variable-length address; and some data if the request is a
|
||||||
|
write. Each response begins with a status byte, followed by some data if the
|
||||||
|
request was a read. The following diagram shows a successful read:
|
||||||
|
|
||||||
|
++++
|
||||||
|
<script type="WaveDrom">
|
||||||
|
{ signal : [
|
||||||
|
{ name: "rx", wave: "z34444z....", data: "CMD ADDR0 ADDR1 ADDR2 ADDR3" },
|
||||||
|
{ name: "tx", wave: "z......655z", data: "STATUS DATA0 DATA1" },
|
||||||
|
],
|
||||||
|
config: { hscale: 2 },
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
++++
|
||||||
|
|
||||||
|
Similarly, this diagram shows a successful write:
|
||||||
|
|
||||||
|
++++
|
||||||
|
<script type="WaveDrom">
|
||||||
|
{ signal : [
|
||||||
|
{ name: "rx", wave: "z3444455z..", data: "CMD ADDR0 ADDR1 ADDR2 ADDR3 DATA0 DATA1" },
|
||||||
|
{ name: "tx", wave: "z........6z", data: "RESP" },
|
||||||
|
],
|
||||||
|
config: { hscale: 2 },
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
++++
|
||||||
|
|
||||||
|
The bridge contains an internal address register that retains its state between
|
||||||
|
different transactions. It possible to reduce the length of requests by
|
||||||
|
partially modifying the address register.
|
||||||
|
|
||||||
|
=== Requests
|
||||||
|
|
||||||
|
Each request begins with a command byte. The format of the command byte is as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
.Command byte
|
||||||
|
[cols="1,1,4a"]
|
||||||
|
|===
|
||||||
|
| Bit | Name | Description
|
||||||
|
|
||||||
|
| 0 | Clear | Setting this bit clears the address register before modifying
|
||||||
|
it. The address register should always be cleared during the
|
||||||
|
first transaction following a reset.
|
||||||
|
| 1 | Write-Enable | If this bit is set, this request is a write, and a data
|
||||||
|
phase follows the address phase. Otherwise, this request
|
||||||
|
is a read, and there is no data phase.
|
||||||
|
| 2 | Post-Increment | If this bit is set, the address register will be
|
||||||
|
incremented when the transaction completes.
|
||||||
|
| 4:3 | Address length
|
||||||
|
| This field indicates the number of bytes in the subsequent address phase.
|
||||||
|
!===
|
||||||
|
! Value ! Address bytes
|
||||||
|
|
||||||
|
! 0 ! 0 (no address phase)
|
||||||
|
! 1 ! 1
|
||||||
|
! 2 ! 2
|
||||||
|
! 3 ! 4
|
||||||
|
!===
|
||||||
|
| 7:5 | Reserved | Reserved, set to 0.
|
||||||
|
|===
|
||||||
|
|
||||||
|
Following the command byte, there is an optional address phase. The length of
|
||||||
|
the address phase is determined by the command byte. Bytes in the address phase
|
||||||
|
are loaded into the address register. The address is transmitted in big-endian
|
||||||
|
byte order (most-significant byte first). If number of bytes in the address
|
||||||
|
phase is smaller than the size of the address register, the lower bytes in the
|
||||||
|
address register will be replaced, and the upper bytes will not be modified.
|
||||||
|
|
||||||
|
The following table shows the value of each byte in the address register after a
|
||||||
|
particular address phase. Bytes are numbered by the order they are transmitted:
|
||||||
|
|
||||||
|
.Address phase
|
||||||
|
|===
|
||||||
|
| Address bytes | Address[31:24] | Address[23:16] | Address[15:8] | Address[7:0]
|
||||||
|
|
||||||
|
| 0 | Unmodified | Unmodified | Unmodified | Unmodifed
|
||||||
|
| 1 | Unmodified | Unmodified | Unmodified | Byte 0
|
||||||
|
| 2 | Unmodified | Unmodified | Byte 0 | Byte 1
|
||||||
|
| 4 | Byte 0 | Byte 1 | Byte 2 | Byte 3
|
||||||
|
|===
|
||||||
|
|
||||||
|
Finally, there is a data phase if the request is a write. Data is transmitted
|
||||||
|
in big-endian byte order (most-significant byte first).
|
||||||
|
|
||||||
|
Any requests transmitted while the bridge is processing another request will not
|
||||||
|
be handled correctly. This condition is indicated by an overflow status in
|
||||||
|
response to the initial request.
|
||||||
|
|
||||||
|
=== Responses
|
||||||
|
|
||||||
|
Each response begins with a status byte. The format of the status byte is as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
.Status byte
|
||||||
|
[cols="1,1,4a"]
|
||||||
|
|===
|
||||||
|
| Bit | Name | Description
|
||||||
|
|
||||||
|
| 0 | Write Response | If set, the response is for a write and no data phase
|
||||||
|
follows. Otherwise, the response is for a read and a data
|
||||||
|
phase will follow.
|
||||||
|
| 1 | Bus Error | There was bus error when servicing the request, and no data
|
||||||
|
phase will follow. This bit has priority over any data phase
|
||||||
|
implied by the Write Response bit.
|
||||||
|
| 2 | Reserved | Reserved, do not use.
|
||||||
|
| 3 | Overflow | While processing this request, the receive UART overflowed, and
|
||||||
|
one or more request bytes were dropped. The bridge must be
|
||||||
|
reset before issuing the next command.
|
||||||
|
| 7:4 | Reserved | Reserved, do not use.
|
||||||
|
|===
|
||||||
|
|
||||||
|
Finally, there is a data phase if the request was a read. Data is transmitted
|
||||||
|
in big-endian byte order (most-significant byte first).
|
||||||
|
|
||||||
|
=== Resetting
|
||||||
|
|
||||||
|
The bridge and wishbone bus may be reset by sending a character with a framing
|
||||||
|
error (a break) over the serial line. The bridge should be reset before each
|
||||||
|
session in order to bring the bridge into a known state.
|
||||||
|
|
||||||
|
=== Examples
|
||||||
|
|
||||||
|
A read of `0xcafe` from `0x00000123` followed by a write of `0xbabe` to the same
|
||||||
|
address:
|
||||||
|
|
||||||
|
++++
|
||||||
|
<script type="WaveDrom">
|
||||||
|
{ signal : [
|
||||||
|
{ name: "rx", wave: "z344z....355z..", data: "11 01 23 02 ba be" },
|
||||||
|
{ name: "tx", wave: "z....655z....6z", data: "00 ca fe 01" },
|
||||||
|
]}
|
||||||
|
</script>
|
||||||
|
++++
|
||||||
|
|
||||||
|
Reading from `0x80001000`, `0x80002000`, and `0x80002001`:
|
||||||
|
|
||||||
|
++++
|
||||||
|
<script type="WaveDrom">
|
||||||
|
{ signal : [
|
||||||
|
{ name: "rx", wave: "z34444z....344z....3z....", data: "18 80 00 10 00 14 20 00 00" },
|
||||||
|
{ name: "tx", wave: "z......655z....655z..655z", data: "00 d0 0d 00 fe ed 00 fa ce" },
|
||||||
|
]}
|
||||||
|
</script>
|
||||||
|
++++
|
|
@ -0,0 +1,210 @@
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-Only
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
`include "common.vh"
|
||||||
|
|
||||||
|
module axis_wb_bridge (
|
||||||
|
input clk,
|
||||||
|
input rst,
|
||||||
|
|
||||||
|
output reg s_axis_ready,
|
||||||
|
input s_axis_valid,
|
||||||
|
input [7:0] s_axis_data,
|
||||||
|
|
||||||
|
input m_axis_ready,
|
||||||
|
output reg m_axis_valid,
|
||||||
|
output reg [7:0] m_axis_data,
|
||||||
|
|
||||||
|
/* Wishbone */
|
||||||
|
input wb_ack, wb_err,
|
||||||
|
output reg wb_cyc, wb_stb, wb_we,
|
||||||
|
output reg [ADDR_WIDTH - 1:0] wb_addr,
|
||||||
|
output reg [15:0] wb_data_write,
|
||||||
|
input [15:0] wb_data_read,
|
||||||
|
|
||||||
|
input overflow
|
||||||
|
);
|
||||||
|
|
||||||
|
parameter ADDR_WIDTH = 32;
|
||||||
|
generate if (ADDR_WIDTH % 8)
|
||||||
|
$error("Unsupported ADDR_WIDTH");
|
||||||
|
endgenerate
|
||||||
|
/* The data width is not parametric for now */
|
||||||
|
localparam DATA_WIDTH = 16;
|
||||||
|
|
||||||
|
localparam IDLE = 0;
|
||||||
|
localparam ADDR3 = 1;
|
||||||
|
localparam ADDR2 = 2;
|
||||||
|
localparam ADDR1 = 3;
|
||||||
|
localparam ADDR0 = 4;
|
||||||
|
localparam DATA1 = 5;
|
||||||
|
localparam DATA0 = 6;
|
||||||
|
localparam BUS = 7;
|
||||||
|
localparam RESP2 = 8;
|
||||||
|
localparam RESP1 = 9;
|
||||||
|
localparam RESP0 = 10;
|
||||||
|
|
||||||
|
reg s_axis_ready_next, s_axis_valid_last, m_axis_ready_last, m_axis_valid_next;
|
||||||
|
reg [7:0] s_axis_data_last, m_axis_data_next;
|
||||||
|
reg wb_ack_last, wb_err_last;
|
||||||
|
reg wb_stb_next, wb_we_next;
|
||||||
|
reg [ADDR_WIDTH - 1:0] wb_addr_next;
|
||||||
|
reg [15:0] wb_data_write_next, wb_data_latch, wb_data_latch_next;
|
||||||
|
reg [3:0] state, state_next;
|
||||||
|
reg overflow_latch, overflow_latch_next, postinc, postinc_next;
|
||||||
|
|
||||||
|
always @(*) begin
|
||||||
|
s_axis_ready_next = s_axis_ready;
|
||||||
|
m_axis_valid_next = m_axis_valid;
|
||||||
|
m_axis_data_next = 8'bX;
|
||||||
|
|
||||||
|
wb_cyc = wb_stb;
|
||||||
|
wb_stb_next = wb_stb;
|
||||||
|
wb_we_next = wb_we;
|
||||||
|
wb_addr_next = wb_addr;
|
||||||
|
wb_data_write_next = wb_data_write;
|
||||||
|
if (wb_stb && (wb_err || wb_ack))
|
||||||
|
wb_data_latch_next = wb_data_read;
|
||||||
|
else
|
||||||
|
wb_data_latch_next = wb_data_latch;
|
||||||
|
|
||||||
|
state_next = state;
|
||||||
|
postinc_next = postinc;
|
||||||
|
overflow_latch_next = overflow_latch || overflow;
|
||||||
|
|
||||||
|
case (state)
|
||||||
|
IDLE: if (s_axis_valid_last) begin
|
||||||
|
if (s_axis_data_last[0])
|
||||||
|
wb_addr_next = {ADDR_WIDTH{1'b0}};
|
||||||
|
wb_we_next = s_axis_data_last[1];
|
||||||
|
postinc_next = s_axis_data_last[2];
|
||||||
|
case (s_axis_data_last[4:3])
|
||||||
|
2'd3: state_next = ADDR3;
|
||||||
|
2'd2: state_next = ADDR1;
|
||||||
|
2'd1: state_next = ADDR0;
|
||||||
|
2'd0: if (wb_we_next) begin
|
||||||
|
state_next = DATA1;
|
||||||
|
end else begin
|
||||||
|
state_next = BUS;
|
||||||
|
wb_stb_next = 1;
|
||||||
|
s_axis_ready_next = 0;
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
ADDR3: if (s_axis_valid_last) begin
|
||||||
|
if (ADDR_WIDTH >= 32)
|
||||||
|
wb_addr_next[31:24] = s_axis_data_last;
|
||||||
|
state_next = ADDR2;
|
||||||
|
end
|
||||||
|
ADDR2: if (s_axis_valid_last) begin
|
||||||
|
if (ADDR_WIDTH >= 24)
|
||||||
|
wb_addr_next[23:16] = s_axis_data_last;
|
||||||
|
state_next = ADDR1;
|
||||||
|
end
|
||||||
|
ADDR1: if (s_axis_valid_last) begin
|
||||||
|
if (ADDR_WIDTH >= 16)
|
||||||
|
wb_addr_next[15:8] = s_axis_data_last;
|
||||||
|
state_next = ADDR0;
|
||||||
|
end
|
||||||
|
ADDR0: if (s_axis_valid_last) begin
|
||||||
|
if (ADDR_WIDTH >= 8)
|
||||||
|
wb_addr_next[7:0] = s_axis_data_last;
|
||||||
|
if (wb_we) begin
|
||||||
|
state_next = DATA1;
|
||||||
|
end else begin
|
||||||
|
state_next = BUS;
|
||||||
|
wb_stb_next = 1;
|
||||||
|
s_axis_ready_next = 0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
DATA1: if (s_axis_valid_last) begin
|
||||||
|
wb_data_write_next = { wb_data_write[7:0], s_axis_data_last };
|
||||||
|
state_next = DATA0;
|
||||||
|
end
|
||||||
|
DATA0: if(s_axis_valid_last) begin
|
||||||
|
wb_data_write_next = { wb_data_write[7:0], s_axis_data_last };
|
||||||
|
state_next = BUS;
|
||||||
|
wb_stb_next = 1;
|
||||||
|
s_axis_ready_next = 0;
|
||||||
|
end
|
||||||
|
BUS: if (wb_ack || wb_err) begin
|
||||||
|
wb_stb_next = 0;
|
||||||
|
wb_addr_next[7:0] = wb_addr[7:0] + postinc;
|
||||||
|
m_axis_valid_next = 1;
|
||||||
|
m_axis_data_next = { 4'b0, overflow_latch_next, 1'b0, wb_err, wb_we };
|
||||||
|
overflow_latch_next = 0;
|
||||||
|
state_next = wb_we || wb_err ? RESP0 : RESP2;
|
||||||
|
end
|
||||||
|
RESP2: if (m_axis_ready_last) begin
|
||||||
|
m_axis_data_next = wb_data_latch[15:8];
|
||||||
|
state_next = RESP1;
|
||||||
|
end
|
||||||
|
RESP1: if (m_axis_ready_last) begin
|
||||||
|
m_axis_data_next = wb_data_latch[7:0];
|
||||||
|
state_next = RESP0;
|
||||||
|
end
|
||||||
|
RESP0: if (m_axis_ready_last) begin
|
||||||
|
m_axis_valid_next = 0;
|
||||||
|
s_axis_ready_next = 1;
|
||||||
|
state_next = IDLE;
|
||||||
|
end
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
s_axis_data_last <= s_axis_data;
|
||||||
|
m_axis_data <= m_axis_data_next;
|
||||||
|
wb_we <= wb_we_next;
|
||||||
|
wb_addr <= wb_addr_next;
|
||||||
|
wb_data_write <= wb_data_write_next;
|
||||||
|
wb_data_latch <= wb_data_latch_next;
|
||||||
|
postinc <= postinc_next;
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(posedge clk, posedge rst) begin
|
||||||
|
if (rst) begin
|
||||||
|
s_axis_ready <= 1;
|
||||||
|
s_axis_valid_last <= 0;
|
||||||
|
m_axis_ready_last <= 0;
|
||||||
|
m_axis_valid <= 0;
|
||||||
|
wb_ack_last <= 0;
|
||||||
|
wb_err_last <= 0;
|
||||||
|
wb_stb <= 0;
|
||||||
|
state <= IDLE;
|
||||||
|
overflow_latch <= 0;
|
||||||
|
end else begin
|
||||||
|
s_axis_ready <= s_axis_ready_next;
|
||||||
|
s_axis_valid_last <= s_axis_valid;
|
||||||
|
m_axis_ready_last <= m_axis_ready;
|
||||||
|
m_axis_valid <= m_axis_valid_next;
|
||||||
|
wb_ack_last <= wb_ack;
|
||||||
|
wb_err_last <= wb_err;
|
||||||
|
wb_stb <= wb_stb_next;
|
||||||
|
state <= state_next;
|
||||||
|
overflow_latch <= overflow_latch_next;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
`ifndef SYNTHESIS
|
||||||
|
reg [255:0] state_text;
|
||||||
|
|
||||||
|
always @(*) begin
|
||||||
|
case (state)
|
||||||
|
IDLE: state_text = "IDLE";
|
||||||
|
ADDR3: state_text = "ADDR3";
|
||||||
|
ADDR2: state_text = "ADDR2";
|
||||||
|
ADDR1: state_text = "ADDR1";
|
||||||
|
ADDR0: state_text = "ADDR0";
|
||||||
|
DATA1: state_text = "DATA1";
|
||||||
|
DATA0: state_text = "DATA0";
|
||||||
|
BUS: state_text = "BUS";
|
||||||
|
RESP2: state_text = "RESP2";
|
||||||
|
RESP1: state_text = "RESP1";
|
||||||
|
RESP0: state_text = "RESP0";
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
`endif
|
||||||
|
|
||||||
|
endmodule
|
|
@ -0,0 +1,166 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-Only
|
||||||
|
# Copyright (C) 2023 Sean Anderson <seanga2@gmail.com>
|
||||||
|
|
||||||
|
import cocotb
|
||||||
|
from cocotb.binary import BinaryValue
|
||||||
|
from cocotb.clock import Clock
|
||||||
|
from cocotb.triggers import FallingEdge, Timer
|
||||||
|
|
||||||
|
from .axis_replay_buffer import send_packet, recv_packet
|
||||||
|
from .mdio import wb_read, wb_write, wb_err
|
||||||
|
from .util import BIT, GENMASK
|
||||||
|
|
||||||
|
CMD_CLEAR = BIT(0)
|
||||||
|
CMD_WE = BIT(1)
|
||||||
|
CMD_POSTINC = BIT(2)
|
||||||
|
CMD_ADDR0 = 0x00
|
||||||
|
CMD_ADDR8 = 0x08
|
||||||
|
CMD_ADDR16 = 0x10
|
||||||
|
CMD_ADDR32 = 0x18
|
||||||
|
|
||||||
|
STATUS_WE = BIT(0)
|
||||||
|
STATUS_ERR = BIT(1)
|
||||||
|
STATUS_OVERFLOW = BIT(3)
|
||||||
|
|
||||||
|
class Encoder:
|
||||||
|
def __init__(self):
|
||||||
|
self.last_addr = None
|
||||||
|
|
||||||
|
def encode(self, addr, data=None, postinc=False):
|
||||||
|
cmd = CMD_POSTINC if postinc else 0
|
||||||
|
if data is None:
|
||||||
|
data_bytes = ()
|
||||||
|
else:
|
||||||
|
cmd |= CMD_WE
|
||||||
|
data_bytes = data.to_bytes(2, 'big')
|
||||||
|
|
||||||
|
if self.last_addr is None:
|
||||||
|
cmd |= CMD_CLEAR
|
||||||
|
self.last_addr = 0
|
||||||
|
|
||||||
|
def addr_len(last):
|
||||||
|
if (addr ^ last) & ~GENMASK(15, 0):
|
||||||
|
return 4
|
||||||
|
if (addr ^ last) & GENMASK(15, 8):
|
||||||
|
return 2
|
||||||
|
if (addr ^ last) & GENMASK(7, 0):
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
len_zero = addr_len(0)
|
||||||
|
len_last = addr_len(self.last_addr)
|
||||||
|
if len_zero < len_last:
|
||||||
|
addr_len = len_zero
|
||||||
|
cmd |= CMD_CLEAR
|
||||||
|
else:
|
||||||
|
addr_len = len_last
|
||||||
|
|
||||||
|
addr_bytes = (addr & GENMASK(addr_len * 8 - 1, 0)).to_bytes(addr_len, 'big')
|
||||||
|
if addr_len == 4:
|
||||||
|
cmd |= CMD_ADDR32
|
||||||
|
elif addr_len == 2:
|
||||||
|
cmd |= CMD_ADDR16
|
||||||
|
elif addr_len == 1:
|
||||||
|
cmd |= CMD_ADDR8
|
||||||
|
else:
|
||||||
|
cmd |= CMD_ADDR0
|
||||||
|
|
||||||
|
self.last_addr = (addr & ~GENMASK(7, 0)) | ((addr + postinc) & GENMASK(7, 0))
|
||||||
|
return (cmd, *addr_bytes, *data_bytes)
|
||||||
|
|
||||||
|
@cocotb.test(timeout_time=10, timeout_unit='us')
|
||||||
|
async def test_bridge(bridge):
|
||||||
|
bridge.clk.value = BinaryValue('Z')
|
||||||
|
bridge.rst.value = 1
|
||||||
|
bridge.s_axis_valid.value = 0
|
||||||
|
bridge.m_axis_ready.value = 1
|
||||||
|
bridge.wb_ack.value = 0
|
||||||
|
bridge.wb_err.value = 0
|
||||||
|
bridge.overflow.value = 0
|
||||||
|
|
||||||
|
await Timer(1)
|
||||||
|
bridge.rst.value = 0
|
||||||
|
await cocotb.start(Clock(bridge.clk, 8, units='ns').start())
|
||||||
|
await FallingEdge(bridge.clk)
|
||||||
|
|
||||||
|
s_axis = {
|
||||||
|
'clk': bridge.clk,
|
||||||
|
'ready': bridge.s_axis_ready,
|
||||||
|
'valid': bridge.s_axis_valid,
|
||||||
|
'data': bridge.s_axis_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
m_axis = {
|
||||||
|
'clk': bridge.clk,
|
||||||
|
'ready': bridge.m_axis_ready,
|
||||||
|
'valid': bridge.m_axis_valid,
|
||||||
|
'data': bridge.m_axis_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
wb = {
|
||||||
|
'clk': bridge.clk,
|
||||||
|
'ack': bridge.wb_ack,
|
||||||
|
'err': bridge.wb_err,
|
||||||
|
'cyc': bridge.wb_cyc,
|
||||||
|
'stb': bridge.wb_stb,
|
||||||
|
'we': bridge.wb_we,
|
||||||
|
'addr': bridge.wb_addr,
|
||||||
|
'data_write': bridge.wb_data_write,
|
||||||
|
'data_read': bridge.wb_data_read,
|
||||||
|
}
|
||||||
|
|
||||||
|
e = Encoder()
|
||||||
|
|
||||||
|
async def read(addr, data, postinc=False, resp=0):
|
||||||
|
await send_packet(s_axis, e.encode(addr, None, postinc))
|
||||||
|
|
||||||
|
bridge.overflow.value = bool(resp & STATUS_OVERFLOW)
|
||||||
|
if resp & STATUS_ERR:
|
||||||
|
await wb_err(wb)
|
||||||
|
bridge.overflow.value = 0
|
||||||
|
await recv_packet(m_axis, (resp,))
|
||||||
|
else:
|
||||||
|
await wb_read(wb, addr, data)
|
||||||
|
bridge.overflow.value = 0
|
||||||
|
await recv_packet(m_axis, (resp, *data.to_bytes(2, 'big')))
|
||||||
|
|
||||||
|
async def write(addr, data, postinc=False, resp=STATUS_WE):
|
||||||
|
await send_packet(s_axis, e.encode(addr, data, postinc))
|
||||||
|
|
||||||
|
bridge.overflow.value = bool(resp & STATUS_OVERFLOW)
|
||||||
|
if resp & STATUS_ERR:
|
||||||
|
await wb_err(wb)
|
||||||
|
else:
|
||||||
|
await wb_write(wb, addr, data)
|
||||||
|
bridge.overflow.value = 0
|
||||||
|
|
||||||
|
await recv_packet(m_axis, (resp,))
|
||||||
|
|
||||||
|
for f in read, write:
|
||||||
|
await f(0x01234567, 0x89ab)
|
||||||
|
await f(0x01234567, 0xcdef)
|
||||||
|
await f(0x012345fe, 1, True)
|
||||||
|
await f(0x012345ff, 2, True)
|
||||||
|
await f(0x01234500, 3)
|
||||||
|
await f(0x012345ff, 4, True)
|
||||||
|
await f(0x01234600, 5)
|
||||||
|
await f(0x0123ffff, 6)
|
||||||
|
await f(0x01ffffff, 7)
|
||||||
|
await f(0xffffffff, 8)
|
||||||
|
await f(0x0000ffff, 9)
|
||||||
|
await f(0x000000ff, 10)
|
||||||
|
await f(0x00000000, 11)
|
||||||
|
|
||||||
|
# fast back-to-back
|
||||||
|
recv = await cocotb.start(recv_packet(m_axis, (STATUS_WE, 0, 0, 4)))
|
||||||
|
await send_packet(s_axis, e.encode(1, 2))
|
||||||
|
await wb_write(wb, 1, 2)
|
||||||
|
await send_packet(s_axis, e.encode(3))
|
||||||
|
await wb_read(wb, 3, 4)
|
||||||
|
await recv
|
||||||
|
|
||||||
|
# bus error/overflow
|
||||||
|
await write(5, 6, resp=STATUS_WE | STATUS_ERR | STATUS_OVERFLOW)
|
||||||
|
await read(7, 8, resp=STATUS_ERR)
|
||||||
|
await read(9, 10, resp=STATUS_OVERFLOW)
|
||||||
|
await write(11, 12)
|
Loading…
Reference in New Issue