add ethernet_tx/rx, semi-working in hardware

This commit is contained in:
Fischer Moseley 2023-04-24 23:13:30 -04:00
parent 0bb3f9c74a
commit c507f795f1
34 changed files with 1381 additions and 302 deletions

View File

@ -31,6 +31,11 @@ auto_gen:
# Functional Simulation
functional_sim: io_core_tb logic_analyzer_tb bit_fifo_tb bridge_rx_tb bridge_tx_tb lut_ram_tb
mac_tb:
iverilog -g2012 -o sim.out -y src/manta test/functional_sim/mac_tb.sv
vvp sim.out
rm sim.out
block_memory_tb:
iverilog -g2012 -o sim.out -y src/manta test/functional_sim/block_memory_tb.sv
vvp sim.out

View File

@ -1,20 +0,0 @@
---
cores:
ether_la:
type: logic_analyzer
sample_depth: 17000
trigger_loc: 50
probes:
eth_crsdv: 1
eth_rxd: 2
eth_txen: 1
eth_txd: 2
triggers:
- eth_crsdv RISING
uart:
port: "auto"
baudrate: 115200
clock_freq: 50000000

View File

@ -1,24 +0,0 @@
from manta import Manta
from scapy.all import *
m = Manta('manta.yaml')
def set_led(val):
src_mac = "00:00:00:00:00:00"
dst_mac = "FF:FF:FF:FF:FF:FF"
ifc = "en8"
mypkt = Ether()
mypkt.src = src_mac
mypkt.dst = dst_mac
mypkt.type = 0x1234
msg = b'\x56\x78' + val.to_bytes(2, 'big')
mypkt = mypkt / msg
mypkt.load = msg
sendpfast(mypkt, iface=ifc)
while(True):
set_led(0)

View File

