分类 💻电子&计算机 下的文章 - 我的学记|刘航宇的博客
首页
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🎵音乐
🏞️壁纸
搜 索
1
【NPN/PNP三极管】放大电路饱和失真和截止失真的区别
12,710 阅读
2
论文写作中如何把word里面所有数字和字母替换为新罗马字体
7,155 阅读
3
【高数】形心计算公式讲解大全
6,638 阅读
4
【1】基于STM32CubeMX-STM32GPIO端口开发
5,149 阅读
5
如何判断运放是工作在线性区还是非线性区
4,995 阅读
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
嵌入式&系统
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
登录
搜 索
标签搜索
嵌入式
ASIC/FPGA
VLSI
SOC设计
机器学习
天线设计
C/C++
EDA&虚拟机
软件算法
小实验
信号处理
电子线路
通信&射频
随笔
笔试面试
硬件算法
Verilog
软件无线电
Python
DL/ML
刘航宇
嵌入式系统&数字IC爱好者博客
累计撰写
302
篇文章
累计收到
527
条评论
首页
栏目
🌻微语&随笔
励志美文
我的随笔
写作办公
📖电子&通信
嵌入式&系统
通信&信息处理
编程&脚本笔记
🗜️IC&系统
FPGA&ASIC
VLSI&IC验证
EDA&虚拟机
💻电子&计算机
IP&SOC设计
机器学习
软硬件算法
页面
📊归档
⏳时光机
📬留言
🐾友链
资助名单
推荐
🎓843课程班
🎵音乐
🏞️壁纸
用户登录
登录
💻电子&计算机(共27篇)
找到
27
篇与
💻电子&计算机
相关的结果
2023-07-10
Verilog实现AMBA--AHB To APB Bridge
1、APB桥APB桥是AMBA APB总线上的唯一主机,也是AMBA AHB的一个从机。下图表示了APB桥接口信号:APB Bridge将AHB传输转成APB传输并实现一下功能:(1)对锁存的地址进行译码并产生选择信号PSELx,在传输过程中只有一个选择信号可以被激活。 也就是选择出唯一 一个APB从设备以进行读写动作。(2)写操作时:负责将AHB送来的数据送上APB总线。(3)读操作时:负责将APB的数据送上AHB系统总线。(4)产生一时序选通信号PENABLE来作为数据传递时的启动信号2、读传输下图表示了APB到AHB的读传输:传输开始于AHB总线上的T1时刻,在T2时刻地址信息被APB采样,如果传输目标是外设总线,那么这个地址就会被译码成选择信号发给外设,T2即为APB总线的SETUP周期,T3为APB的ENABLE周期,PENABLE信号拉高。在该周期内,外设必须提供读数据,通常情况下,读数据直接AHB读数据总线上,总线主机在T4时刻对读数据进行采样。在频率很高的情况下,在ENABLE CYCLE中可能数据不能够直接映射到AHB总线,需要在APB桥中在T4的时候打插入一个额外的等待周期,并在T5的时候才被AHB主采样。虽然需要多一个等待周期(一共2个),但是由于频率提升了因此总的性能也提升了。下图表示了一个读突发传输,所有的传输都只有一个等待周期3、写传输下图表示了一个写传输:APB总线上的单块数据写操作不需要等待周期。 APB桥负责对地址和数据进行采样,并在写操作的过程中保持它们的值。下图表示了一个写突发传输:虽然第一个传输可以零等待完成,但后续每一个传输都需要插入一个等待状态。APB桥需要两个地址寄存器,以便在当前传输进行时,锁存下一次传输的地址。4、背靠背传输下图表示了一个背靠背传输,顺序为写、读、写、读:如果写操作之后跟随着读操作,那么需要 3 个等待周期来完成这个读操作。通常的情况下,不会有读操作之后紧跟着写操作的发生,因为两者之间 CPU 会进行指令读取,并且指令存储器也不太可能挂在在APB总线上。下面以ARM DesignStart项目提供的软件包里的AHB转APB桥的代码,对其进行学习与仿真,以深入理解APB桥的实现方法,该转换桥比较简单,实现的是一对一的转换,也可以配合APB slave multiplexer模块,实现一对多的方式(主要依靠APB高位地址译码得到各个从机的PSEL信号)。如果想学习APB系统总线,可以参考Synopsys公司的DW_APB IP,该IP最多可支持16个APB从机,并支持所有的突发传输类型。5、AHB_to_APB Bridge的Verilog实现`timescale 1ns / 1ps module ahb_to_apb #( // Parameter to define address width // 16 = 2^16 = 64KB APB address space parameter ADDRWIDTH = 16, parameter REGISTER_RDATA = 1, parameter REGISTER_WDATA = 0 ) ( //---------------------------------- // IO Declarations //---------------------------------- input wire HCLK, // Clock input wire HRESETn, // Reset input wire PCLKEN, // APB clock enable signal input wire HSEL, // Device select input wire [ADDRWIDTH-1:0] HADDR, // Address input wire [1:0] HTRANS, // Transfer control input wire [2:0] HSIZE, // Transfer size input wire [3:0] HPROT, // Protection control input wire HWRITE, // Write control input wire HREADY, // Transfer phase done input wire [31:0] HWDATA, // Write data output reg HREADYOUT, // Device ready output wire [31:0] HRDATA, // Read data output output wire HRESP, // Device response // APB Output output wire [ADDRWIDTH-1:0] PADDR, // APB Address output wire PENABLE, // APB Enable output wire PWRITE, // APB Write output wire [3:0] PSTRB, // APB Byte Strobe output wire [2:0] PPROT, // APB Prot output wire [31:0] PWDATA, // APB write data output wire PSEL, // APB Select output wire APBACTIVE, // APB bus is active, for clock gating of APB bus // APB Input input wire [31:0] PRDATA, // Read data for each APB slave input wire PREADY, // Ready for each APB slave input wire PSLVERR // Error state for each APB slave ); //---------------------------------- // Variable Declarations //---------------------------------- reg [ADDRWIDTH-3:0] addr_reg; // Address sample register reg wr_reg; // Write control sample register reg [2:0] state_reg; // State for finite state machine reg [3:0] pstrb_reg; // Byte lane strobe register wire [3:0] pstrb_nxt; // Byte lane strobe next state reg [1:0] pprot_reg; // PPROT register wire [1:0] pprot_nxt; // PPROT register next state wire apb_select; // APB bridge is selected wire apb_tran_end; // Transfer is completed on APB reg [2:0] next_state; // Next state for finite state machine reg [31:0] rwdata_reg; // Read/Write data sample register wire reg_rdata_cfg; // REGISTER_RDATA paramater wire reg_wdata_cfg; // REGISTER_WDATA paramater reg sample_wdata_reg; // Control signal to sample HWDATA //---------------------------------- // Local Parameter Declarations //---------------------------------- // State machine localparam ST_BITS = 3; localparam [ST_BITS-1:0] ST_IDLE = 3'b000; // Idle waiting for transaction localparam [ST_BITS-1:0] ST_APB_WAIT = 3'b001; // Wait APB transfer localparam [ST_BITS-1:0] ST_APB_TRNF = 3'b010; // Start APB transfer localparam [ST_BITS-1:0] ST_APB_TRNF2 = 3'b011; // Second APB transfer cycle localparam [ST_BITS-1:0] ST_APB_ENDOK = 3'b100; // Ending cycle for OKAY localparam [ST_BITS-1:0] ST_APB_ERR1 = 3'b101; // First cycle for Error response localparam [ST_BITS-1:0] ST_APB_ERR2 = 3'b110; // Second cycle for Error response localparam [ST_BITS-1:0] ST_ILLEGAL = 3'b111; // Illegal state //---------------------------------- // Start of Main Code //---------------------------------- // Configuration signal assign reg_rdata_cfg = (REGISTER_RDATA == 0) ? 1'b0 : 1'b1; assign reg_wdata_cfg = (REGISTER_WDATA == 0) ? 1'b0 : 1'b1; // Generate APB bridge select assign apb_select = HSEL & HTRANS[1] & HREADY; // Generate APB transfer ended assign apb_tran_end = (state_reg == 3'b011) & PREADY; assign pprot_nxt[0] = HPROT[1]; // (0) Normal, (1) Privileged assign pprot_nxt[1] = ~HPROT[0]; // (0) Data, (1) Instruction // Byte strobe generation // - Only enable for write operations // - For word write transfers (HSIZE[1]=1), all byte strobes are 1 // - For hword write transfers (HSIZE[0]=1), check HADDR[1] // - For byte write transfers, check HADDR[1:0] assign pstrb_nxt[0] = HWRITE & ((HSIZE[1])|((HSIZE[0])&(~HADDR[1]))|(HADDR[1:0]==2'b00)); assign pstrb_nxt[1] = HWRITE & ((HSIZE[1])|((HSIZE[0])&(~HADDR[1]))|(HADDR[1:0]==2'b01)); assign pstrb_nxt[2] = HWRITE & ((HSIZE[1])|((HSIZE[0])&( HADDR[1]))|(HADDR[1:0]==2'b10)); assign pstrb_nxt[3] = HWRITE & ((HSIZE[1])|((HSIZE[0])&( HADDR[1]))|(HADDR[1:0]==2'b11)); // Sample control signals always @(posedge HCLK or negedge HRESETn) begin if (~HRESETn) begin addr_reg <= }; wr_reg <= 1'b0; pprot_reg <= }; pstrb_reg <= }; end else if (apb_select) begin // Capture transfer information at the end of AHB address phase addr_reg <= HADDR[ADDRWIDTH-1:2]; wr_reg <= HWRITE; pprot_reg <= pprot_nxt; pstrb_reg <= pstrb_nxt; end end // Sample write data control signal // Assert after write address phase, deassert after PCLKEN=1 wire sample_wdata_set = apb_select & HWRITE & reg_wdata_cfg; wire sample_wdata_clr = sample_wdata_reg & PCLKEN; always @(posedge HCLK or negedge HRESETn) begin if (~HRESETn) sample_wdata_reg <= 1'b0; else if (sample_wdata_set | sample_wdata_clr) sample_wdata_reg <= sample_wdata_set; end // Generate next state for FSM // Note : case 3'b111 is not used. The design has been checked that // this illegal state cannot be entered using formal verification. always @(state_reg or PREADY or PSLVERR or apb_select or reg_rdata_cfg or PCLKEN or reg_wdata_cfg or HWRITE) begin case (state_reg) // Idle ST_IDLE : begin if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE)) next_state = ST_APB_TRNF; // Start APB transfer in next cycle else if (apb_select) next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high else next_state = ST_IDLE; // Remain idle end // Transfer announced on AHB, but PCLKEN was low, so waiting ST_APB_WAIT : begin if (PCLKEN) next_state = ST_APB_TRNF; // Start APB transfer in next cycle else next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high end // First APB transfer cycle ST_APB_TRNF : begin if (PCLKEN) next_state = ST_APB_TRNF2; // Change to second cycle of APB transfer else next_state = ST_APB_TRNF; // Change to state-2 end // Second APB transfer cycle ST_APB_TRNF2 : begin if (PREADY & PSLVERR & PCLKEN) // Error received - Generate two cycle // Error response on AHB by next_state = ST_APB_ERR1; // Changing to state-5 and 6 else if (PREADY & (~PSLVERR) & PCLKEN) begin // Okay received if (reg_rdata_cfg) // Registered version next_state = ST_APB_ENDOK; // Generate okay response in state 4 else // Non-registered version next_state = ; // Terminate transfer end else // Slave not ready next_state = ST_APB_TRNF2; // Unchange end // Ending cycle for OKAY (registered response) ST_APB_ENDOK : begin if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE)) next_state = ST_APB_TRNF; // Start APB transfer in next cycle else if (apb_select) next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high else next_state = ST_IDLE; // Remain idle end // First cycle for Error response ST_APB_ERR1 : next_state = ST_APB_ERR2; // Goto 2nd cycle of error response // Second cycle for Error response ST_APB_ERR2 : begin if (PCLKEN & apb_select & ~(reg_wdata_cfg & HWRITE)) next_state = ST_APB_TRNF; // Start APB transfer in next cycle else if (apb_select) next_state = ST_APB_WAIT; // Wait for start of APB transfer at PCLKEN high else next_state = ST_IDLE; // Remain idle end default : // Not used next_state = 3'bxxx; // X-Propagation endcase end // Registering state machine always @(posedge HCLK or negedge HRESETn) begin if (~HRESETn) state_reg <= 3'b000; else state_reg <= next_state; end // Sample PRDATA or HWDATA always @(posedge HCLK or negedge HRESETn) begin if (~HRESETn) rwdata_reg <= }; else if (sample_wdata_reg & reg_wdata_cfg & PCLKEN) rwdata_reg <= HWDATA; else if (apb_tran_end & reg_rdata_cfg & PCLKEN) rwdata_reg <= PRDATA; end // Connect outputs to top level assign PADDR = ; // from sample register assign PWRITE = wr_reg; // from sample register // From sample register or from HWDATA directly assign PWDATA = (reg_wdata_cfg) ? rwdata_reg : HWDATA; assign PSEL = (state_reg == ST_APB_TRNF) | (state_reg == ST_APB_TRNF2); assign PENABLE = (state_reg == ST_APB_TRNF2); assign PPROT = ; assign PSTRB = pstrb_reg[3:0]; // Generate HREADYOUT always @(state_reg or reg_rdata_cfg or PREADY or PSLVERR or PCLKEN) begin case (state_reg) ST_IDLE : HREADYOUT = 1'b1; // Idle ST_APB_WAIT : HREADYOUT = 1'b0; // Transfer announced on AHB, but PCLKEN was low, so waiting ST_APB_TRNF : HREADYOUT = 1'b0; // First APB transfer cycle // Second APB transfer cycle: // if Non-registered feedback version, and APB transfer completed without error // Then response with ready immediately. If registered feedback version, // wait until state_reg == ST_APB_ENDOK ST_APB_TRNF2 : HREADYOUT = (~reg_rdata_cfg) & PREADY & (~PSLVERR) & PCLKEN; ST_APB_ENDOK : HREADYOUT = reg_rdata_cfg; // Ending cycle for OKAY (registered response only) ST_APB_ERR1 : HREADYOUT = 1'b0; // First cycle for Error response ST_APB_ERR2 : HREADYOUT = 1'b1; // Second cycle for Error response default : HREADYOUT = 1'bx; // x propagation (note :3'b111 is illegal state) endcase end // From sample register or from PRDATA directly assign HRDATA = (reg_rdata_cfg) ? rwdata_reg : PRDATA; assign HRESP = (state_reg == ST_APB_ERR1) | (state_reg == ST_APB_ERR2); assign APBACTIVE = (HSEL & HTRANS[1]) | (|state_reg); endmodule使用的测试用例和AHB从机的测试用例基本一样,首先是顶层:`timescale 1ns / 1ps module top_tb(); //---------------------------------- // Local Parameter Declarations //---------------------------------- localparam AHB_CLK_PERIOD = 5; // Assuming AHB CLK to be 100MHz localparam SIZE_IN_BYTES = 2048; localparam ADDRWIDTH = 32; //---------------------------------- // Variable Declarations //---------------------------------- reg HCLK = 0; wire HWRITE; wire [1:0] HTRANS; wire [2:0] HSIZE; wire [2:0] HBURST; wire HREADYIN; wire [31:0] HADDR; wire [3:0] HPROT; wire [31:0] HWDATA; wire HREADYOUT; wire [1:0] HRESP; wire [31:0] HRDATA; reg HRESETn; wire HREADY; wire [ADDRWIDTH-1:0] PADDR; // APB Address wire PENABLE; // APB Enable wire PWRITE; // APB Write wire [31:0] PWDATA; // APB write data wire PSEL; // APB Select wire PREADY; wire PSLVERR; wire [2:0] PPROT; wire [3:0] PSTRB; wire [31:0] PRDATA; //---------------------------------- // Start of Main Code //---------------------------------- assign HREADY = HREADYOUT; //----------------------------------------------------------------------- // Generate HCLK //----------------------------------------------------------------------- always #AHB_CLK_PERIOD HCLK <= ~HCLK; //----------------------------------------------------------------------- // Generate HRESETn //----------------------------------------------------------------------- initial begin HRESETn = 1'b0; repeat(5) @(posedge HCLK); HRESETn = 1'b1; end ahb_master #( .START_ADDR (32'h0), .DEPTH_IN_BYTES (SIZE_IN_BYTES) ) u_ahb_master ( .HRESETn (HRESETn), .HCLK (HCLK), .HADDR (HADDR), .HPROT (HPROT), .HTRANS (HTRANS), .HWRITE (HWRITE), .HSIZE (HSIZE), .HBURST (HBURST), .HWDATA (HWDATA), .HRDATA (HRDATA), .HRESP (HRESP), .HREADY (HREADYOUT) ); ahb_to_apb #( .ADDRWIDTH (ADDRWIDTH), .REGISTER_RDATA (0), .REGISTER_WDATA (0) ) u_ahb_to_apb( .HCLK (HCLK), .HRESETn (HRESETn), .PCLKEN (1'b1), .HSEL (1'b1), .HADDR (HADDR), .HTRANS (HTRANS), .HSIZE (HSIZE), .HPROT (HPROT), .HWRITE (HWRITE), .HREADY (HREADY), .HWDATA (HWDATA), .HREADYOUT (HREADYOUT), .HRDATA (HRDATA), .HRESP (HRESP), .PADDR (PADDR), .PENABLE (PENABLE), .PWRITE (PWRITE), .PREADY (PREADY), .PSLVERR (PSLVERR), .PSTRB (PSTRB), .PPROT (PPROT), .PWDATA (PWDATA), .PSEL (PSEL), .APBACTIVE (APBACTIVE), .PRDATA (PRDATA) ); apb_mem #( .P_SLV_ID (0), .ADDRWIDTH (ADDRWIDTH), .P_SIZE_IN_BYTES (SIZE_IN_BYTES), .P_DELAY (0) ) u_apb_mem ( `ifdef AMBA_APB3 .PREADY (PREADY), .PSLVERR (PSLVERR), `endif `ifdef AMBA_APB4 .PSTRB (PSTRB), .PPROT (PPROT), `endif .PRESETn (HRESETn), .PCLK (HCLK), .PSEL (PSEL), .PENABLE (PENABLE), .PADDR (PADDR), .PWRITE (PWRITE), .PRDATA (PRDATA), .PWDATA (PWDATA) ); `ifdef VCS initial begin $fsdbDumpfile("top_tb.fsdb"); $fsdbDumpvars; end initial begin `ifdef DUMP_VPD $vcdpluson(); `endif end `endif endmodule然后是AHB master的功能模型:`timescale 1ns / 1ps `define SINGLE_TEST `define BURST_TEST module ahb_master #( //---------------------------------- // Paramter Declarations //---------------------------------- parameter START_ADDR = 0, parameter DEPTH_IN_BYTES = 32'h100, parameter END_ADDR = START_ADDR+DEPTH_IN_BYTES-1 ) ( //---------------------------------- // IO Declarations //---------------------------------- input wire HRESETn, input wire HCLK, output reg [31:0] HADDR, output reg [1:0] HTRANS, output reg HWRITE, output reg [2:0] HSIZE, output reg [2:0] HBURST, output reg [3:0] HPROT, output reg [31:0] HWDATA, input wire [31:0] HRDATA, input wire [1:0] HRESP, input wire HREADY ); //---------------------------------- // Variable Declarations //---------------------------------- reg [31:0] data_burst[0:1023]; //---------------------------------- // Start of Main Code //---------------------------------- initial begin HADDR = 0; HTRANS = 0; HPROT = 0; HWRITE = 0; HSIZE = 0; HBURST = 0; HWDATA = 0; while(HRESETn === 1'bx) @(posedge HCLK); while(HRESETn === 1'b1) @(posedge HCLK); while(HRESETn === 1'b0) @(posedge HCLK); `ifdef SINGLE_TEST repeat(3) @(posedge HCLK); memory_test(START_ADDR, END_ADDR, 1); memory_test(START_ADDR, END_ADDR, 2); memory_test(START_ADDR, END_ADDR, 4); `endif `ifdef BURST_TEST repeat(5) @(posedge HCLK); memory_test_burst(START_ADDR, END_ADDR, 1); memory_test_burst(START_ADDR, END_ADDR, 2); memory_test_burst(START_ADDR, END_ADDR, 4); memory_test_burst(START_ADDR, END_ADDR, 6); memory_test_burst(START_ADDR, END_ADDR, 8); memory_test_burst(START_ADDR, END_ADDR, 10); memory_test_burst(START_ADDR, END_ADDR, 16); memory_test_burst(START_ADDR, END_ADDR, 32); memory_test_burst(START_ADDR, END_ADDR, 64); memory_test_burst(START_ADDR, END_ADDR, 128); memory_test_burst(START_ADDR, END_ADDR, 255); repeat(5) @(posedge HCLK); `endif $finish(2); end //----------------------------------------------------------------------- // Single transfer test //----------------------------------------------------------------------- task memory_test; input [31:0] start; // start address input [31:0] finish; // end address input [2:0] size; // data size: 1, 2, 4 integer i; integer error; reg [31:0] data; reg [31:0] gen; reg [31:0] got; reg [31:0] reposit[START_ADDR:END_ADDR]; begin $display("%m: read-after-write test with %d-byte access", size); error = 0; gen = $random(7); for (i = start; i < (finish-size+1); i = i + size) begin gen = $random & ~32'b0; data = align(i, gen, size); ahb_write(i, size, data); ahb_read(i, size, got); got = align(i, got, size); if (got !== data) begin $display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data); error = error + 1; end end if (error == 0) $display("[%10d] %m OK: from %x to %x", $time, start, finish); $display("%m read-all-after-write-all with %d-byte access", size); error = 0; gen = $random(1); for (i = start; i < (finish-size+1); i = i + size) begin gen = & ~32'b0; data = align(i, gen, size); reposit[i] = data; ahb_write(i, size, data); end for (i = start; i < (finish-size+1); i = i + size) begin data = reposit[i]; ahb_read(i, size, got); got = align(i, got, size); if (got !== data) begin $display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data); error = error + 1; end end if (error == 0) $display("[%10d] %m OK: from %x to %x", $time, start, finish); end endtask //----------------------------------------------------------------------- // Burst transfer test //----------------------------------------------------------------------- task memory_test_burst; input [31:0] start; // start address input [31:0] finish; // end address input [7:0] leng; // burst length integer i; integer j; integer k; integer r; integer error; reg [31:0] data; reg [31:0] gen; reg [31:0] got; reg [31:0] reposit[0:1023]; integer seed; begin $display("[%10d] %m: read-all-after-write-all burst test with %d-beat access", $time, leng); error = 0; seed = 111; gen = $random(seed); k = 0; if (finish > (start+leng*4)) begin for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin for (j = 0; j < leng; j = j + 1) begin data_burst[j] = $random; reposit[j+k*leng] = data_burst[j]; end @(posedge HCLK); ahb_write_burst(i, leng); k = k + 1; end gen = $random(seed); k = 0; for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin @(posedge HCLK); ahb_read_burst(i, leng); for (j = 0; j < leng; j = j + 1) begin if (data_burst[j] != reposit[j+k*leng]) begin error = error+1; $display("[%10d] %m A=%hh D=%hh, but %hh expected", $time, i+j*leng, data_burst[j], reposit[j+k*leng]); end end k = k + 1; r = $random & 8'h0F; repeat(r) @(posedge HCLK); end if (error == 0) $display("%m %d-length burst read-after-write OK: from %hh to %hh",leng, start, finish); end else begin $display("%m %d-length burst read-after-write from %hh to %hh ???",leng, start, finish); end end endtask //----------------------------------------------------------------------- // As AMBA AHB bus uses non-justified data bus scheme, data should be // aligned according to the address. //----------------------------------------------------------------------- function [31:0] align; input [ 1:0] addr; input [31:0] data; input [ 2:0] size; // num of bytes begin `ifdef BIG_ENDIAN case (size) 1 : case (addr[1:0]) 0 : align = data & 32'hFF00_0000; 1 : align = data & 32'h00FF_0000; 2 : align = data & 32'h0000_FF00; 3 : align = data & 32'h0000_00FF; endcase 2 : case (addr[1]) 0 : align = data & 32'hFFFF_0000; 1 : align = data & 32'h0000_FFFF; endcase 4 : align = data&32'hFFFF_FFFF; default : $display($time,,"%m ERROR %d-byte not supported for size", size); endcase `else case (size) 1 : case (addr[1:0]) 0 : align = data & 32'h0000_00FF; 1 : align = data & 32'h0000_FF00; 2 : align = data & 32'h00FF_0000; 3 : align = data & 32'hFF00_0000; endcase 2 : case (addr[1]) 0 : align = data & 32'h0000_FFFF; 1 : align = data & 32'hFFFF_0000; endcase 4 : align = data&32'hFFFF_FFFF; default : $display($time,,"%m ERROR %d-byte not supported for size", size); endcase `endif end endfunction `include "ahb_transaction_tasks.v" endmoduleahb_transaction_tasks.v文件如下:`ifndef __AHB_TRANSACTION_TASKS_V__ `define __AHB_TRANSACTION_TASKS_V__ //----------------------------------------------------------------------- // AHB Read Task //----------------------------------------------------------------------- task ahb_read; input [31:0] address; input [2:0] size; output [31:0] data; begin @(posedge HCLK); HADDR <= #1 address; HPROT <= #1 4'b0001; // DATA HTRANS <= #1 2'b10; // NONSEQ; HBURST <= #1 3'b000; // SINGLE; HWRITE <= #1 1'b0; // READ; case (size) 1 : HSIZE <= #1 3'b000; // BYTE; 2 : HSIZE <= #1 3'b001; // HWORD; 4 : HSIZE <= #1 3'b010; // WORD; default : $display($time,, "ERROR: unsupported transfer size: %d-byte", size); endcase @(posedge HCLK); while (HREADY !== 1'b1) @(posedge HCLK); HTRANS <= #1 2'b0; // IDLE @(posedge HCLK); while (HREADY === 0) @(posedge HCLK); data = HRDATA; // must be blocking if (HRESP != 2'b00) $display($time,, "ERROR: non OK response for read"); @(posedge HCLK); end endtask //----------------------------------------------------------------------- // AHB Write Task //----------------------------------------------------------------------- task ahb_write; input [31:0] address; input [2:0] size; input [31:0] data; begin @(posedge HCLK); HADDR <= #1 address; HPROT <= #1 4'b0001; // DATA HTRANS <= #1 2'b10; // NONSEQ HBURST <= #1 3'b000; // SINGLE HWRITE <= #1 1'b1; // WRITE case (size) 1 : HSIZE <= #1 3'b000; // BYTE 2 : HSIZE <= #1 3'b001; // HWORD 4 : HSIZE <= #1 3'b010; // WORD default : $display($time,, "ERROR: unsupported transfer size: %d-byte", size); endcase @(posedge HCLK); while (HREADY !== 1) @(posedge HCLK); HWDATA <= #1 data; HTRANS <= #1 2'b0; // IDLE @(posedge HCLK); while (HREADY === 0) @(posedge HCLK); if (HRESP != 2'b00) $display($time,, "ERROR: non OK response write"); @(posedge HCLK); end endtask //----------------------------------------------------------------------- // AHB Read Burst Task //----------------------------------------------------------------------- task ahb_read_burst; input [31:0] addr; input [31:0] leng; integer i; integer ln; integer k; begin k = 0; @(posedge HCLK); HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b10; // NONSEQ if (leng >= 16) begin HBURST <= #1 3'b111; // INCR16 ln = 16; end else if (leng >= 8) begin HBURST <= #1 3'b101; // INCR8 ln = 8; end else if (leng >= 4) begin HBURST <= #1 3'b011; // INCR4 ln = 4; end else begin HBURST <= #1 3'b001; // INCR ln = leng; end HWRITE <= #1 1'b0; // READ HSIZE <= #1 3'b010; // WORD @(posedge HCLK); while (HREADY == 1'b0) @(posedge HCLK); while (leng > 0) begin for (i = 0; i < ln-1; i = i + 1) begin HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b11; // SEQ; @(posedge HCLK); while (HREADY == 1'b0) @(posedge HCLK); data_burst[k%1024] <= HRDATA; k = k + 1; end leng = leng - ln; if (leng == 0) begin HADDR <= #1 0; HTRANS <= #1 0; HBURST <= #1 0; HWRITE <= #1 0; HSIZE <= #1 0; end else begin HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b10; // NONSEQ if (leng >= 16) begin HBURST <= #1 3'b111; // INCR16 ln = 16; end else if (leng >= 8) begin HBURST <= #1 3'b101; // INCR8 ln = 8; end else if (leng >= 4) begin HBURST <= #1 3'b011; // INCR4 ln = 4; end else begin HBURST <= #1 3'b001; // INCR1 ln = leng; end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); data_burst[k%1024] = HRDATA; // must be blocking k = k + 1; end end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); data_burst[k%1024] = HRDATA; // must be blocking end endtask //----------------------------------------------------------------------- // AHB Write Burst Task // It takes suitable burst first and then incremental. //----------------------------------------------------------------------- task ahb_write_burst; input [31:0] addr; input [31:0] leng; integer i; integer j; integer ln; begin j = 0; ln = 0; @(posedge HCLK); while (leng > 0) begin HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b10; // NONSEQ if (leng >= 16) begin HBURST <= #1 3'b111; // INCR16 ln = 16; end else if (leng >= 8) begin HBURST <= #1 3'b101; // INCR8 ln = 8; end else if (leng >= 4) begin HBURST <= #1 3'b011; // INCR4 ln = 4; end else begin HBURST <= #1 3'b001; // INCR ln = leng; end HWRITE <= #1 1'b1; // WRITE HSIZE <= #1 3'b010; // WORD for (i = 0; i < ln-1; i = i + 1) begin @(posedge HCLK); while (HREADY == 1'b0) @(posedge HCLK); HWDATA <= #1 data_burst[(j+i)%1024]; HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b11; // SEQ; while (HREADY == 1'b0) @(posedge HCLK); end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); HWDATA <= #1 data_burst[(j+i)%1024]; if (ln == leng) begin HADDR <= #1 0; HTRANS <= #1 0; HBURST <= #1 0; HWRITE <= #1 0; HSIZE <= #1 0; end leng = leng - ln; j = j + ln; end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); if (HRESP != 2'b00) begin // OKAY $display($time,, "ERROR: non OK response write"); end `ifdef DEBUG $display($time,, "INFO: write(%x, %d, %x)", addr, size, data); `endif HWDATA <= #1 0; @(posedge HCLK); end endtask `endif还需要一个APB的从机模块:`timescale 1ns / 1ps `define AMBA_APB3 `define AMBA_APB4 module apb_mem #( parameter P_SLV_ID = 0, parameter ADDRWIDTH = 32, parameter P_SIZE_IN_BYTES = 1024, // memory depth parameter P_DELAY = 0 // reponse delay ) ( //---------------------------------- // IO Declarations //---------------------------------- `ifdef AMBA_APB3 output wire PREADY, output wire PSLVERR, `endif `ifdef AMBA_APB4 input wire [2:0] PPROT, input wire [3:0] PSTRB, `endif input wire PRESETn, input wire PCLK, input wire PSEL, input wire PENABLE, input wire [ADDRWIDTH-1:0] PADDR, input wire PWRITE, output reg [31:0] PRDATA = 32'h0, input wire [31:0] PWDATA ); //---------------------------------- // Local Parameter Declarations //---------------------------------- localparam DEPTH = (P_SIZE_IN_BYTES+3)/4; localparam AW = logb2(P_SIZE_IN_BYTES); //---------------------------------- // Variable Declarations //---------------------------------- `ifndef AMBA_APB3 wire PREADY; `else assign PSLVERR = 1'b0; `endif `ifndef AMBA_APB4 wire [3:0] PSTRB = 4'hF; `endif reg [7:0] mem0[0:DEPTH-1]; reg [7:0] mem1[0:DEPTH-1]; reg [7:0] mem2[0:DEPTH-1]; reg [7:0] mem3[0:DEPTH-1]; wire [AW-3:0] TA = PADDR[AW-1:2]; //---------------------------------- // Start of Main Code //---------------------------------- //-------------------------------------------------------------------------- // write transfer // ____ ____ ____ // PCLK ___| |____| |____| |_ // ____ ___________________ _____ // PADDR ____X__A________________X_____ // ____ ___________________ _____ // PWDATA ____X__DW_______________X_____ // ___________________ // PWRITE ____| |_____ // ___________________ // PSEL ____| |_____ // _________ // PENABLE ______________| |_____ //-------------------------------------------------------------------------- always @(posedge PCLK) begin if (PRESETn & PSEL & PENABLE & PWRITE & PREADY) begin if (PSTRB[0]) mem0[TA] <= PWDATA[ 7: 0]; if (PSTRB[1]) mem1[TA] <= PWDATA[15: 8]; if (PSTRB[2]) mem2[TA] <= PWDATA[23:16]; if (PSTRB[3]) mem3[TA] <= PWDATA[31:24]; end end //-------------------------------------------------------------------------- // read // ____ ____ ____ // PCLK ___| |____| |____| |_ // ____ ___________________ _____ // PADDR ____X__A________________X_____ // ____ _________ _____ // PRDATA ____XXXXXXXXXXX__DR_____X_____ // ____ _____ // PWRITE ____|___________________|_____ // ___________________ // PSEL ____| |_____ // _________ // PENABLE ______________| |_____ //-------------------------------------------------------------------------- always @(posedge PCLK) begin if (PRESETn & PSEL & ~PENABLE & ~PWRITE) begin PRDATA[ 7: 0] <= mem0[TA]; PRDATA[15: 8] <= mem1[TA]; PRDATA[23:16] <= mem2[TA]; PRDATA[31:24] <= mem3[TA]; end end `ifdef AMBA_APB3 localparam ST_IDLE = 'h0, ST_CNT = 'h1, ST_WAIT = 'h2; reg [7:0] count; reg ready; reg [1:0] state=ST_IDLE; assign PREADY = (P_DELAY == 0) ? 1'b1 : ready; always @(posedge PCLK or negedge PRESETn) begin if (PRESETn == 1'b0) begin count <= 'h0; ready <= 'b1; state <= ST_IDLE; end else begin case (state) ST_IDLE : begin if (PSEL && (P_DELAY > 0)) begin ready <= 1'b0; count <= 'h1; state <= ST_CNT; end else begin ready <= 1'b1; end end // ST_IDLE ST_CNT : begin count <= count + 1; if (count >= P_DELAY) begin count <= 'h0; ready <= 1'b1; state <= ST_WAIT; end end // ST_CNT ST_WAIT : begin ready <= 1'b1; state <= ST_IDLE; end // ST_WAIT default : begin ready <= 1'b1; state <= ST_IDLE; end endcase end // if end // always `else assign PREADY = 1'b1; `endif // Calculate log-base2 function integer logb2; input [31:0] value; reg [31:0] tmp; begin tmp = value - 1; for (logb2 = 0; tmp > 0; logb2 = logb2 + 1) tmp = tmp >> 1; end endfunction // synopsys translate_off `ifdef RIGOR always @(posedge PCLK or negedge PRESETn) begin if (PRESETn == 1'b0) begin end else begin if (PSEL & PENABLE) begin if (TA >= DEPTH) $display($time,,"%m: ERROR: out-of-bound 0x%x", PADDR); end end end `endif // synopsys translate_on endmodule6、仿真用VCS进行仿真,打印信息如下可见仿真完全正确,这里也只是做了AHB总线的单一传输和各种长度的增量突发,回环突发未涉及(对APB桥来说,它并不关心HBURST的信号值)。下面挂两张仿真截图:单一传输,先写后读:突发传输,先写完,再读完
2023年07月10日
549 阅读
1 评论
1 点赞
2023-06-20
Microsemi Libero SOC常见问题-FPGA全局网络的设置
问题描述最近在一个FPGA工程中分配rst_n引脚时,发现rst_n引脚类型为CLKBUF,而不是常用的INBUF,在分配完引脚commit检查报错,提示需要连接到全局网络引脚上。尝试忽略这个错误,直接进行编译,在布局布线时又报错。尝试取消引脚锁定LOCK,再次commit检查成功,编译下载正常,但是功能不对,再次打开引脚分配界面,发现是rst_n对应的引脚并不是我设置的那个,看来是CLKBUF的原因。问题分析网络上搜索一些资料后,发现是在一些工程中会出现这个问题,如果rst_n信号连接了许多IP核,和很多自己写的模块,这样rst_n就需要很强的驱动能力,即扇出能力(Fan Out),而且布线会很长,所以在分配管脚时,IDE自动添加了CLKBUF,来提供更大的驱动能力和更小的延时。那么什么是FPGA的全局时钟网络资源呢?FPGA全局布线资源简介我们知道FPGA的资源主要由以下几部分组成:可编程输入输出单元(IOB)基本可编程逻辑单元(CLB)数字时钟管理模块(DCM)嵌入块式RAM(BRAM)丰富的布线资源内嵌专用硬件 模块。我们重点介绍布线资源,FPGA中布线的长度和工艺决定着信号在的驱动能力和传输速度。FPGA的布线资源可大概分为4类:全局布线资源:芯片内部全局时钟和全局复位/置位的布线长线资源:完成芯片Bank间的高速信号和第二全局时钟信号的布线短线资源:完成基本逻辑单元之间的逻辑互连和布线分布式布线资源:用于专有时钟、复位等控制信号线。一般设计中,我们不需要直接参与布线资源的分配,IDE中的布局布线器(Place and Route)可以根据输入逻辑网表的拓扑结构,和用户设定的约束条件来自动的选择布线资源。其中全局布线资源具有最强的驱动能力和最小的延时,但是只能限制在全局管脚上,厂商会特殊设计这部分资源,如Xilinx FPGA中的全局时钟资源一般使用全铜层工艺实现,并设计了专门时钟缓冲和驱动结构,从而使全局时钟到达芯片内部的所有可配置逻辑单元(CLB)、I/O单元(IOB)和选择性块RAM(Block Select ROM)的时延和抖动都为最小。一般全局布线资源都是针对输入信号来说的,如果IDE自动把rst_n引脚优化为了全局网络,而硬件电路设计上却把rst_n分配到了普通管脚上,那么就很麻烦了,要么牺牲全局网络的优势,手动将全局网络改为普通网络,要么为了利用全局网络的优势,修改电路,重新分配硬件引脚。所以如果一些关键的信号确定了,如时钟、复位等,产品迭代修改电路时,不要轻易调整这些关键引脚。Microsemi FPGA的全局布线资源Microsemi FPGA的全局时钟管脚编号,我们可以通过官方Datasheet来找到,在手册中关于全局IO的命名规则上,有如下介绍:即只有管脚名称为GFA0/1/2,GFB0/1/2,GFC0/1/2,GCA0/1/2,GCB0/1/2,GCC0/1/2(共18个)才支持全局网络分配,而且,如果使用了GFA0引脚作为全局输入引脚,那么GFA1和GFA2都不能再作为全局网络了,其他GFC等同理,这一点在设计电路时要特别注意。对于Microsemi SmartFusion系列FPGA芯片A2F200M3F-PQ208来说,只有7个,分别是:GFA0-15、GFA1-14、GFA2-13、GCA0-145、GCA1-146、GCC2-151、GCA2-153,引脚分配如下图所示:所以在设计A2F200M3F-PQ208硬件电路时,时钟和复位信号尽量分配在这些管脚上,以获得硬件性能的最大效率。这些全局引脚的延时时间都是非常小的,具体的时间参数可以从数据手册上获得。全局网络改为普通输入像文章开头介绍的情况,IDE自动把rst_n设置为全局网络,而实际硬件却不是全局引脚,应该怎么修改为普通输入呢?即CLKBUF改为普通的INBUF?网络上zlg的教程中使用的是版本较低的Libero IDE 8.0,新版的Libero SoC改动非常大,文中介绍的修改sdc文件的方法已经不能使用了,这里提供新的修改方法——调用INBUF IP Core的方式。这里官方已经考虑到了,在官方提供的INBUF IP Core可以把CLKBUF改为INBUF。在Catalog搜索框中输入:INBUF,可以看到这里也提供了LVDS信号专用的IP Core。拖动到SmartDesign中进行连接或者在源文件中直接例化的方式调用INBUF Core:INBUF INBUF_0( // Inputs .PAD ( rst_n ), // Outputs .Y ( rst_n_Y ) );这两种方法都是一样的。添加完成之后,再进行管脚分配,可以看到rst_n已经是普通的INBUF类型了,可以进行普通管脚的分配,而且commit检查也是没有错误的。普通输入上全局网络如果布局布线器没有把我们要的信号上全局网络,如本工程的CLK信号,IDE自动生成的是INBUF类型,我们想让他变成CLKBUF,即全局网络,来获取最大的驱动能力和最小的延时。那么应该怎么办呢?这里同样要使用到一个IP Core,和INBUF类似,这个IP Core的名称是CLKBUF,同样是在Catalog目录中搜索:CLKBUF,可以看到有CLKBUF开头的很多Core,这里同样也提供了LVDS信号专用的IP Core。可以直接拖动Core到SmartDesign图形编辑窗口:或者是在源文件中以直接例化的方式调用:CLKBUF CLKBUF_0( // Inputs .PAD ( CLKA ), // Outputs .Y ( CLKA_Y ) );这两种方式都是一样的,添加完成之后,再进行管脚分配,可以看到CLKA已经是全局网络了,只能分配在全局管脚上。总结对于不同厂家的FPGA,让某个信号上全局网络的方法都不尽相同,如Xilinx的FPGA是通过BUFG Core来让信号上全局网络,而且还有带使能端的全局缓冲 BUFGCE , BUFGMUX 的应用更为灵活,有2个输入,可通过选择端选择输出哪一个。所以,信号的全局缓冲设置要根据不同厂商Core的不同来使用。
2023年06月20日
678 阅读
0 评论
2 点赞
Microsemi Libero SOC使用示例—建立点灯工程
嵌入式开发中的Hello World,点灯是再也基础不过的实验了,通过点灯实验,可以了解芯片GPIO的控制和开发环境IDE新建工程的流程,对于FPGA来说,每个IO口几乎一样,所以本篇文章主要学习一下如何基于Microsemi Libero集成开发环境建立一个示例工程,让一个LED以500ms的频率闪烁,以Microsemi SmartFusion系列FPGA——A2F200M3F为例,Microsemi其他系列FPGA芯片过程类似。准备工作工欲利其事,必先利其器,充分的准备工作很有必要。软件准备:Microsemi Libero SoC集成开发环境,并已经成功注册,软件版本推荐V11.8或更高版本。硬件准备:Microsemi FPGA开发板,主控芯片A2F200M3F-PQ208,其他型号芯片类似。Flash Pro 4或Flash Pro5下载器,用于给FPGA芯片下载程序和调试。新建工程的主要步骤新建工程,选择芯片型号等新建设计,使用Verilog编写点灯模块。仿真验证,对编写的点灯模块进行时序仿真,来验证是否满足设计需求。综合、管脚分配、布局、布线。生成程序文件,连接开发板,使用FlashPro下载程序到芯片内,观察现象是否和设计的一致。1.新建工程和大多数IDE一样,选择Project -> New Project,新建一个工程。输入工程名称LED_Blink,选择工程存放的路径,工程名称和路径不要有中文字符和空格,选择源文件的类型Verilog或者VHDL。选择芯片型号,这里选择Microsemi SmartFusion系列下的A2F200M3F芯片,PQ208封装,把鼠标放在所选芯片上,可以查看芯片的详细参数:封装、速度等级、温度范围,内核电压、Flash ROM大小、用户IO数目、RAM大小、Flash ROM大小,ARM Cortex-M3 SoC的外设配置等详细的参数。选择IO的电平标准,不同的电平标准,高低电平的电压范围是不同的,这里选择默认的LVTTL。是否创建MSS模块,MSS里有PLL和ARM Cortex-M3的使用,以后用到PLL和ARM核时再添加,这里先不选择,以后需要也可以再创建。是否导入已经存在的HDL文件,如果已经有一些写好的模块,可以在这里直接导入。是否导入已经存在的管脚约束文件,这里选择不添加,我们会在后面通过图形化工具来指定管脚。到这里,工程就创建完成了,然后会在存储路径下生成一个和工程名称一样的文件夹,工程相关的所以文件都存放在这里。主要包括以下几个文件夹:具体每个文件夹存放的是什么文件,我们在以后的文章再详细介绍。以上的工程配置在创建完工程之后,也可以再次更改,可以通过Project->Project Setting查看或更改配置:或者通过点击如下图标来进入配置界面:弹出如下窗口,和新建工程是一样的,可以更改FPGA的型号,但只限于同一个系列内。2.添加设计文件Microsemi Libero开发环境支持HDL方式和SmarDesign方式来创建设计,HDL方式支持VerilogHDL和VHDL两种硬件描述语言,而SmartDesign方式和Xilinx的Schematic原理图方式是一样的,是通过图形化的方式来对各个模块之间的连接方式进行编辑,两种方式都可以完成设计。由于本实验功能简单,所以以使用Verilog文件为例。创建Verilog文件创建Verilog文件有多种方式,可以直接双击左侧菜单中的Create Design->Create HDL或者点击File->New->HDL,这两种方式都可以创建一个Verilog设计文件,这里选择Verilog文件。输入模块名称:led_driver,不用添加.v后缀名,Libero软件会自动添加。源代码:module led_driver( //input input clk, //clk=2MHz input rst_n, //0=reset //output output reg led ); parameter T_500MS = 999999; //1M reg [31:0] cnt; always @ (posedge clk) begin if(!rst_n) cnt <= 32'b0; else if(cnt >= T_500MS) cnt <= 32'b0; else //cnt < T_500MS cnt <= cnt + 32'b1; end always @ (posedge clk) begin if(!rst_n) led <= 1'b1; else if(cnt >= T_500MS) led <= ~led; end endmodule可以看到,代码非常的简单,定义一个计数器,系统时钟为2MHz=500ns,500ms=1M个时钟周期,当计数到500ms时,LED翻转闪烁。3.仿真验证编写完成,之后,点击对号进行语法检查,如果没有语法错误就可以进行时序仿真了。新建Testbench文件底部切换到Design Hierarchy选项卡,在led模块上右键选择Create Testbechch创建仿真文件,选择HDL格式。给创建的testbench文件名一般为模块名后加_tb,这里为:led_driver_tb,因为我们的板子外部晶体为2M,所以这里系统时钟周期为500ns,这个也可以在文件中更改。点击OK之后,可以看到,Libero软件已经为我们生成了一些基本代码,包括输入端口的定义,系统时钟的产生,输入信号的初始化等等。我们只需要再增加几行即可。`timescale 1ns/100ps module led_driver_tb; parameter SYSCLK_PERIOD = 500;// 2MHZ reg SYSCLK; reg NSYSRESET; wire led; //add output reg initial begin SYSCLK = 1'b0; NSYSRESET = 1'b0; end initial begin #(SYSCLK_PERIOD * 10 ) NSYSRESET = 1'b0; //add system reset #(SYSCLK_PERIOD * 100 ) NSYSRESET = 1'b1; //add system set end always @(SYSCLK) //generate system clock #(SYSCLK_PERIOD / 2.0) SYSCLK <= !SYSCLK; led_driver led_driver_0 ( // Inputs .clk(SYSCLK), .rst_n(NSYSRESET), // Outputs .led(led ) //add port // Inouts ); endmodule仿真代码也非常简单,输入信号初始化,NSYSRESET在10个时钟周期之后拉低,100个时钟周期之后拉高。使用ModelSim进行时序仿真仿真代码语法检查无误后,可以进行ModelSim自动仿真,在安装Libero时,已经默认安装了ModelSim仿真软件,并和Libero进行了关联。直接双击Simulate,Libero会自动打开ModelSim。可以看到输入输出信号,已经为我们添加好了:先点击复位按钮,复位系统,然后设置要运行的时间,由于设计的是500ms闪烁一次,这里我们先运行2s,即2000ms,在ModelSim中2秒已经算是很长的时间了,然后点击时间右边的运行按钮,耐心等待,停止之后就会看到led按500ms变化一次的波形了,如下图所示,可以再添加一个cnt信号到波形观察窗口,可以看到cnt周期性的变化。使用2个光标的精确测量,可以看出,led每隔500ms翻转一次,说明程序功能是正确的。4.管脚分配与STM32等MCU不同,FPGA的引脚配置非常灵活,如STM32只有固定的几个引脚才能作为定时器PWM输出,而FPGA通过管脚分配可以设置任意一个IO口输出PWM,而且使用起来非常灵活,这也是FPGA和MCU的一个区别,当然其他的功能,如串口外设,SPI外设等等,都可以根据需要自己用HDL代码来实现,非常方便。时序仿真正常之后,就可以进行管脚分配了,即把模块的输入输出端口,真正的分配到芯片实际的引脚上,毕竟我们的代码是要运行在真正的芯片上的。打开引脚配置图形化界面双击Create/Edit I/O Attributes,打开图形化配置界面,在打开之前,Libero会先进行综合(Synthesize)、编译(Complie),当都运行通过时,才会打开配置界面。分配管脚管脚可视化配置工具使用起来非常简单:引脚号指定、IO的电平标准,内部上下拉等等,非常直观。把时钟、复位、LED这些管脚分配到开发板原理图中对应的引脚,在分配完成之后,可以点击左上角的commit and check进行检查。在分配完成之后,为了以后方便查看已经分配的引脚,可以导出一个pdc引脚约束文件,选择Designer窗口下的File->Export->Constraint File,会导出一个led_driver.pdc文件,保存在工程目录下的constraint文件夹。一些特殊管脚的处理SmartFusion系列的FPGA芯片,在分配个别引脚,如35-39、43-47这些引脚时,直接不能分配,这些引脚属于MSS_FIO特殊引脚,具体怎么配置为通用IO,可以查看下一篇文章。而新一代的SmartFusion 2系列的FPGA芯片则没有这种情况。5.程序下载管脚分配完成之后,连接FlashPro下载器和开发板的JTAG接口,关闭Designer窗口,选择Program Device,耐心等待几分钟,如果连接正常,会在右侧输出编程信息:擦除、验证、编程等操作,下载完成之后,就会看到板子上的LED闪烁起来了。Microsemi FPGA的Flash结构和Altera、Xilinx不同,Microsemi FPGA在下载程序时,并不是下载程序到SPI Flash,而是直接下载到FPGA内部的。目前,FPGA 市场占有率最高的两大公司Xilinx和Altera 生产的 FPGA 都是基于 SRAM 工艺的,需要在使用时外接一个片外存储器以保存程序。上电时,FPGA 将外部存储器中的数据读入片内 RAM,完成配置后,进入工作状态;掉电后 FPGA 恢复为白片,内部逻辑消失。这样 FPGA 不仅能反复使用,还无需专门的 FPGA编程器,只需通用的 EPROM、PROM 编程器即可。而Microsemi的SmartFusion、SmartFusion2、ProASICS3、ProASIC3E系列基于Flash结构,具备反复擦写和掉电后内容非易失性, 因此基于Flash结构的FPGA同时具备了SRAM结构的灵活性和反熔丝结构的可靠性,这种技术是最近几年发展起来的新型FPGA实现工艺,目前实现的成本还偏高,没有得到大规模的应用。示例工程下载基于Libero V11.8.2.4的工程下载:
2023年06月09日
804 阅读
0 评论
2 点赞
2023-05-29
Windows安装Jupyter Notebook及机器学习的库
Jupyter Notebook安装(Windows)下载Jupyter Notebook(1)打开cmd(如果没有把Python安装目录添加到Path,需要切换到Python安装目录的Scripts目录下);(2)输入pip install jupyter;(推荐在外网环境下安装)2.库安装下载下面文件然后执行:pip3 install -r requirements.txt启动Juypter Notebook(1)命令行窗口输入jupyter notebook;浏览器会打开Jupyter Notebook窗口,说明Jupyter Notebook安装成功。(2) 配置Jupyter Notebook1、行窗口输入jupyter notebook --generate-config,会发现C:\Users\用户名\ .jupyter下多出了一个配置文件jupyter_notebook_config.py;2、这个配置文件,找到下面这句#c.NotebookApp.notebook_dir = ''。可以把它修改成c.NotebookApp.notebook_dir = 'D:\jupyter-notebook',当然具体的目录由自己创建的文件夹决定(需要自己创建)。配置文件修改完成后,以后在jupyter notebook中写的代码都会保存在该目录下。现在重新启动jupyter notebook,就进入了新的工作目录;5.添加代码自动补全功能(可选)(1)打开cmd,输入pip install jupyter_contrib_nbextensions,等待安装成功;(2)安装完之后需要配置nbextension(配置前要确保已关闭jupyter notebook),在cmd中输入jupyter contrib nbextension install --user --skip-running-check,等待配置成功;(3)在前两步成功的情况下,启动jupyter notebook,会发现在选项栏中多出了Nbextension的选项,点开该选项,并勾选Hinterland,即可添加代码自动补全功能。
2023年05月29日
172 阅读
0 评论
1 点赞
FPGA&Matlab联合开发之滤波器模块(带通滤波器为例)
在通信或者信号处理中,数字滤波器是非常重要的模块,前面有关博文中提到FIR滤波器的一步步Verilog设计,如https://ee.ac.cn/index.php/archives/511.html本文以带通滤波器为例,利用Matlab进行高效开发MATLAB生成低通滤波器设计步骤:(1)在MATLAB命令窗口中输入“filterDesigner”或“fdatool”出现如下对话框设置FIR滤波器为和需要的阶数滤波器,选择窗函数的类型为海明窗函数,海明窗函数可以得到旁瓣更小的效果,能量更加集中在主瓣中设置带通滤波器的上下截至频率分别为4MHz 和 5MHz(2)量化输入输出,点击工作栏左边的量化选项,即“set quantization parameters”选项,选择定点,设置输入字长为8,其他选择默认,如下图示:(3)根据自己需求,细化一些配置。这里不难探索设置完成后,点击Targets中Generate HDL,选择生成Verilog 代码,设置路径,MATLAB即可生成设计好的滤波器Verilog HDL 代码以及测试文件:(4)根据需求,配置输出.v文件的全局信号、测试文件,点击生成,生成后,Matlab主页面会提示.v生成的文件路径Modelsim仿真上述文件可以看到输入信号在4MHZ~5MHZ备保留,设计无误。需要注意一点,一般Modelsim仿真输出波形都是离散的01信号,这里需要配置一下,在上图被选中的信号中,在左侧右键鼠标。右击,format,analog(automatic);右击,radix,decimal;这两个步骤完成之后,就出现上图模拟信号的效果
2023年05月21日
297 阅读
0 评论
2 点赞
2023-05-15
机器学习代码实现:线性回归与岭回归
1.线性回归模型线性回归模型是最简单的一种线性模型,模型的形式就是:$y=W^T x+b$我们可以通过对原本的输入向量x扩增一个为1的维度将参数W和b统一成一个参数W,即模型变成了$y=W^T x$这里的W是原本两个参数合并之后的而其损失函数的形式是残差平方损失RSS$L=\frac \sum_^m\left(W^T x_i-y_i\right)^2=\frac\left(W^T X-y\right)^T\left(W^T X-y\right)$我们很容易就可以通过求导得到线性回归模型的关于W的梯度$\nabla_W L=\frac \sum_^m\left(W^T x_i-y_i\right) x_i=\frac X^T\left(W^T X-y\right)$这样一来我们就可以通过梯度下降的方式来训练参数W,可以用下面的公式表示$W:=W-\alpha \frac X^T\left(W^T X-y\right)$但实际上线性模型的参数W可以直接求解出,即:$W=\left(X^T X\right)^ X^T y$2.线性回归的编程实现具体代码中的参数的形式可能和上面的公式推导略有区别,我们实现了一个LinearRegression的类,包含fit,predict和loss三个主要的方法,fit方法就是求解线性模型的过程,这里我们直接使用了正规方程来解class LinearRegression: def fit(self, X: np.ndarray, y: np.ndarray) -> float: N, D = X.shape # 将每个样本的特征增加一个维度,用1表示,使得bias和weight可以一起计算 # 这里在输入的样本矩阵X末尾增加一列来给每个样本的特征向量增加一个维度 # 现在X变成了N*(D+1)维的矩阵了 expand_X = np.column_stack((X, np.ones((N, 1)))) self.w = np.matmul(np.matmul(np.linalg.inv(np.matmul(expand_X.T, expand_X)), expand_X.T), y) return self.loss(X, y)predict实际上就是将输入的矩阵X放到模型中进行计算得到对应的结果,loss给出了损失函数的计算方式:def loss(self, X: np.ndarray, y: np.ndarray): """ 线性回归模型使用的是RSS损失函数 :param X:需要预测的特征矩阵X,维度是N*D :param y:标签label :return: """ delta = y - self.predict(X) total_loss = np.sum(delta ** 2) / X.shape[0] return total_loss3.岭回归Ridge Regression与代码实现岭回归实际上就是一种使用了正则项的线性回归模型,也就是在损失函数上加上了正则项来控制参数的规模,即:$L=\frac \sum_^m\left(W^T x_i-y_i\right)^2+\lambda\|W\|_2=\frac\left(W^T X-y\right)^T\left(W^T X-y\right)+\lambda W^T W$因此最终的模型的正规方程就变成了:$W=\left(X^T X+\lambda I\right)^ X^T y$这里的\lambda是待定的正则项参数,可以根据情况选定,岭回归模型训练的具体代码如下class RidgeRegression: def fit(self, X: np.ndarray, y: np.ndarray): N, D = X.shape I = np.identity(D + 1) I[D][D] = 0 expand_X = np.column_stack((X, np.ones((N, 1)))) self.W = np.matmul(np.matmul(np.linalg.inv(np.matmul(expand_X.T, expand_X) + self.reg * I), expand_X.T), y) return self.loss(X, y)4.数据集实验这里使用了随机生成的二位数据点来对线性模型进行测试,测试结果如下:线性模型测试结果岭回归也使用同样的代码进行测试。
2023年05月15日
123 阅读
0 评论
1 点赞
联发科2024年数字IC设计验证实习生考题解析
1、(20分)逻辑化简:(1)列出真值表(2)列出其卡诺图(3)写出Z的最简表达式答:卡诺图:卡诺图画完后勾1就完事了提示:约束项的一般形式为:与或式 = 0 (如果不是此种形式,化为此种形式);如此题的BC = 0;或者AB +CD = 0;ABC + CD = 0;等等。BC=0(即B=1,且C=1)对应的格子画X。2、(5分)ASIC flow 中综合工具的作用是什么?综合的时候需要SDC文件进行约束,请列举3条SDC的语法。答:ASIC flow 中综合工具的作用是将RTL级的硬件描述语言转换为与特定工艺库相匹配的门级网表,同时进行优化以满足时序、面积和功耗等约束。综合的时候需要SDC文件进行约束,SDC文件是一种基于Tcl的格式,用于指定设计的时序约束34。SDC文件中的常用时序约束语法有:create_clock -name <clock_name> -period <clock_period> [get_ports <clock_port>] 用于创建时钟源并指定时钟周期。 set_input_delay -clock <clock_name> <delay_value> [get_ports <input_port>] 用于指定输入端口相对于时钟源的延迟。 set_output_delay -clock <clock_name> <delay_value> [get_ports <output_port>] 用于指定输出端口相对于时钟源的延迟。 set_clock_uncertainty -setup <setup_value> -hold <hold_value> <clock_name> 用于指定时钟源的不确定性,包括建立时间和保持时间。 set_false_path -from [get_ports <source_port>] -to [get_ports <destination_port>] 用于指定不需要进行时序分析的路径。 set_multicycle_path -setup -from [get_clocks <source_clock>] -to [get_clocks <destination_clock>] <cycle_number> 用于指定多周期路径,即源时钟和目标时钟之间有多个周期的时间差。3、(10分)智力题(1)2 12 1112 3112 132112 ,下一个数?给理由;答:第一个数是2,第二个数是12,表示前一个数有1个2;第三个数是1112,表示前一个数有1个1和1个2;以此类推。所以,下一个数是1113122112,表示前一个数有1个1,1个3,2个1和2个2(2)有一个小偷费劲力气进入到了银行的金库里。在金库里他找到了一百个箱子,每一个箱子里都装满了金币。不过,只有一个箱子里装的是真的金币,剩下的99个箱子里都是假的。真假金币的外形和质感完全一样,任何人都无法通过肉眼分辨出来。它们只有一个区别:真金币每一个重量为101克,而假金币的重量是100克。在金库里有一个电子秤,它可以准确地测量出任何物品的重量,精确到克。但很不幸的是,这个电子秤和银行的报警系统相连接,只要被使用一次就会立刻失效。请问,小偷怎么做才能只使用一次电子秤就找到装着真金币的箱子呢?答:小偷可以这样做:从第一个箱子里拿出1个金币,从第二个箱子里拿出2个金币,从第三个箱子里拿出3个金币,以此类推,直到从第一百个箱子里拿出100个金币。然后,把所有拿出来的金币放在电子秤上,测量它们的总重量。如果所有的金币都是假的,那么总重量应该是5050克(等于1+2+3+…+100)。如果有一个箱子里是真的金币,那么总重量会比5050克多出一些。这个多出来的部分就是真金币的数量乘以1克。例如,如果第十一个箱子里是真的金币,那么总重量会比5050克多出11克,因为从第十一个箱子里拿出了11个真金币。所以,小偷只要看电子秤上显示的数字减去5050,就能知道哪个箱子里是真的金币了。4、(10分)选择参与过的任一个项目,简述项目内容以及流程,讲述您在项目中承担的任务,挑一项你认为难的地方并阐述解决方案。答:优先答ASIC的设计与验证项目,其次是FPGA项目(如基于FPGA的图像处理、天线阵、雷达、加速器等等),其它项目不要答。5、(5分)用python写一个冒泡排序的函数以及测试程序。# 定义冒泡排序函数 def bubble_sort(lst): # 获取列表长度 n = len(lst) # 遍历列表n-1次 for i in range(n-1): # 设置一个标志,用于判断是否发生交换 swapped = False # 遍历未排序的部分 for j in range(n-1-i): # 如果前一个元素大于后一个元素,交换位置 if lst[j] > lst[j+1]: lst[j], lst[j+1] = lst[j+1], lst[j] # 标志设为True,表示发生了交换 swapped = True # 如果没有发生交换,说明列表已经有序,提前结束循环 if not swapped: break # 返回排序后的列表 return lst # 定义测试程序 # 创建一个乱序的列表 lst = [5, 3, 8, 2, 9, 1, 4, 7, 6] # 打印原始列表 print("Original list:", lst) # 调用冒泡排序函数,对列表进行排序 lst = bubble_sort(lst) # 打印排序后的列表 print("Sorted list:", lst)结果图6、(15分)用Verilog 写一个 Round Robin 仲裁器。模块端口如下:input clock; input reset_b; input [N-1:0] request; input [N-1] lock; output [N-1] grant; //one-hot此处的 lock 输入信号,表示请求方收到了仲裁许可,在对应的lock拉低之前,仲裁器不可以开启新的仲裁。(可简单理解为仲裁器占用)该题要求参数化编程,在模块例化时可调整参数。也即是说你不能写一个固定参数,比如N=8的模块。参考波形图:答:// 功能: // -1- Round Robin 仲裁器 // -2- 仲裁请求个数N可变 // -3- 加入lock机制(类似握手) // -4- 复位时的最高优先级定为 0 ,次优先级:1 -> 2 …… -> N-2 -> N-1 `timescale 1ns / 1ps module RoundRobinArbiter #( parameter N = 4 //仲裁请求个数 )( input clock, input reset_b, input [N-1:0] request, input [N-1:0] lock, output reg [N-1:0] grant//one-hot ); // 模块内部参数 localparam IDLE = 3'b001;// 复位进入空闲状态,接收并处理系统的初次仲裁请求 localparam WAIT_REQ_GRANT = 3'b010;// 等待后续仲裁请求到来,并进行仲裁 localparam WAIT_LOCK = 3'b100;// 等待LOCK拉低 // 模块内部信号 reg [2:0] R_STATUS; //请求状态 reg [N-1:0] R_MASK; //掩码 wire [N-1:0] W_REQ_MASKED; assign W_REQ_MASKED = request & R_MASK; //屏蔽低位 always @ (posedge clock) begin if(~reset_b) begin R_STATUS <= IDLE; R_MASK <= 0; grant <= 0; end else begin case(R_STATUS) IDLE: begin if(|request) //首次仲裁请求,不全为0 begin R_STATUS <= WAIT_LOCK; //首先需要找到request中优先级最高的比特位,对优先级最高的比特位给出许可信号。 //这一步可以通过request和它的2的补码按位与。这是因为一个数和它的补码相与,得到的结果是一个独热码,独热码为1的那一位是这个数最低的1 grant <= request & ((~request)+1); R_MASK <= ~((request & ((~request)+1))-1 | (request & ((~request)+1))); //得到掩码的方法是,对第一步的许可信号grant-1,再与grant本身相或,相或的结果再取反。 end else begin R_STATUS <= IDLE; end end WAIT_REQ_GRANT://处理后续的仲裁请求 begin if(|request) begin R_STATUS <= WAIT_LOCK; //在下一轮仲裁中,已经被仲裁许可的比特位变成了最低优先级,而未被仲裁许可的比特位将会被仲裁。 //因此对第一步中给出许可的比特位(假设是第2位)以及它的低比特位进行屏蔽,对request中的第5位到第3位进行保持 //这个操作可以利用掩码111000和request相与实现得到。 if(|(request & R_MASK))//不全为零 begin grant <= W_REQ_MASKED & ((~W_REQ_MASKED)+1); R_MASK <= ~((W_REQ_MASKED & ((~W_REQ_MASKED)+1))-1 | (W_REQ_MASKED & ((~W_REQ_MASKED)+1))); end else begin grant <= request & ((~request)+1); R_MASK <= ~((request & ((~request)+1))-1 | (request & ((~request)+1))); end end else begin R_STATUS <= WAIT_REQ_GRANT; grant <= 0; R_MASK <= 0; end end //通过第二步得到第2位到第0位被屏蔽的request_new信号, //判断request_new是否为全0信号,如果是全0信号,代表此时不存在需要被仲裁的比特位,则返回第一步:找到request中优先级最高的比特位, //对优先级最高的比特位给出许可信号,然后进行第二步。如果request_new不是全0信号,代表存在未被仲裁的比特位, //则找到request_new中优先级最高的比特位,对优先级最高的比特位给出许可信号,然后进行第二步。 WAIT_LOCK: begin if(|(lock & grant)) //未释放仲裁器 begin R_STATUS <= WAIT_LOCK; end else if(|request) //释放的同时存在仲裁请求 begin R_STATUS <= WAIT_LOCK; if(|(request & R_MASK))//不全为零 begin grant <= W_REQ_MASKED & ((~W_REQ_MASKED)+1); R_MASK <= ~((W_REQ_MASKED & ((~W_REQ_MASKED)+1))-1 | (W_REQ_MASKED & ((~W_REQ_MASKED)+1))); end else begin grant <= request & ((~request)+1); R_MASK <= ~((request & ((~request)+1))-1 | (request & ((~request)+1))); end end else begin R_STATUS <= WAIT_REQ_GRANT; grant <= 0; R_MASK <= 0; end end default: begin R_STATUS <= IDLE; R_MASK <= 0; grant <= 0; end endcase end end endmodule测试代码`timescale 1ns / 1ps module RoundRobinArbiter_tb; parameter N = 4; // 可以在测试时调整参数 // 定义测试信号 reg clock; reg reset_b; reg [N-1:0] request; reg [N-1:0] lock; wire [N-1:0] grant; // 定义时钟信号 initial clock = 0; always #10 clock = ~clock; // 实例化仲裁器模块 RoundRobinArbiter #( .N(N) ) inst_RoundRobinArbiter ( .clock (clock), .reset_b (reset_b), .request (request), .lock (lock), .grant (grant) ); // 定义时钟周期和初始值 initial begin reset_b <= 1'b0; request <= 0; lock <= 0; end // 定义请求和锁定信号的变化 initial begin #20; reset_b <= 1'b1; @(posedge clock) request <= 2; lock <= 2; @(posedge clock) request <= 0; @(posedge clock) request <= 5; lock <= 7; @(posedge clock) lock <= 5; @(posedge clock) request <= 1; @(posedge clock) lock <= 1; @(posedge clock) request <= 0; @(posedge clock) lock <= 0; #1000 $stop; // 测试结束 end // 显示测试结果和波形图 initial begin $monitor("Time=%t, clock=%b, reset_b=%b, request=%b, lock=%b, grant=%b", $time, clock, reset_b, request, lock, grant); $dumpfile("RoundRobinArbiter_tb.vcd"); $dumpvars(0,RoundRobinArbiter_tb); end endmodule结果:如果对波形图无法理解可以看此博文https://blog.csdn.net/m0_49540263/article/details/1149674437、(15分)关于DMA寄存器配置,DMA寄存器(地址 0x81050010)表:Type 表示读写类型。Reset 表示复位值。写一个C函数 void dma_driver(void),按步骤完成以下需求:分配DMA所需的源地址(0x30)分配DMA所需的目的地址(0x300)设置传输128 Byte 数据开始DMA传输等待DMA传输结束答:// 假设有以下宏定义 #define DMA_REG 0x81050010 // DMA控制寄存器的地址 #define DMA_SRC_ADDR 0x30 // DMA源地址 #define DMA_DST_ADDR 0x300 // DMA目的地址 #define DMA_SIZE 128 // DMA传输大小 #define DMA_START 1 // DMA开始传输的标志位 // 定义C函数 void dma_driver(void) void dma_driver(void) { // 定义一个指向DMA控制寄存器的指针 volatile uint32_t *dma_reg = (volatile uint32_t *)DMA_REG; // 清空DMA控制寄存器的值 *dma_reg = 0; // 设置DMA源地址,目的地址和传输大小 *dma_reg |= (DMA_SRC_ADDR << 2) | (DMA_DST_ADDR << 13) | (DMA_SIZE << 24); // 开始DMA传输 *dma_reg |= DMA_START; // 等待DMA传输结束 while (*dma_reg & DMA_START) { // 可以在这里做一些其他的事情,比如打印日志或者检查错误 // printf("Waiting for DMA to finish...\n"); // check_error(); } }8、(20分)二阶带通滤波器,利用RC组件搭建,通带范围 1kHz~30kHz ,两个电阻 R 均为10kΩ ,问两个电容容值多少?答:第一步首得知道二阶带通(RC)滤波器的电路长啥样,高、低通组合一下就是带通,自己思考一下高、低通组合:如串联或并联,会得到带通还是带组?电路图:这个一看就是总传递函数=A1*A2(模电二阶有源或无源滤波器绝对有)然后化简根据推导得到的表达式,对于 jwRC2 ,这一项,当 w 趋于无穷大时,uo/ui 趋于零。那么高频的临界点就是 wRC2 = 1+2C2/C1;(此时忽略低频项1/jwRC1)同理,对于低频项 1 /jwRC1, w 趋于无穷小时,uo/ui 趋于零 ,那么低频的临界点就是 1/wRC1 = 1+2C2/C1;然后解二元一次方程两个电容就被解出来了 这里提供一种更简单方法: 二阶带通滤波器的中心频率 f0 和品质因数 Q 可以用下面的公式计算:已知 R1 = R2 = 10kΩ,f0 = (1kHz + 30kHz) / 2 = 15.5kHz,Q = f0 / (30kHz - 1kHz) = 0.54,代入上面的公式,可以求得:这是一个二元一次方程组,可以用任意方法求解,例如消元法或代入法。为了方便起见,我们假设 C1 和 C2 的值相近,那么可以近似地认为 C1 = C2 = 3.45nF。这样就得到了两个电容的容值。当然,也可以选择其他的电容值,只要满足上面的方程组即可。
2023年04月23日
1,132 阅读
0 评论
7 点赞
【硬件算法进阶】Verilog实现802.3 CRC-32校验运算电路
循环冗余校验(Cyclic Redundancy Check,CRC)是通信中常用的差错检测编码方式,其基本工作原理是根据输入的信息位(信息码元),按照给定的生成多项式产生校验位(校验码元),并一起传送到接收端。在接收端,接收电路按照相同的规则对接收数据进行计算并生成本地的校验位,然后与收到的校验位进行对比,如果二者不同,则说明传输过程中发生了错误,否则说明传输是正确的。带有CRC校验结果的数据帧结构如表1-2所示。CRC检验位生成与检测工作包括以下基本步骤。图1-6是一个并行CRC-32校验运算电路。图中的d[7:0]是输入的用户数据,它是按照字节的方式输入的。load_ini是在对一个新的数据包开始校验计算之前对电路进行初始化的控制信号,经过初始化后,电路内部32比特寄存器的值改变为全1。calc是电路运算指示信号,在整个数据帧输入和CRC校验结果输出的过程中其都应该保持有效(高电平有效)。d_valid为1时表示当前输入的是需要进行校验运算的有效数据。crc[7:0]是电路输出的CRC校验运算结果,它是按照字节方式,在有效数据输入完成后开始输出的,一共有4个有效字节。crc_reg[31:0]是内部寄存器的值,具体使用时不需要该输出。并行计算的思想,输入数据S要并行输入到G(x)系数为1的支路中,输入数据从输入端按高到低逐bit输入,就可以实现。假如被除数是2位的数据S[1:0]=01,多项式是10011,x4 +x+1。在CRC校验里面,习惯省略最高位的1,多项式用0011表示。那么S除以0011的模二运算数字电路结构为:其中d1~ d4是寄存器输入;q1~q4是寄存器输出。寄存器需要赋初值,一般赋全1或全0。d1=S[1]^q4;d2= S[1]^ q1^q4;d3=q2;d4=q3。经过一次移位后:q1=d1= S[1]^q4;q2= d2= S[1]^ q1^q4;q3= d3=q2;q4= d4=q3。此时有:d1=S[0]^q3;d2= S[0]^ S[1]^ q4^q3;d3= S[1]^ q1^q4;d4= q2。令c[3:0]=,d[3:0]=,那么d就是最终的运算结果表达式,如下d[3]=c[1];d[2]= S[1]^ c[0]^c[3];d[1]= S[0]^ S[1]^ c[3]^ c[2];d[0]= S[0]^ c[2]。令c的初值为0,则01对0011的模二除法的余数为0011。再比如多项式为x5 +x3 +x+1,简记式为01011,其数字电路结构为:输入数据S要全部输入完,寄存器得到的结果才是最后的结果。同理可推导出其他多项式和输入数据的情况。对于循环检验,这里举个例子,如果数据是10bit*100个包,则每次输入10bit得到校验码后,该检验码为下次数据计算时寄存器D的初值,如此反复计算得到最后的检验码添加到整个数据后面即可,而不需要每个数据包后面都添加检验码。下面是以太网循环冗余校验电路的设计代码:module crc32_8023( clk, reset, d, load_init, calc, d_valid, crc_reg, crc ); input clk; input reset; input [7:0] d; input load_init; input calc; input d_valid; output reg [31:0] crc_reg; output reg [7:0] crc; wire [2:0] ctl; wire [31:0] next_crc; wire [31:0] i; assign i = crc_reg; assign ctl = ; always @(posedge clk or posedge reset) begin if(reset) crc_reg <= 32'hffffffff; else begin case (ctl) // 3'b000,3'b010: begin crc_reg <= crc_reg; crc <= crc;end 3'b001: begin crc_reg <= ; crc <= ~; //crc <= ~ crc_reg[16:23]; end 3'b011: begin crc_reg <= next_crc[31:0]; crc <= ~; //crc <= ~ next_crc[24:31]; end 3'b100,3'b110: begin crc_reg <= 32'hffffffff; crc <= crc; end 3'b101: begin crc_reg <= 32'hffffffff; crc <= ~; //crc <= ~ crc_reg[16:23]; end 3'b111: begin crc_reg <= 32'hffffffff; crc <= ~; //crc <= ~ next_crc[24:31]; end endcase end end assign next_crc[0] = d[7]^i[24]^d[1]^i[30]; //d+i=31 assign next_crc[1] = d[6]^d[0]^d[7]^d[1]^i[24]^i[25]^i[30]^i[31]; assign next_crc[2] = d[5]^d[6]^d[0]^d[7]^d[1]^i[24]^i[25]^i[26]^i[30]^i[31]; assign next_crc[3] = d[4]^d[5]^d[6]^d[0]^i[25]^i[26]^i[27]^i[31]; assign next_crc[4] = d[3]^d[4]^d[5]^d[7]^d[1]^i[24]^i[26]^i[27]^i[28]^i[30]; assign next_crc[5] = d[0]^d[1]^d[2]^d[3]^d[4]^d[6]^d[7]^i[24]^i[25]^i[27]^i[28]^i[29]^i[30]^i[31]; assign next_crc[6] = d[0]^d[1]^d[2]^d[3]^d[5]^d[6]^i[25]^i[26]^i[28]^i[29]^i[30]^i[31]; assign next_crc[7] = d[0]^d[2]^d[4]^d[5]^d[7]^i[24]^i[26]^i[27]^i[29]^i[31]; assign next_crc[8] = d[3]^d[4]^d[6]^d[7]^i[24]^i[25]^i[27]^i[28]^i[0]; //每项多出i[i],i=0、1、2...23 assign next_crc[9] = d[2]^d[3]^d[5]^d[6]^i[1]^i[25]^i[26]^i[28]^i[29]; assign next_crc[10] =d[2]^d[4]^d[5]^d[7]^i[2]^i[24]^i[26]^ i[27]^i[29]; assign next_crc[11] =i[3]^d[3]^i[28]^d[4]^i[27]^d[6]^i[25]^d[7]^i[24]; assign next_crc[12] =d[1]^d[2]^d[3]^d[5]^d[6]^d[7]^i[4]^i[24]^i[25]^i[26]^i[28]^i[29]^i[30]; assign next_crc[13] =d[0]^d[1]^d[2]^d[4]^d[5]^d[6]^i[5]^i[25]^i[26]^i[27]^i[29]^i[30]^i[31]; assign next_crc[14] =d[0]^d[1]^d[3]^d[4]^d[5]^i[6]^i[26]^i[27]^i[28]^i[30]^i[31]; assign next_crc[15] =d[0]^d[2]^d[3]^d[4]^i[7]^i[27]^i[28]^i[29]^i[31]; assign next_crc[16] =d[2]^d[3]^d[7]^i[8]^i[24]^i[28]^i[29]; assign next_crc[17] =d[1]^d[2]^d[6]^i[9]^i[25]^i[29]^i[30]; assign next_crc[18] =d[0]^d[1]^d[5]^i[10]^i[26]^i[30]^i[31]; assign next_crc[19] =d[0]^d[4]^i[11]^i[27]^i[31]; assign next_crc[20] =d[3]^i[12]^i[28]; assign next_crc[21] =d[2]^i[13]^i[29]; assign next_crc[22] =d[7]^i[14]^i[24]; assign next_crc[23] =d[1]^d[6]^d[7]^i[15]^i[24]^i[25]^i[30]; assign next_crc[24] =d[0]^d[5]^d[6]^i[16]^i[25]^i[26]^i[31]; assign next_crc[25] =d[4]^d[5]^i[17]^i[26]^i[27]; assign next_crc[26] =d[1]^d[3]^d[4]^d[7]^i[18]^i[28]^i[27]^i[24]^i[30]; assign next_crc[27] =d[0]^d[2]^d[3]^d[6]^i[19]^i[29]^i[28]^i[25]^i[31]; assign next_crc[28] =d[1]^d[2]^d[5]^i[20]^i[30]^i[29]^i[26]; assign next_crc[29] =d[0]^d[1]^d[4]^i[21]^i[31]^i[30]^i[27]; assign next_crc[30] =d[0]^d[3]^i[22]^i[31]^i[28]; assign next_crc[31] =d[2]^i[23]^i[29]; endmodule测试代码`timescale 1ns/1ns module crc_test(); reg clk, reset; reg [7:0] d; reg load_init; reg calc; reg data_valid; wire [31:0] crc_reg; wire [7:0] crc; initial begin clk=0; reset=0; load_init=0; calc=0; data_valid=0; d=0; end always begin #10 clk=1; #10 clk=0; end always begin crc_reset; crc_cal; end task crc_reset; begin reset=1; repeat(2)@(posedge clk); #5; reset=0; repeat(2)@(posedge clk); end endtask task crc_cal; begin repeat(5) @ (posedge clk); //通过losd_init=1 对CRC计算电路进行初始化 #5; load_init= 1; repeat(1)@ (posedge clk); //设置1oad_init=0,data_valid= 1,calc=1 //开始对输人数据进行CRC校验运算 #5; load_init= 0; data_valid=1; calc=1; d=8'haa; repeat(1)@ (posedge clk); #5; data_valid=1; calc=1; d=8'hbb; repeat(1)@ (posedge clk); #5; data_valid=1; calc=1; d=8'hcc; repeat(1)@ (posedge clk); #5; data_valid=1; calc=1; d=8'hdd; repeat(1)@ (posedge clk); //设置load_init=0,data_valid=1,calc=0 //停止对数据进行CRC校验运算,开始输出 //计算结果 #5; data_valid=1; calc=0; d=8'haa; repeat(1)@ (posedge clk); #5; data_valid=1; calc=0; d=8'hbb; repeat(1)@ (posedge clk); #5; data_valid=1; calc=0; d=8'hee; repeat(1)@ (posedge clk); #5; data_valid=1; calc=0; d=8'hdd; repeat(1)@ (posedge clk); #5; data_valid=0; repeat(10)@ (posedge clk); end endtask crc32_8023 my_crc_test(.clk(clk),.reset(reset),.d(d),.load_init(load_init),.calc(calc),.d_valid(data_valid),.crc_reg(crc_reg),.crc(crc)); endmodule图1-7是电路的仿真结果。图中①是电路进行CRC校验计算之前对电路进行初始化操作的过程,经过初始化之后,crc_reg内部数值为全1。②是对输入数据aa-> bb-> cc-> dd进行运算操作的过程,此时calc和data_valid均为1。③是输出计算结果的过程,CRC校验运算结果a7、01、b4和55先后被输出。在接收方向上,可以采用相同的电路进行校验检查,判断是否在传输过程中发生了差错。具体工作时,可以边接收用户数据边进行校验运算,当一个完整的MAC帧接收完成后(此时接收数据帧中的校验结果也参加了校验运算),如果当前校验电路的crc_reg值为0xC704DD7B(对于以太网中使用的CRC-32校验,无论原始数据是什么,正确接收时校验和都是此固定数值),说明没有发生错误,否则说明MAC帧有错。CRC-32校验值的作用是用于检测数据传输或存储中的错误。发送数据时,会根据数据内容生成简短的校验和,并将其与数据一起发送。接收数据时,将再次生成校验和并将其与发送的校验和进行比较。如果两者相等,则没有数据损坏。如果两者不相等,则说明数据在传输或存储过程中发生了改变,可能是由于噪声、干扰、故障或恶意篡改等原因造成的。CRC-32校验值可以有效地检测出数据中的随机错误,但是不能保证检测出所有的错误。例如,如果数据中有偶数个比特发生了翻转,那么CRC-32校验值可能不会改变,从而无法发现错误。因此,CRC-32校验值只能作为一种辅助的错误检测手段,不能完全依赖它来保证数据的正确性和完整性。相关工具如果不理解推导过程的话,可以由相关工具帮忙计算出结果和得到Verilog代码:CRC校验Verilog代码生成链接:http://outputlogic.com/?page_id=321CRC校验计算工具链接:http://www.ip33.com/crc.html,这个工具只能计算16bit为一个数据包的数据,如果数据包为10bit等之类的就不太适用在线计算器使用举例报文 : 1011001 (0x59)生成多项式 : g(x) = x^4 + x^3 + 1CRC : 1010 ( 0xa)CRC计算结果截图:参考文献Verilog HDL算法与电路设计-乔庐峰
2023年04月12日
1,221 阅读
0 评论
2 点赞
【IC/CPU设计】极简的RISC_CPU设计
CPU为SOC系统的核心环节,该项目来自于夏宇闻老师的经典教材——《Verilog 数字系统设计教程》,通过此练习方便数字ICer更好的入门本次项目实践环境:前仿: Modelsim综合: Design CompileCPU简介CPU(Central Processing Unit),中文全称中央处理器,作为四大U之首(CPU/GPU/TPU/NPU),是计算机系统的运算和控制核心,也是当今数字系统中不可或缺的组成部分。CPU自诞生到如今发展超过50年,借助冯诺依曼体系,CPU掀起一股又一股的科技浪潮。RISC作为精简了指令集的CPU,除了指令更加简洁,还拥有简单合理的内部结构,从而提高了运算速度。CPU工作的5个阶段:(1)取指(IF,Instruction Fetch),将指令从存储器取出到指令寄存器。每取一条指令,程序计数器自加一。(2)译指(ID,Instruction Decode),对取出的指令按照规定格式进行拆分和译码。(3)执行(EX,Execute),执行具体指令操作。(4)访问存储(MEM,Memory),根据指令访问存储、完成存储和读取。(5)写回(WB,Write Back),将计算结果写回到存储器。CPU内部关键结构:(1)算术逻辑运算器(ALU);(2)累加器;(3)程序计数器;(4)指令寄存器和译码器;(5)时序和控制部件。RISC_CPU内部结构和Verilog实现本项目中的RISC_CPU一共有9个模块组成,具体如下:(1)时钟发生器;(2)指令寄存器;(3)累加器;(4)算术逻辑运算单元;(5)数据控制器;(6)状态控制器;(7)主状态机;(8)程序计数器;(9)地址多路器。整体结构时钟发生器模块图:端口描述:reset是高电平复位信号;clk是外部时钟信号;fetch是控制信号,是clk的八分频信号;fetch为高电平时,触发执行指令以及地址多路器输出指令地址和数据地址。alu_ena是算术逻辑运算单元的使能信号。可以看到alu_ena提前fetch高电平一个clk周期,fetch是clk的8分频信号。Verilog代码:// Description: RISC——CPU 时钟发生器 // ----------------------------------------------------------------------------- module clk_gen ( input clk , // Clock input reset , // High level reset output reg fetch , // 8 frequency division output reg alu_ena // Arithmetic enable ); reg [7:0] state; //One-piece state machine parameter S1 = 8'b0000_0001, S2 = 8'b0000_0010, S3 = 8'b0000_0100, S4 = 8'b0000_1000, S5 = 8'b0001_0000, S6 = 8'b0010_0000, S7 = 8'b0100_0000, S8 = 8'b1000_0000, idle = 8'b0000_0000; always@(posedge clk)begin if(reset)begin fetch <= 0; alu_ena <= 0; state <= idle; end else begin case(state) S1: begin alu_ena <= 1; state <= S2; end S2: begin alu_ena <= 0; state <= S3; end S3: begin fetch <= 1; state <=S4; end S4: begin state <= S5; end S5: state <= S6; S6: state <= S7; S7: begin fetch <= 0; state <= S8; end S8: begin state <= S1; end idle: state <= S1; default: state <=idle; endcase end end endmodule指令寄存器模块图:端口描述:寄存器是将数据总线送来的指令存入高8位或低8位寄存器中。ena信号用来控制是否寄存。每条指令为两个字节,16位,高3位是操作码,低13位是地址(CPU地址总线为13位,寻址空间为8K字节)。本设计的数据总线为8位,每条指令需要取两次,先取高8位,再取低8位。Verilog代码:// Description: RISC—CPU 指令寄存器 // ----------------------------------------------------------------------------- module register ( input [7:0] data , input clk , input rst , input ena , output reg [15:0] opc_iraddr ); reg state ; // always@( posedge clk ) begin if( rst ) begin opc_iraddr <= 16'b 0000_0000_0000_0000; state <= 1'b 0; end // if rst // If load_ir from machine actived, load instruction data from rom in 2 clock periods. // Load high 8 bits first, and then low 8 bits. else if( ena ) begin case( state ) 1'b0 : begin opc_iraddr [ 15 : 8 ] <= data; state <= 1; end 1'b1 : begin opc_iraddr [ 7 : 0 ] <= data; state <= 0; end default : begin opc_iraddr [ 15 : 0 ] <= 16'bxxxx_xxxx_xxxx_xxxx; state <= 1'bx; end endcase // state end // else if ena else state <= 1'b0; end endmodule 累加器模块图:端口描述:累加器用于存放当前结果,ena信号有效时,在clk上升沿输出数据总线的数据。// Description: RISC-CPU 累加器模块 // ----------------------------------------------------------------------------- module accum ( input clk , // Clock input ena , // Enable input rst , // Asynchronous reset active high input [7:0] data , // Data bus output reg [7:0] accum ); always@(posedge clk)begin if(rst) accum <= 8'b0000_0000;//Reset else if(ena) accum <= data; end endmodule 算术运算器模块图:端口描述:算术逻辑运算单元可以根据输入的操作码分别实现相应的加、与、异或、跳转等基本操作运算。本单元支持8种操作运算。opcode用来选择计算模式data是数据输入accum是累加器输出alu_ena是模块使能信号clk是系统时钟Verilog代码:// Description: RISC-CPU 算术运算器 // ----------------------------------------------------------------------------- module alu ( input clk , // Clock input alu_ena , // Enable input [2:0] opcode , // High three bits are used as opcodes input [7:0] data , // data input [7:0] accum , // accum out output reg [7:0] alu_out , output zero ); parameter HLT = 3'b000 , SKZ = 3'b001 , ADD = 3'b010 , ANDD = 3'b011 , XORR = 3'b100 , LDA = 3'b101 , STO = 3'b110 , JMP = 3'b111 ; always @(posedge clk) begin if(alu_ena) begin casex(opcode)//操作码来自指令寄存器的输出 opc_iaddr(15..0)的第三位 HLT: alu_out <= accum ; SKZ: alu_out <= accum ; ADD: alu_out <= data + accum ; ANDD: alu_out <= data & accum ; XORR: alu_out <= data ^ accum ; LDA : alu_out <= data ; STO : alu_out <= accum ; JMP : alu_out <= accum ; default: alu_out <= 8'bxxxx_xxxx ; endcase end end assign zero = !accum; endmodule 数据控制器模块图:端口描述:数据控制器的作用是控制累加器的数据输出,数据总线是分时复用的,会根据当前状态传输指令或者数据。数据只在往RAM区或者端口写时才允许输出,否则呈现高阻态。in是8bit数据输入data_ena是使能信号data是8bit数据输出Verilog代码:// Description: RISC-CPU 数据控制器 // ----------------------------------------------------------------------------- module datactl ( input [7:0] in , // Data input input data_ena , // Data Enable output wire [7:0] data // Data output ); assign data = (data_ena )? in: 8'bzzzz_zzzz ; endmodule 地址多路器模块图:端口描述:用于选择输出地址是PC(程序计数)地址还是数据/端口地址。每个指令周期的前4个时钟周期用于从ROM种读取指令,输出的是PC地址;后四个时钟周期用于对RAM或端口读写。地址多路器和数据控制器实现的功能十分相似。fetch信号用来控制地址输出,高电平输出pc_addr ,低电平输出ir_addr ;pc_addr 指令地址;ir_addr ram或端口地址。Verilog代码:// Description: RISC-CPU 地址多路器 // ----------------------------------------------------------------------------- module adr ( input fetch , // enable input [12:0] ir_addr , // input [12:0] pc_addr , // output wire [12:0] addr ); assign addr = fetch? pc_addr :ir_addr ; endmodule 程序计数器模块图:端口描述:程序计数器用来提供指令地址,指令按照地址顺序存放在存储器中。包含两种生成途径:(1)顺序执行的情况(2)需要改变顺序,例如JMP指令rst复位信号,高电平时地址清零;clock 时钟信号,系统时钟;ir_addr目标地址,当加载信号有效时输出此地址;pc_addr程序计数器地址load地址装载信号Verilog代码:// Description: RISC-CPU 程序计数器 // ----------------------------------------------------------------------------- module counter ( input [12:0] ir_addr , // program address input load , // Load up signal input clock , // CLock input rst , // Reset output reg [12:0] pc_addr // insert program address ); always@(posedge clock or posedge rst) begin if(rst) pc_addr <= 13'b0_0000_0000_0000; else if(load) pc_addr <= ir_addr; else pc_addr <= pc_addr + 1; end endmodule 状态控制器&主状态机模块图:(图左边)状态机端口描述:状态控制器接收复位信号rst,rst有效,控制输出ena为0,fetch有效控制ena为1。// Description: RISC-CPU 状态控制器 // ----------------------------------------------------------------------------- module machinectl ( input clk , // Clock input rst , // Asynchronous reset input fetch , // Asynchronous reset active low output reg ena // Enable ); always@(posedge clk)begin if(rst) ena <= 0; else if(fetch) ena <=1; end endmodule (图右边)主状态端口描述:主状态机是CPU的控制核心,用于产生一系列控制信号。指令周期由8个时钟周期组成,每个时钟周期都要完成固定的操作。(1)第0个时钟,CPU状态控制器的输出rd和load_ir 为高电平,其余为低电平。指令寄存器寄存由ROM送来的高8位指令代码。(2)第1个时钟,与上一个时钟相比只是inc_pc从0变为1,故PC增1,ROM送来低8位指令代码,指令寄存器寄存该8位指令代码。(3)第2个时钟,空操作。(4)第3个时钟,PC增1,指向下一条指令。操作符为HLT,输出信号HLT为高。操作符不为HLT,除PC增1外,其余控制线输出为0.(5)第4个时钟,操作。操作符为AND,ADD,XOR或LDA,读取相应地址的数据;操作符为JMP,将目的地址送给程序计数器;操作符为STO,输出累加器数据。(6)第5个时钟,若操作符为ANDD,ADD或者XORR,算术运算器完成相应的计算;操作符为LDA,就把数据通过算术运算器送给累加器;操作符为SKZ,先判断累加器的值是否为0,若为0,PC加1,否则保持原值;操作符为JMP,锁存目的地址;操作符为STO,将数据写入地址处。(7)第6个时钟,空操作。(8)第7个时钟,若操作符为SKZ且累加器为0,则PC值再加1,跳过一条指令,否则PC无变化。// Description: RISC-CPU 主状态机 // ----------------------------------------------------------------------------- module machine ( input clk , // Clock input ena , // Clock Enable input zero , // Asynchronous reset active low input [2:0] opcode , // OP code output reg inc_pc , // output reg load_acc , // output reg load_pc , // output reg rd , // output reg wr , // output reg load_ir , // output reg datactl_ena , // output reg halt ); reg [2:0] state ; //parameter parameter HLT = 3'b000 , SKZ = 3'b001 , ADD = 3'b010 , ANDD = 3'b011 , XORR = 3'b100 , LDA = 3'b101 , STO = 3'b110 , JMP = 3'b111 ; always@(negedge clk) begin if(!ena) //收到复位信号rst,进行复位操作 begin state <= 3'b000; <= 4'b0000; <= 4'b0000; end else ctl_cycle; end //------- task ctl_cycle ------- task ctl_cycle; begin casex(state) 3'b000: //load high 8bits in struction begin <= 4'b0001; <= 4'b0100; state <= 3'b001; end 3'b001://pc increased by one then load low 8bits instruction begin <= 4'b1001; <= 4'b0100; state <= 3'b010; end 3'b010: //idle begin <= 4'b0000; <= 4'b0000; state <= 3'b011; end 3'b011: //next instruction address setup 分析指令开始点 begin if(opcode == HLT)//指令为暂停HLT begin <= 4'b1000; <= 4'b0001; end else begin <= 4'b1000; <= 4'b0000; end state <= 3'b100; end 3'b100: //fetch oprand begin if(opcode == JMP) begin <= 4'b0010; <= 4'b0000; end else if(opcode == ADD || opcode == ANDD || opcode == XORR || opcode == LDA) begin <= 4'b0001; <= 4'b0000; end else if(opcode == STO) begin <= 4'b0000; <= 4'b0010; end else begin <= 4'b0000; <= 4'b0000; end state <= 3'b101; end 3'b101://operation begin if(opcode == ADD || opcode == ANDD ||opcode ==XORR ||opcode == LDA)//过一个时钟后与累加器的内存进行运算 begin <= 4'b0101; <= 4'b0000; end else if(opcode == SKZ && zero == 1)// & and && begin <= 4'b1000; <= 4'b0000; end else if(opcode == JMP) begin <= 4'b1010; <= 4'b0000; end else if(opcode == STO) begin//过一个时钟后吧wr变为1,写到RAM中 <= 4'b0000; <= 4'b1010; end else begin <= 4'b0000; <= 4'b0000; end state <= 3'b110; end 3'b110: begin if(opcode == STO) begin <= 4'b0000; <= 4'b0010; end else if(opcode == ADD || opcode == ANDD || opcode == XORR || opcode == LDA) begin <= 4'b0001; <= 4'b0000; end else begin <= 4'b0000; <= 4'b0000; end state <= 3'b111; end 3'b111: begin if(opcode == SKZ && zero == 1) begin <= 4'b1000; <= 4'b0000; end else begin <= 4'b0000; <= 4'b0000; end state <= 3'b000; end default: begin <= 4'b0000; <= 4'b0000; state <= 3'b000; end endcase end endtask endmodule 外围模块为了对RISC-CPU进行测试,需要对ROM、RAM和地址译码器进行设计。地址译码器模块说明:地址译码器用于产生选通信号,选通ROM或者RAM1FFFH —— 1800H RAM(范围):1_1xxx_xxxx_xxxx17FFH —— 0000H ROM(范围):0_xxxx_xxxx_xxxx+1_0xxx_xxxx_xxxxVerilog代码:// Description: RISC-CPU 地址译码器 // ----------------------------------------------------------------------------- module addr_decode ( input [12:0] addr , // Address output reg ram_sel , // Ram sel output reg rom_sel // Rom sel ); always@(addr)begin casex(addr) 13'b1_1xxx_xxxx_xxxx: <= 2'b01; 13'b0_xxxx_xxxx_xxxx: <= 2'b10; 13'b1_0xxx_xxxx_xxxx: <= 2'b10; default: <= 2'b00; endcase end endmodule RAM模块说明:RAM用于存放临时数据,可读可写。Verilog代码:// Description: RISC-CPU RAM模块 // ----------------------------------------------------------------------------- module ram ( input ena , // Enable input read , // read Enable input write , // write Enable inout wire [7:0] data , // data input [9:0] addr // address ); reg [7:0] ram [10'h3ff:0] ; assign data = (read && ena )? ram[addr]:8'h zz; always@(posedge write) begin ram[addr] <= data; end endmodule ROM模块说明:RAM用于存放只读数据。Verilog代码:// Description: RISC-CPU ROM模块 // ----------------------------------------------------------------------------- module rom ( input [12:0] addr , input read , input ena , output wire [7:0] data ); reg [7:0] memory [13'h1ff:0]; assign data = (read && ena)? memory[addr]:8'b zzzz_zzzz; endmodule 顶层模块模块图:Verilog代码:// Description: RISC-CPU 顶层模块 // ----------------------------------------------------------------------------- //`include "clk_gen.v" //`include "accum.v" //`include "adr.v" //`include "alu.v" //`include "machine.v" //`include "counter.v" //`include "machinectl.v" //`iclude "machine.v" //`include "register.v" //`include "datactl.v" module RISC_CPU ( input clk , input reset , output wire rd , output wire wr , output wire halt , output wire fetch , //addr output wire [12:0] addr , output wire [12:0] ir_addr , output wire [12:0] pc_addr , inout wire [7:0] data , //op output wire [2:0] opcode ); wire [7:0] alu_out ; wire [7:0] accum ; wire zero ; wire inc_pc ; wire load_acc ; wire load_pc ; wire load_ir ; wire data_ena ; wire contr_ena ; wire alu_ena ; //inst clk_gen mclk_gen( .clk (clk ), .reset (reset ), .fetch (fetch ), .alu_ena (alu_ena ) ); register m_register( .data (data ), .ena (load_ir ), .rst (reset ), .clk (clk ), .opc_iraddr ( ) ); accum m_accum( .data (alu_out ), .ena (load_acc ), .clk (clk ), .rst (reset ), .accum (accum ) ); alu m_alu( .data (data ), .accum (accum ), .clk (clk ), .alu_ena (alu_ena ), .opcode (opcode ), .alu_out (alu_out ), .zero (zero ) ); machinectl m_machinectl( .clk (clk ), .rst (reset ), .fetch (fetch ), .ena (contr_ena ) ); machine m_machine( .inc_pc (inc_pc ), .load_acc (load_acc ), .load_pc (load_pc ), .rd (rd ), .wr (wr ), .load_ir (load_ir ), .clk (clk ), .datactl_ena(data_ena ), .halt (halt ), .zero (zero ), .ena (contr_ena ), .opcode (opcode ) ); datactl m_datactl( .in (alu_out ), .data_ena (data_ena ), .data (data ) ); adr m_adr( .fetch (fetch ), .ir_addr (ir_addr ), .pc_addr (pc_addr ), .addr (addr ) ); counter m_counter( .clock (inc_pc ), .rst (reset ), .ir_addr (ir_addr ), .load (load_pc ), .pc_addr (pc_addr ) ); endmodule TestbenchTestbench包含三个测试程序,这个部分不能综合。Test1程序TEST1程序用于验证RISC-CPU的逻辑功能,根据汇编语言由人工编译的。若各条指令正确,应该在地址2E(hex)处,在执行HLT时刻停止。若程序在任何其他位置停止,则必有一条指令运行错误,可以按照注释找到错误的指令。test1汇编程序:(.pro文件/存放于ROM)//机器码-地址-汇编助记符-注释 @00 //address statement 111_0000 //00 BEGIN: JMP TST_JMP 0011_1100 000_0000 //02 HLT //JMP did not work 0000_0000 000_00000 //04 HLT //JMP did not load PC skiped 0000_0000 101_1100 //06 JMP_OK: LDA DATA 0000_0000 001_00000 //08 SKZ 0000_0000 000_0000 //0a HLT 0000_0000 101_11000 //0C LDA DATA_2 0000_0001 001_00000 //0E SKZ 0000_0000 111_0000 //10 JMP SKZ_OK 001_0100 000_0000 //12 HLT 0000_0000 110_11000 //14 SKZ_OK: STO TEMP 0000_0010 101_11000 //16 LDA DATA_1 0000_0000 110_11000 //18 STO TEMP 0000_0010 101_11000 //1A LDA TEMP 0000_0010 001_00000 //1C SKZ 0000_0000 000_00000 //1E HLT 0000_0000 100_11000 //20 XOR DATA_2 0000_0001 001_00000 //22 SKZ 0000_0000 111_00000 //24 JMP XOR_OK 0010_1000 000_00000 //26 HLT 0000_0000 100_11000 //28 XOR_OK XOR DATA_2 0000_0001 001_00000 //2A SKZ 0000_0000 000_00000 //2C HLT 0000_0000 000_0000 //2E END 0000_0000 111_00000 //30 JMP BEGIN 0000_0000 @3c 111_00000 //3c TST_JMP IMR OK 0000_0110 000_00000 //3E HLT test1数据文件:(.dat/存放于RAM)/----------------------------------- @00 ///address statement at RAM 00000000 //1800 DATA_1 11111111 //1801 DATA_2 10101010 //1082 TEMPTest2程序TEST1程序用于验证RISC-CPU的逻辑功能,根据汇编语言由人工编译的。这个程序是用来测试RISC-CPU的高级指令集,若执行正确,应在地址20(hex)处在执行HLT时停止。test2汇编程序:@00 101_11000 //00 BEGIN 0000_0001 011_11000 //02 AND DATA_3 0000_0010 100_11000 //04 XOR DATA_2 0000_0001 001_00000 //06 SKZ 0000_0000 000_00000 //08 HLT 0000_0000 010_11000 //0A ADD DATA_1 0000_0000 001_00000 //0C SKZ 0000_0000 111_00000 //0E JMP ADD_OK 0001_0010 111_00000 //10 HLT 0000_0000 100_11000 //12 ADD_OK XOR DATA_3 0000_0010 010_11000 //14 ADD DATA_1 0000_0000 110_11000 //16 STO TEMP 0000_0011 101_11000 //18 LDA DATA_1 0000_0000 010_11000 //1A ADD TEMP 0000_0001 001_00000 //1C SKZ 0000_0000 000_00000 //1E HLT 0000_0000 000_00000 //END HLT 0000_0000 111_00000 //JMP BEGIN 0000_0000test2数据文件:@00 00000001 //1800 DATA_1 10101010 //1801 DATA_2 11111111 //1802 DATA_3 00000000 //1803 TEMPTest3程序TEST3程序是一个计算0~144的斐波那契数列的程序,用来验证CPU整体功能。test3汇编程序:@00 101_11000 //00 LOOP:LDA FN2 0000_0001 110_11000 //02 STO TEMP 0000_0010 010_11000 //04 ADD FN1 0000_0000 110_11000 //06 STO FN2 0000_0001 101_11000 //08 VLDA TEMP 0000_0010 110_11000 //0A STO FN1 0000_0000 100_11000 //0C XOR LIMIT 0000_0011 001_00000 //0E SKZ 0000_0000 111_00000 //10 JMP LOOP 0000_0000 000_00000 //12 DONE HLT 0000_0000test3数据文件:@00 00000001 //1800 FN1 00000000 //1801 FN2 00000000 //1802 TEMP 10010000 //1803 LIMIT完整的testbenchVerilog代码:// Description: RISC-CPU 测试程序 // ----------------------------------------------------------------------------- `include "RISC_CPU.v" `include "ram.v" `include "rom.v" `include "addr_decode.v" `timescale 1ns/1ns `define PERIOD 100 // matches clk_gen.v module cputop_tb; reg [( 3 * 8 ): 0 ] mnemonic; // array that holds 3 8 bits ASCII characters reg [ 12 : 0 ] PC_addr, IR_addr; reg reset_req, clock; wire [ 12 : 0 ] ir_addr, pc_addr; // for post simulation. wire [ 12 : 0 ] addr; wire [ 7 : 0 ] data; wire [ 2 : 0 ] opcode; // for post simulation. wire fetch; // for post simulation. wire rd, wr, halt, ram_sel, rom_sel; integer test; //-----------------DIGITAL LOGIC---------------------- RISC_CPU t_cpu (.clk( clock ),.reset( reset_req ),.halt( halt ),.rd( rd ),.wr( wr ),.addr( addr ),.data( data ),.opcode( opcode ),.fetch( fetch ),.ir_addr( ir_addr ),.pc_addr( pc_addr )); ram t_ram (.addr ( addr [ 9 : 0 ]),.read ( rd ),.write ( wr ),.ena ( ram_sel ),.data ( data )); rom t_rom (.addr ( addr ),.read ( rd ), .ena ( rom_sel ),.data ( data )); addr_decode t_addr_decoder (.addr( addr ),.ram_sel( ram_sel ),.rom_sel( rom_sel )); //-------------------SIMULATION------------------------- initial begin clock = 0; // display time in nanoseconds $timeformat ( -9, 1, "ns", 12 ); display_debug_message; sys_reset; test1; $stop; test2; $stop; test3; $finish; // simulation is finished here. end // initial task display_debug_message; begin $display ("\n************************************************" ); $display ( "* THE FOLLOWING DEBUG TASK ARE AVAILABLE: *" ); $display ( "* \"test1;\" to load the 1st diagnostic program. *"); $display ( "* \"test2;\" to load the 2nd diagnostic program. *"); $display ( "* \"test3;\" to load the Fibonacci program. *"); $display ( "************************************************\n"); end endtask // display_debug_message task test1; begin test = 0; disable MONITOR; $readmemb ("test1.pro", t_rom.memory ); $display ("rom loaded successfully!"); $readmemb ("test1.dat", t_ram.ram ); $display ("ram loaded successfully!"); #1 test = 1; #14800; sys_reset; end endtask // test1 task test2; begin test = 0; disable MONITOR; $readmemb ("test2.pro", t_rom.memory ); $display ("rom loaded successfully!"); $readmemb ("test2.dat", t_ram.ram ); $display ("ram loaded successfully!"); #1 test = 2; #11600; sys_reset; end endtask // test2 task test3; begin test = 0; disable MONITOR; $readmemb ("test3.pro", t_rom.memory ); $display ("rom loaded successfully!"); $readmemb ("test3.dat", t_ram.ram ); $display ("ram loaded successfully!"); #1 test = 3; #94000; sys_reset; end endtask // test1 task sys_reset; begin reset_req = 0; #( `PERIOD * 0.7 ) reset_req = 1; #( 1.5 * `PERIOD ) reset_req = 0; end endtask // sys_reset //--------------------------MONITOR-------------------------------- always@( test ) begin: MONITOR case( test ) 1: begin // display results when running test 1 $display("\n*** RUNNING CPU test 1 - The Basic CPU Diagnostic Program ***"); $display("\n TIME PC INSTR ADDR DATA "); $display(" ------ ---- ------- ------ ------ "); while( test == 1 )@( t_cpu.pc_addr ) begin // fixed if(( t_cpu.pc_addr % 2 == 1 )&&( t_cpu.fetch == 1 )) begin // fixed #60 PC_addr <= t_cpu.pc_addr - 1; IR_addr <= t_cpu.ir_addr; #340 $strobe("%t %h %s %h %h", $time, PC_addr, mnemonic, IR_addr, data ); // Here data has been changed t_cpu.m_register.data end // if t_cpu.pc_addr % 2 == 1 && t_cpu.fetch == 1 end // while test == 1 @ t_cpu.pc_addr end 2: begin // display results when running test 2 $display("\n*** RUNNING CPU test 2 - The Basic CPU Diagnostic Program ***"); $display("\n TIME PC INSTR ADDR DATA "); $display(" ------ ---- ------- ------ ------ "); while( test == 2 )@( t_cpu.pc_addr ) begin // fixed if(( t_cpu.pc_addr % 2 == 1 )&&( t_cpu.fetch == 1 )) begin // fixed #60 PC_addr <= t_cpu.pc_addr - 1; IR_addr <= t_cpu.ir_addr; #340 $strobe("%t %h %s %h %h", $time, PC_addr, mnemonic, IR_addr, data ); // Here data has been changed t_cpu.m_register.data end // if t_cpu.pc_addr % 2 == 1 && t_cpu.fetch == 1 end // while test == 2 @ t_cpu.pc_addr end 3: begin // display results when running test 3 $display("\n*** RUNNING CPU test 3 - An Executable Program **************"); $display("***** This program should calculate the fibonacci *************"); $display("\n TIME FIBONACCI NUMBER "); $display(" ------ -----------------_ "); while( test == 3 ) begin wait( t_cpu.opcode == 3'h 1 ) // display Fib. No. at end of program loop $strobe("%t %d", $time, t_ram.ram [ 10'h 2 ]); wait( t_cpu.opcode != 3'h 1 ); end // while test == 3 end endcase // test end // MONITOR: always@ test //-------------------------HALT------------------------------- always@( posedge halt ) begin // STOP when HALT intruction decoded #500 $display("\n******************************************"); $display( "** A HALT INSTRUCTION WAS PROCESSED !!! **"); $display( "******************************************"); end // always@ posedge halt //-----------------------CLOCK & MNEMONIC------------------------- always#(`PERIOD / 2 ) clock = ~ clock; always@( t_cpu.opcode ) begin // get an ASCII mnemonic for each opcode case( t_cpu.opcode ) 3'b 000 : mnemonic = "HLT"; 3'b 001 : mnemonic = "SKZ"; 3'b 010 : mnemonic = "ADD"; 3'b 011 : mnemonic = "AND"; 3'b 100 : mnemonic = "XOR"; 3'b 101 : mnemonic = "LDA"; 3'b 110 : mnemonic = "STO"; 3'b 111 : mnemonic = "JMP"; default : mnemonic = "???"; endcase end endmodule $ readmemb ( "test1. pro" ,t_ rom. . memory ); $ readmemb ( "testl. dat",t_ ram_ . ram); 即可把编译好的汇编机器码装人虚拟ROM,把需要参加运算的数据装人虚拟RAM就可以开始仿真。上面语句中的第一项为打开的文件名,后一项为系统层次管理下的ROM模块和RAM模块中的存储器memory和ram。源代码&脚本隐藏内容,请前往内页查看详情前仿真结果test1 test2 test3 DC后仿真采用SMIC180工艺在典型环境下进行测试 时序报告: 面积报告: 功耗报告: 综合电路图: 总结该项目更加偏向于教学练习,CPU也是数字IC的重要研究方向,对此感兴趣的同学可以找点论文和开源资料进行学习。可以进一步优化如流水线、运算单元,扩展成SOC系统等。
2023年04月08日
716 阅读
7 评论
5 点赞
2023-04-06
【硬件算法】Verilog之FPGA实现信号移相法
实现信号移相可以用FPGA控制信号0-360度连续可调,对于高频信号(1GHZ以上)超出了FPGA工作频率还有一种办法是FPGA-》DA-》射频前端-》移相器-》阻抗匹配-》天线。本案例直接采用FPGA对数字中频信号处理(kHZ、MHZ)本质是边沿检测与分频现象:代码:/* Function : Phase Shift Interface : clk_fre---unit(MHZ) din_fre---unit(KHz) phase_angle---unit(0-360 Angle) Date: 2023/04/05 Description: Phase shift is carried out on the input square wave. The phase Angle unit is Angle (0-360), or the input can be greater than 360. The system clock unit is MHz and the input signal clock is KHz. */ module PhaseShift( input clk, //clk input rst_n, //rest input [7:0] clk_fre, //system clock frequency,MHZ input [15:0] din_fre, //input signal clock frequency,KHZ input [8:0] phase_angle, //phase shift angle input din, //input signal output reg dout //output signal ); reg [31:0] posedge_counter; reg [31:0] negedge_counter; reg [31:0] delay_counter; reg in_posedge_flg; reg in_negedge_flg; reg out_posedge_flg; reg out_negedge_flg; reg old_din; reg init_data=1'b1; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin posedge_counter <= 1'b0; negedge_counter <= 1'b0; in_posedge_flg <= 1'b0; in_negedge_flg <= 1'b0; out_posedge_flg <= 1'b0; out_negedge_flg <= 1'b0; end else begin if(~old_din & din) in_posedge_flg = 1'b1; if(old_din & ~din) in_negedge_flg = 1'b1; old_din <= din; if(init_data) begin delay_counter <= ((1000000000)/din_fre*clk_fre)/360*(phase_angle%360)/1000000; dout <= din ; init_data <= 1'b0; end if(in_posedge_flg && posedge_counter <= delay_counter) begin posedge_counter <= posedge_counter + 1'b1 ; out_posedge_flg <= 1'b0; end else begin posedge_counter <= 1'b0 ; in_posedge_flg <= 1'b0 ; if(~out_posedge_flg) begin dout <= 1'b1 ; out_posedge_flg <= 1'b1 ; end end if (in_negedge_flg && negedge_counter <= delay_counter) begin negedge_counter <= negedge_counter + 1'b1 ; out_negedge_flg <= 1'b0 ; end else begin negedge_counter <= 1'b0 ; in_negedge_flg <= 1'b0 ; if(~out_negedge_flg) begin dout <= 1'b0 ; out_negedge_flg <= 1'b1; end end end end endmodule测试代码:`timescale 100ps/10ps // module test_PhaseShift; reg clk; reg rst_n; reg [7:0] clk_fre; //system clock frequency,MHZ reg [15:0] din_fre ; //input signal clock frequency,KHZ reg [8:0] phase_angle; //phase shift angle reg din ; //input signal wire dout ; //output signal initial begin clk = 0; din = 0; din_fre = 4000; phase_angle = 90; clk_fre = 100; rst_n = 1; #12 rst_n = 0; #4 rst_n = 1; #10000 $stop; //end end always #5 clk = ~clk; always #125 din = ~din; PhaseShift u1( .clk(clk), .rst_n(rst_n), .clk_fre(clk_fre), .din_fre(din_fre), .phase_angle(phase_angle), .din(din), .dout(dout) ); endmodule
2023年04月06日
889 阅读
0 评论
3 点赞
使用Verilog实现与仿真AMBA--AHB总线协议(三)
1、AHB从机AHB从机应答来自系统主主机发起的传输。从机使用从译码器输出的 HSELx 信号来决定它什么时候作应答。其它传输需要的信号,如地址与控制信息由主机产生。下图为一个AHB从机的接口框图:下面是一个AHB接口的SRAM控制器的程序://------------------------------------------------------------------------------ // File : ahb_sram.v // Editor : VSCode, Tab Size(4) // Description : Top Module of AHB SRAM Controller. // //------------------------------------------------------------------------------ `timescale 1ns / 1ps module ahb_sram #( parameter SYNC_RESET = 1, parameter AHB_DWIDTH = 32, parameter AHB_AWIDTH = 32, parameter SIZE_IN_BYTES = 2048, parameter ADD_WIDTH = $clog2(SIZE_IN_BYTES) ) ( //---------------------------------- // IO Declarations //---------------------------------- // Inputs input HCLK, input HRESETN, input HSEL, input HREADYIN, input [1:0] HTRANS, input [2:0] HBURST, input [2:0] HSIZE, input [AHB_DWIDTH-1:0] HWDATA, input [AHB_AWIDTH-1:0] HADDR, input HWRITE, // Outputs output [AHB_DWIDTH-1:0] HRDATA, output [1:0] HRESP, output HREADYOUT ); //---------------------------------- // Variable Declarations //---------------------------------- wire [ADD_WIDTH-1:0] HADDR_cal; wire [2:0] ahbsram_size; wire [ADD_WIDTH-1:0] ahbsram_addr; wire [31:0] ahbsram_wdata; wire ahbsram_write; wire [31:0] sramahb_rdata; wire sramahb_ack; //---------------------------------- // Start of Main Code //---------------------------------- assign HADDR_cal = HADDR[ADD_WIDTH-1:0]; // Instantiations ahb_sram_if #( .AHB_DWIDTH (AHB_DWIDTH), .AHB_AWIDTH (AHB_AWIDTH), .ADD_WIDTH (ADD_WIDTH), .SYNC_RESET (SYNC_RESET) ) u_ahb_sram_if ( .HCLK (HCLK), .HRESETN (HRESETN), .HSEL (HSEL), .HTRANS (HTRANS), .HBURST (HBURST), .HWRITE (HWRITE), .HWDATA (HWDATA), .HSIZE (HSIZE), .HADDR (HADDR_cal), .HREADYIN (HREADYIN), // From SRAM Control signals .sramahb_ack (sramahb_ack), .sramahb_rdata (sramahb_rdata), // Outputs .HREADYOUT (HREADYOUT), .HRESP (HRESP), // To SRAM Control signals .ahbsram_req (ahbsram_req), .ahbsram_write (ahbsram_write), .ahbsram_wdata (ahbsram_wdata), .ahbsram_size (ahbsram_size), .ahbsram_addr (ahbsram_addr), .HRDATA (HRDATA) ); sram_ctrl_if #( .ADD_WIDTH (ADD_WIDTH), .SYNC_RESET (SYNC_RESET) ) u_sram_ctrl_if ( .HCLK (HCLK), .HRESETN (HRESETN), // From AHB Interface signals .ahbsram_req (ahbsram_req), .ahbsram_write (ahbsram_write), .ahbsram_wdata (ahbsram_wdata), .ahbsram_size (ahbsram_size), .ahbsram_addr (ahbsram_addr), // Outputs // To AHB Interface signals .sramahb_ack (sramahb_ack), .sramahb_rdata (sramahb_rdata) ); endmodule //------------------------------------------------------------------------------ // File : ahb_sram_if.v // Editor : VSCode, Tab Size(4) // Description : // //------------------------------------------------------------------------------ `timescale 1ns / 1ps module ahb_sram_if #( parameter AHB_DWIDTH = 32, parameter AHB_AWIDTH = 32, parameter ADD_WIDTH = 11, parameter SYNC_RESET = 0 ) ( //---------------------------------- // IO Declarations //---------------------------------- // Inputs input HCLK, input HRESETN, input HSEL, input HREADYIN, input [1:0] HTRANS, input [2:0] HBURST, input [2:0] HSIZE, input [ADD_WIDTH-1:0] HADDR, input [AHB_DWIDTH-1:0] HWDATA, input HWRITE, input sramahb_ack, input [AHB_DWIDTH-1:0] sramahb_rdata, // Outputs output HREADYOUT, output [1:0] HRESP, output reg [AHB_DWIDTH-1:0] HRDATA, output ahbsram_req, output ahbsram_write, output [AHB_AWIDTH-1:0] ahbsram_wdata, output [2:0] ahbsram_size, output [ADD_WIDTH-1:0] ahbsram_addr ); //---------------------------------- // Local Parameter Declarations //---------------------------------- // State Machine parameters localparam IDLE = 2'b00; localparam AHB_WR = 2'b01; localparam AHB_RD = 2'b10; parameter RESP_OKAY = 2'b00; parameter RESP_ERROR = 2'b01; // AHB HTRANS definition parameter TRN_IDLE = 2'b00; parameter TRN_BUSY = 2'b01; parameter TRN_SEQ = 2'b11; parameter TRN_NONSEQ = 2'b10; parameter SINGLE = 3'b000; parameter INCR = 3'b001; parameter WRAP4 = 3'b010; parameter INCR4 = 3'b011; parameter WRAP8 = 3'b100; parameter INCR8 = 3'b101; parameter WRAP16 = 3'b110; parameter INCR16 = 3'b111; //---------------------------------- // Variable Declarations //---------------------------------- reg [1:0] HTRANS_d; reg [2:0] HBURST_d; reg [2:0] HSIZE_d; reg [ADD_WIDTH-1:0] HADDR_d; reg [AHB_DWIDTH-1:0] HWDATA_d; reg HWRITE_d; reg HSEL_d; reg HREADYIN_d; reg [1:0] ahbcurr_state; reg [1:0] ahbnext_state; reg latchahbcmd; reg ahbsram_req_int; reg ahbsram_req_d1; reg [AHB_DWIDTH-1:0] HWDATA_cal; reg [4:0] burst_count; reg [4:0] burst_count_reg; reg [4:0] count; wire aresetn; wire sresetn; //---------------------------------- // Start of Main Code //---------------------------------- assign aresetn = (SYNC_RESET==1) ? 1'b1 : HRESETN; assign sresetn = (SYNC_RESET==1) ? HRESETN : 1'b1; // Generation of valid AHB Command which triggers the AHB Slave State Machine assign validahbcmd = HREADYIN & HSEL & (HTRANS == TRN_NONSEQ); // Generation of HRESP assign HRESP = RESP_OKAY; always @(*) begin HWDATA_cal = HWDATA; end // Latch all the AHB signals always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin HADDR_d <= }; HWDATA_d <= }; HTRANS_d <= 2'b00; HSIZE_d <= 2'b00; HBURST_d <= 3'b000; HWRITE_d <= 1'b0; HSEL_d <= 1'b0; HREADYIN_d <= 1'b0; end else if (HREADYIN == 1'b1 & HSEL == 1'b1 & HREADYOUT == 1'b1) begin HADDR_d <= HADDR; HTRANS_d <= HTRANS; HSIZE_d <= HSIZE; HBURST_d <= HBURST; HWRITE_d <= HWRITE; HWDATA_d <= HWDATA_cal; HSEL_d <= HSEL; HREADYIN_d <= HREADYIN; end end // Current State generation always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin ahbcurr_state <= IDLE; end else begin ahbcurr_state <= ahbnext_state; end end // Next State and output decoder logic always @(*) begin latchahbcmd = 1'b0; ahbsram_req_int = 1'b0; ahbnext_state = ahbcurr_state; case (ahbcurr_state) IDLE : begin if (HREADYIN == 1'b1 && HSEL == 1'b1 && ((HTRANS == TRN_NONSEQ) || HTRANS == TRN_SEQ)) begin latchahbcmd = 1'b1; if (HWRITE == 1'b1) begin ahbnext_state = AHB_WR; end else begin ahbnext_state = AHB_RD; end end else begin ahbnext_state = IDLE; end end AHB_WR : begin latchahbcmd = 1'b0; ahbsram_req_int = 1'b1; if (sramahb_ack == 1'b1) begin if (count == burst_count_reg) begin ahbnext_state = IDLE; end else begin ahbsram_req_int = 1'b0; end end end AHB_RD : begin latchahbcmd = 1'b0; ahbsram_req_int = 1'b1; if (sramahb_ack == 1'b1) begin ahbnext_state = IDLE; end end default : begin ahbnext_state = IDLE; end endcase end // LOGIC FOR BURST COUNT always @(*) begin burst_count = burst_count_reg; if (HSEL == 1'b1 && HTRANS == TRN_NONSEQ && HREADYIN == 1'b1 && HREADYOUT == 1'b1) begin case (HBURST) SINGLE : burst_count = 5'b00001; WRAP4,INCR4 : burst_count = 5'b00100; WRAP8,INCR8 : burst_count = 5'b01000; WRAP16,INCR16 : burst_count = 5'b10000; default : burst_count = 4'b0001; endcase end end always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin burst_count_reg <= 'h0; end else begin burst_count_reg <= burst_count; end end always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin count <= 5'h0; end else begin if (count == burst_count_reg) begin count <= 5'h0; end else if (ahbsram_req == 1'b1) begin count <= count + 1'b1; end else begin count <= count; end end end assign HREADYOUT = !ahbsram_req_int; // Generation of signals required for SRAM assign ahbsram_write = ahbsram_req ? HWRITE_d : 1'b0; assign ahbsram_wdata = HWDATA; assign ahbsram_addr = ahbsram_req ? HADDR_d : HADDR_d; assign ahbsram_size = ahbsram_req ? HSIZE_d : HSIZE_d; always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin ahbsram_req_d1 <= 1'b0; end else begin ahbsram_req_d1 <= ahbsram_req_int; end end // Generate the request to the SRAM contol logic when there is AHB read or write request assign ahbsram_req = ahbsram_req_int & !ahbsram_req_d1; // HRDATA generation always @(*) begin if (HREADYOUT && HREADYIN) begin HRDATA = sramahb_rdata; end else begin HRDATA = sramahb_rdata; end end endmodule //------------------------------------------------------------------------------ // File : sram_ctrl_if.v // Editor : VSCode, Tab Size(4) // Description : // //------------------------------------------------------------------------------ `timescale 1ns / 1ps module sram_ctrl_if #( parameter AHB_DWIDTH = 32, parameter ADD_WIDTH = 11, parameter SYNC_RESET = 0 ) ( //---------------------------------- // IO Declarations //---------------------------------- // Inputs input HCLK, input HRESETN, input ahbsram_req, input ahbsram_write, input [2:0] ahbsram_size, input [ADD_WIDTH-1:0] ahbsram_addr, input [AHB_DWIDTH-1:0] ahbsram_wdata, // Outputs output sramahb_ack, output reg [AHB_DWIDTH-1: 0] sramahb_rdata ); //---------------------------------- // Local Parameter Declarations //---------------------------------- // State Machine parameters localparam S_IDLE = 2'b00; localparam S_WR = 2'b01; localparam S_RD = 2'b10; //---------------------------------- // Variable Declarations //---------------------------------- reg [3:0] sram_wen_mem; reg [1:0] sramcurr_state; reg [1:0] sramnext_state; reg sram_wen; reg sram_ren; reg sramahb_ack_int; reg sram_ren_d; reg sram_done; wire [AHB_DWIDTH-1:0] ram_rdata; wire aresetn; wire sresetn; //---------------------------------- // Start of Main Code //---------------------------------- assign aresetn = (SYNC_RESET == 1) ? 1'b1 : HRESETN; assign sresetn = (SYNC_RESET == 1) ? HRESETN : 1'b1; // Current State generation always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin sramcurr_state <= S_IDLE; end else begin sramcurr_state <= sramnext_state; end end // Next State and output decoder logic always @(*) begin sramahb_ack_int = 1'b0; sram_wen = 1'b0; sram_ren = 1'b0; sramnext_state = sramcurr_state; case (sramcurr_state) S_IDLE : begin if (ahbsram_req == 1'b1) begin if (ahbsram_write == 1'b1) begin sramnext_state = S_WR; sram_wen = 1'b1; end else begin sram_ren = 1'b1; sramnext_state = S_RD; end end end S_WR : begin if (sram_done == 1'b1) begin sramnext_state = S_IDLE; sramahb_ack_int = 1'b1; end end S_RD : begin if (sram_done == 1'b1) begin sramnext_state = S_IDLE; sramahb_ack_int = 1'b1; end end default : begin sramnext_state = S_IDLE; end endcase end always @(*) begin sram_wen_mem = 4'b0000; if (ahbsram_size == 3'b010) begin sram_wen_mem = }; end else if (ahbsram_size == 3'b001) begin case (ahbsram_addr[1]) 1'b0 : begin sram_wen_mem[0] = sram_wen; sram_wen_mem[1] = sram_wen; sram_wen_mem[2] = 1'b0; sram_wen_mem[3] = 1'b0; end 1'b1 : begin sram_wen_mem[0] = 1'b0; sram_wen_mem[1] = 1'b0; sram_wen_mem[2] = sram_wen; sram_wen_mem[3] = sram_wen; end endcase end else if (ahbsram_size == 3'b000) begin case (ahbsram_addr[1:0]) 2'b00 : begin sram_wen_mem[0] = sram_wen; sram_wen_mem[1] = 1'b0; sram_wen_mem[2] = 1'b0; sram_wen_mem[3] = 1'b0; end 2'b01 : begin sram_wen_mem[0] = 1'b0; sram_wen_mem[1] = sram_wen; sram_wen_mem[2] = 1'b0; sram_wen_mem[3] = 1'b0; end 2'b10 : begin sram_wen_mem[0] = 1'b0; sram_wen_mem[1] = 1'b0; sram_wen_mem[2] = sram_wen; sram_wen_mem[3] = 1'b0; end 2'b11 : begin sram_wen_mem[0] = 1'b0; sram_wen_mem[1] = 1'b0; sram_wen_mem[2] = 1'b0; sram_wen_mem[3] = sram_wen; end endcase end else begin sram_wen_mem = }; end end // SRAM Instantiations sram_model #( .SYNC_RESET (SYNC_RESET), .ADDR_WIDTH (ADD_WIDTH) ) u_sram_model ( .writedata (ahbsram_wdata), .readdata (ram_rdata[31:0]), .wren (sram_wen_mem), .rden (sram_ren), .writeaddr (ahbsram_addr[ADD_WIDTH-1:2]), .readaddr (ahbsram_addr[ADD_WIDTH-1:2]), .clk (HCLK), .resetn (HRESETN) ); always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin sramahb_rdata <= 32'h0; end else if (sram_ren_d == 1'b1) begin sramahb_rdata <= ram_rdata; end else begin sramahb_rdata <= sramahb_rdata; end end always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin sram_ren_d <= 32'h0; end else begin sram_ren_d <= sram_ren; end end // Generate the SRAM done when the SRAM wren/rden is done always @(posedge HCLK or negedge aresetn) begin if ((aresetn == 1'b0) || (sresetn == 1'b0)) begin sram_done <= 1'b0; end else if (sram_wen || sram_ren) begin sram_done <= 1'b1; end else begin sram_done <= 1'b0; end end // Generate the SRAM ack assign sramahb_ack = sramahb_ack_int; endmodule //------------------------------------------------------------------------------ // File : sram_model.v // Editor : GVIM, Tab Size(4) // Description : FPGA Block Ram/Onchip SRAM. // //------------------------------------------------------------------------------ `timescale 1ns / 1ps module sram_model #( parameter SYNC_RESET = 0, parameter ADDR_WIDTH = 16 ) ( //---------------------------------- // IO Declarations //---------------------------------- input [31:0] writedata, output [31:0] readdata, input [3:0] wren, input rden, input [ADDR_WIDTH-1:2] writeaddr, input [ADDR_WIDTH-1:2] readaddr, input clk, input resetn ); //---------------------------------- //--Local Parameter Declarations //---------------------------------- localparam AWT = ((1<<(ADDR_WIDTH-2))-1); //---------------------------------- // Variable Declarations //---------------------------------- reg [7:0] bram0[AWT:0]; reg [7:0] bram1[AWT:0]; reg [7:0] bram2[AWT:0]; reg [7:0] bram3[AWT:0]; reg [ADDR_WIDTH-3:0] addr_q1; reg rden_r = 1'b0; wire [31:0] readdata_i; wire aresetn; wire sresetn; //---------------------------------- // Start of Main Code //---------------------------------- assign aresetn = (SYNC_RESET == 1) ? 1'b1 : resetn; assign sresetn = (SYNC_RESET == 1) ? resetn : 1'b1; always @(posedge clk) begin rden_r <= rden; end // Infer Block RAM always @(posedge clk) begin if (wren[0]) bram0[writeaddr] <= writedata[7:0]; if (wren[1]) bram1[writeaddr] <= writedata[15:8]; if (wren[2]) bram2[writeaddr] <= writedata[23:16]; if (wren[3]) bram3[writeaddr] <= writedata[31:24]; end always @(posedge clk) begin addr_q1 <= readaddr[ADDR_WIDTH-1:2]; end assign readdata_i = ; assign readdata = rden_r ? readdata_i : }; endmodule下面是测试用例代码://------------------------------------------------------------------------------ // File : top_tb.v // Editor : VSCode, Tab Size(4) // Description : Testbench of AHB SRAM Controller. // //------------------------------------------------------------------------------ `timescale 1ns / 1ps module top_tb(); //---------------------------------- // Local Parameter Declarations //---------------------------------- localparam AHB_CLK_PERIOD = 5; // Assuming AHB CLK to be 100MHz localparam SIZE_IN_BYTES =2048; //---------------------------------- // Variable Declarations //---------------------------------- reg HCLK = 0; wire HWRITE; wire [1:0] HTRANS; wire [2:0] HSIZE; wire [2:0] HBURST; wire HREADYIN; wire [31:0] HADDR; wire [31:0] HWDATA; wire HREADYOUT; wire [1:0] HRESP; wire [31:0] HRDATA; reg HRESETn; //---------------------------------- // Start of Main Code //---------------------------------- //----------------------------------------------------------------------- // Generate HCLK //----------------------------------------------------------------------- always #AHB_CLK_PERIOD HCLK <= ~HCLK; //----------------------------------------------------------------------- // Generate HRESETn //----------------------------------------------------------------------- initial begin HRESETn = 1'b0; repeat(5) @(posedge HCLK); HRESETn = 1'b1; end ahb_master #( .START_ADDR (32'h0), .DEPTH_IN_BYTES (SIZE_IN_BYTES) ) u_ahb_master ( .HRESETn (HRESETn), .HCLK (HCLK), .HADDR (HADDR), .HTRANS (HTRANS), .HWRITE (HWRITE), .HSIZE (HSIZE), .HBURST (HBURST), .HWDATA (HWDATA), .HRDATA (HRDATA), .HRESP (HRESP), .HREADY (HREADYOUT) ); ahb_sram # ( .AHB_AWIDTH (32), .AHB_DWIDTH (32), .SIZE_IN_BYTES (SIZE_IN_BYTES), .SYNC_RESET (1) ) u_ahb_sram ( .HCLK (HCLK), .HRESETN (HRESETn), .HSEL (1'b1), .HWRITE (HWRITE), .HADDR (HADDR), .HWDATA (HWDATA), .HRDATA (HRDATA), .HSIZE (HSIZE), .HTRANS (HTRANS), .HBURST (HBURST), .HRESP (HRESP), .HREADYIN (1'b1), .HREADYOUT (HREADYOUT) ); `ifdef VCS initial begin $fsdbDumpfile("top_tb.fsdb"); $fsdbDumpvars; end initial begin `ifdef DUMP_VPD $vcdpluson(); `endif end `endif endmodule这个模块主要是产生了时钟和复位信号,例化了待测模块,以及一个AHB Master的功能模型。具体见下面的代码://------------------------------------------------------------------------------ // File : ahb_master.v // Editor : VSCode, Tab Size(4) // Description : AHB Master Simulation Model. // //------------------------------------------------------------------------------ `timescale 1ns / 1ps `define SINGLE_TEST `define BURST_TEST module ahb_master #( //---------------------------------- // Paramter Declarations //---------------------------------- parameter START_ADDR = 0, parameter DEPTH_IN_BYTES = 32'h100, parameter END_ADDR = START_ADDR+DEPTH_IN_BYTES-1 ) ( //---------------------------------- // IO Declarations //---------------------------------- input wire HRESETn, input wire HCLK, output reg [31:0] HADDR, output reg [1:0] HTRANS, output reg HWRITE, output reg [2:0] HSIZE, output reg [2:0] HBURST, output reg [31:0] HWDATA, input wire [31:0] HRDATA, input wire [1:0] HRESP, input wire HREADY ); //---------------------------------- // Variable Declarations //---------------------------------- reg [31:0] data_burst[0:1023]; //---------------------------------- // Start of Main Code //---------------------------------- initial begin HADDR = 0; HTRANS = 0; HWRITE = 0; HSIZE = 0; HBURST = 0; HWDATA = 0; while(HRESETn === 1'bx) @(posedge HCLK); while(HRESETn === 1'b1) @(posedge HCLK); while(HRESETn === 1'b0) @(posedge HCLK); `ifdef SINGLE_TEST repeat(3) @(posedge HCLK); memory_test(START_ADDR, END_ADDR, 1); memory_test(START_ADDR, END_ADDR, 2); memory_test(START_ADDR, END_ADDR, 4); `endif `ifdef BURST_TEST repeat(5) @(posedge HCLK); memory_test_burst(START_ADDR, END_ADDR, 1); memory_test_burst(START_ADDR, END_ADDR, 2); memory_test_burst(START_ADDR, END_ADDR, 4); memory_test_burst(START_ADDR, END_ADDR, 6); memory_test_burst(START_ADDR, END_ADDR, 8); memory_test_burst(START_ADDR, END_ADDR, 10); memory_test_burst(START_ADDR, END_ADDR, 16); memory_test_burst(START_ADDR, END_ADDR, 32); memory_test_burst(START_ADDR, END_ADDR, 64); memory_test_burst(START_ADDR, END_ADDR, 128); memory_test_burst(START_ADDR, END_ADDR, 255); repeat(5) @(posedge HCLK); `endif $finish(2); end //----------------------------------------------------------------------- // Single transfer test //----------------------------------------------------------------------- task memory_test; input [31:0] start; // start address input [31:0] finish; // end address input [2:0] size; // data size: 1, 2, 4 integer i; integer error; reg [31:0] data; reg [31:0] gen; reg [31:0] got; reg [31:0] reposit[START_ADDR:END_ADDR]; begin $display("%m: read-after-write test with %d-byte access", size); error = 0; gen = $random(7); for (i = start; i < (finish-size+1); i = i + size) begin gen = $random & ~32'b0; data = align(i, gen, size); ahb_write(i, size, data); ahb_read(i, size, got); got = align(i, got, size); if (got !== data) begin $display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data); error = error + 1; end end if (error == 0) $display("[%10d] %m OK: from %x to %x", $time, start, finish); $display("%m read-all-after-write-all with %d-byte access", size); error = 0; gen = $random(1); for (i = start; i < (finish-size+1); i = i + size) begin gen = & ~32'b0; data = align(i, gen, size); reposit[i] = data; ahb_write(i, size, data); end for (i = start; i < (finish-size+1); i = i + size) begin data = reposit[i]; ahb_read(i, size, got); got = align(i, got, size); if (got !== data) begin $display("[%10d] %m A:%x D:%x, but %x expected", $time, i, got, data); error = error + 1; end end if (error == 0) $display("[%10d] %m OK: from %x to %x", $time, start, finish); end endtask //----------------------------------------------------------------------- // Burst transfer test //----------------------------------------------------------------------- task memory_test_burst; input [31:0] start; // start address input [31:0] finish; // end address input [7:0] leng; // burst length integer i; integer j; integer k; integer r; integer error; reg [31:0] data; reg [31:0] gen; reg [31:0] got; reg [31:0] reposit[0:1023]; integer seed; begin $display("%m: read-all-after-write-all burst test with %d-beat access", leng); error = 0; seed = 111; gen = $random(seed); k = 0; if (finish > (start+leng*4)) begin for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin for (j = 0; j < leng; j = j + 1) begin data_burst[j] = $random; reposit[j+k*leng] = data_burst[j]; end @(posedge HCLK); ahb_write_burst(i, leng); k = k + 1; end gen = $random(seed); k = 0; for (i = start; i < (finish-(leng*4)+1); i = i + leng*4) begin @(posedge HCLK); ahb_read_burst(i, leng); for (j = 0; j < leng; j = j + 1) begin if (data_burst[j] != reposit[j+k*leng]) begin error = error+1; $display("%m A=%hh D=%hh, but %hh expected",i+j*leng, data_burst[j], reposit[j+k*leng]); end end k = k + 1; r = $random & 8'h0F; repeat(r) @(posedge HCLK); end if (error == 0) $display("%m %d-length burst read-after-write OK: from %hh to %hh",leng, start, finish); end else begin $display("%m %d-length burst read-after-write from %hh to %hh ???",leng, start, finish); end end endtask //----------------------------------------------------------------------- // As AMBA AHB bus uses non-justified data bus scheme, data should be // aligned according to the address. //----------------------------------------------------------------------- function [31:0] align; input [ 1:0] addr; input [31:0] data; input [ 2:0] size; // num of bytes begin `ifdef BIG_ENDIAN case (size) 1 : case (addr[1:0]) 0 : align = data & 32'hFF00_0000; 1 : align = data & 32'h00FF_0000; 2 : align = data & 32'h0000_FF00; 3 : align = data & 32'h0000_00FF; endcase 2 : case (addr[1]) 0 : align = data & 32'hFFFF_0000; 1 : align = data & 32'h0000_FFFF; endcase 4 : align = data&32'hFFFF_FFFF; default : $display($time,,"%m ERROR %d-byte not supported for size", size); endcase `else case (size) 1 : case (addr[1:0]) 0 : align = data & 32'h0000_00FF; 1 : align = data & 32'h0000_FF00; 2 : align = data & 32'h00FF_0000; 3 : align = data & 32'hFF00_0000; endcase 2 : case (addr[1]) 0 : align = data & 32'h0000_FFFF; 1 : align = data & 32'hFFFF_0000; endcase 4 : align = data&32'hFFFF_FFFF; default : $display($time,,"%m ERROR %d-byte not supported for size", size); endcase `endif end endfunction `include "ahb_transaction_tasks.v" endmodule这个模块主要是实现了两个testcase,一个是测试single传输,另一个测试burst传输。AHB读写传输任务被封装在ahb_transaction_tasks.v文件中://------------------------------------------------------------------------------ // File : ahb_transaction_tasks.v // Editor : VSCode, Tab Size(4) // Description : AHB Transaction Tasks. // //------------------------------------------------------------------------------ `ifndef __AHB_TRANSACTION_TASKS_V__ `define __AHB_TRANSACTION_TASKS_V__ //----------------------------------------------------------------------- // AHB Read Task //----------------------------------------------------------------------- task ahb_read; input [31:0] address; input [2:0] size; output [31:0] data; begin @(posedge HCLK); HADDR <= #1 address; HTRANS <= #1 2'b10; // NONSEQ; HBURST <= #1 3'b000; // SINGLE; HWRITE <= #1 1'b0; // READ; case (size) 1 : HSIZE <= #1 3'b000; // BYTE; 2 : HSIZE <= #1 3'b001; // HWORD; 4 : HSIZE <= #1 3'b010; // WORD; default : $display($time,, "ERROR: unsupported transfer size: %d-byte", size); endcase @(posedge HCLK); while (HREADY !== 1'b1) @(posedge HCLK); HTRANS <= #1 2'b0; // IDLE @(posedge HCLK); while (HREADY === 0) @(posedge HCLK); data = HRDATA; // must be blocking if (HRESP != 2'b00) $display($time,, "ERROR: non OK response for read"); @(posedge HCLK); end endtask //----------------------------------------------------------------------- // AHB Write Task //----------------------------------------------------------------------- task ahb_write; input [31:0] address; input [2:0] size; input [31:0] data; begin @(posedge HCLK); HADDR <= #1 address; HTRANS <= #1 2'b10; // NONSEQ HBURST <= #1 3'b000; // SINGLE HWRITE <= #1 1'b1; // WRITE case (size) 1 : HSIZE <= #1 3'b000; // BYTE 2 : HSIZE <= #1 3'b001; // HWORD 4 : HSIZE <= #1 3'b010; // WORD default : $display($time,, "ERROR: unsupported transfer size: %d-byte", size); endcase @(posedge HCLK); while (HREADY !== 1) @(posedge HCLK); HWDATA <= #1 data; HTRANS <= #1 2'b0; // IDLE @(posedge HCLK); while (HREADY === 0) @(posedge HCLK); if (HRESP != 2'b00) $display($time,, "ERROR: non OK response write"); @(posedge HCLK); end endtask //----------------------------------------------------------------------- // AHB Read Burst Task //----------------------------------------------------------------------- task ahb_read_burst; input [31:0] addr; input [31:0] leng; integer i; integer ln; integer k; begin k = 0; @(posedge HCLK); HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b10; // NONSEQ if (leng >= 16) begin HBURST <= #1 3'b111; // INCR16 ln = 16; end else if (leng >= 8) begin HBURST <= #1 3'b101; // INCR8 ln = 8; end else if (leng >= 4) begin HBURST <= #1 3'b011; // INCR4 ln = 4; end else begin HBURST <= #1 3'b001; // INCR ln = leng; end HWRITE <= #1 1'b0; // READ HSIZE <= #1 3'b010; // WORD @(posedge HCLK); while (HREADY == 1'b0) @(posedge HCLK); while (leng > 0) begin for (i = 0; i < ln-1; i = i + 1) begin HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b11; // SEQ; @(posedge HCLK); while (HREADY == 1'b0) @(posedge HCLK); data_burst[k%1024] <= HRDATA; k = k + 1; end leng = leng - ln; if (leng == 0) begin HADDR <= #1 0; HTRANS <= #1 0; HBURST <= #1 0; HWRITE <= #1 0; HSIZE <= #1 0; end else begin HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b10; // NONSEQ if (leng >= 16) begin HBURST <= #1 3'b111; // INCR16 ln = 16; end else if (leng >= 8) begin HBURST <= #1 3'b101; // INCR8 ln = 8; end else if (leng >= 4) begin HBURST <= #1 3'b011; // INCR4 ln = 4; end else begin HBURST <= #1 3'b001; // INCR1 ln = leng; end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); data_burst[k%1024] = HRDATA; // must be blocking k = k + 1; end end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); data_burst[k%1024] = HRDATA; // must be blocking end endtask //----------------------------------------------------------------------- // AHB Write Burst Task // It takes suitable burst first and then incremental. //----------------------------------------------------------------------- task ahb_write_burst; input [31:0] addr; input [31:0] leng; integer i; integer j; integer ln; begin j = 0; ln = 0; @(posedge HCLK); while (leng > 0) begin HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b10; // NONSEQ if (leng >= 16) begin HBURST <= #1 3'b111; // INCR16 ln = 16; end else if (leng >= 8) begin HBURST <= #1 3'b101; // INCR8 ln = 8; end else if (leng >= 4) begin HBURST <= #1 3'b011; // INCR4 ln = 4; end else begin HBURST <= #1 3'b001; // INCR ln = leng; end HWRITE <= #1 1'b1; // WRITE HSIZE <= #1 3'b010; // WORD for (i = 0; i < ln-1; i = i + 1) begin @(posedge HCLK); while (HREADY == 1'b0) @(posedge HCLK); HWDATA <= #1 data_burst[(j+i)%1024]; HADDR <= #1 addr; addr = addr + 4; HTRANS <= #1 2'b11; // SEQ; while (HREADY == 1'b0) @(posedge HCLK); end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); HWDATA <= #1 data_burst[(j+i)%1024]; if (ln == leng) begin HADDR <= #1 0; HTRANS <= #1 0; HBURST <= #1 0; HWRITE <= #1 0; HSIZE <= #1 0; end leng = leng - ln; j = j + ln; end @(posedge HCLK); while (HREADY == 0) @(posedge HCLK); if (HRESP != 2'b00) begin // OKAY $display($time,, "ERROR: non OK response write"); end `ifdef DEBUG $display($time,, "INFO: write(%x, %d, %x)", addr, size, data); `endif HWDATA <= #1 0; @(posedge HCLK); end endtask `endif使用VCS仿真,打印信息如下:整体仿真波形如下:不喜欢用DVE的话,也可以选择用Verdi查看波形,这种比较简单的调试,DVE也足够用了。下面贴一张突发传输的细节图:
2023年03月16日
238 阅读
0 评论
1 点赞
2023-03-04
AMBA--AHB总线协议介绍(二)
1、突发提前终止在特定情况下,一个突发传输会被提前终止,此时,要求从机能根据突发信息采取正确的动作。从机能够通过监控HTRANS信号一个突发传输是否被提前终止。在突发传输开始后,从机每次传输都会监测HTRANS信号是否为SEQ或BUSY,当HTRANS信号上出现NONSEQ或者IDLE时,则表明前一次突发传输已经终止。当一个主机因为仲裁失败导致突发传输未能完成,则在该主机下一次获得总线所有权时,必须能够保证重建该突发完成剩余传输。例如,当主机只完成一个4拍突发传输的一拍,那么它必须使用一个未定长度的突发来完成剩下的三拍传输。下图表示了一个四拍的回环突发操作,从机在第一次传输插入了一个等待状态,该突发传输大小为4Byte,为4拍传输,所以地址将在16字节边界回环,因此传输到0x3C后,下一次的传输地址将返回到0x30:下图表示了一个4拍增量突发,在第一次传输时从机也插入了一个等待状态,地址是连续的,跨过了16字节地址地址边界:下图表示了一个8拍的回环突发传输,传输大小为4Byte,所以地址将在32字节边界回环,回环之后的地址为0x20:下图表示8拍增量突发,传输大小为2Byte,所以地址增量为2,同时传输会跨过16字节的地址边界:下图表示了未定义长度的突发传输,从图中可以看出,该图包含两个未定义长度的突发传输,第一个位起始地址为0x20,传输大小为2Byte;第二个突发起始地址为0x5C,传输大小为4Byte,地址增量为4:2、控制信号和传输类型可突发类型信号一样,每次传输都会有一组控制信号来提供传输的附加信息。这些控制信号和地址总线具有相同的时序,和地址信号不同的是,这些控制信号在突发传输的过程中,必须保持不变。2.1 传输方向HWRITE为高时,表示写传输,并且主机需要数据广播到写数据总线(HWDATA)上;该信号为低时,表示读传输,从机必须产生数据到读数据总线(HRDATA)。2.1 传输大小HSIZE[2:0]表示传输大小,见下表:注:传输大小和传输类型一起被用于计算回环突发传输的地址边界。2.3 保护控制HPROT[3:0]为总线协议保护信号,用于定义存取的型态与特性,表示传输是否是:一次预取址或者数据访问;特权模式访问或者用户模式访问;对于带有存储器管理单元的主机来说这些信号也表示当前访问是带高速缓存(cache)的或是带缓冲(buffer)的;HPROT[3]高速缓存HPROT[2]带缓冲的HPROT[1]特权模式HPROT[0]数据/预取指描述---0预取指---1数据访问--0-用户模式访问--1-特权模式访问-0--无缓冲-1--带缓冲0---无高速缓存1---带高速缓存注意:并非所有的主机都能产生正确的保护控制信号,因此建议从机没有严格必要的情况下,不要使用HPROT信号。3、地址译码AHB总线中,会使用一个中央译码器来产生HSELx信号,用于选择从机,选择信号由地址高位信号组合译码产生。建议使用简单的译码方案来保证高速操作。从机只能在HREADY信号为高时,采样地址和控制信号以及HSELx,当HREADY信号为高时,表示传输完成。AHB有几个特别的规定:(1)每个从机至少都有 1KB 的内存空间。(2)每个主机每次存取的空间不可超过 1KB。(3)如果在 NONSEQ 或 SEQ 型态下存取到不存在的地址时,会有一个预设的从机发出 ERROR 的响应信号。(4)如果在 IDLE 或 BUSY 型态下存取到不存在的地址时,会有 OKAY 的响应信号。(5)预设的从机是中央译码器的一部分。(6)根据系统设计,使用地址的高位来产生从机选择信号。(7)地址的低位送给从机以寻址其的内部存储器或缓存器4、从机传输响应主机发起传输后,由从机决定传输如何进行。一旦传输发起,协议不允许主机取消传输。被访问的从机必须提供一个传输状态的响应。从机使用HREADY信号和HRESP[1:0]信号的组合,来提供传输状态。从机可以提供多种方式来完成传输:(1)立刻完成传输;(2)插入一个或多个等待状态以获得充分时间来完成传输;(3)发出一个错误应答表示传输失败;(4)延时传输的完成,允许主机和从机放弃总线,让其他主机先完成传输,再完成剩下的传输。4.1 传输完成HREADY信号用于扩展AHB传输中的数据相位,HREADY为低表示传输被扩展,为高表示传输完成。注意:每个从必须预先设定它放弃总线之前能插入的最大等待周期,用来计算访问总线的延时。推荐但不必须的是, 等待状态最多只可使用 16 个周期,已防止任何访问长时间占用总线。从机需要 HREADY 是双向信号(HREADY 做为总线上的信号,它是主机和从机的输入;同时每个从机自己的 HREADY 信号是输出。所以对于从机会有两个 HREADY 信号,一个来自总线的输入,一个自己给到多路器的输出。4.2 传输响应HRESP[1:0]的编码及传输响应信号如下表:HRESP[1:0]ReponseDescription00OKAY从机可以用HREADY (拉高)与OKAY 的信号响应,代表传送已成功完成。HREADY 被拉低,加上OKAY的信号可插入在任何的响应信号(ERROR、 RETRY 或SPLIT) 之间,也就是当从机在未能正确的选择ERROR、RETRY 或SPLIT时,可以插入HREADY (拉低)与OKAY信号,但是等待状态最多只可使用16个周期01ERROR此次数据传送发生错误,该信号必须维持两个时钟周期10RETRY传输未完成,主机应尝试再次发起传输,直到传输完毕,该信号必须维持两个时钟周期11SPLIT此信号必须维持两个周期,从机响应此信号表示此次的传送无法完成,要以分段传送方式来达成。等到从机可以完成时,会知会仲裁器,让重提数据传送要求的主机,完成传送4.3 双周期响应仅有OKAY响应可以在单周期给出,ERROR、RETRY、SPLIT响应至少需要两个周期。为了完成这些响应,从机会在倒数第二个周期驱动HRESP为ERROR或RETRY或SPLIT,同时拉低HREADY以获得一个额外的扩展周期。在最后一个周期,HREADY拉高,表示传输完成,同时保持HRESP。如果从机需要两个以上的周期来完成ERROR、RETRY、SPLIT响应,则从机可能在传输开始时,插入等待状态。在这段周期内,从机拉低HREADY,同时只能给出OKAY响应。需要双周期响应是AHB流水线传输的本质决定的,当从机发起ERROR、RETRY、SPLIT响应时,下一次传输的地址已经被广播到地址总线上了,双周期响应允许主机有足够的时间来取消该地址,并在下一次传输开始之前驱动HTRANS为IDLE。下图表示了一个RETRY应答,从图中可知,传输的起始地址为A,传输大小为4Byte,主机在获得响应之前,已经将地址设置为A+4,从机在地址A不能立即完成传输,因此给出RETRY响应,这个响应指示主机地址A的传输未完成,并且地址A+4的传输被IDLE传输类型取代。下图表示了传输中,从机需要一个周期(HRESP为OKAY)来决定给出何种响应的例子,之后从机给出双周期的ERROR响应结束了传输。4.4 错误响应当主机收到一个 ERROR响应时,它可以选择结束当前的批量数据传送,也可以继续传送剩下的批量数据,但通常不这样做。在从机送出 ERROR的信号之前必须先送出OKEY + HREADY (LOW)的信号,而 ERROR 信号至少需要维持两个周期。如上图所示。(1)在第二个周期 S 送出 OKAY+ low HREADY 信号,使从机有充分的时间决定是否需要发出 ERROR的信号;(2)主机收到ERROR应答后,立刻结束当前的数据传送;注意:从机必须维持ERROR至少两个周期4.5 分块和重试分块和重试响应给从机提供了在无法立刻给传输提供数据时释放总线的机制。这两种响应都允许在总线上结束传输,因此允许更高优先级的主机获得总线访问权。SPLIT和RETRY的不同之处在于仲裁器在发生SPLIT和RETRY后分配总线的方式:(1)对RETRY而言,仲裁器将继续使用常规优先级方案,因此只有拥有更高优先级的主机能获得总线访问权;(2)对SPLIT而言,仲裁器将调整优先级方案,以便其他任何主机申请总线即能获取总线,即使这个主机拥有更低的优先级。为了完成SPLIT传输,从机必须通知仲裁器数据何时可用,以便相应主机完成传输。SPLIT传输机制增加了仲裁器和从机的复杂度,却拥有完全释放总线给其他主机使用的有点;RETRY相应就只允许更高优先级的主机使用总线。主机使用相同的方式处理SPLIT和RETRY相应。主机应继续请求总线,并尝试传输,知道传输完成或收到ERROR相应为止。5、数据总线为了避免使用三态数据总线,AHB协议将数据总线分开为读数据总线和写数据总线。最小的数据位宽为32位,且位宽可增加。5.1 写数据总线HWDATA[31:0]1、写出数据由主机发出;2、主机必须将数据总线上的数据维持住,直到从机完成数据的接收 (HREADY HIGH);3、所有传送所对应的地址都必须对齐,例如: 32 位的传送地址 Addr[1:0] = 00;4、大端摆放法(big endian)或小端摆放法(little endian)都可使用4.11小端摆放法(以 32 位为例)(1)低字节在 HWDATA.的低字节地址(2)Byte 0 (address offset = 0) 在 HWDATA[7:0](3)Byte 1 (address offset = 1) 在 HWDATA[15:8](4)Byte 2 (address offset = 2) 在 HWDATA[23:16](5)Byte 3 (address offset = 3) 在 HWDATA[31:24]4.12大端摆放法(1)低字节在 HWDATA.的高字节地址(2)Byte 0 (address offset = 0) 在 HWDATA[31:24](3)Byte 1 (address offset = 1) 在 HWDATA[23:16](4)Byte 2 (address offset = 2) 在 HWDATA[15:8](5)Byte 3 (address offset = 3) 在 HWDATA[7:0]5.2 读数据总线HRDATA[31:0]读数据总线在读数据期间,由被选中的从机驱动,如果从机通过拉低HREADY信号来扩展传输,则从机只需在最后一个周期提供有效数据,由HREADY信号为高表示。端结构与写数据总线相同。从机只需要在OKAY响应周期提供有效数据,SPLIT、RETRY以及ERROR响应不需要提供有效的读数据。6、总线仲裁机制仲裁机制确保了任何时刻只有一个主机可以访问总线。仲裁器的功能如下:(1)通过观察不同的总线请求信号,通过仲裁算法来决定哪个主机拥有最高优先级;(2)接收从机的完成分段传送的请求仲裁信号描述如下表所示:NameWidthDescriptionHBUSREQx1bit由主机到仲裁器,主机通过此信号对仲裁器提出使用总线的要求。主机可在任何时间要求仲裁器对于固定长度的传送,只要发起一次请求即可, 仲裁器会依据HBURST[2:0]来判断传送的长度。对于未定的传送长度,主机必须持续提出请求,直到完成全部的传送为止。当所有主机都不需要使用总线时,必须在HTRANS.上送出IDLE信号,仲裁器就会将总线的使用权交给预设主机(default M)HLOCKx1bit此信号必须与HBUSREQx配合,表示一个不可分段的传送动作,HLOCKx必须再指定地址之前使能一个周期HGRANTx1bit由仲裁器输出到主机,仲裁器通知某个主机表示此主机得到总线的使用权.主机在HGRANT信号为高且HREADY信号也为高的那个HCLK上升沿处获得总线使用权 HMASTER4bit仲裁器以此信号表示当前是哪一个主机在使用总线,此信号也可用来控制地址多任务器HMASTERLOCK1bit仲裁器以此信号表示当前的总线是被锁住的,此信号与地址和控制信号具有相同的时序HSPLIT16bit一个可执行分段传送的从机以此信号让仲裁器知道要让哪一个主机完成其未完成的分段传送6.1 总线访问请求主机可以在任何周期内使用HBUSREQx信号来请求总线。仲裁器会在时钟上升沿根据内部的优先级仲裁算法决定接下来哪一个主机将获得总线访问权。通常仲裁器会在一个突发传输完成后才会授权另一个主机,但是如果需要的话,仲裁器也可以提前终止突发,将总线访问权授予更高优先级的主机。如果一个主机需要锁定总线,那么它必须声明HLOCKx信号,告知仲裁器其他主机不应该被授予总线。当主得到总线授权,并且正在执行一个定长的突发传输,它不需要继续总线请求即可完成这个 burst。仲裁器观察 突发的进程,并使用 HBURST[2:0]来决定主机有多少个传送。如果主机希望在当前正在进行的突发之后继续 1 次传输,它应在当前突发期间重新声明请求信号。如果主机在突发传输过程中失去了总线访问权,那么它必须重新声明HBUSREQx信号以获得重新授权。对于未定长度的突发,主机需持续声明HBUSREQx,知道最后一个传输已经开始。因为在未定长度的突发中,仲裁器不知道何时可以改变总线授权。主机没有请求,也可能获得了总线控制权。这是因为当没有主机去请求总线时,总线会授权给一个默认的主机。 所以当某个主机不需要总线访问,它需要把传送类型 HTRANS 设置为 IDLE,这点很重要。6.2 授予总线访问仲裁器通过声明HGRANTx信号来表示当前拥有最高优先级的主机,HREADY信号为高时,主机将被授予总线,同时仲裁器改变HMASTER[3:0]来表示主机序号。下图表示一个零等待的总线授予访问:下图表示了有等待状态总线授予访问:数据总线的拥有权必须比地址总线的拥有权晚释放出去, 这是因为数据周期发生在地址周期之后。(1)在 T5 正沿,从机送出 HREADY 信号,表示 M1成功传送最后一次传输的地址及控制信号(2)地址总线在 T5 正沿开始交换使用者,当 HGRANT_M2 AND HREADY = HIGH,表示地址总线交换成功(3)仲裁器必须知道 M1还有多少笔数据要传送,才可正确的发出 HGRANT_M2 信号(4)当仲裁器收到最后一个传输地址后,仲裁器就会改变 HGRANTx 信号(5)T5 刚开始时, M2 送出地址与控制信号(6)数据总线的交换只有在上一笔数据传送结束后才会开始(7)在 T7 正沿,开始数据总线的交换下图表示了仲裁器在一次突发结束后移交总线的例子:(1)仲裁器在倒数第二个地址被采样时(T5),改变HGRANTx信号;(2)新的HGRANTx信息将在最后一个地址被采样时(T7)同时被采样。下图表示了HGRANTx和HMASTER信号在系统中是如何被使用的:!注意:由于使用了中央地址译码器,每个主机可以立刻输出它想被执行的地址,而不用等到被授予总线。HGRANTx信号仅被主机用来决定它何时拥有总线,并考虑何时地址被选中的从机采样。HMASTER的延迟信号被用来控制写数据总线数据选择器。6.3 锁定传输仲裁必须从观察每一个主机的 HLOCKx 信号, 来决定什么时候主机希望执行一个锁定顺序传输。之后仲裁器负责确保没有其他主机被授权,直到这个锁定传输完成。在一个连续锁定传输之后,仲裁将总是还会保持当前的主机的授权来传一个附加的传送来保证锁定传输的序列的最后一个传送已经成功完成,并且没有收到 SPLIT or RETRY 的应答之一。所以推荐但并不是强制要求的做法是,主机在任何锁定传输后插入一个 IDLE 传输,在开始其它突传输之前,来提供一个仲裁切换的机会。仲裁器也负责声明HMASTERLOCK信号,该信号与地址和控制信号具有相同的时序。该信号指示从机当前传输时锁定的,因此必须在其他主机被授予总线之前处理完成。6.4 默认总线主机每个系统都必须包含一个默认总线主机,当其他主机都不能使用总线时,该主机被授予总线。默认主机在获得授予总线时,只能执行IDLE传输。如果所有主机都还在等待 SPLIT传输的完成,也会将总线使用权交给默认主机。7、分段传输从机可以用 SPLIT 来解决延迟太长的传输。仲裁器必须观察响应信号并且遮掉(mask)已被分段传送的主机,使其等候 HSPLIT 信号的通知后才授予其总线使用权。仲裁器必须在 HMASTER[3:0] 上产生一个标签表示哪一个主机正在执行数据传送。当从机要执行分段传输的动作时,这个从机必须存下主机序号。当从机可以完成被分段的传输时会在 HSPLITx[15:0]信号上,送出要完成传送的主机序号。仲裁器以 HSPLITx[15:0] 来决定哪一个主机 可以重新获得 bus 的使用权。仲裁器会在每个周期去观察 HSPLITx 信号。当有多个 HSPLITx 信号时,会被逻辑或在一起传送给仲裁器。7.1 分段传输顺序1、主机送出地址与控制信号,开始传送。2、被寻址到的从机立即提供数据,或者送出SPLIT应答信号(如果需要等些周期来获得数据)。在每次的传输,仲裁器会广播出当前是哪一个主机在使用总线。 从机需要记录主机序列号,以便后面重传使用。3、有 SPLIT 信号响应时,仲裁器会将总线使用权交给别的主机使用,如果所有其它主机都收到SPLIT 信号,则移交给默认主机。4、当从机要完成分段传送时, 从机会在 HSPLITx 上设定对应的主机 ID,来指示哪个主机可以被允许再次访问总线。5、仲裁器会在每个周期观察 HSPLITx 信号,如果有变化,就更改正确的主机的优先权。6、仲裁器将会允许重新要求使用权的主机,但如果有更高优先级的主机在用总线,可能会延迟。7、从机会响应 OKAY 信号表示传送完成。7.2 多重分段传输只要一个主机有额外的 HBUSREQ 与 HGRANT 线, 即可发出超过一个以上的传送要求。仲裁器将一组信号 (REQ and GNT) 视为一个主机。可分段传输的从机可被设计去处理额外的传送要求而不需纪录额外的地址与控制信号,但要记录主机序号。从机可利用 HSPLITx 来表示当前正在处理的是哪一个主机要完成分段传输。仲裁器可重新仲裁这些主机的请求,高优先权的主机可被重新给予使用权。7.3 预防死锁Deadlock可能发生在很多不同的主机对同一个发出 SPLIT 和 RETRY 的从机身上, 而该从机又无法处理那么多 SPLIT and RETRY 时,便会发生 deadlock。Deadlock 可以透过限制分段传送的数目(最多 16 个)来避免。从机并不会记录下每个要求的地址与控制讯息,如果要作 SPLIT 的话,只会记录下当前传送对象的 ID 讯息。当 S 可完成分段传送时,才会锁定要求的地址与控制讯息。从机会依序地让仲裁器知道从机要服务哪些要求。从机可以任何顺序处理要求的服务。重传:当从机无法在很短的时间内完成传送的动作时, 它可以要求主机重传。 从机一次只可对一个主机发出 RETRY信号,确定所服务的主机是同一个时, RETRY 的动作才能完成。如果 RETRY 是不同主机时可以采取下列措施:(1)给一个 error 回应(2)通知仲裁器(3)产生中断(4)重置系统7.4 分块传输的总线移交当主机收到 SPLIT 或 RETRY 响应信号时,必须先进入 IDLE 状态并且重新请求总线使用权,一个新的主机会在 IDLE 周期结束前由仲裁器决定出来。主机在收到 SPLIT 或 RETRY 信号后须立刻进入 IDLE 状态。(1)在 T2 与 T3, 从机会传两个周期的 SPILT 信号。(2)该主机在 T3 正沿时发现有 SPLIT 响应信号,于是进入 IDLE 状态。(3)仲裁器在 T3 正沿时,决定下一个总线的使用者。(4)在 T4,新的主机送出地址与控制信号
2023年03月04日
279 阅读
0 评论
1 点赞
1
2
3