用Verilog设计Memory Decoder

-- TOC --

本文假设了一个简易的memory decoder结构,用verilog将其实现,并仿真测试。

假设的Memory Decoder

假设有一块64位的CPU,它的RAM的寻址能力为 \(2^{40}\) 字节,即1T(terabyte),40位RAM地址线。用它配置一个低版本的电脑,只提供4G内存,即32线的RAM访问能力,\(2^{32}\) 字节。这4G内存由4块1G内存组合而成,每一块1G的内存提供 \(2^{30}\) 个字节。

原理图如下:

mem_decoder

40条地址线,高8位固定为0,这样才能够选择1T空间中的低4G部分。上图中用一个8输入的NOR门连接地址线的高8位,产生EN_MEM信号,注意图中画的是8-input的not-and(不是NAND),等同于NOR。紧接着是2位的选片信号,选择4块1G内存中的哪一块,这就是一个带EN信号的2to4 decoder。再后面的27位信号连接全部的4块1G内存,用来选择一个longword,即8byte。最后8位信号用来选择8byte中的具体哪一个byte,这是一个3to8 decoder。

另外,图中还有2位额外的SIZE信号输入给3to8 decoder,用来指示其操作的宽度,比如00表示1byte,01表示2byte,10表示4byte,11表示8byte。当这两个信号不等于00的时候,这个3to8 decoder的部分输入会无效。比如当SIZE等于01的时候,每次选2个byte,INPUT每增加2时,其输入有效,如下表,NA表示无效输入:

SIZE INPUT OUTPUT
01 000 BE0,BE1
01 001 NA
01 010 BE2,BE3
01 011 NA
01 100 BE4,BE5
01 101 NA
01 110 BE6,BE7
01 111 NA

复习:如何用verilog实现decoder

Verilog实现代码

module mem_decoder(HADDR, LADDR, SIZE, EN_L, BE_L);
    input [39:30] HADDR;
    input [2:0] LADDR;
    input [1:0] SIZE;
    output reg [3:0] EN_L;
    output reg [7:0] BE_L;
    reg EN_MEM;

    parameter BYTE  = 2'b00,
              HWORD = 2'b01,
              WORD  = 2'b10,
              LWORD = 2'b11;

    always @ (*) begin
        EN_L = 4'b1111; BE_L = 8'hFF;
        EN_MEM = (HADDR[39:32] == 8'h00);
        if (EN_MEM) begin
            EN_L[HADDR[31:30]] = 1'b0;
            if (SIZE == BYTE)
                BE_L[LADDR] = 1'b0;
            else if (SIZE == HWORD)
                case (LADDR)
                    3'b000: BE_L = 8'b11111100;
                    3'b010: BE_L = 8'b11110011;
                    3'b100: BE_L = 8'b11001111;
                    3'b110: BE_L = 8'b00111111;
                endcase
            else if (SIZE == WORD)
                case (LADDR)
                    3'b000: BE_L = 8'hF0;
                    3'b100: BE_L = 8'h0F;
                endcase
            else  // LWORD
                if (LADDR == 3'b000) BE_L = 8'h00;
                else BE_L = 8'hFF;
        end
    end
endmodule

代码说明:

  1. array(或者vector)可以直接作用index使用,EN_L[HADDR[31:30]] = 1'b0;BE_L[LADDR] = 1'b0;
  2. 以上代码中的case语句块没有default部分;(写出default是个好的编程习惯,但是它有的时候并不是必须的)
  3. EN_L和BE_L都是active_low信号,不要把0和1搞反了,我一开始就搞反了,有些不习惯。

测试代码

module test_mem_decoder();
    integer size, haddr, laddr;
    reg [39:30] HADDR;
    reg [2:0] LADDR;
    reg [1:0] SIZE;
    wire [3:0] EN_L;
    wire [7:0] BE_L;
    reg [3:0] expectEN;
    reg [7:0] expectBE;

    parameter BYTE  = 2'b00,
              HWORD = 2'b01,
              WORD  = 2'b10,
              LWORD = 2'b11;

    task showerror;
        $display("HADDR=%10b, LADDR=%3b, SIZE=%2b, EN_L=%4b, BE_L=%8b",
                    HADDR, LADDR, SIZE, EN_L, BE_L);
    endtask

    mem_decoder UUT(HADDR, LADDR, SIZE, EN_L, BE_L);
    initial begin
        for (haddr=0; haddr<1024; haddr=haddr+1)
            for (size=0; size<4; size=size+1)
                for (laddr=0; laddr<8; laddr=laddr+1) begin
                    // set input and wait 10ns
                    HADDR = haddr; LADDR = laddr; SIZE = size;
                    #10;
                    //
                    expectEN = 4'b0000;
                    expectBE = 8'h00;
                    // check enable
                    if (HADDR[39:32] != 8'h00)
                        if ((EN_L==~expectEN) && (BE_L==~expectBE)) ;
                        else showerror;
                    else begin
                        // check EN_L
                        expectEN[HADDR[31:30]] = 1'b1;
                        if (EN_L != ~expectEN) showerror;
                        // check BE_L
                        if (SIZE == BYTE) begin
                            expectBE[LADDR] = 1'b1;
                            if (BE_L != ~expectBE) showerror;
                            end
                        else if (SIZE == HWORD)
                            case (LADDR)
                                3'b000: if (BE_L!=8'b11111100) showerror;
                                3'b010: if (BE_L!=8'b11110011) showerror;
                                3'b100: if (BE_L!=8'b11001111) showerror;
                                3'b110: if (BE_L!=8'b00111111) showerror;
                                default if (BE_L!=8'b11111111) showerror;
                            endcase
                        else if (SIZE == WORD)
                            case (LADDR)
                                3'b000: if (BE_L!=8'b11110000) showerror;
                                3'b100: if (BE_L!=8'b00001111) showerror;
                                default if (BE_L!=8'b11111111) showerror;
                            endcase
                        else  // LWORD
                            case (LADDR)
                                3'b000: if (BE_L!=8'b00000000) showerror;
                                default if (BE_L!=8'b11111111) showerror;
                            endcase
                    end
                end
        $display("Test Done!");
    end
endmodule

测试代码说明:

  1. 代码中有一个task块,可以理解为verilog的一个函数调用,每当出错的时候,就调用这个task,显示UUT的所有输入和输出,以便debug;task也可以有输入和输出,以及内部的reg和integer变量,就是定义module一样,但是不能定义wire;
  2. initial块中是一个triple loop,三重循环,因为我们必须要遍历所有可能的输入组合,并检查每一个输入组合对应的输出是否符合预期。(有的时候感觉测试代码比模块代码还要复杂一些)

仿真结果

本文以上代码测试OK,仿真过程没有任何error信息打印出来。

下面是wave图,看起来稍微有些费劲:

wave_mem_decoder

红线右边的部分,是HADDR不为0的部分,符合预期,BE_L和EB_L都一直保持全0状态。红线左边可以仔细看看,在SIZE不断变化之下,BE_L呈现有规律的变化;而EN_L和HADDR[31:30]的变化保持同步。花了半天时间编写调试代码,成功了!:)

本文链接:https://cs.pynote.net/hd/verilog/202109275/

-- EOF --

-- MORE --