@ -1,111 +0,0 @@
`default_nettype none
`timescale 1ns/1ps
module mac_tx_tb();
logic ethclk;
logic rst;
always begin
#5;
ethclk = !ethclk;
end
/* batteries... */
logic eth_crsdv;
logic[1:0] eth_rxd;
/* ether -> { cksum, bitorder } */
logic[1:0] ether_axiod;
logic ether_axiov;
/* cksum -> top_level */
logic cksum_done, cksum_kill;
/* bitorder -> firewall */
logic[1:0] bitorder_axiod;
logic bitorder_axiov;
/* firewall -> aggregate */
logic[1:0] firewall_axiod;
logic firewall_axiov;
/* aggregate output */
logic[31:0] aggregate_axiod;
logic aggregate_axiov;
/* and here's the pipeline... */
logic eth_crsdv_mtx;
logic [1:0] eth_rxd_mtx;
logic mtx_start;
mac_tx mtx (
.clk(ethclk),
.data(16'h5679),
.start(mtx_start),
.txen(eth_crsdv_mtx),
.txd(eth_rxd_mtx));
ether e(
.clk(ethclk),
.rst(rst),
.rxd(eth_rxd_mtx),
.crsdv(eth_crsdv_mtx),
.axiov(ether_axiov),
.axiod(ether_axiod));
bitorder b(
.clk(ethclk),
.rst(rst),
.axiiv(ether_axiov),
.axiid(ether_axiod),
.axiov(bitorder_axiov),
.axiod(bitorder_axiod));
firewall f(
.clk(ethclk),
.rst(rst),
.axiiv(bitorder_axiov),
.axiid(bitorder_axiod),
.axiov(firewall_axiov),
.axiod(firewall_axiod));
aggregate a(
.clk(ethclk),
.rst(rst),
.axiiv(firewall_axiov),
.axiid(firewall_axiod),
.axiov(aggregate_axiov),
.axiod(aggregate_axiod));
cksum c(
.clk(ethclk),
.rst(rst),
.axiiv(ether_axiov),
.axiid(ether_axiod),
.done(cksum_done),
.kill(cksum_kill));
initial begin
ethclk = 0;
$dumpfile("mac_tx_tb.vcd");
$dumpvars(0, mac_tx_tb);
rst = 0;
mtx_start = 0;
#10;
rst = 1;
#10;
rst = 0;
#10;
mtx_start = 1;
#10000;
$finish();
end
endmodule
`default_nettype wire

View File

@ -1,49 +0,0 @@
module bto7s(
input wire [3:0] x_in,
output logic [6:0] s_out);
logic sa, sb, sc, sd, se, sf, sg;
assign s_out = {sg, sf, se, sd, sc, sb, sa};
// array of bits that are "one hot" with numbers 0 through 15
logic [15:0] num;
assign num[0] = ~x_in[3] && ~x_in[2] && ~x_in[1] && ~x_in[0];
assign num[1] = ~x_in[3] && ~x_in[2] && ~x_in[1] && x_in[0];
assign num[2] = x_in == 4'd2;
assign num[3] = x_in == 4'd3;
assign num[4] = x_in == 4'd4;
assign num[5] = x_in == 4'd5;
assign num[6] = x_in == 4'd6;
assign num[7] = x_in == 4'd7;
assign num[8] = x_in == 4'd8;
assign num[9] = x_in == 4'd9;
assign num[10] = x_in == 4'd10;
assign num[11] = x_in == 4'd11;
assign num[12] = x_in == 4'd12;
assign num[13] = x_in == 4'd13;
assign num[14] = x_in == 4'd14;
assign num[15] = x_in == 4'd15;
/* you could also do this with generation, like this:
*
* genvar i;
* generate
* for (i=0; i<16; i=i+1)begin
* assign num[i] = (x_in == i);
* end
* endgenerate
*/
/* assign the seven output segments, sa through sg, using a "sum of products"
* approach and the diagram above.
*/
assign sa = num[0] || num[2] || num[3] || num[5] || num[6] || num[7] || num[8] || num[9] || num[10] || num[12] ||num[14] ||num[15];
assign sb = num[0] || num[1] || num[2] || num[3] || num[4] || num[7] || num[8] || num[9] || num[10] || num[13];
assign sc = num[0] || num[1] || num[3] || num[4] || num[5] || num[6] || num[7] || num[8] || num[9] || num[10] || num[11] || num[13];
assign sd = num[0] || num[2] || num[3] || num[5] || num[6] || num[8] || num[9] || num[11] || num[12] || num[13] || num[14];
assign se = num[0] || num[2] || num[6] || num[8] || num[10] || num[11] || num[12] || num[13] || num[14] || num[15];
assign sf = num[0] || num[4] || num[5] || num[6] || num[8] || num[9] || num[10] || num[11] || num[12] || num[14] || num[15];
assign sg = num[2] || num[3] || num[4] || num[5] || num[6] || num[8] || num[9] || num[10] || num[11] || num[13] || num[14] ||num[15];
endmodule

View File

@ -1,45 +0,0 @@
module seven_segment_controller #(parameter COUNT_TO = 100000)
( input wire clk_in,
input wire rst_in,
input wire [31:0] val_in,
output logic[6:0] cat_out,
output logic[7:0] an_out
);
logic[7:0] segment_state;
logic[31:0] segment_counter;
logic [3:0] routed_vals;
logic [6:0] led_out;
bto7s mbto7s (.x_in(routed_vals), .s_out(led_out));
assign cat_out = ~led_out;
assign an_out = ~segment_state;
always_comb begin
case(segment_state)
8'b0000_0001: routed_vals = val_in[3:0];
8'b0000_0010: routed_vals = val_in[7:4];
8'b0000_0100: routed_vals = val_in[11:8];
8'b0000_1000: routed_vals = val_in[15:12];
8'b0001_0000: routed_vals = val_in[19:16];
8'b0010_0000: routed_vals = val_in[23:20];
8'b0100_0000: routed_vals = val_in[27:24];
8'b1000_0000: routed_vals = val_in[31:28];
default: routed_vals = val_in[3:0];
endcase
end
always_ff @(posedge clk_in)begin
if (rst_in)begin
segment_state <= 8'b0000_0001;
segment_counter <= 32'b0;
end else begin
if (segment_counter == COUNT_TO)begin
segment_counter <= 32'd0;
segment_state <= {segment_state[6:0],segment_state[7]};
end else begin
segment_counter <= segment_counter +1;
end
end
end
endmodule //seven_segment_controller

View File

@ -0,0 +1,10 @@
---
cores:
my_lut_ram:
type: lut_ram
size: 64
uart:
port: "auto"
baudrate: 115200
clock_freq: 100000000

View File

@ -0,0 +1,12 @@
from manta import Manta
from random import randint
m = Manta('manta.yaml')
for addr in range(m.my_lut_ram.size):
write_data = randint(0, (2**16)-1)
m.my_lut_ram.write(addr, write_data)
read_data = m.my_lut_ram.read(addr)
print(f"test addr: {addr} with data: {write_data}")
print(f" -> correct data received on readback?: {write_data == read_data}")

View File

@ -0,0 +1,20 @@
from manta import Manta
from scapy.all import *
m = Manta('manta.yaml')
src_mac = "00:00:00:00:00:00"
dst_mac = "FF:FF:FF:FF:FF:FF"
ifc = "en8"
mypkt = Ether()
mypkt.src = src_mac
mypkt.dst = dst_mac
mypkt.type = 0x0002
msg = b'\x00\x00'
mypkt = mypkt / msg
mypkt.load = msg
p = srp(mypkt, iface=ifc)
p.show()

View File

@ -13,7 +13,7 @@
* of your module!
*/
`define AGR_MAX 32
`define AGR_MAX 48
`define AGR_SHOW 64
module aggregate(clk, rst, axiid, axiiv, axiod, axiov);
@ -36,9 +36,9 @@ module aggregate(clk, rst, axiid, axiiv, axiod, axiov);
* just the first 32 bits of the incoming transmission,
* asserted for a single cycle
*/
output logic[31:0] axiod;
output logic[47:0] axiod;
output logic axiov;
/* A quick and dirty counter. As long as this is below
* 32, we'll dump packets into the AXI output data buffer.
* Once the counter gets to AGR_MAX, we'll assert AXI valid.
@ -56,7 +56,7 @@ module aggregate(clk, rst, axiid, axiiv, axiod, axiov);
always_ff @(posedge clk) begin: AXIOD
if (rst || !axiiv) axiod <= 32'b0;
else if (counter < `AGR_MAX && axiiv)
axiod[`AGR_MAX - counter - 2 +: 2] = axiid;
axiod[`AGR_MAX - counter - 2 +: 2] <= axiid;
end
endmodule

View File

@ -1,15 +1,3 @@
/* The catsoop checker does not like to see `timescale declarations
* in your code. However, we obviously need them when we synthesize
* for iverilog - they tell iverilog that #5 means 5ns, for example.
* Without these iverilog has no basis for how long *stuff* should
* wait for when you use #, so it blows up!
*
* When you use the catsoop checker, it `defines the CATSOOP macro.
* So we can check whether CATSOOP is defined or not - if it isn't,
* then we'll put the timescale thing in so code works right on your
* system.
*/
`default_nettype none
`timescale 1ns / 1ps
@ -37,7 +25,7 @@
`define EF_IDLE 3'b000
`define EF_PREAM 3'b001
`define EF_DATA 3'b011
`define EF_BAD 3'b101
`define EF_BAD 3'b101
`define PREAM_BITS 64
`define PREAM_SIZE (`PREAM_BITS / 2)
@ -68,7 +56,7 @@ module ether(clk, rst, rxd, crsdv, axiov, axiod);
* So just use this input to determine whether valid
* data is on the line coming in.
*/
input logic crsdv;
input logic crsdv;
/* Receive Data (RXD): If crsdv is high, receives
* two bits off the wire. Otherwise, undefined
@ -108,7 +96,7 @@ module ether(clk, rst, rxd, crsdv, axiov, axiod);
preamok = crsdv && rxd == preamex;
end
always @(*) start = crsdv && rxd != `PREAM_FIRST;
always @(posedge clk) begin: COUNT

View File

@ -0,0 +1,40 @@
`default_nettype none
`timescale 1ns/1ps
module ethernet_rx (
input wire clk,
input wire crsdv,
input wire [1:0] rxd,
output reg [15:0] addr_o,
output reg [15:0] wdata_o,
output reg rw_o,
output reg valid_o
);
// we know if the packet is a read or write
// based on the ethertype.
reg [15:0] ethertype;
reg [31:0] data;
reg valid;
mac_rx mrx (
.clk(clk),
.crsdv(crsdv),
.rxd(rxd),
.ethertype(ethertype),
.data(data),
.valid(valid));
assign addr_o = data[31:16];
assign wdata_o = data[15:0];
assign rw_o = (ethertype == 4);
assign valid_o = valid;
endmodule
`default_nettype wire

View File

@ -0,0 +1,27 @@
`default_nettype none
`timescale 1ns/1ps
module ethernet_tx(
input wire clk,
output reg txen,
output reg [1:0] txd,
input wire [15:0] rdata_i,
input wire rw_i,
input wire valid_i
);
mac_tx mtx (
.clk(clk),
.data(rdata_i),
.ethertype(16'h2),
.start(~rw_i && valid_i),
.txen(txen),
.txd(txd));
endmodule
`default_nettype wire

View File

@ -32,7 +32,7 @@
`define FW_DESTSTART 0
`define FW_DESTEND (`FW_DESTSTART + 48)
`define FW_DATASTART (48 + 48 + 16)
`define FW_DATASTART (48 + 48)
module firewall(clk, rst, axiiv, axiid, axiov, axiod);

View File

@ -0,0 +1,116 @@
`default_nettype none
`timescale 1ns/1ps
module mac_rx (
input wire clk,
input wire crsdv,
input wire [1:0] rxd,
output reg [15:0] ethertype,
output reg [31:0] data,
output reg valid);
// TODO: rewrite modules to not need external reset
reg rst = 1;
always @(posedge clk) rst <= 0;
/* ether -> { cksum, bitorder } */
reg[1:0] ether_axiod;
reg ether_axiov;
ether e(
.clk(clk),
.rst(rst),
.rxd(rxd),
.crsdv(crsdv),
.axiov(ether_axiov),
.axiod(ether_axiod));
/* bitorder -> firewall */
reg[1:0] bitorder_axiod;
reg bitorder_axiov;
bitorder b(
.clk(clk),
.rst(rst),
.axiiv(ether_axiov),
.axiid(ether_axiod),
.axiov(bitorder_axiov),
.axiod(bitorder_axiod));
/* firewall -> aggregate */
reg[1:0] firewall_axiod;
reg firewall_axiov;
firewall f(
.clk(clk),
.rst(rst),
.axiiv(bitorder_axiov),
.axiid(bitorder_axiod),
.axiov(firewall_axiov),
.axiod(firewall_axiod));
/* aggregate output */
reg[47:0] aggregate_axiod;
reg aggregate_axiov;
aggregate a(
.clk(clk),
.rst(rst),
.axiiv(firewall_axiov),
.axiid(firewall_axiod),
.axiov(aggregate_axiov),
.axiod(aggregate_axiod));
/* cksum -> top_level */
reg cksum_done;
reg cksum_kill;
cksum c(
.clk(clk),
.rst(rst),
.axiiv(ether_axiov),
.axiid(ether_axiod),
.done(cksum_done),
.kill(cksum_kill));
// state machine
localparam IDLE = 0;
localparam WAIT_FOR_DATA = 1;
localparam WAIT_FOR_FCS = 2;
reg [1:0] state = IDLE;
initial valid = 0;
initial data = 0;
always @(posedge clk) begin
valid <= 0;
if(state == IDLE) begin
if(crsdv) state <= WAIT_FOR_DATA;
end
else if(state == WAIT_FOR_DATA) begin
if(aggregate_axiov) begin
state <= WAIT_FOR_FCS;
ethertype <= aggregate_axiod[47:32];
data <= aggregate_axiod[31:0];
end
// if aggregate never gives us data, go back to idle when the packet ends
else if(cksum_done) state <= IDLE;
end
else if(state == WAIT_FOR_FCS) begin
if(cksum_done) begin
state <= IDLE;
valid <= ~cksum_kill;
end
end
end
endmodule
`default_nettype wire

View File

@ -6,6 +6,7 @@ module mac_tx (
// TODO: make this variable width
input wire [15:0] data,
input wire [15:0] ethertype,
input wire start,
output reg txen,
@ -16,7 +17,6 @@ module mac_tx (
localparam SFD = 8'b11010101;
parameter SRC_MAC = 48'h69_69_69_69_69_69;
parameter DST_MAC = 48'hFF_FF_FF_FF_FF_FF;
parameter ETHERTYPE = 16'h1234;
// all lengths are in units of dibits, hence all the mulitplies by four
localparam PREAMBLE_LEN = 7 * 4;
@ -202,7 +202,7 @@ module mac_tx (
ETHERTYPE_STATE: begin
bitorder_axiiv = 1;
bitorder_axiid = ETHERTYPE[2*(ETHERTYPE_LEN-counter)-1-:2];
bitorder_axiid = ethertype[2*(ETHERTYPE_LEN-counter)-1-:2];
txen = bitorder_axiov;
txd = bitorder_axiod;
end

View File

@ -30,40 +30,12 @@ module top_level (
manta manta_inst (
.clk(clk_50mhz),
.rx(uart_txd_in),
.tx(uart_rxd_out),
.eth_crsdv(eth_crsdv),
.eth_rxd(eth_rxd),
.eth_txen(eth_txen),
.eth_txd(eth_txd));
// packet_blaster_9k pb9k (
// .clk(clk_50mhz),
// .rst(btnc),
// //.src_mac(48'h69_2C_08_30_75_FD),
// .src_mac(48'b00_00_00_00_00_00),
// .dst_mac(48'hFF_FF_FF_FF_FF_FF),
// .data(16'h5678),
// .start(btnd),
// .txen(eth_txen),
// .txd(eth_txd));
mac_tx mtx (
.clk(clk_50mhz),
.data(sw),
.start(btnd),
.crsdv(eth_crsdv),
.rxd(eth_rxd),
.txen(eth_txen),
.txd(eth_txd));
endmodule
`default_nettype wire

