diff --git a/hw/super6502_fpga/src/sub/network_processor/sim/cocotb/tests/scapy_irl_test.py b/hw/super6502_fpga/src/sub/network_processor/sim/cocotb/tests/scapy_irl_test.py new file mode 100644 index 0000000..6024c2b --- /dev/null +++ b/hw/super6502_fpga/src/sub/network_processor/sim/cocotb/tests/scapy_irl_test.py @@ -0,0 +1,270 @@ +from http import server +from scapy.layers.inet import Ether, IP, TCP +from scapy.layers.l2 import ARP +from scapy.data import IP_PROTOS + +from scapy import sendrecv + +from scapy.config import conf + +from scapy.supersocket import L3RawSocket + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import Timer +from cocotb.triggers import RisingEdge +from cocotbext.axi import AxiLiteBus, AxiLiteMaster, AxiLiteRam +from cocotbext.eth import MiiPhy, GmiiFrame +import struct + +from scapy.layers.inet import Ether, IP, TCP +from scapy.layers.l2 import ARP +from scapy.utils import PcapWriter + +from scapy.layers.tuntap import TunTapInterface +import logging + +from decimal import Decimal + +CLK_PERIOD_NS = 10 + +MII_CLK_PERIOD_NS = 40 + + +import socket + +# In order for this to work, you need to run these commands: +# sudo ip tuntap add name tun0 mode tun user $USER +# sudo ip a add 172.0.0.1 peer 172.0.0.2 dev tun0 +# sudo ip link set tun0 up + + +def main(): + serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serversocket.bind(("172.0.0.1", 5678)) + serversocket.listen(5) + + t = TunTapInterface('tun0') + + tcp_syn = IP(src="172.0.0.2", dst="172.0.0.1")/TCP(sport=1234, dport=5678, seq=0, ack=0, flags="S") + t.send(tcp_syn) + + pkt = t.recv() + print(pkt) + + +if __name__ == "__main__": + main() + + + +class TB: + def __init__(self, dut): + self.dut = dut + + self.log = logging.getLogger("cocotb.tb") + self.log.setLevel(logging.DEBUG) + + cocotb.start_soon(Clock(dut.clk, CLK_PERIOD_NS, units="ns").start()) + cocotb.start_soon(Clock(dut.mii_rx_clk, MII_CLK_PERIOD_NS, units="ns").start()) + cocotb.start_soon(Clock(dut.mii_tx_clk, MII_CLK_PERIOD_NS, units="ns").start()) + + + self.axil_master = AxiLiteMaster(AxiLiteBus.from_prefix(dut, "s_regs_axil"), dut.clk, dut.rst) + self.axil_ram = AxiLiteRam(AxiLiteBus.from_prefix(dut, "m_dma_axil"), dut.clk, dut.rst, size=2**16) + + self.mii_phy = MiiPhy(dut.mii_txd, dut.mii_tx_er, dut.mii_tx_en, dut.mii_tx_clk, + dut.mii_rxd, dut.mii_rx_er, dut.mii_rx_dv, dut.mii_rx_clk, None, speed=100e6) + + async def cycle_reset(self): + self.dut.rst.setimmediatevalue(0) + await RisingEdge(self.dut.clk) # type: ignore + await RisingEdge(self.dut.clk) # type: ignore + self.dut.rst.value = 1 + await RisingEdge(self.dut.clk) # type: ignore + await RisingEdge(self.dut.clk) # type: ignore + self.dut.rst.value = 0 + await RisingEdge(self.dut.clk) # type: ignore + await RisingEdge(self.dut.clk) # type: ignore + +def ip_to_hex(ip: str) -> int: + octets = [int(i) for i in ip.split(".")] + + result = int.from_bytes(struct.pack("BBBB", octets[0], octets[1], octets[2], octets[3])) + + return result + +@cocotb.test() +async def test_irl(dut): + tb = TB(dut) + + await tb.cycle_reset() + + dut_ip = "172.0.0.2" + tb_ip = "172.0.0.1" + + tb_mac = "02:00:00:11:22:33" + + serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + serversocket.bind((tb_ip, 5678)) + serversocket.listen(5) + t = TunTapInterface('tun0') + + + dut_port = 1234 + tb_port = 5678 + + await tb.axil_master.write_dword(0x0, 0x1807) + + await tb.axil_master.write_dword(0x200, dut_port) + await tb.axil_master.write_dword(0x204, ip_to_hex(dut_ip)) + await tb.axil_master.write_dword(0x208, tb_port) + await tb.axil_master.write_dword(0x20c, ip_to_hex(tb_ip)) + await tb.axil_master.write_dword(0x210, 0x3) + + resp = await tb.mii_phy.tx.recv() # type: GmiiFrame + + packet = Ether(resp.get_payload()) + + tb.log.info(f"Packet Type: {packet.type:x}") + + assert packet.type == 0x806, "Packet type is not ARP!" + + + arp_request = packet.payload + assert isinstance(arp_request, ARP) + + tb.log.info(f"Arp OP: {arp_request.op}") + tb.log.info(f"Arp hwsrc: {arp_request.hwsrc}") + tb.log.info(f"Arp hwdst: {arp_request.hwdst}") + tb.log.info(f"Arp psrc: {arp_request.psrc}") + tb.log.info(f"Arp pdst: {arp_request.pdst}") + + dut_mac = arp_request.hwsrc + dut_ip = arp_request.psrc + + assert arp_request.op == 1, "ARP type is not request!" + assert arp_request.hwsrc == "02:00:00:aa:bb:cc", "ARP hwsrc does not match expected" + assert arp_request.hwdst == "00:00:00:00:00:00", "ARP hwdst does not match expected" + assert arp_request.psrc == dut_ip, "ARP psrc does not match expected" + assert arp_request.pdst == tb_ip, "ARP pdst does not match expected" + + arp_response = Ether(dst=dut_mac, src=tb_mac) + arp_response /= ARP(op="is-at", hwsrc=tb_mac, hwdst=dut_mac, psrc=tb_ip, pdst=dut_ip) + arp_response = arp_response.build() + + await tb.mii_phy.rx.send(GmiiFrame.from_payload(arp_response)) + + resp = await tb.mii_phy.tx.recv() # type: GmiiFrame + packet = Ether(resp.get_payload()) + tb.log.info(f"Packet Type: {packet.type:x}") + + ip_packet = packet.payload + assert isinstance(ip_packet, IP) + + tcp_packet = ip_packet.payload + assert isinstance(tcp_packet, TCP) + + tb.log.info(f"Source Port: {tcp_packet.sport}") + tb.log.info(f"Dest Port: {tcp_packet.dport}") + tb.log.info(f"Seq: {tcp_packet.seq}") + tb.log.info(f"Ack: {tcp_packet.ack}") + tb.log.info(f"Data Offs: {tcp_packet.dataofs}") + tb.log.info(f"flags: {tcp_packet.flags}") + tb.log.info(f"window: {tcp_packet.window}") + tb.log.info(f"Checksum: {tcp_packet.chksum}") + + t.send(ip_packet) + + pkt = t.recv() + print(pkt) + + tcp_synack = Ether(dst=dut_mac, src=tb_mac) / pkt + + await tb.mii_phy.rx.send(GmiiFrame.from_payload(tcp_synack.build())) + + resp = await tb.mii_phy.tx.recv() # type: GmiiFrame + packet = Ether(resp.get_payload()) + tb.log.info(f"Packet Type: {packet.type:x}") + + ip_packet = packet.payload + assert isinstance(ip_packet, IP) + + tcp_packet = ip_packet.payload + assert isinstance(tcp_packet, TCP) + + tb.log.info(f"Source Port: {tcp_packet.sport}") + tb.log.info(f"Dest Port: {tcp_packet.dport}") + tb.log.info(f"Seq: {tcp_packet.seq}") + tb.log.info(f"Ack: {tcp_packet.ack}") + tb.log.info(f"Data Offs: {tcp_packet.dataofs}") + tb.log.info(f"flags: {tcp_packet.flags}") + tb.log.info(f"window: {tcp_packet.window}") + tb.log.info(f"Checksum: {tcp_packet.chksum}") + + t.send(ip_packet) + + con, addr = serversocket.accept() + + con.close() + serversocket.close() + + while True: + pkt = t.recv() + if (pkt.proto == IP_PROTOS.tcp): + break + print(pkt) + + tcp_fin = Ether(dst=dut_mac, src=tb_mac) / pkt + + await tb.mii_phy.rx.send(GmiiFrame.from_payload(tcp_fin.build())) + + resp = await tb.mii_phy.tx.recv() # type: GmiiFrame + packet = Ether(resp.get_payload()) + tb.log.info(f"Packet Type: {packet.type:x}") + + ip_packet = packet.payload + assert isinstance(ip_packet, IP) + + tcp_packet = ip_packet.payload + assert isinstance(tcp_packet, TCP) + + tb.log.info(f"Source Port: {tcp_packet.sport}") + tb.log.info(f"Dest Port: {tcp_packet.dport}") + tb.log.info(f"Seq: {tcp_packet.seq}") + tb.log.info(f"Ack: {tcp_packet.ack}") + tb.log.info(f"Data Offs: {tcp_packet.dataofs}") + tb.log.info(f"flags: {tcp_packet.flags}") + tb.log.info(f"window: {tcp_packet.window}") + tb.log.info(f"Checksum: {tcp_packet.chksum}") + + t.send(ip_packet) + + return + + + + # Construct a descriptor in memry + tb.axil_ram.write_dword(0x00000000, 0x00001000) + tb.axil_ram.write_dword(0x00000004, 64) + tb.axil_ram.write_dword(0x00000008, 0) + tb.axil_ram.write_dword(0x0000000c, 0) + + test_data = bytearray([x % 256 for x in range(256)]) + + tb.axil_ram.write(0x1000, test_data) + + + + await tb.axil_master.write_dword(0x22c, 0) + await tb.axil_master.write_dword(0x220, 0x00000000) + await tb.axil_master.write_dword(0x224, 0x00000000) + + resp = await tb.mii_phy.tx.recv() # type: GmiiFrame + packet = Ether(resp.get_payload()) + + t.send(packet.payload) + + # con.recv(64) + + serversocket.close() diff --git a/hw/super6502_fpga/src/sub/network_processor/src/tcp_dest_decap.sv b/hw/super6502_fpga/src/sub/network_processor/src/tcp_dest_decap.sv new file mode 100644 index 0000000..0ec4796 --- /dev/null +++ b/hw/super6502_fpga/src/sub/network_processor/src/tcp_dest_decap.sv @@ -0,0 +1,139 @@ +module tcp_dest_decap ( + input i_clk, + input i_rst, + + ip_intf.SLAVE s_ip, + ip_intf.MASTER m_ip, + + output wire [15:0] o_tcp_dest, + output wire o_tcp_dest_valid +); + + +logic [15:0] tcp_dest, tcp_dest_next; +logic [31:0] pipe, pipe_next; +logic [3:0] pipe_valid, pipe_valid_next; +logic [3:0] pipe_last, pipe_last_next; + +logic valid; + + +enum logic [1:0] {PORTS, PASSTHROUGH} state, state_next; +logic [1:0] counter, counter_next; + + +// We don't need the mac addresses or the ethertype. +assign m_ip.eth_src_mac = '0; +assign m_ip.eth_dest_mac = '0; +assign m_ip.eth_type = '0; + +assign o_tcp_dest_valid = valid; +assign o_tcp_dest = tcp_dest; + +skidbuffer #( + .DW(160) +) u_tcp_ip_hdr_skidbuffer ( + .i_clk (i_clk), + .i_reset (i_rst), + + .i_valid (s_ip.ip_hdr_valid), + .o_ready (s_ip.ip_hdr_ready), + .i_data ({ + s_ip.ip_version, + s_ip.ip_ihl, + s_ip.ip_dscp, + s_ip.ip_ecn, + s_ip.ip_length, + s_ip.ip_identification, + s_ip.ip_flags, + s_ip.ip_fragment_offset, + s_ip.ip_ttl, + s_ip.ip_protocol, + s_ip.ip_header_checksum, + s_ip.ip_source_ip, + s_ip.ip_dest_ip + }), + .o_valid (m_ip.ip_hdr_valid), + .i_ready (m_ip.ip_hdr_ready), + .o_data ({ + m_ip.ip_version, + m_ip.ip_ihl, + m_ip.ip_dscp, + m_ip.ip_ecn, + m_ip.ip_length, + m_ip.ip_identification, + m_ip.ip_flags, + m_ip.ip_fragment_offset, + m_ip.ip_ttl, + m_ip.ip_protocol, + m_ip.ip_header_checksum, + m_ip.ip_source_ip, + m_ip.ip_dest_ip + }) +); + +always_ff @(posedge i_clk) begin + if (i_rst) begin + tcp_dest <= '0; + pipe <= '0; + pipe_valid <= '0; + pipe_last <= '0; + state <= PORTS; + counter <= '0; + end else begin + tcp_dest <= tcp_dest_next; + pipe <= pipe_next; + pipe_valid <= pipe_valid_next; + pipe_last <= pipe_last_next; + state <= state_next; + counter <= counter_next; + end +end + +always_comb begin + tcp_dest_next = tcp_dest; + state_next = state; + pipe_next = pipe; + pipe_valid_next = pipe_valid; + pipe_last_next = pipe_last; + counter_next = pipe; + + s_ip.ip_payload_axis_tready = '0; + + case (state) + PORTS: begin + s_ip.ip_payload_axis_tready = 1; + valid = '0; + + if (s_ip.ip_payload_axis_tvalid) begin + counter_next = counter + 1; + pipe_valid_next = {pipe_valid[2:0], 1'b1}; + pipe_next = {pipe_next[23:0], s_ip.ip_payload_axis_tdata}; + if (counter == 2'h3) begin + state_next = PASSTHROUGH; + tcp_dest_next = pipe_next[15:0]; + end + end + end + + PASSTHROUGH: begin + // match ready except if we have seen last, then just finish it out. + pipe_valid_next = {pipe_valid[2:0], s_ip.ip_payload_axis_tvalid}; + pipe_last_next = {pipe_last[2:0], s_ip.ip_payload_axis_tlast}; + pipe_next = {pipe_next[23:0], s_ip.ip_payload_axis_tdata}; + + s_ip.ip_payload_axis_tready = m_ip.ip_payload_axis_tready; + m_ip.ip_payload_axis_tvalid = pipe_valid[3]; + m_ip.ip_payload_axis_tlast = pipe_last[3]; + m_ip.ip_payload_axis_tdata = pipe[31:24]; + + valid = '1; + + if (pipe_last[3] && pipe_valid[3]) begin + state_next = PORTS; + end + end + endcase +end + +endmodule \ No newline at end of file diff --git a/hw/super6502_fpga/src/sub/network_processor/src/tcp_rx_ctrl.sv b/hw/super6502_fpga/src/sub/network_processor/src/tcp_rx_ctrl.sv new file mode 100644 index 0000000..8a46e02 --- /dev/null +++ b/hw/super6502_fpga/src/sub/network_processor/src/tcp_rx_ctrl.sv @@ -0,0 +1,49 @@ +import tcp_pkg::*; + +module tcp_rx_ctrl ( + input logic i_clk, + input logic i_rst, + + output tcp_pkg::rx_msg_t o_rx_msg, + output logic o_rx_msg_valid, + input logic i_rx_msg_ack, + + input logic [31:0] i_seq_number, + input logic [31:0] i_ack_number, + input logic [15:0] i_source_port, + input logic [15:0] i_dest_port, + input logic [7:0] i_flags, + input logic [15:0] i_window_size, + input logic i_hdr_valid, + + output logic [31:0] o_ack_number +); + +logic [31:0] ack_num, ack_num_next; +assign o_ack_number = ack_num; + +always_ff @(posedge i_clk) begin + if (i_rst) begin + ack_num <= '0; + end else begin + ack_num <= ack_num_next; + end +end + +always_comb begin + if (i_hdr_valid) begin + if (i_flags == 8'h12) begin + o_rx_msg = RX_MSG_RECV_SYNACK; + o_rx_msg_valid = '1; + + ack_num_next = i_seq_number + 1; + end + + if (i_flags == 8'h11) begin + o_rx_msg = RX_MSG_RECV_FIN; + o_rx_msg_valid = '1; + end + end +end + +endmodule \ No newline at end of file diff --git a/hw/super6502_fpga/src/sub/network_processor/src/tcp_state_manager.sv b/hw/super6502_fpga/src/sub/network_processor/src/tcp_state_manager.sv new file mode 100644 index 0000000..3a07f6f --- /dev/null +++ b/hw/super6502_fpga/src/sub/network_processor/src/tcp_state_manager.sv @@ -0,0 +1,100 @@ +import tcp_pkg::*; + +module tcp_state_manager( + input wire i_clk, + input wire i_rst, + + input wire i_enable, + + input wire i_open, + output logic o_open_clr, + input wire i_close, + output logic o_close_clr, + + output tcp_pkg::tx_ctrl_t o_tx_ctrl, + output logic o_tx_ctrl_valid, + input logic i_tx_ctrl_ack, + + input tcp_pkg::rx_msg_t i_rx_msg, + input wire i_rx_msg_valid, + output logic o_rx_msg_ack +); + +enum logic [3:0] { + IDLE, + SYN_RCVD, // In this design, this state should not be reached! + SYN_SENT_1, + SYN_SENT_2, + ESTABLISHED, + WAIT_CLOSE, + LAST_ACK, + TIME_WAIT, + FIN_WAIT_1, + FIN_WAIT_2 +} tcp_state, tcp_state_next; + + +always_ff @(posedge i_clk) begin + if (i_rst) begin + tcp_state <= IDLE; + end else begin + if (~i_enable) begin + tcp_state <= IDLE; + end else begin + tcp_state <= tcp_state_next; + end + end +end + +always_comb begin + tcp_state_next = tcp_state; + + o_tx_ctrl_valid = '0; + + o_tx_ctrl = TX_CTRL_NOP; + o_tx_ctrl_valid = '0; + + o_rx_msg_ack = '0; + + case (tcp_state) + IDLE: begin + if (i_open) begin + o_tx_ctrl = TX_CTRL_SEND_SYN; + o_tx_ctrl_valid = '1; + + if (i_tx_ctrl_ack) begin + tcp_state_next = SYN_SENT_1; + end + end + end + + SYN_SENT_1: begin + if (i_rx_msg_valid && i_rx_msg== RX_MSG_RECV_SYNACK) begin + tcp_state_next = SYN_SENT_2; + end + end + + SYN_SENT_2: begin + o_tx_ctrl = TX_CTRL_SEND_ACK; + o_tx_ctrl_valid = '1; + + if (i_tx_ctrl_ack) begin + tcp_state_next = ESTABLISHED; + end + end + + ESTABLISHED: begin + if (i_rx_msg_valid && i_rx_msg == RX_MSG_RECV_FIN) begin + o_tx_ctrl = TX_CTRL_SEND_FIN; + o_tx_ctrl_valid = '1; + tcp_state_next = LAST_ACK; + end + end + + LAST_ACK: begin + + end + endcase +end + +endmodule \ No newline at end of file diff --git a/hw/super6502_fpga/src/sub/network_processor/src/tcp_tx_ctrl.sv b/hw/super6502_fpga/src/sub/network_processor/src/tcp_tx_ctrl.sv new file mode 100644 index 0000000..58f6abe --- /dev/null +++ b/hw/super6502_fpga/src/sub/network_processor/src/tcp_tx_ctrl.sv @@ -0,0 +1,131 @@ +import tcp_pkg::*; + +module tcp_tx_ctrl( + input i_clk, + input i_rst, + + input tcp_pkg::tx_ctrl_t i_tx_ctrl, + input logic i_tx_ctrl_valid, + output logic o_tx_ctrl_ack, + + output logic [15:0] o_ip_len, + output logic [31:0] o_seq_number, + output logic [31:0] o_ack_number, + output logic [7:0] o_flags, + output logic [15:0] o_window_size, + output logic o_hdr_valid, + + axis_intf.SLAVE s_axis, + input logic [15:0] s_axis_len, + axis_intf.MASTER m_axis, + + input wire i_packet_done +); + +assign m_axis.tdata = s_axis.tdata; +assign m_axis.tkeep = s_axis.tkeep; +assign m_axis.tvalid = s_axis.tvalid; +assign s_axis.tready = m_axis.tready; +assign m_axis.tlast = s_axis.tlast; +assign m_axis.tid = s_axis.tid; +assign m_axis.tdest = s_axis.tdest; +assign m_axis.tuser = s_axis.tuser; + +localparam FLAG_FIN = (1 << 0); +localparam FLAG_SYN = (1 << 1); +localparam FLAG_RST = (1 << 2); +localparam FLAG_PSH = (1 << 3); +localparam FLAG_ACK = (1 << 4); +localparam FLAG_URG = (1 << 5); +localparam FLAG_ECE = (1 << 6); +localparam FLAG_CWR = (1 << 7); + +logic [31:0] seq_num, seq_num_next; +assign o_seq_number = seq_num; + +enum logic [2:0] {IDLE, SEND_SYN, SEND_ACK, SEND_FIN, SEND_DATA} state, state_next; + +always_ff @(posedge i_clk) begin + if (i_rst) begin + state <= IDLE; + seq_num <= '0; + end else begin + state <= state_next; + seq_num <= seq_num_next; + end +end + +always_comb begin + state_next = state; + + o_ack_number = '0; + o_flags = '0; + o_window_size = 16'b1; + o_hdr_valid = '0; + + seq_num_next = seq_num; + + o_ip_len = 16'd40; // default length of IP packet + + case (state) + IDLE: begin + if (i_tx_ctrl_valid) begin + o_tx_ctrl_ack = '1; + + case (i_tx_ctrl) + TX_CTRL_SEND_SYN: state_next = SEND_SYN; + TX_CTRL_SEND_ACK: state_next = SEND_ACK; + TX_CTRL_SEND_FIN: state_next = SEND_FIN; + endcase + end + + if (s_axis.tvalid) begin + state_next = SEND_DATA; + end + end + + SEND_SYN: begin + o_flags = FLAG_SYN; + o_hdr_valid = '1; + + if (i_packet_done) begin + state_next = IDLE; + seq_num_next = seq_num + 1; + end + end + + SEND_ACK: begin + o_flags = FLAG_ACK; + o_hdr_valid = '1; + + if (i_packet_done) begin + state_next = IDLE; + seq_num_next = seq_num; + end + end + + SEND_DATA: begin + o_flags = FLAG_ACK | FLAG_PSH; + o_ip_len = 16'd40 + s_axis_len; // default length of IP packet + o_hdr_valid = '1; + + if (i_packet_done) begin + state_next = IDLE; + seq_num_next = seq_num + s_axis_len; + end + end + + SEND_FIN: begin + o_flags = FLAG_ACK | FLAG_FIN; + o_ip_len = 16'd40 + s_axis_len; // default length of IP packet + o_hdr_valid = '1; + + if (i_packet_done) begin + state_next = IDLE; + seq_num_next = seq_num + s_axis_len; + end + end + endcase +end + +endmodule \ No newline at end of file diff --git a/init_env.sh b/init_env.sh index 7302039..8e51a6a 100644 --- a/init_env.sh +++ b/init_env.sh @@ -8,12 +8,11 @@ export KICAD7_SYMBOL_DIR=$REPO_TOP/hw/kicad_library/symbols export KICAD7_3DMODEL_DIR=$REPO_TOP/hw/kicad_library/3dmodels export KICAD7_FOOTPRINT_DIR=$REPO_TOP/hw/kicad_library/footprints - -python3.11 -m venv .user_venv -. .user_venv/bin/activate - -module load efinity/2023.1 -module load iverilog/12.0 +#module load efinity/2023.1 +module load verilator module load gtkwave/3.3_gtk3 -# pip install -r requirements.txt +python3.12 -m venv .user_venv +. .user_venv/bin/activate + +pip install -r requirements.txt