64
src/manta/aggregate.v Normal file
View File

@ -0,0 +1,64 @@
`default_nettype none
`timescale 1ns / 1ps
/* Aggregates the first 64 bits of an incoming
* Ethernet transmission (thus shedding the FCS
* and anything else extraneous) and outputs the
* first 32 bits on an AXI bus for a single cycle.
* If the packet is not at least 64 bits long,
* nothing happens
*
* This value can then be fed into the seven
* segment display to verify proper operation
* of your module!
*/
`define AGR_MAX 48
`define AGR_SHOW 64
module aggregate(clk, rst, axiid, axiiv, axiod, axiov);
/* batteries */
input logic clk, rst;
/* AXI input valid, and AXI input data from the
* Ethernet pipeline. Comprises only data, i.e.
* source/destination/ethertype are omitted via
* previous stages in the pipeline. Also technically
* comprises the FCS, but we assume that the actual
* data in the ethernet frame is >= 32 bits long
* so we'll lose the FCS in this stage by design
*/
input logic[1:0] axiid;
input logic axiiv;
/* AXI output valid, and AXI output data. Comprises
* just the first 32 bits of the incoming transmission,
* asserted for a single cycle
*/
output logic[47:0] axiod;
output logic axiov;
/* A quick and dirty counter. As long as this is below
* 32, we'll dump packets into the AXI output data buffer.
* Once the counter gets to AGR_MAX, we'll assert AXI valid.
* Then we'll hang until axiiv drops
*/
logic[31:0] counter;
assign axiov = counter == `AGR_SHOW;
always_ff @(posedge clk) begin: COUNTER
if (rst || !axiiv) counter <= 32'b0;
else counter <= counter + 2;
end
always_ff @(posedge clk) begin: AXIOD
if (rst || !axiiv) axiod <= 32'b0;
else if (counter < `AGR_MAX && axiiv)
axiod[`AGR_MAX - counter - 2 +: 2] <= axiid;
end
endmodule
`default_nettype wire

138
src/manta/bitorder.v Normal file
View File

@ -0,0 +1,138 @@
`default_nettype none
`timescale 1ns / 1ps
/* Ethernet sends packets in least significant
* bit order, most significant byte order. This
* module is responsible for changing that to
* make things legible - in particular, we convert
* from LSb/MSB to MSb/MSB.
*/
`define BO_SENDA 2'b00
`define BO_SENDB 2'b01
`define BO_EMPTYA 2'b10
`define BO_EMPTYB 2'b11
module bitorder(clk, rst, axiiv, axiid, axiod, axiov);
/* batteries */
input logic clk, rst;
/* AXI input valid and AXI input data
* from the module upstream from us in the
* pipeline - that being the physical layer
* This will transport bits from off the wire
* in least significant bit first order
*/
input logic[1:0] axiid;
input logic axiiv;
/* AXI output valid and AXI output data
* Transports the correctly bit-ordered Ethernet
* data (most significant bit first) up the pipeline
* for further processing...
*/
output logic[1:0] axiod;
output logic axiov;
/* Two registers to hold data coming in off the wire,
* byte by byte. This is where we'll buffer until
* we've received a byte of data, at which point
* we'll start sending out the byte in the correct
* order using one register. Meanwhile, we'll start
* receiving into the other register - dual buffers.
*/
logic[7:0] bufa, bufb;
/* A counter. This indicates what 'stage' we're in,
* and always refers to the index we're reading into
* in the receiving buffer or sending out of in the
* sending buffer
*/
logic[2:0] countera, counterb;
/* Which state we're in - should we be using buffer
* A to send, buffer B to send, or neither because
* we've just come out of reset?
*/
logic[1:0] state;
always_comb begin: AXIOV
if (state == `BO_SENDA || state == `BO_SENDB) axiov = 1'b1;
else axiov = 1'b0;
end
always_comb begin: AXIOD
if (state == `BO_SENDA) axiod = bufa[countera +: 2];
else if (state == `BO_SENDB) axiod = bufb[counterb +: 2];
else axiod = 1'b0;
end
always_ff @(posedge clk) begin: BUFFERIN
if (rst) begin
bufa <= 8'b0;
bufb <= 8'b0;
end else if (axiiv) begin
case (state)
`BO_EMPTYB, `BO_SENDB:
bufa[countera +: 2] <= axiid;
`BO_EMPTYA, `BO_SENDA:
bufb[counterb +: 2] <= axiid;
endcase
end else if (state == `BO_EMPTYB || state == `BO_EMPTYA) begin
bufa <= 8'b0;
bufb <= 8'b0;
end
end
always_ff @(posedge clk) begin: STATES
if (rst) begin
state <= `BO_EMPTYB;
countera <= 3'b0;
counterb <= 3'b0;
end else begin
case (state)
`BO_EMPTYB: begin
if (axiiv) begin
if (countera == 3'h6)
state <= `BO_SENDA;
else countera <= countera + 2;
end else countera <= 3'b0;
end
`BO_EMPTYA: begin
if (axiiv) begin
if (counterb == 3'h6)
state <= `BO_SENDB;
else counterb <= counterb + 2;
end else counterb <= 3'b0;
end
`BO_SENDB: begin
if (counterb == 3'h0) state <= `BO_EMPTYB;
else counterb <= counterb - 2;
if (axiiv) begin
if (countera == 3'h6)
state <= `BO_SENDA;
else countera <= countera + 2;
end
end
`BO_SENDA: begin
if (countera == 3'h0) state <= `BO_EMPTYA;
else countera <= countera - 2;
if (axiiv) begin
if (counterb == 3'h6)
state <= `BO_SENDB;
else counterb <= counterb + 2;
end
end
endcase
end
end
endmodule
`default_nettype wire

95
src/manta/cksum.v Normal file
View File

@ -0,0 +1,95 @@
`default_nettype none
`timescale 1ns / 1ps
/* Computes the ethernet checksum
* The following combinations of `done` and `kill`
* represent the state of the module:
*
* - done = 0, kill = 0: processing data or freshly reset
* - done = 1, kill = 0: correct ethernet checksum verified
* - done = 1, kill = 1: data valid set to zero before correct
* checksum value computed, therefore bad checksum
* - done = 0, kill = 1: never asserted
*
* the done and/or kill signals are asserted high beginning
* the cycle after input data ceases, and until new data
* is received via the AXI input
*/
`define CK_FRESH 2'b00
`define CK_COMPUTING 2'b01
`define CK_DONE 2'b10
`define MAGIC_CHECK 32'h38_fb_22_84
module cksum(clk, rst, axiid, axiiv, done, kill);
/* batteries */
input logic clk, rst;
/* AXI input valid, and AXI input data from the
* physical layer. Comprises unmodified data directly
* from the wire, in Ethernet bit order, to be fed
* directly into the CRC32 module you wrote for the
* pset
*/
input logic[1:0] axiid;
input logic axiiv;
/* Done and kill, as described in the module synopsis */
output logic done, kill;
/* CRC32 AXI output data bus, which is the 32-bit
* checksum calculated so far via the checksum module
* you implemented in one of the last psets (CRC32-BZIP2)
*/
logic[31:0] crcd;
logic crcv;
/* Decoupled logic to reset the CRC module independently
* Used to compute multiple CRCs back to back
*/
logic crcrst;
/* Our finite state machine - bonus points if you can identify
* whether this is a Moore or Mealy FSM!
*/
logic[1:0] state;
crc32 cksum(.clk(clk),
.rst(crcrst | rst),
.axiiv(axiiv),
.axiid(axiid),
.axiov(crcv),
.axiod(crcd));
always_ff @(posedge clk) begin: OUTPUTS
if (rst || axiiv) begin
done <= 1'b0;
kill <= 1'b0;
crcrst <= 1'b0;
end else begin
if (state == `CK_COMPUTING && !axiiv) begin
done <= 1'b1;
crcrst <= 1'b1;
if (crcd == `MAGIC_CHECK) kill <= 1'b0;
else kill <= 1'b1;
end else crcrst <= 1'b0;
end
end
always_ff @(posedge clk) begin: FSM
if (rst) state <= `CK_FRESH;
else begin
case (state)
`CK_FRESH: if (axiiv) state <= `CK_COMPUTING;
`CK_COMPUTING: if (!axiiv) state <= `CK_DONE;
`CK_DONE: if (axiiv) state <= `CK_COMPUTING;
endcase
end
end
endmodule
`default_nettype wire

74
src/manta/crc32.v Normal file
View File

@ -0,0 +1,74 @@
`default_nettype none
`timescale 1ns / 1ps
`define LAGGING_SHIFT_IN (caxiod[30] ^ axiid[1])
`define LEADING_SHIFT_IN (caxiod[31] ^ axiid[0])
`define DOUBLED_SHIFT_IN (`LEADING_SHIFT_IN ^ `LAGGING_SHIFT_IN)
`define LAGGING_TAPS 4, 7, 10, 16, 22, 26
`define DOUBLED_TAPS 2, 5, 8, 11, 12, 23
`define LEADING_TAPS 3, 6, 9, 13, 17, 24, 27
/* this module implements CRC32-BZIP2, with a two bit input:
* - poly 0x04C11DB7
* - init 0xFFFFFFFF
* - NEW: XOR outputs
*
* == check: 0xfc891918 ==
*
* this is the ethernet checksum!!
*/
module crc32(clk, rst, axiiv, axiid, axiov, axiod);
/* old style i/o declaration, for clarity.
* easier on 80-char line limits...
* use this if you want, we don't care
*/
input logic clk, rst;
input logic axiiv;
input logic[1:0] axiid;
output logic axiov;
output logic[31:0] axiod;
logic[31:0] caxiod, saxiod;
integer i;
assign axiov = 1;
assign axiod = ~caxiod;
always @(*) begin
for (i = 0; i < 32; i = i + 1) begin
case (i)
0: saxiod[i] = `LAGGING_SHIFT_IN;
1: saxiod[i] = `DOUBLED_SHIFT_IN;
`LAGGING_TAPS:
saxiod[i] = caxiod[i - 2] ^ `LAGGING_SHIFT_IN;
`DOUBLED_TAPS:
saxiod[i] = caxiod[i - 2] ^ `DOUBLED_SHIFT_IN;
`LEADING_TAPS:
saxiod[i] = caxiod[i - 2] ^ `LEADING_SHIFT_IN;
default: saxiod[i] = caxiod[i - 2];
endcase
end
end
always @(posedge clk) begin
if (rst) caxiod <= 32'hFFFF_FFFF;
/* our output validity hinges on whether
* we are calculating anything or not
* on this clock cycle. if there is no
* valid input for us, don't do a shift
* this cycle
*/
else caxiod <= (axiiv) ? saxiod : caxiod;
end
endmodule
`default_nettype wire

139
src/manta/ether.v Normal file
View File

@ -0,0 +1,139 @@
`default_nettype none
`timescale 1ns / 1ps
/* This module receives packets from the RJ45 (Ethernet) port on your
* FPGA from the _physical layer_, i.e. the electronic (or optical!)
* connection between devices running through an Ethernet cable.
*
* Ethernet is very diverse set of specifications, with all sorts of
* different physical layers (which we abbreviate 'the medium' or
* 'media' in plural). Ethernet media have transfer speeds ranging
* from 1 Mbps to 400 gigabits per second in modern data centers!
* For this implementation, we will implement "Fast Ethernet", which
* (not fast by today's standards) utilizes a physical layer capable
* of 100 Mbps communication. Most modern cables are backwards
* compatible with this standard.
*/
/* Note: this file uses tabs instead of spaces for indentation.
* More modern editors should pick this up automatically, but if yours
* doesn't that might explain trouble getting things to line up. You
* can probably configure this (e.g. in vim, type ":set noexpandtab"
* in normal mode and then type ":set tabstop=8")
*/
`define EF_IDLE 3'b000
`define EF_PREAM 3'b001
`define EF_DATA 3'b011
`define EF_BAD 3'b101
`define PREAM_BITS 64
`define PREAM_SIZE (`PREAM_BITS / 2)
`define PREAM_FIRST 2'b00
`define PREAM_EXPECT 2'b01
`define PREAM_LAST 2'b11
`define PREAM_BAD 2'b10
module ether(clk, rst, rxd, crsdv, axiov, axiod);
input logic clk, rst;
/* Carrier Sense (CRS) / Data Valid (DV): indicates
* whether there is a valid signal currently being
* transmitted over the Ethernet medium by another
* sender.
*
* In the past, >2 computers were connected
* to the same Ethernet cable, so this helped
* distinguish if one machine was trying to talk over
* another. Nowadays, usually Ethernet connections are
* point to point (have you ever seen an Ethernet cable
* with three ports on it? no? that's what i thought),
* and shared media isn't even supported in the 100 Mbps
* spec.
*
* So just use this input to determine whether valid
* data is on the line coming in.
*/
input logic crsdv;
/* Receive Data (RXD): If crsdv is high, receives
* two bits off the wire. Otherwise, undefined
* (let's say 2'bXX)
*
* According to the IEEE 802.3 Fast Ethernet
* specification, with the exception of the FCS,
* bytes in Ethernet world are sent least significant
* bit first, most significant byte first. Confusing!
* Basically, if you have a two byte (16 bit) message,
* the bits will come in over RXD as:
*
* 7:6 -> 5:4 -> 3:2 -> 1:0 -> 15:14 -> ... -> 9:8
*
* For now, this idiosyncracy won't matter to you.
* Later, it will.
*/
input logic[1:0] rxd;
/* 2-bit AXI output: forward whatever we receive
* on to the outside world for further processing
*/
output logic axiov;
output logic[1:0] axiod;
/* END OF STARTER CODE */
logic[4:0] count;
logic[2:0] state;
logic[1:0] preamex;
logic preamok, start;
always @(*) begin: PREAM
if (count == `PREAM_SIZE - 1) preamex = `PREAM_LAST;
else preamex = `PREAM_EXPECT;
preamok = crsdv && rxd == preamex;
end
always @(*) start = crsdv && rxd != `PREAM_FIRST;
always @(posedge clk) begin: COUNT
if (state == `EF_PREAM) count <= count + 1;
else if (state == `EF_IDLE && start) count <= count + 1;
else count <= 0;
end
always @(posedge clk) begin: FSM
if (rst) begin
axiod <= 2'b0;
axiov <= 1'b0;
state <= 3'b0;
end else begin
case (state)
`EF_BAD: if (!crsdv) state <= `EF_IDLE;
`EF_IDLE: if (start) state <= `EF_PREAM;
`EF_PREAM: begin
if (!preamok || !crsdv) state <= `EF_BAD;
else if (count == `PREAM_SIZE - 1)
state <= `EF_DATA;
end
`EF_DATA: begin
if (crsdv) begin
axiov <= 1'b1;
axiod <= rxd;
end else begin
axiov <= 1'b0;
axiod <= 2'b0;
state <= `EF_IDLE;
end
end
endcase
end
end
endmodule
`default_nettype wire

40
src/manta/ethernet_rx.v Normal file
View File

@ -0,0 +1,40 @@
`default_nettype none
`timescale 1ns/1ps
module ethernet_rx (
input wire clk,
input wire crsdv,
input wire [1:0] rxd,
output reg [15:0] addr_o,
output reg [15:0] wdata_o,
output reg rw_o,
output reg valid_o
);
// we know if the packet is a read or write
// based on the ethertype.
reg [15:0] ethertype;
reg [31:0] data;
reg valid;
mac_rx mrx (
.clk(clk),
.crsdv(crsdv),
.rxd(rxd),
.ethertype(ethertype),
.data(data),
.valid(valid));
assign addr_o = data[31:16];
assign wdata_o = data[15:0];
assign rw_o = (ethertype == 4);
assign valid_o = valid;
endmodule
`default_nettype wire

27
src/manta/ethernet_tx.v Normal file
View File

@ -0,0 +1,27 @@
`default_nettype none
`timescale 1ns/1ps
module ethernet_tx(
input wire clk,
output reg txen,
output reg [1:0] txd,
input wire [15:0] rdata_i,
input wire rw_i,
input wire valid_i
);
mac_tx mtx (
.clk(clk),
.data(rdata_i),
.ethertype(16'h2),
.start(~rw_i && valid_i),
.txen(txen),
.txd(txd));
endmodule
`default_nettype wire

121
src/manta/firewall.v Normal file
View File

@ -0,0 +1,121 @@
`default_nettype none
`timescale 1ns / 1ps
/* Sometimes we might receive packets not
* intended for us on the wire. This is especially
* true if we're operating in old school mode, where
* Ethernet used to have many devices on the same
* medium. Alternatively, several virtual NICs may
* exist in software on a given machine, or a machine
* may support MAC address randomization.
*
* All of this means we need to make sure inbound
* packets are actually intended for us, and not
* for some other NIC. That's what this module is
* for. In addition, this module will also strip
* the MAC address pair (and Ethertype) off of the
* Ethernet header, leaving only data and the FCS.
* We'll clean up the FCS later...
*/
/* "Intended for us" means the following:
* - has the destination MAC "`FW_ME" as defined below. Make this
* your own MAC of your choice - get creative!
* - has the destination MAC of all 1s, i.e. a 'broadcast' packet
*
* If these conditions are not met on a given input stream, data
* from the packet is dropped / not forwarded on through the
* pipeline
*/
`define FW_ME 48'h69_69_5A_06_54_91
`define FW_DESTSTART 0
`define FW_DESTEND (`FW_DESTSTART + 48)
`define FW_DATASTART (48 + 48)
module firewall(clk, rst, axiiv, axiid, axiov, axiod);
/* batteries */
input logic clk, rst;
/* AXI input valid, and AXI input data from the bit order
* flip module. So this will transport MSb/MSB first data
* coming off the wire - allowing you to compare with src/dst
* MAC addresses a bit more easily
*/
input logic[1:0] axiid;
input logic axiiv;
/* AXI output valid, and AXI output data. If and only if
* the packet is intended for our device as described above,
* this line will stream out the _data_ and _fcs_ (NOT the MAC
* addresses in the header, nor the ethertype - we're ignoring
* the latter this time around) we're receiving off the wire.
* If a kill-worthy condition is detected, these lines are
* deasserted for the duration of the incoming packet
*/
output logic[1:0] axiod;
output logic axiov;
/* Buffers to hold our MAC address in the reverse order,
* to make comparison easier than it otherwise would be
*/
logic[0:47] me;
/* A counter, to determine whether we should be comparing
* with a MAC address or stripping off data
*/
logic[31:0] counter;
/* An internal set of flags to mark whether the currently
* traversing packet is valid, i.e we should forward data,
* or not. One of these flags tracks whether the destination
* MAC address matches _our_ (FW_ME) mac address, the other
* tracks whether the destination matches the broadcast
* (FW_BCAST) MAC. If either one of these is high once the
* destination MAC finishes rolling through, the packet
* is forwarded.
*/
logic matchme, matchbcast;
assign me = `FW_ME;
always_ff @(posedge clk) begin: MATCH
if (counter == 32'b0) begin
matchme <= 1'b1;
matchbcast <= 1'b1;
end
/* could overwrite the above, which is ideal if
* FW_DESTSTART == 0 (it is) and we have a mismatch
* out the gate
*/
if (counter >= `FW_DESTSTART && counter < `FW_DESTEND) begin
if (axiiv) begin
if (axiid != {me[counter], me[counter + 1]})
matchme <= 1'b0;
if (axiid != 2'b11)
matchbcast <= 1'b0;
end
end
end
always_comb begin: AXIOUT
if (counter >= `FW_DATASTART && (matchme | matchbcast)) begin
axiod = axiid;
axiov = axiiv;
end else begin
axiod = 2'b00;
axiov = 1'b0;
end
end
always_ff @(posedge clk) begin: COUNTER
if (axiiv) counter <= counter + 2;
else counter <= 32'b0;
end
endmodule
`default_nettype wire

116
src/manta/mac_rx.v Normal file
View File

@ -0,0 +1,116 @@
`default_nettype none
`timescale 1ns/1ps
module mac_rx (
input wire clk,
input wire crsdv,
input wire [1:0] rxd,
output reg [15:0] ethertype,
output reg [31:0] data,
output reg valid);
// TODO: rewrite modules to not need external reset
reg rst = 1;
always @(posedge clk) rst <= 0;
/* ether -> { cksum, bitorder } */
reg[1:0] ether_axiod;
reg ether_axiov;
ether e(
.clk(clk),
.rst(rst),
.rxd(rxd),
.crsdv(crsdv),
.axiov(ether_axiov),
.axiod(ether_axiod));
/* bitorder -> firewall */
reg[1:0] bitorder_axiod;
reg bitorder_axiov;
bitorder b(
.clk(clk),
.rst(rst),
.axiiv(ether_axiov),
.axiid(ether_axiod),
.axiov(bitorder_axiov),
.axiod(bitorder_axiod));
/* firewall -> aggregate */
reg[1:0] firewall_axiod;
reg firewall_axiov;
firewall f(
.clk(clk),
.rst(rst),
.axiiv(bitorder_axiov),
.axiid(bitorder_axiod),
.axiov(firewall_axiov),
.axiod(firewall_axiod));
/* aggregate output */
reg[47:0] aggregate_axiod;
reg aggregate_axiov;
aggregate a(
.clk(clk),
.rst(rst),
.axiiv(firewall_axiov),
.axiid(firewall_axiod),
.axiov(aggregate_axiov),
.axiod(aggregate_axiod));
/* cksum -> top_level */
reg cksum_done;
reg cksum_kill;
cksum c(
.clk(clk),
.rst(rst),
.axiiv(ether_axiov),
.axiid(ether_axiod),
.done(cksum_done),
.kill(cksum_kill));
// state machine
localparam IDLE = 0;
localparam WAIT_FOR_DATA = 1;
localparam WAIT_FOR_FCS = 2;
reg [1:0] state = IDLE;
initial valid = 0;
initial data = 0;
always @(posedge clk) begin
valid <= 0;
if(state == IDLE) begin
if(crsdv) state <= WAIT_FOR_DATA;
end
else if(state == WAIT_FOR_DATA) begin
if(aggregate_axiov) begin
state <= WAIT_FOR_FCS;
ethertype <= aggregate_axiod[47:32];
data <= aggregate_axiod[31:0];
end
// if aggregate never gives us data, go back to idle when the packet ends
else if(cksum_done) state <= IDLE;
end
else if(state == WAIT_FOR_FCS) begin
if(cksum_done) begin
state <= IDLE;
valid <= ~cksum_kill;
end
end
end
endmodule
`default_nettype wire

248
src/manta/mac_tx.v Normal file
View File

@ -0,0 +1,248 @@
`default_nettype none
`timescale 1ns/1ps
module mac_tx (
input wire clk,
// TODO: make this variable width
input wire [15:0] data,
input wire [15:0] ethertype,
input wire start,
output reg txen,
output reg [1:0] txd);
// packet magic numbers
localparam PREAMBLE = {7{8'b01010101}};
localparam SFD = 8'b11010101;
parameter SRC_MAC = 48'h69_69_69_69_69_69;
parameter DST_MAC = 48'hFF_FF_FF_FF_FF_FF;
// all lengths are in units of dibits, hence all the mulitplies by four
localparam PREAMBLE_LEN = 7 * 4;
localparam SFD_LEN = 1 * 4;
localparam SRC_MAC_LEN = 6 * 4;
localparam DST_MAC_LEN = 6 * 4;
localparam ETHERTYPE_LEN = 2 * 4;
localparam PAYLOAD_LEN = 2 * 4;
// localparam ZERO_PAD_LEN = (46 * 4) - PAYLOAD_LEN ; // minimum payload size is 46 bytes
localparam ZERO_PAD_LEN = (46 * 4) - PAYLOAD_LEN + 4; // minimum payload size is 46 bytes
localparam FCS_LEN = 4 * 4;
localparam IPG_LEN = 96 / 2;
// TODO: make crc and bitorder modules not need reset
reg rst = 1;
always @(posedge clk) rst <= 0;
reg [1:0] bitorder_axiid;
reg [1:0] bitorder_axiod;
reg bitorder_axiiv;
reg bitorder_axiov;
bitorder bitorder (
.clk(clk),
.rst(rst),
.axiiv(bitorder_axiiv),
.axiid(bitorder_axiid),
.axiov(bitorder_axiov),
.axiod(bitorder_axiod));
reg crc_rst = 1;
reg crc_axiiv = 0;
reg [31:0] crc_axiod;
crc32 crc (
.clk(clk),
.rst(crc_rst),
.axiiv(crc_axiiv),
.axiid(bitorder_axiod),
// TODO: remove axiov from crc32 module, it's always valid
.axiov(),
.axiod(crc_axiod));
// state machine
reg [8:0] counter = 0;
reg [3:0] state = 0;
localparam IDLE_STATE = 0;
localparam PREAMBLE_STATE = 1;
localparam SFD_STATE = 2;
localparam DST_MAC_STATE = 3;
localparam SRC_MAC_STATE = 4;
localparam ETHERTYPE_STATE = 5;
localparam PAYLOAD_STATE = 6;
localparam ZERO_PAD_STATE = 7;
localparam FCS_STATE = 8;
localparam IPG_STATE = 9;
// sequential logic manages the state machine
always @(posedge clk) begin
counter <= counter + 1;
crc_rst <= 0;
if(state == IDLE_STATE) begin
counter <= 0;
crc_axiiv <= 0;
if(start) state <= PREAMBLE_STATE;
end
else if(state == PREAMBLE_STATE) begin
if(counter == PREAMBLE_LEN - 1) begin
counter <= 0;
state <= SFD_STATE;
end
end
else if(state == SFD_STATE) begin
if(counter == SFD_LEN - 1) begin
counter <= 0;
state <= DST_MAC_STATE;
end
end
else if(state == DST_MAC_STATE) begin
// this is because the crc module lags behind the FSM,
// as it has to go through bitorder first
if(counter == 3) crc_axiiv <= 1;
if(counter == DST_MAC_LEN - 1) begin
counter <= 0;
state <= SRC_MAC_STATE;
end
end
else if(state == SRC_MAC_STATE) begin
if(counter == SRC_MAC_LEN - 1) begin
counter <= 0;
state <= ETHERTYPE_STATE;
end
end
else if(state == ETHERTYPE_STATE) begin
if(counter == ETHERTYPE_LEN - 1) begin
counter <= 0;
state <= PAYLOAD_STATE;
end
end
else if(state == PAYLOAD_STATE) begin
if(counter == PAYLOAD_LEN - 1) begin
counter <= 0;
state <= ZERO_PAD_STATE;
end
end
else if(state == ZERO_PAD_STATE) begin
if(counter == ZERO_PAD_LEN - 1) begin
crc_axiiv <= 0;
counter <= 0;
state <= FCS_STATE;
end
end
else if(state == FCS_STATE) begin
if(counter == FCS_LEN - 1) begin
counter <= 0;
state <= IPG_STATE;
end
end
else if(state == IPG_STATE) begin
if(counter == IPG_LEN - 1) begin
crc_rst <= 1;
counter <= 0;
state <= IDLE_STATE;
end
end
end
// combinational logic handles the pipeline
always @(*) begin
case (state)
IDLE_STATE: begin
bitorder_axiiv = 0;
bitorder_axiid = 0;
txen = 0;
txd = 0;
end
PREAMBLE_STATE: begin
bitorder_axiiv = 1;
bitorder_axiid = PREAMBLE[2*(PREAMBLE_LEN-counter)-1-:2];
txen = bitorder_axiov;
txd = bitorder_axiod;
end
SFD_STATE: begin
bitorder_axiiv = 1;
bitorder_axiid = SFD[2*(SFD_LEN-counter)-1-:2];
txen = bitorder_axiov;
txd = bitorder_axiod;
end
DST_MAC_STATE: begin
bitorder_axiiv = 1;
bitorder_axiid = DST_MAC[2*(DST_MAC_LEN-counter)-1-:2];
txen = bitorder_axiov;
txd = bitorder_axiod;
end
SRC_MAC_STATE: begin
bitorder_axiiv = 1;
bitorder_axiid = SRC_MAC[2*(SRC_MAC_LEN-counter)-1-:2];
txen = bitorder_axiov;
txd = bitorder_axiod;
end
ETHERTYPE_STATE: begin
bitorder_axiiv = 1;
bitorder_axiid = ethertype[2*(ETHERTYPE_LEN-counter)-1-:2];
txen = bitorder_axiov;
txd = bitorder_axiod;
end
PAYLOAD_STATE: begin
bitorder_axiiv = 1;
bitorder_axiid = data[2*(PAYLOAD_LEN-counter)-1-:2];
txen = bitorder_axiov;
txd = bitorder_axiod;
end
ZERO_PAD_STATE: begin
bitorder_axiiv = 1;
bitorder_axiid = 0;
txen = bitorder_axiov;
txd = bitorder_axiod;
end
FCS_STATE: begin
bitorder_axiiv = 0;
bitorder_axiid = 0;
txen = 1;
txd = {crc_axiod[2*(FCS_LEN-counter)-2], crc_axiod[2*(FCS_LEN-counter)-1]};
end
IPG_STATE: begin
bitorder_axiiv = 0;
bitorder_axiid = 0;
txen = 0;
txd = 0;
end
default: begin
bitorder_axiiv = 0;
bitorder_axiid = 0;
txen = 0;
txd = 0;
end
endcase
end
endmodule
`default_nettype wire

View File

@ -0,0 +1,76 @@
`default_nettype none
`timescale 1ns/1ps
module mac_tb();
logic clk;
always begin
#5;
clk = !clk;
end
logic crsdv;
logic [1:0] rxd;
logic txen;
logic [1:0] txd;
logic [15:0] mtx_data;
logic [15:0] mtx_ethertype;
logic mtx_start;
logic [15:0] mrx_data;
logic [15:0] mrx_ethertype;
logic mrx_valid;
mac_tx mtx (
.clk(clk),
.data(mtx_data),
.ethertype(mtx_ethertype),
.start(mtx_start),
.txen(txen),
.txd(txd));
assign rxd = txd;
assign crsdv = txen;
mac_rx mrx (
.clk(clk),
.crsdv(crsdv),
.rxd(rxd),
.data(mrx_data),
.ethertype(mrx_ethertype),
.valid(mrx_valid));
initial begin
$dumpfile("mac_tb.vcd");
$dumpvars(0, mac_tb);
clk = 0;
mtx_ethertype = 0;
mtx_data = 0;
mtx_start = 0;
#10;
for (int i=0; i<128; i=i+1) begin
mtx_data = i;
mtx_ethertype = i;
mtx_start = 0;
#10;
mtx_start = 1;
#10;
mtx_start = 0;
while(!mrx_valid) #10;
#1000;
assert(mrx_data == i) else $error("data mismatch!");
end
$finish();
end
endmodule
`default_nettype wire